[
  {
    "path": ".browserslistrc",
    "content": "> 1%\nlast 2 versions\nnot dead\n"
  },
  {
    "path": ".dockerignore",
    "content": "node_modules\n"
  },
  {
    "path": ".eslintignore",
    "content": "src/plugins/*"
  },
  {
    "path": ".eslintrc-auto-import.json",
    "content": "{\n  \"globals\": {\n    \"Component\": true,\n    \"ComponentPublicInstance\": true,\n    \"ComputedRef\": true,\n    \"DARK\": true,\n    \"EffectScope\": true,\n    \"InjectionKey\": true,\n    \"LIGHT\": true,\n    \"PropType\": true,\n    \"Ref\": true,\n    \"VNode\": true,\n    \"computed\": true,\n    \"createApp\": true,\n    \"customRef\": true,\n    \"defineAsyncComponent\": true,\n    \"defineComponent\": true,\n    \"effectScope\": true,\n    \"getCurrentInstance\": true,\n    \"getCurrentScope\": true,\n    \"getRanking\": true,\n    \"h\": true,\n    \"inject\": true,\n    \"isProxy\": true,\n    \"isReactive\": true,\n    \"isReadonly\": true,\n    \"isRef\": true,\n    \"markRaw\": true,\n    \"nextTick\": true,\n    \"onActivated\": true,\n    \"onBeforeMount\": true,\n    \"onBeforeRouteLeave\": true,\n    \"onBeforeRouteUpdate\": true,\n    \"onBeforeUnmount\": true,\n    \"onBeforeUpdate\": true,\n    \"onDeactivated\": true,\n    \"onErrorCaptured\": true,\n    \"onMounted\": true,\n    \"onRenderTracked\": true,\n    \"onRenderTriggered\": true,\n    \"onScopeDispose\": true,\n    \"onServerPrefetch\": true,\n    \"onUnmounted\": true,\n    \"onUpdated\": true,\n    \"provide\": true,\n    \"reactive\": true,\n    \"readonly\": true,\n    \"ref\": true,\n    \"resolveComponent\": true,\n    \"shallowReactive\": true,\n    \"shallowReadonly\": true,\n    \"shallowRef\": true,\n    \"toRaw\": true,\n    \"toRef\": true,\n    \"toRefs\": true,\n    \"triggerRef\": true,\n    \"unref\": true,\n    \"useAliasAction\": true,\n    \"useApolloQuery\": true,\n    \"useApp\": true,\n    \"useAttrs\": true,\n    \"useClient\": true,\n    \"useCopy\": true,\n    \"useCssModule\": true,\n    \"useCssVars\": true,\n    \"useDelegate\": true,\n    \"useEns\": true,\n    \"useExtendedSpaces\": true,\n    \"useFlashNotification\": true,\n    \"useFollowSpace\": true,\n    \"useFormSpaceProposal\": true,\n    \"useFormSpaceSettings\": true,\n    \"useFormValidation\": true,\n    \"useGnosis\": true,\n    \"useI18n\": true,\n    \"useImageUpload\": true,\n    \"useInfiniteLoader\": true,\n    \"useIntl\": true,\n    \"useLink\": true,\n    \"useMeta\": true,\n    \"useModal\": true,\n    \"useModalNotification\": true,\n    \"useNetworksFilter\": true,\n    \"useNotifications\": true,\n    \"usePlugins\": true,\n    \"useProfiles\": true,\n    \"useProposalVotes\": true,\n    \"useProposals\": true,\n    \"useQuorum\": true,\n    \"useReportDownload\": true,\n    \"useRoute\": true,\n    \"useRouter\": true,\n    \"useSafe\": true,\n    \"useSharing\": true,\n    \"useShortUrls\": true,\n    \"useSkin\": true,\n    \"useSkinsFilter\": true,\n    \"useSlots\": true,\n    \"useSnapshot\": true,\n    \"useSpaceController\": true,\n    \"useSpaceSubscription\": true,\n    \"useSpaces\": true,\n    \"useStrategies\": true,\n    \"useTerms\": true,\n    \"useTreasury\": true,\n    \"useTxStatus\": true,\n    \"useUnseenProposals\": true,\n    \"useUsername\": true,\n    \"useWeb3\": true,\n    \"useDelegates\": true,\n    \"useResolveName\": true,\n    \"watch\": true,\n    \"watchEffect\": true,\n    \"watchPostEffect\": true,\n    \"watchSyncEffect\": true,\n    \"watchTxStatus\": true,\n    \"toValue\": true,\n    \"useFlaggedMessageStatus\": true,\n    \"useEmailSubscription\": true,\n    \"useEmailFetchClient\": true,\n    \"useStatement\": true,\n    \"useBalances\": true,\n    \"useAccount\": true,\n    \"usePayment\": true,\n    \"useChangeNetwork\": true,\n    \"useBoost\": true\n  }\n}\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fabien@bonustrack.co. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "### Opening an issue\n\nYou should usually open an issue in the following situations:\n\n* Report an error you can’t solve yourself\n* Discuss a high-level topic or idea (for example, community, vision or policies)\n* Propose a new feature or other project idea\n\nTips for communicating on issues:\n\n* **If you see an open issue that you want to tackle,** comment on the issue to let people know you’re on it. That way, people are less likely to duplicate your work.\n* **If an issue was opened a while ago,** it’s possible that it’s being addressed somewhere else, or has already been resolved, so comment to ask for confirmation before starting work.\n* **If you opened an issue, but figured out the answer later on your own,** comment on the issue to let people know, then close the issue. Even documenting that outcome is a contribution to the project.\n\n### Opening a pull request\n\nYou should usually open a pull request in the following situations:\n\n* Submit trivial fixes (for example, a typo, a broken link or an obvious error)\n* Start work on a contribution that was already asked for, or that you’ve already discussed, in an issue\n\nA pull request doesn’t have to represent finished work. It’s usually better to open a pull request early on, so others can watch or give feedback on your progress. Just mark it as a “WIP” (Work in Progress) in the subject line. You can always add more commits later.\n\nIf the project is on GitHub, here’s how to submit a pull request:\n\n* **Fork the repository** and clone it locally. Connect your local to the original repository by adding it as a remote. Pull in changes from this repository often so that you stay up to date so that when you submit your pull request, merge conflicts will be less likely.\n* **Create a branch** for your edits.\n* **Reference any relevant issues** or supporting documentation in your PR (for example, “Closes #37.”)\n* **Include screenshots of the before and after** if your changes include differences in HTML/CSS. Drag and drop the images into the body of your pull request.\n* **Test your changes!** Run your changes against any existing tests if they exist and create new ones when needed. Whether tests exist or not, make sure your changes don’t break the existing project.\n* **Contribute in the style of the project** to the best of your abilities. This may mean using indents, semi-colons or comments differently than you would in your own repository, but makes it easier for the maintainer to merge, others to understand and maintain in the future.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/BUG_REPORT.yml",
    "content": "name: Bug report\ndescription: File a bug report\ntitle: \"[BUG] - \"\nlabels: [\"bug-report\"]\nbody:\n  - type: markdown\n    attributes:\n      value: Thanks for making Snapshot awesome for everyone. \n  - type: input\n    id: title\n    attributes:\n      label: Briefly describe the bug.\n      description: A clear and concise description of what the bug is.\n      placeholder: ex. Unable to vote using metamask wallet on chrome.\n    validations: \n      required: true\n  - type: textarea\n    id: reproduce\n    attributes:\n      label: How can we reproduce the bug?\n      placeholder: Steps to reproduce the behaviour.\n      value: |\n        1. Go to '...'\n        2. Click on '....'\n        3. Scroll down to '....'\n        4. See error\n    validations:\n      required: true\n  - type: textarea\n    id: expectation\n    attributes:\n      label: What is the expected behaviour?\n      description: A clear and concise description of what you expected to happen. \n      placeholder: ex. I should be able to vote using metamask wallet on chrome. \n    validations:\n      required: false\n  - type: textarea\n    id: screenshots\n    attributes:\n      label: Can you attach screenshots?\n      description: Please attach all the screenshots that can help us visually see the problem. \n      placeholder: You can drag-and-drop the image, copy paste the image in the field below. \n    validations:\n      required: false\n  - type: textarea\n    id: device\n    attributes:\n      label: Can you provide your device specific details below? \n      value: |\n        1. Device Type - [ex. Desktop or Mobile]\n        2. Device OS - [ex. Android, MacOS, Windows, iOS]\n        3. OS Version - [ex. iOS 11, Windows 10]\n        4. Browser - [ex. chrome, safari]\n        5. Browser Version - [ex. 101]\n        6. Wallet - [metamask, portis, walletconnect]\n        7. Space - [ex. ENS, Gitcoin]\n        8. Any additional information we should know - \n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml",
    "content": "name: Feature request\ndescription: Suggest an idea for this project\ntitle: \"[NEW FEATURE] - \"\nlabels: [\"feature-request\"]\nbody:\n  - type: markdown\n    attributes:\n      value: Thanks for making Snapshot awesome for everyone. \n  - type: input\n    id: title\n    attributes:\n      label: Briefly describe the feature.\n      description: A clear and concise description of the feature you are requesting.\n    validations: \n      required: true\n  - type: textarea\n    id: problem\n    attributes:\n      label: Which problem is this feature trying to solve?\n      description: A clear description of the problem or frustration that will be solved with this feature.\n      placeholder: ex. I'm always frustrated when I am viewing the proposal metrics\n    validations:\n      required: true\n  - type: textarea\n    id: solution\n    attributes:\n      label: What is the expected solution?\n      description: A clear and concise description of what you expected to happen. \n      placeholder: ex. Having a chart to read the votes on the proposal will make it very easy to read the proposal metrics. \n    validations:\n      required: true\n  - type: textarea\n    id: alternatives\n    attributes:\n      label: How do you currently handle this problem?\n      description: A clear and concise description of any alternative solutions or features you've considered. \n    validations:\n      required: false\n  - type: textarea\n    id: additional\n    attributes:\n      label: Anything else you'd like to mention?\n      description: Please mention any technical details, screenshots, mock-ups that you might have for us to better understand the feature.   \n    validations:\n      required: false\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "### Summary\n\n<!-- Related issues, a description or list of the changes and the motivation behind them -->\n\nCloses: #\n\n### How to test\n\n1.\n\n### To-Do\n\n- [ ]\n\n<!--\n### Self-review checklist\n- [ ] I have performed a full self-review of my changes\n- [ ] I have tested my changes on a preview deployment\n- [ ] I have tested my changes on different screen sizes (sm, md)\n-->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"npm\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    ignore:\n      - dependency-name: \"@snapshot-labs/*\"\n    groups:\n      production-dependencies:\n        dependency-type: \"production\"\n      development-dependencies:\n        dependency-type: \"development\"\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "daysUntilStale: 120\ndaysUntilClose: 14\nexemptLabels:\n  - pinned\n  - enhancement\n  - bug\nstaleLabel: stale\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: \"CodeQL\"\n\non:\n  push:\n    branches: [ 'master' ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ 'master' ]\n  schedule:\n    - cron: '36 5 * * 6'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}\n    timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'javascript' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]\n        # Use only 'java' to analyze code written in Java, Kotlin or both\n        # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both\n        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v3\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v2\n      with:\n        languages: ${{ matrix.language }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n\n        # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs\n        # queries: security-extended,security-and-quality\n\n\n    # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v2\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun\n\n    #   If the Autobuild fails above, remove it and uncomment the following three lines.\n    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.\n\n    # - run: |\n    #     echo \"Run, Build Application using script\"\n    #     ./location_of_script_within_repo/buildscript.sh\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v2\n      with:\n        category: \"/language:${{matrix.language}}\"\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Deploy\n\non:\n  workflow_dispatch:\n    inputs:\n      target:\n        type: choice\n        description: Target\n        options: \n        - stable\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n\n    steps:\n      # Checks-out repo on develop branch\n      - uses: actions/checkout@v3\n        with:\n          ref: 'master'\n      # Overwrite target\n      - run: |\n          git checkout -b ${{ github.event.inputs.target }}\n          git push --set-upstream origin ${{ github.event.inputs.target }} --force\n"
  },
  {
    "path": ".github/workflows/nodejs.yml",
    "content": "name: Node CI\n\non:\n  push:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [16.x]\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v3\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: Install dependencies\n        run: yarn\n\n      - name: Build\n        run: yarn run build\n\n      - name: Lint\n        run: yarn run lint\n"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "name: Tests\non: [push]\njobs:\n  test:\n    runs-on: ubuntu-20.04\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-node@v3\n        with:\n          node-version: '16'\n          cache: 'yarn'\n      - run: yarn\n      - run: yarn test:unit:coverage\n      - name: Upload coverage reports to Codecov\n        uses: codecov/codecov-action@v3\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/update-snapshot-packages.yml",
    "content": "name: Update Snapshot packages\n\non: workflow_dispatch\n\njobs:\n  update-dep:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v1\n        with:\n          node-version: '16.x'\n      - name: Update snapshot.js\n        run: |\n          yarn upgrade @snapshot-labs/snapshot.js --latest\n      - name: Update lock.js\n        run: |\n          yarn upgrade @snapshot-labs/lock --latest\n      - name: Create Pull Request\n        uses: peter-evans/create-pull-request@v3\n        with:\n            commit-message: Update Snapshot packages\n            title: Update Snapshot packages\n            body: |\n              - Updates from snapshot.js or lock packages\n  \n              Auto-generated by Github Actions\n            branch: update-snapshot-packages\n"
  },
  {
    "path": ".github/workflows/update-snapshot-submodules.yml",
    "content": "name: Update Snapshot submodules\n\non: workflow_dispatch\n\njobs:\n  update-dep:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          submodules: 'recursive'\n      - uses: actions/setup-node@v1\n        with:\n          node-version: '16.x'\n      - name: Update submodules\n        run: |\n          git submodule update --remote\n      - name: Create Pull Request\n        uses: peter-evans/create-pull-request@v3\n        with:\n            title: Update Snapshot submodules\n            body: |\n              - Updates from submodules\n  \n              Auto-generated by Github Actions\n            branch: update-snapshot-submodules\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n.yalc\ncomponents.d.ts\nauto-imports.d.ts\n\n# local env file\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n# Vitest\n\n/coverage\n# Sentry Auth Token\n.env.sentry-build-plugin\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"snapshot-spaces\"]\n\tpath = snapshot-spaces\n\turl = https://github.com/snapshot-labs/snapshot-spaces\n"
  },
  {
    "path": ".gitpod.yml",
    "content": "tasks:\n  - name: Setup\n    init: |\n      yarn install --frozen-lockfile --silent --network-timeout 100000\n    command: yarn dev\n\nports:\n  - port: 3000\n    onOpen: open-preview\n"
  },
  {
    "path": ".husky/post-checkout",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nyarn run init-submodules\n"
  },
  {
    "path": ".husky/post-merge",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nyarn run init-submodules\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n"
  },
  {
    "path": ".husky/pre-push",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:18-alpine\n\nRUN apk update && apk upgrade && \\\n    apk add --no-cache git py3-pip python3 gcc g++ make\n\nWORKDIR /app\n\nCOPY . .\n\nRUN yarn install --frozen-lockfile\nRUN yarn install --frozen-lockfile\n\nRUN yarn run build\n\nEXPOSE 8080\n\nCMD [\"yarn\", \"run\", \"dev\", \"--host\"]\n"
  },
  {
    "path": "FUNDING.json",
    "content": "{\n  \"drips\": {\n    \"ethereum\": {\n      \"ownedBy\": \"0x8C28Cf33d9Fd3D0293f963b1cd27e3FF422B425c\"\n    }\n  },\n  \"opRetro\": {\n    \"projectId\": \"0x5e6c436e48e56d6d9622ba5d0be0035c314e2b29d2afc8f5f1ee8ac75cd42532\"\n  }\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) Snapshot Labs\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n    <img src=\"public/icon.svg\" height=\"70\" alt=\"Snapshot Logo\">\n    <h1>Snapshot</h1>\n    <strong>Snapshot is an off-chain gasless multi-governance client with easy to verify and hard to contest results.</strong>\n</div>\n<br>\n<div align=\"center\">\n    <a href=\"https://github.com/snapshot-labs/snapshot/actions/workflows/nodejs.yml\">\n        <img src=\"https://github.com/snapshot-labs/snapshot/actions/workflows/nodejs.yml/badge.svg\" alt=\"Node CI\">\n    </a>\n    <img src=\"https://img.shields.io/github/commit-activity/w/snapshot-labs/snapshot\" alt=\"GitHub commit activity\">\n    <a href=\"https://github.com/snapshot-labs/snapshot/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22\">\n        <img src=\"https://img.shields.io/github/issues/snapshot-labs/snapshot/help wanted\" alt=\"GitHub issues help wanted\">\n    </a>\n    <a href=\"https://discord.snapshot.org/\">\n        <img src=\"https://img.shields.io/discord/707079246388133940.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2\" alt=\"Discord\">\n    </a>\n    <a href=\"https://x.com/SnapshotLabs\">\n        <img src=\"https://img.shields.io/twitter/follow/SnapshotLabs?label=SnapshotLabs&style=social\">\n    </a>\n</div>\n<div align=\"center\">\n    <br>\n    <a href=\"https://snapshot.org\"><b>snapshot.org »</b></a>\n    <br><br>\n    <a href=\"https://docs.snapshot.org\"><b>Documentation</b></a>\n    •\n    <a href=\"https://features.snapshot.org/feature-requests\"><b>Feature requests</b></a>\n    •\n    <a href=\"https://docs.snapshot.org/introduction#contributing\"><b>Contribute</b></a>\n</div>\n\n## Project setup\n\n```\nyarn\n```\n\n### Compiles and hot-reloads for development\n\n```\nyarn dev\n```\n\n### Compiles and minifies for production\n\n```\nyarn build\n```\n\n### Lints and fixes files\n\n```\nyarn run lint\n```\n\n### Development Guide\n\nUse `http://localhost:8080/#/fabien.eth` for testing your code.\n\nBy default your instance will connect to the hub at `https://testnet.hub.snapshot.org`. To change that (or other values) you can create a `.env.local` and overwrite the values from `.env`.\n\n## Running service locally with Docker\n1. Run `docker build -t snapshot .` to build the image\n2. Run `docker run --name snapshot -p 8080:8080 snapshot` to run the container\n3. Go to `http://localhost:8080/#/fabien.eth` to test your code\n\n## License\n\nSnapshot is open-sourced software licensed under the © [MIT license](LICENSE).\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = {\n  presets: ['@vue/cli-plugin-babel/preset']\n};\n"
  },
  {
    "path": "crowdin.yml",
    "content": "files:\n  - source: /src/locales/default.json\n    translation: /src/locales/%locale%.json\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/favicon.png\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1.0, minimum-scale=1.0, user-scalable=no\"\n    />\n    <title>Snapshot</title>\n  </head>\n  <body class=\"bg-skin-bg font-sans text-base text-skin-text antialiased\">\n    <div id=\"app\"></div>\n    <div id=\"modal\" />\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"snapshot\",\n  \"version\": \"0.1.4\",\n  \"license\": \"MIT\",\n  \"repository\": \"snapshot-labs/snapshot\",\n  \"scripts\": {\n    \"dev\": \"vite --port=8080\",\n    \"build\": \"vite build\",\n    \"lint\": \"eslint \\\"*.{ts,js,vue,json}\\\" src/ --ext .ts,.js,.vue,.json\",\n    \"lint:fix\": \"yarn lint --fix\",\n    \"preinstall\": \"yarn run init-submodules\",\n    \"postinstall\": \"patch-package && husky install\",\n    \"init-submodules\": \"git submodule update --init\",\n    \"test:unit\": \"vitest run\",\n    \"test:unit:coverage\": \"vitest run --coverage\"\n  },\n  \"eslintConfig\": {\n    \"extends\": [\n      \"@snapshot-labs/vue\",\n      \"./.eslintrc-auto-import.json\"\n    ]\n  },\n  \"prettier\": \"@snapshot-labs/prettier-config\",\n  \"dependencies\": {\n    \"@apollo/client\": \"^3.8.7\",\n    \"@braintree/sanitize-url\": \"^6.0.4\",\n    \"@ensdomains/eth-ens-namehash\": \"^2.0.15\",\n    \"@ethersproject/abi\": \"^5.7.0\",\n    \"@ethersproject/address\": \"^5.7.0\",\n    \"@ethersproject/bignumber\": \"^5.6.1\",\n    \"@ethersproject/bytes\": \"^5.7.0\",\n    \"@ethersproject/contracts\": \"^5.7.0\",\n    \"@ethersproject/providers\": \"^5.7.1\",\n    \"@ethersproject/random\": \"^5.7.0\",\n    \"@ethersproject/solidity\": \"^5.7.0\",\n    \"@ethersproject/strings\": \"^5.7.0\",\n    \"@ethersproject/units\": \"^5.7.0\",\n    \"@ethersproject/wallet\": \"^5.7.0\",\n    \"@headlessui-float/vue\": \"^0.13.0\",\n    \"@headlessui/vue\": \"^1.7.17\",\n    \"@pusher/push-notifications-web\": \"^1.1.0\",\n    \"@sentry/vite-plugin\": \"^2.10.1\",\n    \"@sentry/vue\": \"^7.80.1\",\n    \"@shutter-network/shutter-crypto\": \"1.0.1\",\n    \"@snapshot-labs/lock\": \"^0.2.11\",\n    \"@snapshot-labs/pineapple\": \"^1.1.0\",\n    \"@snapshot-labs/snapshot.js\": \"^0.14.18\",\n    \"@vue/apollo-composable\": \"4.0.0-beta.11\",\n    \"@vueuse/core\": \"^10.6.1\",\n    \"@vueuse/head\": \"^2.0.0\",\n    \"autolinker\": \"^4.0.0\",\n    \"bluebird\": \"^3.7.2\",\n    \"evm-proxy-detection\": \"^1.2.0\",\n    \"graphql\": \"16.6.0\",\n    \"graphql-tag\": \"^2.12.6\",\n    \"js-sha256\": \"^0.10.1\",\n    \"jsonexport\": \"^3.2.0\",\n    \"kubo-rpc-client\": \"^3.0.2\",\n    \"lodash\": \"^4.17.21\",\n    \"minisearch\": \"^6.2.0\",\n    \"remarkable\": \"^2.0.1\",\n    \"remove-markdown\": \"^0.5.0\",\n    \"typescript\": \"^5.2.2\",\n    \"v-viewer\": \"^3.0.11\",\n    \"vite-compatible-readable-stream\": \"^3.6.1\",\n    \"vue\": \"^3.3.8\",\n    \"vue-i18n\": \"^9.7.0\",\n    \"vue-router\": \"^4.2.5\",\n    \"vue-tippy\": \"^6.3.1\",\n    \"vuedraggable\": \"^4.0.2\"\n  },\n  \"devDependencies\": {\n    \"@iconify-json/heroicons-outline\": \"^1.1.7\",\n    \"@rushstack/eslint-patch\": \"^1.3.3\",\n    \"@snapshot-labs/eslint-config-vue\": \"^0.1.0-beta.15\",\n    \"@snapshot-labs/prettier-config\": \"0.1.0-beta.15\",\n    \"@tailwindcss/forms\": \"^0.5.6\",\n    \"@types/bluebird\": \"^3.5.38\",\n    \"@types/lodash\": \"^4.14.198\",\n    \"@types/node\": \"^20.5.0\",\n    \"@vitejs/plugin-vue\": \"^2.3.4\",\n    \"@vitest/coverage-v8\": \"^0.34.5\",\n    \"@vue/test-utils\": \"^2.4.1\",\n    \"autoprefixer\": \"^10.4.15\",\n    \"env-cmd\": \"^10.1.0\",\n    \"eslint\": \"^8.46.0\",\n    \"happy-dom\": \"^10.11.0\",\n    \"husky\": \"^8.0.3\",\n    \"patch-package\": \"^7.0.0\",\n    \"postcss\": \"^8.4.28\",\n    \"postinstall-postinstall\": \"^2.1.0\",\n    \"prettier\": \"^3.1.0\",\n    \"prettier-plugin-tailwindcss\": \"^0.5.7\",\n    \"rollup-plugin-visualizer\": \"^5.9.0\",\n    \"sass\": \"~1.62.1\",\n    \"sass-loader\": \"^13.2.2\",\n    \"start-server-and-test\": \"^2.0.0\",\n    \"tailwindcss\": \"^3.3.5\",\n    \"unplugin-auto-import\": \"^0.16.1\",\n    \"unplugin-icons\": \"^0.16.5\",\n    \"unplugin-vue-components\": \"^0.25.2\",\n    \"vite\": \"^2.9.14\",\n    \"vitest\": \"^0.33.0\"\n  },\n  \"resolutions\": {\n    \"ansi-regex\": \"^5.0.1\",\n    \"axios\": \"^0.21.1\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {}\n  }\n};\n"
  },
  {
    "path": "public/.well-known/assetlinks.json",
    "content": "[\n  {\n    \"relation\": [\"delegate_permission/common.handle_all_urls\"],\n    \"target\": {\n      \"namespace\": \"android_app\",\n      \"package_name\": \"org.snapshot\",\n      \"sha256_cert_fingerprints\": [\n        \"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\"\n      ]\n    }\n  }\n]\n"
  },
  {
    "path": "public/.well-known/did.json",
    "content": "{\n  \"@context\": [\n    \"https://www.w3.org/ns/did/v1\",\n    \"https://w3id.org/security/suites/jws-2020/v1\"\n  ],\n  \"id\": \"did:web:snapshot.org\",\n  \"verificationMethod\": [\n    {\n      \"id\": \"did:web:snapshot.org#wc-notify-subscribe-key\",\n      \"type\": \"JsonWebKey2020\",\n      \"controller\": \"did:web:snapshot.org\",\n      \"publicKeyJwk\": {\n        \"kty\": \"OKP\",\n        \"crv\": \"X25519\",\n        \"x\": \"gL37Y5hbjW4__xM7n4JrtAqc1OAGUWKiASb1W1cReA0\"\n      }\n    },\n    {\n      \"id\": \"did:web:snapshot.org#wc-notify-authentication-key\",\n      \"type\": \"JsonWebKey2020\",\n      \"controller\": \"did:web:snapshot.org\",\n      \"publicKeyJwk\": {\n        \"kty\": \"OKP\",\n        \"crv\": \"Ed25519\",\n        \"x\": \"iCZGVaUtesmG6RinZVff-GlW5qzap8uAI1opXKmuPA4\"\n      }\n    }\n  ],\n  \"keyAgreement\": [\n    \"did:web:snapshot.org#wc-notify-subscribe-key\"\n  ],\n  \"authentication\": [\n    \"did:web:snapshot.org#wc-notify-authentication-key\"\n  ]\n}\n"
  },
  {
    "path": "public/manifest.json",
    "content": "{\n  \"short_name\": \"Snapshot\",\n  \"name\": \"Snapshot\",\n  \"description\": \"Where decisions get made\",\n  \"iconPath\": \"icon.svg\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n      \"sizes\": \"64x64 32x32 24x24 16x16\",\n      \"type\": \"image/x-icon\"\n    },\n    {\n      \"src\": \"avatar.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"avatar.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    }\n  ],\n  \"start_url\": \".\",\n  \"display\": \"standalone\",\n  \"theme_color\": \"#000000\",\n  \"background_color\": \"#ffffff\"\n}\n"
  },
  {
    "path": "public/service-worker.js",
    "content": "// importScripts('https://js.pusher.com/beams/service-worker.js');\n"
  },
  {
    "path": "src/App.vue",
    "content": "<script setup lang=\"ts\">\nconst { domain, init, isReady, showSidebar } = useApp();\nconst route = useRoute();\nconst { restorePendingTransactions } = useTxStatus();\n\nonMounted(async () => {\n  await init();\n  restorePendingTransactions();\n});\n</script>\n\n<template>\n  <LoadingSpinner v-if=\"!isReady\" class=\"overlay big animate-fade-in\" />\n  <div v-else class=\"flex min-h-screen\">\n    <div v-if=\"!domain\" id=\"sidebar\" class=\"flex flex-col\">\n      <div\n        class=\"sticky top-0 z-40 h-screen overflow-hidden bg-skin-bg transition-all sm:w-[60px]\"\n        :class=\"{ 'max-w-0 sm:max-w-none': !showSidebar }\"\n      >\n        <TheSidebar class=\"border-r border-skin-border\" />\n      </div>\n    </div>\n    <div\n      class=\"relative flex w-screen min-w-0 shrink-0 flex-col sm:w-auto sm:shrink sm:grow\"\n    >\n      <div\n        class=\"absolute bottom-0 left-0 right-0 top-0 z-50 bg-skin-bg opacity-60\"\n        :class=\"{ hidden: !showSidebar }\"\n        @click=\"showSidebar = false\"\n      />\n      <div\n        id=\"navbar\"\n        class=\"sticky top-0 z-40 border-b border-skin-border bg-skin-bg\"\n      >\n        <TheNavbar />\n      </div>\n      <div id=\"content\" class=\"pb-6 pt-4\">\n        <router-view v-slot=\"{ Component }\">\n          <KeepAlive :include=\"['ExploreView', 'RankingView']\">\n            <component :is=\"Component\" :key=\"route.path\" />\n          </KeepAlive>\n        </router-view>\n      </div>\n      <footer\n        v-if=\"route.name === 'home' || route.name === 'terms-and-conditions'\"\n        class=\"mt-auto\"\n      >\n        <TheFooter />\n      </footer>\n      <div id=\"action-bar\" />\n    </div>\n  </div>\n  <TheFlashNotification />\n  <TheModalNotification />\n</template>\n"
  },
  {
    "path": "src/assets/css/main.scss",
    "content": "@import '../fonts/iconfont.css';\n@import 'viewerjs/dist/viewer.css';\n@import './tippy.scss';\n@import './tune.scss';\n@import '../snapshot-spaces/skins';\n\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n[data-color-scheme='dark'] {\n  color-scheme: dark;\n  --primary-color: #ffffff;\n  --bg-color: #1c1b20;\n  --text-color: #8b949e;\n  --link-color: #ffffff;\n  --heading-color: #ffffff;\n  --border-color: #343434;\n  --header-bg: #1c1b20;\n  --block-bg: transparent;\n  --shadow-color: rgba(255, 255, 255, 0.036);\n  --border-color-soft: rgba(45, 45, 45, 0.8);\n  --border-color-subtle: rgba(45, 45, 45, 0.5);\n  --border-color-faint: rgba(45, 45, 45, 0.3);\n  --network-blocks: url('@/assets/images/blocks-dark.svg');\n}\n\n[data-color-scheme='light'] {\n  color-scheme: light;\n  --primary-color: #000000;\n  --bg-color: white;\n  --text-color: #57606a;\n  --link-color: #444444;\n  --heading-color: #111111;\n  --border-color: #e0e0e0;\n  --header-bg: white;\n  --block-bg: transparent;\n  --shadow-color: #0001;\n  --border-color-soft: rgb(224, 224, 224, 0.6);\n  --border-color-subtle: rgb(224, 224, 224, 0.32);\n  --border-color-faint: rgb(224, 224, 224, 0.12);\n  --network-blocks: url('@/assets/images/blocks-light.svg');\n}\n\n@layer base {\n  @font-face {\n    font-family: 'Calibre';\n    src: url('../fonts/Calibre-Medium-Custom.woff2') format('woff2');\n    font-weight: 500;\n    font-style: normal;\n  }\n\n  @font-face {\n    font-family: 'Calibre';\n    src: url('../fonts/Calibre-Semibold-Custom.woff2') format('woff2');\n    font-weight: 600;\n    font-style: normal;\n  }\n\n  @font-face {\n    font-family: 'SpaceMono';\n    src: url('../fonts/space-mono-v11-latin-700.woff2') format('woff2');\n  }\n\n  @font-face {\n    font-family: 'Space Mono';\n    font-style: normal;\n    font-weight: 700;\n    src: url('../fonts/SpaceMono-Bold.woff2') format('woff2');\n  }\n\n  .mono {\n    font-size: 44px;\n    font-family: 'Space Mono', Helvetica, Arial, sans-serif;\n    letter-spacing: -1px;\n    font-weight: 700;\n    line-height: 1.1em;\n  }\n\n  .eyebrow {\n    text-transform: uppercase;\n    letter-spacing: 1px;\n    font-size: 16px;\n  }\n\n  html {\n    scrollbar-gutter: stable;\n    overflow: visible !important;\n  }\n\n  *,\n  ::before,\n  ::after {\n    @apply border-skin-border;\n  }\n\n  body {\n    overflow-x: hidden;\n  }\n\n  h1 {\n    @apply text-2xl;\n  }\n  h2 {\n    @apply text-xl;\n  }\n  h3 {\n    @apply my-2 text-lg;\n  }\n  h4 {\n    @apply text-md;\n  }\n\n  h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6 {\n    @apply font-semibold text-skin-heading;\n  }\n\n  a,\n  button,\n  input {\n    @apply transition-colors;\n  }\n\n  b {\n    @apply font-semibold;\n  }\n\n  select {\n    @apply bg-skin-bg;\n  }\n}\n\n@layer utilities {\n  // This allows us to use 'no-scrollbar' with Tailwind breakpoints like this 'md:no-scrollbar'\n  /* Chrome, Safari and Opera */\n  .no-scrollbar::-webkit-scrollbar {\n    display: none;\n  }\n\n  .no-scrollbar {\n    -ms-overflow-style: none; /* IE and Edge */\n    scrollbar-width: none; /* Firefox */\n  }\n}\n\n// Don't prevent overscroll on touch devices\n@media (pointer: fine) {\n  body {\n    overscroll-behavior-y: none;\n  }\n}\n\n// Fade transition for Vue's transition and transition-group\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.2s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n\na,\n.a {\n  color: var(--link-color);\n\n  &:hover {\n    cursor: pointer;\n    text-decoration: none;\n  }\n}\n\n.input {\n  @apply bg-transparent text-skin-link outline-none;\n}\n\n::placeholder {\n  color: var(--text-color);\n  opacity: 0.6 !important;\n}\n\ninput[type='number']::-webkit-inner-spin-button,\ninput[type='number']::-webkit-outer-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\ninput[type='time']::-webkit-calendar-picker-indicator {\n  background: none;\n  display: none;\n}\n\n// Pointer cursor for file input buttons\ninput[type=file], /* FF, IE7+, chrome (except button) */\ninput[type=file]::-webkit-file-upload-button {\n  /* chromes and blink button */\n  cursor: pointer;\n}\n\ninput[type='number'] {\n  -moz-appearance: textfield;\n}\n\n.lazy-loading {\n  @apply animate-pulse-fast bg-skin-border;\n}\n\n// Shot UI kit\n\n.s-input {\n  @apply block w-full rounded-full border border-skin-border bg-skin-bg px-3 py-2 text-skin-link outline-none focus-within:border-skin-text;\n}\n\n.s-error {\n  @apply flex items-center rounded-b-3xl bg-red px-3 pb-2 pt-4 text-sm text-white transition-all duration-200;\n}\n\n// Fix for chrome. Overwrites tailwinds break-words class.\n// For some reason chrome doesn't break links with just overflow-wrap.\n// It also needs word-break.\n.break-words {\n  overflow-wrap: break-word;\n  word-break: break-word;\n}\n\n// v-viewer package styling\n.viewer-backdrop {\n  background-color: rgba(0, 0, 0, 80%) !important;\n}\n\n.viewer-close {\n  scale: 1.5 !important;\n  margin-top: 50px !important;\n  margin-right: 66px !important;\n  width: 50px !important;\n  height: 50px !important;\n  background-color: transparent !important;\n  box-shadow: none !important;\n}\n\n@media (max-width: 544px) {\n  .viewer-close {\n    margin-right: 50px !important;\n  }\n}\n\n.network-blocks {\n  background-repeat: repeat-x;\n  background-position: top -40px center;\n  background-image: var(--network-blocks);\n}"
  },
  {
    "path": "src/assets/css/tippy.scss",
    "content": "// V-Tippy tooltip package styling - could be moved somewhere else?\n.tippy-box[data-animation='fade'][data-state='hidden'] {\n  opacity: 0;\n}\n[data-tippy-root] {\n  max-width: calc(100vw - 10px);\n}\n.tippy-box {\n  position: relative;\n  background-color: var(--text-color);\n  color: var(--header-bg);\n  border-radius: 7px;\n  font-size: 16px;\n  line-height: 1.4;\n  outline: 0;\n  transition-property: transform, visibility, opacity;\n  padding: 2px 4px;\n  text-align: center;\n  font-weight: 500;\n}\n.tippy-box[data-placement^='top'] > .tippy-arrow {\n  bottom: 0;\n}\n.tippy-box[data-placement^='top'] > .tippy-arrow:before {\n  bottom: -7px;\n  left: 0;\n  border-width: 8px 8px 0;\n  border-top-color: var(--text-color);\n  transform-origin: center top;\n}\n.tippy-box[data-placement^='bottom'] > .tippy-arrow {\n  top: 0;\n}\n.tippy-box[data-placement^='bottom'] > .tippy-arrow:before {\n  top: -7px;\n  left: 0;\n  border-width: 0 8px 8px;\n  border-bottom-color: var(--text-color);\n  transform-origin: center bottom;\n}\n.tippy-box[data-placement^='left'] > .tippy-arrow {\n  right: 0;\n}\n.tippy-box[data-placement^='left'] > .tippy-arrow:before {\n  border-width: 8px 0 8px 8px;\n  border-left-color: var(--text-color);\n  right: -7px;\n  transform-origin: center left;\n}\n.tippy-box[data-placement^='right'] > .tippy-arrow {\n  left: 0;\n}\n.tippy-box[data-placement^='right'] > .tippy-arrow:before {\n  left: -7px;\n  border-width: 8px 8px 8px 0;\n  border-right-color: var(--text-color);\n  transform-origin: center right;\n}\n.tippy-box[data-inertia][data-state='visible'] {\n  transition-timing-function: cubic-bezier(0.54, 1.5, 0.38, 1.11);\n}\n.tippy-arrow {\n  width: 16px;\n  height: 16px;\n  color: #333;\n}\n.tippy-arrow:before {\n  content: '';\n  position: absolute;\n  border-color: transparent;\n  border-style: solid;\n}\n.tippy-content {\n  position: relative;\n  padding: 5px 9px;\n  z-index: 1;\n  overflow-wrap: break-word;\n}\n.tippy-box[data-theme~='urlified'] {\n  max-width: 200px;\n  white-space: pre-wrap;\n  color: var(--header-bg);\n\n  a {\n    color: var(--header-bg);\n  }\n}\n"
  },
  {
    "path": "src/assets/css/tune.scss",
    "content": ".tune-input {\n  @apply form-input rounded-full border-skin-border bg-transparent text-base text-skin-link focus:border-skin-text focus:outline-none focus:ring-0;\n}\n\n.tune-textarea {\n  @extend .tune-input;\n  @apply no-scrollbar rounded-3xl px-3;\n\n  &.disabled {\n    @apply cursor-not-allowed;\n  }\n}\n\n.tune-textarea-json {\n  @apply no-scrollbar overflow-x-scroll whitespace-pre;\n\n  &.disabled {\n    @apply cursor-not-allowed;\n  }\n}\n\n.tune-switch {\n  @apply border-transparent;\n\n  &.switched-on-bg {\n    @apply bg-green;\n  }\n  &.switched-off-bg {\n    @apply bg-skin-border;\n  }\n\n  &.switched-on-text {\n    @apply text-green;\n  }\n  &.switched-off-text {\n    @apply text-skin-text;\n  }\n}\n\n.tune-switch-button {\n  @apply bg-skin-bg;\n}\n\n.tune-input-loading {\n  @apply rounded-r-full bg-skin-bg;\n}\n\n.tune-button {\n  @apply h-[46px] cursor-pointer text-skin-link rounded-full bg-transparent border border-skin-border px-[22px] hover:border-skin-text;\n\n  &.disabled {\n    @apply border-skin-border bg-skin-bg text-skin-border cursor-not-allowed;\n  }\n\n  &.primary {\n    @apply border-skin-primary bg-skin-primary text-white hover:brightness-95;\n\n    &:disabled {\n      @apply border-skin-primary bg-skin-primary text-white opacity-40;\n    }\n  }\n\n  &.white-border {\n    @apply border-white/30  hover:border-white;\n\n    &:disabled {\n      @apply border-white text-white opacity-40;\n    }\n  }\n\n  &.danger {\n    @apply text-red hover:border-red border-red/40;\n\n    &:disabled {\n      @apply text-skin-border hover:border-skin-border;\n    }\n  }\n}\n\n.tune-button-select {\n  @apply relative h-[42px] w-full justify-start truncate pl-3 pr-5 text-left text-base text-skin-link rounded-full;\n\n  &.disabled {\n    @apply cursor-not-allowed border-skin-border;\n  }\n}\n\n.tune-input-duration {\n  @apply rounded-full border border-skin-border px-3 text-base text-skin-link focus-within:border-skin-text;\n  &.disabled {\n    @apply cursor-not-allowed;\n  }\n}\n\n.tune-input-duration-label {\n  @apply text-skin-text;\n}\n\n.tune-select {\n  @apply form-select text-left px-3 border border-skin-border w-full rounded-full outline-none focus:ring-0 focus:border-skin-text text-skin-link bg-skin-bg text-base;\n\n  &.disabled {\n    @apply cursor-not-allowed;\n  }\n}\n\n.tune-listbox-button {\n  @apply rounded-full border border-skin-border text-base text-skin-link hover:border-skin-text;\n\n  &.disabled {\n    @apply hover:border-skin-border;\n  }\n\n  &.error {\n    @apply border-red;\n  }\n}\n\n.tune-listbox-options {\n  @apply rounded-md border border-skin-border bg-skin-bg shadow-lg;\n}\n\n.tune-listbox-item {\n  &.selected {\n    @apply text-skin-link;\n  }\n  &.disabled {\n    @apply text-skin-border;\n  }\n  &.active {\n    @apply bg-skin-border;\n  }\n}\n\n.tune-listbox-placeholder {\n  @apply text-skin-text opacity-60;\n}\n\n.tune-label {\n  @apply font-sans text-skin-text;\n}\n\n.tune-sublabel {\n  @apply font-sans text-skin-text opacity-60;\n}\n\n.tune-icon-hint {\n  @apply text-xs hover:text-skin-link;\n}\n\n.tune-error-text {\n  @apply font-sans text-sm text-red;\n}\n\n.tune-error-border {\n  @apply border-red;\n}\n\n.tune-tag {\n  @apply rounded-lg border border-[--border-color-soft] bg-[--border-color-subtle] px-[6px] py-[3px] text-sm text-skin-text;\n}\n\n.tune-menu-list {\n  @apply rounded-md border border-skin-border bg-skin-header-bg shadow-lg;\n}\n\n.tune-menu-list-item {\n  @apply bg-skin-header-bg text-skin-text;\n\n  &.active {\n    @apply bg-skin-border text-skin-link;\n  }\n}\n\n.tune-popover {\n  @apply rounded-md border bg-skin-header-bg shadow-lg;\n}\n\n.tune-input-checkbox {\n  @apply form-checkbox h-[20px] w-[20px] rounded-lg border-skin-text text-skin-text focus:ring-0 focus:ring-offset-0 focus-visible:ring-offset-1 focus-visible:ring-offset-skin-text;\n}\n\n.tune-input-radio {\n  @apply form-radio h-[20px] w-[20px] rounded-full border-skin-text text-skin-text focus:ring-0 focus:ring-offset-0 focus-visible:ring-offset-1 focus-visible:ring-offset-skin-text;\n}\n\n.tune-form-array-objects {\n  @apply rounded-xl border p-3;\n}\n"
  },
  {
    "path": "src/assets/fonts/iconfont.css",
    "content": "@font-face {\n  font-family: 'iconfont'; /* Project id 1946815 */\n  src: url('iconfont.woff2?t=1648383247623') format('woff2'),\n    url('iconfont.woff?t=1648383247623') format('woff'),\n    url('iconfont.ttf?t=1648383247623') format('truetype');\n}\n\n.iconfont {\n  font-family: 'iconfont' !important;\n  font-size: 16px;\n  font-style: normal;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.iconnotificationsnone:before {\n  content: '\\e708';\n}\n\n.iconearth:before {\n  content: '\\e70d';\n}\n\n.iconmarkdown:before {\n  content: '\\ec0f';\n}\n\n.iconcoins:before {\n  content: '\\e6c6';\n}\n\n.iconcheck3:before {\n  content: '\\e605';\n}\n\n.iconskip:before {\n  content: '\\e8c5';\n}\n\n.iconclose2:before {\n  content: '\\e6c3';\n}\n\n.iconcalendar:before {\n  content: '\\e71f';\n}\n\n.icondraggable:before {\n  content: '\\e617';\n}\n\n.iconpreview:before {\n  content: '\\e637';\n}\n\n.iconfilter:before {\n  content: '\\e74f';\n}\n\n.iconhome:before {\n  content: '\\e673';\n}\n\n.iconapps:before {\n  content: '\\e613';\n}\n\n.iconcheck1:before {\n  content: '\\e607';\n}\n\n.iconremind:before {\n  content: '\\e69e';\n}\n\n.iconemoji:before {\n  content: '\\e709';\n}\n\n.iconflag_fill:before {\n  content: '\\e711';\n}\n\n.iconmine:before {\n  content: '\\e732';\n}\n\n.iconmore:before {\n  content: '\\e736';\n}\n\n.iconpeople:before {\n  content: '\\e73d';\n}\n\n.iconstealth_fill:before {\n  content: '\\e75f';\n}\n\n.iconnotifications-on:before {\n  content: '\\ec64';\n}\n\n.iconnotifications:before {\n  content: '\\e630';\n}\n\n.iconnotifications-off:before {\n  content: '\\e6e4';\n}\n\n.iconstamp:before {\n  content: '\\e6de';\n}\n\n.iconinsertlink:before {\n  content: '\\e621';\n}\n\n.iconupload:before {\n  content: '\\e735';\n}\n\n.iconplay:before {\n  content: '\\e628';\n}\n\n.iconfeed:before {\n  content: '\\e6f6';\n}\n\n.icongitbook:before {\n  content: '\\e604';\n}\n\n.iconreload:before {\n  content: '\\e603';\n}\n\n.iconrefresh:before {\n  content: '\\e78f';\n}\n\n.iconwarning1:before {\n  content: '\\e602';\n}\n\n.icontoggle_off:before {\n  content: '\\e9ad';\n}\n\n.icontoggle-on:before {\n  content: '\\e8b2';\n}\n\n.iconthreedots:before {\n  content: '\\e730';\n}\n\n.iconmoon:before {\n  content: '\\e780';\n}\n\n.iconsun:before {\n  content: '\\e634';\n}\n\n.iconinfo:before {\n  content: '\\e93b';\n}\n\n.iconlogin:before {\n  content: '\\e66a';\n}\n\n.iconcopy:before {\n  content: '\\e638';\n}\n\n.iconsnapshot:before {\n  content: '\\e6da';\n}\n\n.iconstars:before {\n  content: '\\e6a3';\n}\n\n.iconfavorite-off:before {\n  content: '\\e6d7';\n}\n\n.iconfavorite-on:before {\n  content: '\\e6d8';\n}\n\n.iconloveit:before {\n  content: '\\e60a';\n}\n\n.iconbyteball:before {\n  content: '\\e6bc';\n}\n\n.iconandroid:before {\n  content: '\\e601';\n}\n\n.icontwitter:before {\n  content: '\\ec9c';\n}\n\n.iconreddit:before {\n  content: '\\ea3c';\n}\n\n.icongoogle:before {\n  content: '\\e723';\n}\n\n.iconchrome:before {\n  content: '\\e6c2';\n}\n\n.iconapple:before {\n  content: '\\e6bd';\n}\n\n.iconfacebook:before {\n  content: '\\e6be';\n}\n\n.iconwindows:before {\n  content: '\\e649';\n}\n\n.icondiscord:before {\n  content: '\\e6fe';\n}\n\n.icontelegram:before {\n  content: '\\e731';\n}\n\n.iconmacos:before {\n  content: '\\e6ba';\n}\n\n.iconline:before {\n  content: '\\e6c0';\n}\n\n.iconmenu1:before {\n  content: '\\e6c8';\n}\n\n.iconclose1:before {\n  content: '\\e6c9';\n}\n\n.icongear:before {\n  content: '\\e6ca';\n}\n\n.iconbullet:before {\n  content: '\\e619';\n}\n\n.iconplus:before {\n  content: '\\e727';\n}\n\n.iconwallet:before {\n  content: '\\e97c';\n}\n\n.iconsignature:before {\n  content: '\\e600';\n}\n\n.iconreceipt-outlined:before {\n  content: '\\e8eb';\n}\n\n.iconcheck:before {\n  content: '\\e679';\n}\n\n.iconwarning:before {\n  content: '\\e62c';\n}\n\n.iconsearch:before {\n  content: '\\e6f2';\n}\n\n.iconmenu:before {\n  content: '\\e609';\n}\n\n.iconclose:before {\n  content: '\\e63e';\n}\n\n.icongithub:before {\n  content: '\\e667';\n}\n\n.iconexternal-link:before {\n  content: '\\e636';\n}\n\n.icongo:before {\n  content: '\\e6cb';\n}\n\n.iconback:before {\n  content: '\\e6cc';\n}\n\n.iconuser:before {\n  content: '\\e66f';\n}\n\n.iconarrow-up:before {\n  content: '\\e75c';\n}\n\n.iconarrow-down:before {\n  content: '\\e75d';\n}\n"
  },
  {
    "path": "src/assets/fonts/iconfont.json",
    "content": "{\n  \"id\": \"1946815\",\n  \"name\": \"snapshot\",\n  \"font_family\": \"iconfont\",\n  \"css_prefix_text\": \"icon\",\n  \"description\": \"\",\n  \"glyphs\": [\n    {\n      \"icon_id\": \"736871\",\n      \"name\": \"notifications-none\",\n      \"font_class\": \"notificationsnone\",\n      \"unicode\": \"e708\",\n      \"unicode_decimal\": 59144\n    },\n    {\n      \"icon_id\": \"1444429\",\n      \"name\": \"earth\",\n      \"font_class\": \"earth\",\n      \"unicode\": \"e70d\",\n      \"unicode_decimal\": 59149\n    },\n    {\n      \"icon_id\": \"27365167\",\n      \"name\": \"markdown\",\n      \"font_class\": \"markdown\",\n      \"unicode\": \"ec0f\",\n      \"unicode_decimal\": 60431\n    },\n    {\n      \"icon_id\": \"1444287\",\n      \"name\": \"coins\",\n      \"font_class\": \"coins\",\n      \"unicode\": \"e6c6\",\n      \"unicode_decimal\": 59078\n    },\n    {\n      \"icon_id\": \"11398722\",\n      \"name\": \"check\",\n      \"font_class\": \"check3\",\n      \"unicode\": \"e605\",\n      \"unicode_decimal\": 58885\n    },\n    {\n      \"icon_id\": \"15617307\",\n      \"name\": \"skip-next\",\n      \"font_class\": \"skip\",\n      \"unicode\": \"e8c5\",\n      \"unicode_decimal\": 59589\n    },\n    {\n      \"icon_id\": \"980400\",\n      \"name\": \"close\",\n      \"font_class\": \"close2\",\n      \"unicode\": \"e6c3\",\n      \"unicode_decimal\": 59075\n    },\n    {\n      \"icon_id\": \"7239657\",\n      \"name\": \"calendar\",\n      \"font_class\": \"calendar\",\n      \"unicode\": \"e71f\",\n      \"unicode_decimal\": 59167\n    },\n    {\n      \"icon_id\": \"27154983\",\n      \"name\": \"draggable\",\n      \"font_class\": \"draggable\",\n      \"unicode\": \"e617\",\n      \"unicode_decimal\": 58903\n    },\n    {\n      \"icon_id\": \"20997029\",\n      \"name\": \"preview\",\n      \"font_class\": \"preview\",\n      \"unicode\": \"e637\",\n      \"unicode_decimal\": 58935\n    },\n    {\n      \"icon_id\": \"8555524\",\n      \"name\": \"filter-3-fill\",\n      \"font_class\": \"filter\",\n      \"unicode\": \"e74f\",\n      \"unicode_decimal\": 59215\n    },\n    {\n      \"icon_id\": \"14401724\",\n      \"name\": \"home\",\n      \"font_class\": \"home\",\n      \"unicode\": \"e673\",\n      \"unicode_decimal\": 58995\n    },\n    {\n      \"icon_id\": \"7988769\",\n      \"name\": \"apps\",\n      \"font_class\": \"apps\",\n      \"unicode\": \"e613\",\n      \"unicode_decimal\": 58899\n    },\n    {\n      \"icon_id\": \"583318\",\n      \"name\": \"check\",\n      \"font_class\": \"check1\",\n      \"unicode\": \"e607\",\n      \"unicode_decimal\": 58887\n    },\n    {\n      \"icon_id\": \"1410439\",\n      \"name\": \"notifications-none\",\n      \"font_class\": \"remind\",\n      \"unicode\": \"e69e\",\n      \"unicode_decimal\": 59038\n    },\n    {\n      \"icon_id\": \"2045201\",\n      \"name\": \"emoji\",\n      \"font_class\": \"emoji\",\n      \"unicode\": \"e709\",\n      \"unicode_decimal\": 59145\n    },\n    {\n      \"icon_id\": \"2045216\",\n      \"name\": \"flag_fill\",\n      \"font_class\": \"flag_fill\",\n      \"unicode\": \"e711\",\n      \"unicode_decimal\": 59153\n    },\n    {\n      \"icon_id\": \"2045276\",\n      \"name\": \"mine\",\n      \"font_class\": \"mine\",\n      \"unicode\": \"e732\",\n      \"unicode_decimal\": 59186\n    },\n    {\n      \"icon_id\": \"2045280\",\n      \"name\": \"more\",\n      \"font_class\": \"more\",\n      \"unicode\": \"e736\",\n      \"unicode_decimal\": 59190\n    },\n    {\n      \"icon_id\": \"2045289\",\n      \"name\": \"people\",\n      \"font_class\": \"people\",\n      \"unicode\": \"e73d\",\n      \"unicode_decimal\": 59197\n    },\n    {\n      \"icon_id\": \"2045337\",\n      \"name\": \"stealth_fill\",\n      \"font_class\": \"stealth_fill\",\n      \"unicode\": \"e75f\",\n      \"unicode_decimal\": 59231\n    },\n    {\n      \"icon_id\": \"490399\",\n      \"name\": \"notifications_active\",\n      \"font_class\": \"notifications-on\",\n      \"unicode\": \"ec64\",\n      \"unicode_decimal\": 60516\n    },\n    {\n      \"icon_id\": \"5226577\",\n      \"name\": \"notifications\",\n      \"font_class\": \"notifications\",\n      \"unicode\": \"e630\",\n      \"unicode_decimal\": 58928\n    },\n    {\n      \"icon_id\": \"7371992\",\n      \"name\": \"round-notifications_\",\n      \"font_class\": \"notifications-off\",\n      \"unicode\": \"e6e4\",\n      \"unicode_decimal\": 59108\n    },\n    {\n      \"icon_id\": \"24125360\",\n      \"name\": \"stamp\",\n      \"font_class\": \"stamp\",\n      \"unicode\": \"e6de\",\n      \"unicode_decimal\": 59102\n    },\n    {\n      \"icon_id\": \"11931885\",\n      \"name\": \"link\",\n      \"font_class\": \"insertlink\",\n      \"unicode\": \"e621\",\n      \"unicode_decimal\": 58913\n    },\n    {\n      \"icon_id\": \"1393807\",\n      \"name\": \"upload\",\n      \"font_class\": \"upload\",\n      \"unicode\": \"e735\",\n      \"unicode_decimal\": 59189\n    },\n    {\n      \"icon_id\": \"7428691\",\n      \"name\": \"play\",\n      \"font_class\": \"play\",\n      \"unicode\": \"e628\",\n      \"unicode_decimal\": 58920\n    },\n    {\n      \"icon_id\": \"7685091\",\n      \"name\": \"feed\",\n      \"font_class\": \"feed\",\n      \"unicode\": \"e6f6\",\n      \"unicode_decimal\": 59126\n    },\n    {\n      \"icon_id\": \"20531430\",\n      \"name\": \"gitbook\",\n      \"font_class\": \"gitbook\",\n      \"unicode\": \"e604\",\n      \"unicode_decimal\": 58884\n    },\n    {\n      \"icon_id\": \"88076\",\n      \"name\": \"reload\",\n      \"font_class\": \"reload\",\n      \"unicode\": \"e603\",\n      \"unicode_decimal\": 58883\n    },\n    {\n      \"icon_id\": \"2958463\",\n      \"name\": \"refresh\",\n      \"font_class\": \"refresh\",\n      \"unicode\": \"e78f\",\n      \"unicode_decimal\": 59279\n    },\n    {\n      \"icon_id\": \"18442559\",\n      \"name\": \"warning\",\n      \"font_class\": \"warning1\",\n      \"unicode\": \"e602\",\n      \"unicode_decimal\": 58882\n    },\n    {\n      \"icon_id\": \"11982163\",\n      \"name\": \"toggle_off\",\n      \"font_class\": \"toggle_off\",\n      \"unicode\": \"e9ad\",\n      \"unicode_decimal\": 59821\n    },\n    {\n      \"icon_id\": \"15617211\",\n      \"name\": \"toggle-on\",\n      \"font_class\": \"toggle-on\",\n      \"unicode\": \"e8b2\",\n      \"unicode_decimal\": 59570\n    },\n    {\n      \"icon_id\": \"1393793\",\n      \"name\": \"threedots\",\n      \"font_class\": \"threedots\",\n      \"unicode\": \"e730\",\n      \"unicode_decimal\": 59184\n    },\n    {\n      \"icon_id\": \"13896012\",\n      \"name\": \"moon\",\n      \"font_class\": \"moon\",\n      \"unicode\": \"e780\",\n      \"unicode_decimal\": 59264\n    },\n    {\n      \"icon_id\": \"18019049\",\n      \"name\": \"sun\",\n      \"font_class\": \"sun\",\n      \"unicode\": \"e634\",\n      \"unicode_decimal\": 58932\n    },\n    {\n      \"icon_id\": \"8827141\",\n      \"name\": \"info\",\n      \"font_class\": \"info\",\n      \"unicode\": \"e93b\",\n      \"unicode_decimal\": 59707\n    },\n    {\n      \"icon_id\": \"1567557\",\n      \"name\": \"login\",\n      \"font_class\": \"login\",\n      \"unicode\": \"e66a\",\n      \"unicode_decimal\": 58986\n    },\n    {\n      \"icon_id\": \"11968691\",\n      \"name\": \"copy\",\n      \"font_class\": \"copy\",\n      \"unicode\": \"e638\",\n      \"unicode_decimal\": 58936\n    },\n    {\n      \"icon_id\": \"17540404\",\n      \"name\": \"snapshot\",\n      \"font_class\": \"snapshot\",\n      \"unicode\": \"e6da\",\n      \"unicode_decimal\": 59098\n    },\n    {\n      \"icon_id\": \"10331571\",\n      \"name\": \"stars\",\n      \"font_class\": \"stars\",\n      \"unicode\": \"e6a3\",\n      \"unicode_decimal\": 59043\n    },\n    {\n      \"icon_id\": \"17018087\",\n      \"name\": \"favorite-off\",\n      \"font_class\": \"favorite-off\",\n      \"unicode\": \"e6d7\",\n      \"unicode_decimal\": 59095\n    },\n    {\n      \"icon_id\": \"17018088\",\n      \"name\": \"favorite-on\",\n      \"font_class\": \"favorite-on\",\n      \"unicode\": \"e6d8\",\n      \"unicode_decimal\": 59096\n    },\n    {\n      \"icon_id\": \"562650\",\n      \"name\": \"love it\",\n      \"font_class\": \"loveit\",\n      \"unicode\": \"e60a\",\n      \"unicode_decimal\": 58890\n    },\n    {\n      \"icon_id\": \"248947\",\n      \"name\": \"Vignetting\",\n      \"font_class\": \"byteball\",\n      \"unicode\": \"e6bc\",\n      \"unicode_decimal\": 59068\n    },\n    {\n      \"icon_id\": \"261002\",\n      \"name\": \"android\",\n      \"font_class\": \"android\",\n      \"unicode\": \"e601\",\n      \"unicode_decimal\": 58881\n    },\n    {\n      \"icon_id\": \"348956\",\n      \"name\": \"twitter\",\n      \"font_class\": \"twitter\",\n      \"unicode\": \"ec9c\",\n      \"unicode_decimal\": 60572\n    },\n    {\n      \"icon_id\": \"703493\",\n      \"name\": \"reddit\",\n      \"font_class\": \"reddit\",\n      \"unicode\": \"ea3c\",\n      \"unicode_decimal\": 59964\n    },\n    {\n      \"icon_id\": \"836629\",\n      \"name\": \"google\",\n      \"font_class\": \"google\",\n      \"unicode\": \"e723\",\n      \"unicode_decimal\": 59171\n    },\n    {\n      \"icon_id\": \"929164\",\n      \"name\": \"chrome\",\n      \"font_class\": \"chrome\",\n      \"unicode\": \"e6c2\",\n      \"unicode_decimal\": 59074\n    },\n    {\n      \"icon_id\": \"944996\",\n      \"name\": \"mac\",\n      \"font_class\": \"apple\",\n      \"unicode\": \"e6bd\",\n      \"unicode_decimal\": 59069\n    },\n    {\n      \"icon_id\": \"1192366\",\n      \"name\": \"facebook\",\n      \"font_class\": \"facebook\",\n      \"unicode\": \"e6be\",\n      \"unicode_decimal\": 59070\n    },\n    {\n      \"icon_id\": \"1275498\",\n      \"name\": \"windows\",\n      \"font_class\": \"windows\",\n      \"unicode\": \"e649\",\n      \"unicode_decimal\": 58953\n    },\n    {\n      \"icon_id\": \"1444399\",\n      \"name\": \"discord\",\n      \"font_class\": \"discord\",\n      \"unicode\": \"e6fe\",\n      \"unicode_decimal\": 59134\n    },\n    {\n      \"icon_id\": \"2612016\",\n      \"name\": \"mac os的icon\",\n      \"font_class\": \"macos\",\n      \"unicode\": \"e6ba\",\n      \"unicode_decimal\": 59066\n    },\n    {\n      \"icon_id\": \"3876347\",\n      \"name\": \"line\",\n      \"font_class\": \"line\",\n      \"unicode\": \"e6c0\",\n      \"unicode_decimal\": 59072\n    },\n    {\n      \"icon_id\": \"11262564\",\n      \"name\": \"menu\",\n      \"font_class\": \"menu1\",\n      \"unicode\": \"e6c8\",\n      \"unicode_decimal\": 59080\n    },\n    {\n      \"icon_id\": \"11262644\",\n      \"name\": \"close\",\n      \"font_class\": \"close1\",\n      \"unicode\": \"e6c9\",\n      \"unicode_decimal\": 59081\n    },\n    {\n      \"icon_id\": \"11262645\",\n      \"name\": \"gear\",\n      \"font_class\": \"gear\",\n      \"unicode\": \"e6ca\",\n      \"unicode_decimal\": 59082\n    },\n    {\n      \"icon_id\": \"6145465\",\n      \"name\": \"bullet\",\n      \"font_class\": \"bullet\",\n      \"unicode\": \"e619\",\n      \"unicode_decimal\": 58905\n    },\n    {\n      \"icon_id\": \"1176968\",\n      \"name\": \"plus\",\n      \"font_class\": \"plus\",\n      \"unicode\": \"e727\",\n      \"unicode_decimal\": 59175\n    },\n    {\n      \"icon_id\": \"924591\",\n      \"name\": \"wallet\",\n      \"font_class\": \"wallet\",\n      \"unicode\": \"e97c\",\n      \"unicode_decimal\": 59772\n    },\n    {\n      \"icon_id\": \"11399282\",\n      \"name\": \"signature\",\n      \"font_class\": \"signature\",\n      \"unicode\": \"e600\",\n      \"unicode_decimal\": 58880\n    },\n    {\n      \"icon_id\": \"15617545\",\n      \"name\": \"receipt-outlined\",\n      \"font_class\": \"receipt-outlined\",\n      \"unicode\": \"e8eb\",\n      \"unicode_decimal\": 59627\n    },\n    {\n      \"icon_id\": \"10561798\",\n      \"name\": \"Check, label\",\n      \"font_class\": \"check\",\n      \"unicode\": \"e679\",\n      \"unicode_decimal\": 59001\n    },\n    {\n      \"icon_id\": \"59347\",\n      \"name\": \"warning\",\n      \"font_class\": \"warning\",\n      \"unicode\": \"e62c\",\n      \"unicode_decimal\": 58924\n    },\n    {\n      \"icon_id\": \"1262104\",\n      \"name\": \"search\",\n      \"font_class\": \"search\",\n      \"unicode\": \"e6f2\",\n      \"unicode_decimal\": 59122\n    },\n    {\n      \"icon_id\": \"1336076\",\n      \"name\": \"Menu\",\n      \"font_class\": \"menu\",\n      \"unicode\": \"e609\",\n      \"unicode_decimal\": 58889\n    },\n    {\n      \"icon_id\": \"2518398\",\n      \"name\": \"close\",\n      \"font_class\": \"close\",\n      \"unicode\": \"e63e\",\n      \"unicode_decimal\": 58942\n    },\n    {\n      \"icon_id\": \"5679219\",\n      \"name\": \"mark-github\",\n      \"font_class\": \"github\",\n      \"unicode\": \"e667\",\n      \"unicode_decimal\": 58983\n    },\n    {\n      \"icon_id\": \"7141712\",\n      \"name\": \"external-link\",\n      \"font_class\": \"external-link\",\n      \"unicode\": \"e636\",\n      \"unicode_decimal\": 58934\n    },\n    {\n      \"icon_id\": \"11262646\",\n      \"name\": \"go\",\n      \"font_class\": \"go\",\n      \"unicode\": \"e6cb\",\n      \"unicode_decimal\": 59083\n    },\n    {\n      \"icon_id\": \"11262647\",\n      \"name\": \"back\",\n      \"font_class\": \"back\",\n      \"unicode\": \"e6cc\",\n      \"unicode_decimal\": 59084\n    },\n    {\n      \"icon_id\": \"13519508\",\n      \"name\": \"user-solid\",\n      \"font_class\": \"user\",\n      \"unicode\": \"e66f\",\n      \"unicode_decimal\": 58991\n    },\n    {\n      \"icon_id\": \"13895880\",\n      \"name\": \"arrow-up\",\n      \"font_class\": \"arrow-up\",\n      \"unicode\": \"e75c\",\n      \"unicode_decimal\": 59228\n    },\n    {\n      \"icon_id\": \"14232782\",\n      \"name\": \"arrow-up\",\n      \"font_class\": \"arrow-down\",\n      \"unicode\": \"e75d\",\n      \"unicode_decimal\": 59229\n    }\n  ]\n}\n"
  },
  {
    "path": "src/components/AboutMembersListItem.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div class=\"flex justify-between border-t px-4 py-3 first:border-t-0\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/AvatarOverlayEdit.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  loading: boolean;\n  avatar?: string;\n  isViewOnly?: boolean;\n}>();\n</script>\n\n<template>\n  <transition name=\"fade\">\n    <div\n      v-if=\"isViewOnly\"\n      class=\"absolute bottom-0 left-0 right-0 top-0 cursor-not-allowed\"\n    />\n    <div\n      v-else\n      class=\"group absolute bottom-0 left-0 right-0 top-0 flex cursor-pointer items-center justify-center rounded-full transition-colors ease-out hover:bg-skin-border hover:opacity-80\"\n      :class=\"{\n        'bg-skin-border opacity-80': loading\n      }\"\n    >\n      <div\n        v-if=\"!loading\"\n        class=\"hidden transition-all ease-out group-hover:block\"\n      >\n        {{ avatar ? $t('edit') : $t('upload') }}\n      </div>\n      <LoadingSpinner v-if=\"loading\" />\n    </div>\n  </transition>\n</template>\n"
  },
  {
    "path": "src/components/AvatarSpace.vue",
    "content": "<script setup lang=\"ts\">\nimport { sha256 } from 'js-sha256';\n\nconst props = withDefaults(\n  defineProps<{\n    space: { id: string; avatar?: string };\n    size?: string;\n    previewFile?: File;\n  }>(),\n  {\n    size: '20',\n    previewFile: undefined\n  }\n);\n\nconst { env } = useApp();\n\nconst avatarHash = computed(() => {\n  if (!props.space?.avatar) return '';\n  const hash = sha256(props.space.avatar).slice(0, 16);\n  return `&cb=${hash}`;\n});\n</script>\n\n<template>\n  <BaseAvatar\n    :preview-file=\"previewFile\"\n    :size=\"size\"\n    :src=\"`https://cdn.stamp.fyi/space/${env === 'demo' ? 's-tn' : 's'}:${\n      space.id\n    }?s=${Number(size) * 2}${avatarHash}`\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/AvatarToken.vue",
    "content": "<script setup lang=\"ts\">\nwithDefaults(\n  defineProps<{\n    address: string;\n    size?: string;\n  }>(),\n  {\n    size: '22'\n  }\n);\n</script>\n\n<template>\n  <img\n    :src=\"`https://cdn.stamp.fyi/token/eth:${address}?s=100`\"\n    class=\"rounded-full bg-skin-border object-cover\"\n    :style=\"{\n      width: `${Number(size)}px`,\n      height: `${Number(size)}px`,\n      minWidth: `${Number(size)}px`\n    }\"\n    alt=\"Token logo\"\n    @error=\"\n      ($event.target as HTMLImageElement).src =\n        `https://cdn.stamp.fyi/token/eth:${address}?s=100`\n    \"\n  />\n</template>\n"
  },
  {
    "path": "src/components/AvatarUser.vue",
    "content": "<script setup lang=\"ts\">\nimport { getAddress } from '@ethersproject/address';\n\nconst props = withDefaults(\n  defineProps<{\n    address: string;\n    size?: string;\n    previewFile?: File | undefined;\n  }>(),\n  {\n    size: '22',\n    previewFile: undefined\n  }\n);\n\nconst { profilesCreated } = useProfiles();\n\nconst normalizedAddress = computed(() => getAddress(props.address));\n\nconst timestamp = computed(() => {\n  if (\n    !normalizedAddress.value ||\n    !profilesCreated.value?.[normalizedAddress.value]\n  ) {\n    return '';\n  }\n\n  return `&ts=${profilesCreated.value[normalizedAddress.value]}`;\n});\n</script>\n\n<template>\n  <BaseAvatar\n    :src=\"`https://cdn.stamp.fyi/avatar/eth:${normalizedAddress}?s=${\n      Number(size) * 2\n    }${timestamp}`\"\n    :preview-file=\"previewFile\"\n    :size=\"size\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/Banner.vue",
    "content": "<script setup lang=\"ts\">\nconst route = useRoute();\nconst { env, domain } = useApp();\n\nconst link = computed(() => {\n  const baseUrl =\n    env === 'demo'\n      ? 'https://testnet.snapshot.box/#/'\n      : 'https://snapshot.box/#/';\n  let path = 'home';\n  const prefix = env === 'demo' ? `s-tn` : 's';\n\n  switch (route.name) {\n    case 'home': {\n      path = 'explore';\n      break;\n    }\n    case 'spaceProposals': {\n      path = `${prefix}:${route.params.key}`;\n      break;\n    }\n    case 'spaceSettings': {\n      path = `${prefix}:${route.params.key}/settings`;\n      break;\n    }\n    case 'spaceAbout': {\n      path = `${prefix}:${route.params.key}`;\n      break;\n    }\n    case 'spaceProposal': {\n      path = `${prefix}:${route.params.key}/proposal/${route.params.id}`;\n      break;\n    }\n    case 'spaceCreate': {\n      path = `${prefix}:${route.params.key}/create`;\n      break;\n    }\n    case 'spaceDelegates': {\n      path = `${prefix}:${route.params.key}/delegates`;\n      break;\n    }\n    case 'delegate': {\n      path = route.params.key\n        ? `${prefix}:${route.params.key}/delegates`\n        : 'explore';\n      break;\n    }\n    case 'profileAbout': {\n      path = `profile/${route.params.address}`;\n      break;\n    }\n    case 'profileActivity': {\n      path = `profile/${route.params.address}`;\n      break;\n    }\n  }\n\n  return `${baseUrl}${path}`;\n});\n</script>\n\n<template>\n  <a\n    v-if=\"!domain\"\n    :href=\"link\"\n    class=\"flex bg-blue-700 text-white rounded-full px-[10px] py-[2px] gap-1\"\n  >\n    <div class=\"leading-6 hidden md:block\">Switch to the new interface</div>\n    <div class=\"leading-6 md:hidden\">Switch to v2</div>\n    <i-ho-arrow-narrow-right class=\"shrink-0 hidden xs:block\" />\n  </a>\n</template>\n"
  },
  {
    "path": "src/components/BaseAvatar.vue",
    "content": "<script setup lang=\"ts\">\ninterface Props {\n  src: string;\n  size?: string;\n  previewFile?: File | undefined;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  size: '22',\n  previewFile: undefined\n});\n\nconst avatarImage = ref<HTMLImageElement | null>(null);\n\nwatch(\n  () => props.previewFile,\n  () => {\n    // Preview can be used to show a local image instantly (f.e after uploading an image)\n    if (avatarImage.value && props.previewFile) {\n      return (avatarImage.value.src = URL.createObjectURL(props.previewFile));\n    }\n    // This removes the preview image if it's a blob and the previewFile is blank\n    if (avatarImage.value?.src.startsWith('blob') && !props.previewFile) {\n      return (avatarImage.value.src = '');\n    }\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <div>\n    <!-- Show local review image if previewFile is defined -->\n    <img\n      v-show=\"previewFile\"\n      ref=\"avatarImage\"\n      class=\"rounded-full bg-skin-border object-cover\"\n      :style=\"{\n        width: `${Number(size)}px`,\n        height: `${Number(size)}px`,\n        minWidth: `${Number(size)}px`\n      }\"\n      alt=\"avatar\"\n    />\n    <!-- else show image from ipfs or stamp -->\n    <img\n      v-show=\"!previewFile && src\"\n      :src=\"src\"\n      class=\"rounded-full bg-skin-border object-cover\"\n      :style=\"{\n        width: `${Number(size)}px`,\n        height: `${Number(size)}px`,\n        minWidth: `${Number(size)}px`\n      }\"\n      alt=\"avatar\"\n    />\n    <div\n      v-if=\"!src && !previewFile\"\n      class=\"rounded-full bg-skin-border\"\n      :style=\"{\n        width: `${Number(size)}px`,\n        height: `${Number(size)}px`,\n        minWidth: `${Number(size)}px`\n      }\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BaseBadge.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{ address: string; members?: string[] }>();\n\nconst isCore = computed(() => {\n  if (!props.members) return false;\n  const members = props.members.map(address => address.toLowerCase());\n  return members.includes(props.address.toLowerCase());\n});\n</script>\n\n<template>\n  <div\n    v-if=\"isCore\"\n    class=\"ml-1 rounded-full border px-[7px] text-xs text-skin-text leading-[22px]\"\n  >\n    {{ $t('isCore') }}\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BaseBlock.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  title?: string;\n  counter?: number;\n  slim?: boolean;\n  loading?: boolean;\n  hideBottomBorder?: boolean;\n  label?: string;\n  labelTooltip?: string;\n  information?: string;\n  isCollapsable?: boolean;\n  showMoreButton?: boolean;\n  showMoreButtonLabel?: string;\n  loadingMore?: boolean;\n}>();\n\ndefineEmits(['showMore']);\n\nconst isCollapsed = ref(true);\n</script>\n\n<template>\n  <div\n    class=\"border-y border-skin-border bg-skin-block-bg text-base md:rounded-xl md:border\"\n  >\n    <div\n      v-if=\"title\"\n      class=\"group flex h-[57px] justify-between rounded-t-none border-b border-skin-border px-4 pb-[12px] pt-3 md:rounded-t-lg\"\n      :class=\"[\n        {\n          'border-b-0': hideBottomBorder || (isCollapsable && isCollapsed)\n        },\n        { 'cursor-pointer': isCollapsable }\n      ]\"\n      @click=\"isCollapsable ? (isCollapsed = !isCollapsed) : null\"\n    >\n      <h4 class=\"flex items-center\">\n        <div>\n          {{ title }}\n        </div>\n        <IconInformationTooltip\n          :information=\"information\"\n          class=\"ml-1 text-sm text-skin-text\"\n        />\n        <BaseCounter :counter=\"counter\" class=\"ml-1 inline-block\" />\n      </h4>\n      <div class=\"flex items-center\">\n        <div\n          v-if=\"label\"\n          v-tippy=\"{ content: labelTooltip ? labelTooltip : null }\"\n          class=\"text-xs text-skin-link\"\n          :class=\"{ 'cursor-help': labelTooltip }\"\n        >\n          {{ label }}\n        </div>\n      </div>\n      <slot name=\"button\" />\n      <BaseButtonIcon\n        v-if=\"isCollapsable\"\n        class=\"pr-0 group-hover:text-skin-link\"\n      >\n        <i-ho-chevron-up :class=\"[{ 'rotate-180': isCollapsed }]\" />\n      </BaseButtonIcon>\n    </div>\n    <div v-if=\"loading\" class=\"block px-4 py-4\">\n      <LoadingList />\n    </div>\n    <Transition name=\"fade\">\n      <div\n        v-if=\"!loading && (!isCollapsed || !isCollapsable)\"\n        :class=\"!slim && 'p-4'\"\n        class=\"leading-5 sm:leading-6 break-words\"\n      >\n        <slot />\n        <div\n          v-if=\"showMoreButton\"\n          class=\"block rounded-b-none border-t px-4 py-3 text-center md:rounded-b-md\"\n        >\n          <LoadingSpinner v-if=\"loadingMore\" />\n          <button v-else @click=\"$emit('showMore')\">\n            <span v-text=\"$t(showMoreButtonLabel || 'seeMore')\" />\n          </button>\n        </div>\n      </div>\n    </Transition>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BaseBreadcrumbs.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  pages: { id?: string; name: string; to: string; current: boolean }[];\n}>();\n</script>\n\n<template>\n  <div class=\"flex items-center overflow-x-scroll no-scrollbar\">\n    <div v-for=\"(page, i) in pages\" :key=\"i\" class=\"flex items-center\">\n      <router-link v-if=\"!page.current\" :to=\"page.to\" class=\"flex items-center\">\n        <span class=\"text-skin-link truncate max-w-[180px]\">{{\n          page.name\n        }}</span>\n      </router-link>\n      <div v-else class=\"flex cursor-default items-center\">\n        <span\n          class=\"text-skin-link opacity-40 truncate max-w-[180px]\"\n          :class=\"{\n            '!max-w-[320px]': page.id === 'proposal-title'\n          }\"\n          >{{ page.name }}</span\n        >\n      </div>\n      <div class=\"mx-1 flex h-[20px] w-[20px] items-center justify-center\">\n        <i-ho-chevron-right\n          v-if=\"i < pages.length - 1\"\n          class=\"shrink-0 text-xs text-skin-text\"\n        />\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BaseButtonIcon.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  loading?: boolean;\n  isDisabled?: boolean;\n}>();\n</script>\n\n<template>\n  <button\n    type=\"button\"\n    :disabled=\"isDisabled\"\n    :class=\"{ '!cursor-not-allowed': isDisabled }\"\n    class=\"flex items-center rounded-full p-[6px] text-md text-skin-text transition-colors duration-200 hover:text-skin-link\"\n  >\n    <LoadingSpinner v-if=\"loading\" />\n    <slot v-else />\n  </button>\n</template>\n"
  },
  {
    "path": "src/components/BaseButtonRound.vue",
    "content": "<script setup lang=\"ts\">\nwithDefaults(\n  defineProps<{\n    isDisabled?: boolean;\n    size?: string;\n  }>(),\n  {\n    isDisabled: false,\n    size: '46px'\n  }\n);\n</script>\n\n<template>\n  <button\n    :disabled=\"isDisabled\"\n    :class=\"{ '!cursor-not-allowed': isDisabled }\"\n    class=\"flex cursor-pointer select-none items-center justify-center rounded-full border hover:border-skin-text\"\n    :style=\"`width: ${size}; height: ${size}`\"\n  >\n    <slot />\n  </button>\n</template>\n"
  },
  {
    "path": "src/components/BaseCalendar.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  modelValue: string;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst { currentLocale: locale } = useI18n();\n\nconst [\n  yearNow = new Date().getFullYear(),\n  monthNow = new Date().getMonth()\n  // dayNow = new Date().getDate()\n] = props.modelValue ? props.modelValue.split('-') : [];\n\nconst input = ref(props.modelValue);\nconst year = ref(Number(yearNow));\nconst month = ref(Number(monthNow) - 1);\n// const day = ref(dayNow);\n\nconst fullYear = computed(() =>\n  new Date(year.value, month.value).getFullYear()\n);\nconst days = computed(() => new Date(year.value, month.value + 1, 0).getDate());\nconst emptyDays = computed(() => new Date(year.value, month.value, 1).getDay());\n\nconst today = computed(() => {\n  return formatDate(\n    new Date().getFullYear(),\n    new Date().getMonth(),\n    new Date().getDate()\n  );\n});\n\nconst daysOfWeek = computed(() => {\n  const sunday = new Date(2017, 0, 0);\n  return [...Array(7)].map(() => {\n    sunday.setDate(sunday.getDate() + 1);\n    return sunday.toLocaleDateString(locale.value, {\n      weekday: 'short'\n    });\n  });\n});\n\nconst monthName = computed(() => {\n  const name = new Date(year.value, month.value).toLocaleString(locale.value, {\n    month: 'long'\n  });\n  return `${name.charAt(0).toUpperCase()}${name.slice(1)}`;\n});\n\nfunction formatDate(year, month, day) {\n  let date = new Date(year, month, day);\n  const offset = date.getTimezoneOffset();\n  date = new Date(date.getTime() - offset * 60 * 1000);\n  return date.toISOString().split('T')[0];\n}\n\nfunction toggleDay(year, month, day) {\n  input.value = formatDate(year, month, day);\n  emit('update:modelValue', input.value);\n}\n\nfunction isSelectable(year, month, day) {\n  const date = new Date(year, month, day);\n  const dateNow = new Date().setHours(0, 0, 0, 0);\n  return !(dateNow - Number(date) > 0);\n}\n</script>\n\n<template>\n  <div class=\"calendar\">\n    <div class=\"mb-2 flex items-center\">\n      <div class=\"w-1/4 text-left\">\n        <button\n          class=\"iconfont iconback text-lg font-semibold text-skin-text\"\n          @click=\"month--\"\n        />\n      </div>\n      <h4 class=\"h-full w-full text-center\">{{ monthName }} {{ fullYear }}</h4>\n      <div class=\"w-1/4 text-right\">\n        <button\n          class=\"iconfont icongo text-lg font-semibold text-skin-text\"\n          @click=\"month++\"\n        />\n      </div>\n    </div>\n    <div class=\"overflow-hidden border-l border-t\">\n      <div\n        v-for=\"dayOfWeek in daysOfWeek\"\n        :key=\"dayOfWeek\"\n        class=\"day border-b border-r text-skin-link\"\n        v-text=\"dayOfWeek\"\n      />\n      <div\n        v-for=\"emptyDay in emptyDays\"\n        :key=\"`empty-${emptyDay}`\"\n        class=\"day border-b border-r\"\n      />\n      <div v-for=\"day in days\" :key=\"day\">\n        <a\n          v-if=\"isSelectable(year, month, day)\"\n          class=\"day border-b border-r bg-transparent text-skin-link hover:bg-skin-link hover:text-skin-bg\"\n          :class=\"{\n            'ring-1 ring-inset ring-skin-primary':\n              formatDate(year, month, day) === today,\n            '!bg-skin-link !text-skin-bg': input.includes(\n              formatDate(year, month, day)\n            )\n          }\"\n          tabindex=\"0\"\n          @click=\"toggleDay(year, month, day)\"\n          @keypress=\"toggleDay(year, month, day)\"\n          v-text=\"day\"\n        />\n        <div\n          v-else\n          class=\"day cursor-not-allowed border-b border-r text-skin-border\"\n          v-text=\"day\"\n        />\n      </div>\n    </div>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.calendar {\n  width: 309px;\n  margin: 0 auto;\n\n  .day {\n    text-decoration: none;\n    font-size: 17px !important;\n    float: left;\n    text-align: center;\n    line-height: 44px;\n    width: 44px;\n    height: 44px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/BaseCombobox.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  Combobox,\n  ComboboxInput,\n  ComboboxOptions,\n  ComboboxOption,\n  ComboboxLabel,\n  ComboboxButton\n} from '@headlessui/vue';\n\nconst props = defineProps<{\n  label: string;\n  items: { id: number | string; name: string }[];\n  selectedId?: number | string;\n  information?: string;\n  isDisabled?: boolean;\n}>();\n\nconst emit = defineEmits(['select', 'search']);\n\nconst selectedItem = ref<{ id: number | string; name: string }>(props.items[0]);\n\nwatch(selectedItem, () => emit('select', selectedItem.value));\n\nwatch(\n  () => props.selectedId,\n  () => {\n    const selected = props.items.find(item => item.id === props.selectedId);\n    selectedItem.value = selected ? selected : props.items[0];\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <Combobox\n    v-model=\"selectedItem\"\n    :disabled=\"isDisabled\"\n    as=\"div\"\n    class=\"w-full\"\n  >\n    <ComboboxLabel v-if=\"label\" class=\"block\">\n      <LabelInput :information=\"information\">{{ label }}</LabelInput>\n    </ComboboxLabel>\n    <div class=\"relative\">\n      <ComboboxButton class=\"w-full\">\n        <ComboboxInput\n          class=\"s-input w-full py-2 !pr-[30px] pl-3 focus:outline-none\"\n          spellcheck=\"false\"\n          :display-value=\"(item: any) => item.name\"\n          :class=\"{ 'cursor-not-allowed': isDisabled }\"\n          :disabled=\"isDisabled\"\n          @change=\"emit('search', $event.target.value)\"\n        />\n      </ComboboxButton>\n      <ComboboxButton\n        class=\"absolute inset-y-0 right-1 flex items-center px-2 focus:outline-none\"\n        :class=\"{ 'cursor-not-allowed': isDisabled }\"\n      >\n        <i-ho-chevron-down class=\"text-[14px] text-skin-link\" />\n      </ComboboxButton>\n      <ComboboxOptions\n        v-if=\"items.length > 0\"\n        class=\"absolute z-40 mt-1 w-full overflow-hidden rounded-md border border-skin-border bg-skin-bg text-base shadow-lg focus:outline-none sm:text-sm\"\n      >\n        <div class=\"max-h-[180px] overflow-y-auto\">\n          <ComboboxOption\n            v-for=\"item in items\"\n            v-slot=\"{ active, selected, disabled }\"\n            :key=\"item.id\"\n            as=\"template\"\n            :value=\"item\"\n          >\n            <li\n              :class=\"[\n                { 'bg-skin-border': active },\n                'relative cursor-default select-none truncate py-2 pl-3 pr-[50px]'\n              ]\"\n            >\n              <span\n                :class=\"[\n                  selected ? 'font-semibold text-skin-link' : 'font-normal',\n                  { 'text-skin-border': disabled },\n                  'flex w-full items-center truncate'\n                ]\"\n              >\n                <slot v-if=\"$slots.item\" name=\"item\" :item=\"item\" />\n                <span v-else>\n                  {{ item.name }}\n                </span>\n              </span>\n\n              <span\n                v-if=\"selected\"\n                :class=\"['absolute inset-y-0 right-0 flex items-center pr-3']\"\n              >\n                <i-ho-check class=\"text-green\" />\n              </span>\n            </li>\n          </ComboboxOption>\n        </div>\n      </ComboboxOptions>\n    </div>\n  </Combobox>\n</template>\n"
  },
  {
    "path": "src/components/BaseContainer.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  slim?: boolean;\n}>();\n</script>\n\n<template>\n  <div :class=\"slim ? 'px-0 md:px-4' : 'px-4'\" class=\"mx-auto max-w-[1012px]\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BaseCounter.vue",
    "content": "<script setup lang=\"ts\">\nconst { formatNumber } = useIntl();\n\ndefineProps<{\n  counter?: number | string;\n}>();\n</script>\n\n<template>\n  <div\n    v-if=\"(counter && counter >= 0) || typeof counter === 'string'\"\n    class=\"h-[20px] min-w-[20px] rounded-full bg-skin-text px-1 text-center text-xs leading-normal text-white\"\n    v-text=\"formatNumber(Number(counter))\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/BaseIcon.vue",
    "content": "<script setup lang=\"ts\">\nwithDefaults(\n  defineProps<{\n    name: string;\n    size?: string;\n  }>(),\n  {\n    name: '',\n    size: '16'\n  }\n);\n</script>\n\n<template>\n  <i\n    class=\"iconfont\"\n    :class=\"`icon${name}`\"\n    :style=\"size ? `font-size: ${size}px; line-height: ${size}px;` : ''\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/BaseIndicator.vue",
    "content": "<template>\n  <span class=\"inline-block h-[12px] w-[12px] rounded-full bg-skin-primary\" />\n</template>\n"
  },
  {
    "path": "src/components/BaseInput.vue",
    "content": "<script lang=\"ts\">\nexport default {\n  inheritAttrs: false\n};\n</script>\n\n<script setup lang=\"ts\">\nimport { FormError } from '@/helpers/interfaces';\n\nconst props = withDefaults(\n  defineProps<{\n    type?: 'text' | 'number' | 'email';\n    modelValue?: string | number;\n    definition?: any;\n    error?: FormError | null;\n    focusOnMount?: boolean;\n    hideInput?: boolean;\n    placeholder?: string;\n    title?: string;\n    maxLength?: number;\n    readonly?: boolean;\n    information?: string;\n    loading?: boolean;\n    isDisabled?: boolean;\n    success?: boolean;\n    failed?: boolean;\n  }>(),\n  {\n    type: 'text',\n    modelValue: undefined,\n    definition: undefined,\n    error: null,\n    focusOnMount: false,\n    hideInput: false,\n    placeholder: undefined,\n    title: undefined,\n    maxLength: undefined,\n    readonly: false,\n    information: undefined,\n    loading: false,\n    isDisabled: false,\n    success: false,\n    failed: false\n  }\n);\n\ndefineEmits(['update:modelValue']);\n\nconst BaseInputEL = ref<HTMLDivElement | undefined>(undefined);\n\nconst visited = ref(false);\n\nconst showErrorMessage = computed(() => visited.value || props.error?.push);\n\nonMounted(() => {\n  if (props.focusOnMount) {\n    BaseInputEL?.value?.focus();\n  }\n});\n</script>\n\n<template>\n  <div class=\"w-full\">\n    <LabelInput v-if=\"title || definition?.title\" :information=\"information\">\n      {{ title ?? definition.title }}\n    </LabelInput>\n\n    <div class=\"group relative z-10\">\n      <div\n        v-if=\"$slots.before\"\n        class=\"pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3\"\n      >\n        <slot name=\"before\" />\n      </div>\n      <input\n        v-bind=\"$attrs\"\n        ref=\"BaseInputEL\"\n        :type=\"type\"\n        :value=\"modelValue\"\n        :class=\"[\n          's-input !h-[42px]',\n          { '!border-red': error?.message && showErrorMessage },\n          { 'cursor-not-allowed placeholder:!opacity-30': isDisabled }\n        ]\"\n        :maxlength=\"maxLength ?? definition?.maxLength\"\n        :placeholder=\"placeholder ?? definition?.examples?.[0] ?? ''\"\n        :readonly=\"readonly\"\n        :disabled=\"isDisabled\"\n        @blur=\"error?.message ? (visited = true) : null\"\n        @focus=\"error?.message ? null : (visited = false)\"\n        @input=\"\n          $emit('update:modelValue', ($event.target as HTMLInputElement).value)\n        \"\n      />\n      <div\n        v-if=\"loading || success || failed\"\n        class=\"absolute inset-y-0 right-0 top-[1px] mr-1 hidden h-[40px] items-center overflow-hidden rounded-r-full bg-skin-bg pl-2 pr-2 group-focus-within:flex\"\n      >\n        <LoadingSpinner v-if=\"loading\" class=\"pb-[3px]\" />\n        <i-ho-check v-if=\"success\" class=\"text-md text-green\" />\n        <i-ho-x v-if=\"failed\" class=\"text-sm text-red\" />\n      </div>\n      <div\n        v-else-if=\"$slots.after\"\n        class=\"absolute inset-y-0 right-0 flex items-center pr-4\"\n      >\n        <slot name=\"after\" />\n      </div>\n    </div>\n    <div\n      :class=\"[\n        's-error',\n        !!error?.message && showErrorMessage\n          ? '-mt-[21px] opacity-100'\n          : '-mt-[40px] h-6 opacity-0'\n      ]\"\n    >\n      <BaseIcon\n        v-if=\"error?.message && showErrorMessage\"\n        name=\"warning\"\n        class=\"mr-2 text-white\"\n      />\n      {{ error?.message }}\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BaseInterpunct.vue",
    "content": "<template>\n  <div class=\"mx-2 bg-skin-text w-1 h-1 opacity-60 rounded-full\" />\n</template>\n"
  },
  {
    "path": "src/components/BaseLink.vue",
    "content": "<script setup lang=\"ts\">\nimport { sanitizeUrl } from '@braintree/sanitize-url';\n\ntype Link = Record<string, any> | string;\n\ndefineProps<{\n  link: Link;\n  hideExternalIcon?: boolean;\n  disabled?: boolean;\n}>();\n</script>\n\n<template>\n  <a\n    v-if=\"typeof link === 'string'\"\n    :href=\"sanitizeUrl(link)\"\n    target=\"_blank\"\n    :class=\"['whitespace-nowrap', { 'pointer-events-none': disabled }]\"\n    rel=\"noopener noreferrer\"\n  >\n    <slot />\n    <i-ho-external-link\n      v-if=\"!hideExternalIcon\"\n      class=\"mb-[2px] ml-1 inline-block text-xs\"\n    />\n  </a>\n  <router-link\n    v-else\n    :to=\"link\"\n    :class=\"['whitespace-nowrap', { 'pointer-events-none': disabled }]\"\n  >\n    <slot />\n  </router-link>\n</template>\n"
  },
  {
    "path": "src/components/BaseListbox.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  Listbox,\n  ListboxButton,\n  ListboxOptions,\n  ListboxOption,\n  ListboxLabel\n} from '@headlessui/vue';\nimport isEqual from 'lodash/isEqual';\n\ntype ListboxItem = {\n  value: any;\n  title?: string;\n  extras?: Record<string, any>;\n};\n\nconst props = defineProps<{\n  items: ListboxItem[];\n  modelValue: any;\n  label?: string;\n  disableInput?: boolean;\n  definition?: any;\n  information?: string;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst selectedItem = computed({\n  get: () =>\n    props.items.find(item => isEqual(item.value, props.modelValue)) ||\n    props.items[0],\n  set: newVal => emit('update:modelValue', newVal.value)\n});\n</script>\n\n<template>\n  <Listbox v-model=\"selectedItem\" as=\"div\" :disabled=\"disableInput\">\n    <ListboxLabel v-if=\"label || definition?.title\">\n      <LabelInput :information=\"information || definition?.description\">\n        {{ label || definition?.title }}\n      </LabelInput>\n    </ListboxLabel>\n    <div class=\"relative\">\n      <ListboxButton\n        class=\"relative h-[46px] w-full truncate rounded-full border border-skin-border pl-3 pr-[40px] text-left text-skin-link hover:border-skin-text\"\n        :class=\"{ 'cursor-not-allowed text-skin-border': disableInput }\"\n      >\n        <slot\n          v-if=\"$slots.selected\"\n          name=\"selected\"\n          :selected-item=\"selectedItem\"\n        />\n\n        <span v-else-if=\"selectedItem\">\n          {{ selectedItem?.title || selectedItem.value }}\n        </span>\n        <span\n          class=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-[12px]\"\n        >\n          <i-ho-chevron-down class=\"text-[14px] text-skin-link\" />\n        </span>\n      </ListboxButton>\n      <transition\n        enter-active-class=\"transition duration-100 ease-out\"\n        enter-from-class=\"transform -translate-y-2 scale-95 opacity-0\"\n        enter-to-class=\"transform scale-100 opacity-100\"\n        leave-active-class=\"transition duration-75 ease-out\"\n        leave-from-class=\"transform scale-100 opacity-100\"\n        leave-to-class=\"transform scale-95 opacity-0\"\n      >\n        <ListboxOptions\n          class=\"absolute z-40 mt-1 w-full overflow-hidden rounded-md border border-skin-border bg-skin-bg text-base shadow-lg focus:outline-none sm:text-sm\"\n        >\n          <div class=\"max-h-[180px] overflow-y-auto\">\n            <ListboxOption\n              v-for=\"item in items\"\n              :key=\"item.value\"\n              v-slot=\"{ active, selected, disabled }\"\n              as=\"template\"\n              :value=\"item\"\n            >\n              <li\n                :class=\"[\n                  { 'bg-skin-border': active },\n                  'relative cursor-default select-none py-2 pl-3 pr-[50px]'\n                ]\"\n              >\n                <span\n                  :class=\"[\n                    selected ? 'font-semibold text-skin-link' : 'font-normal',\n                    { 'text-skin-border': disabled },\n                    'block truncate'\n                  ]\"\n                >\n                  <slot v-if=\"$slots.item\" name=\"item\" :item=\"item\" />\n                  <span v-else>\n                    {{ item?.title || item.value }}\n                  </span>\n                </span>\n\n                <span\n                  v-if=\"selected\"\n                  :class=\"['absolute inset-y-0 right-0 flex items-center pr-3']\"\n                >\n                  <i-ho-check class=\"text-green\" />\n                </span>\n              </li>\n            </ListboxOption>\n          </div>\n        </ListboxOptions>\n      </transition>\n    </div>\n  </Listbox>\n</template>\n"
  },
  {
    "path": "src/components/BaseLoading.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{ block?: boolean }>();\n</script>\n\n<template>\n  <BaseBlock v-if=\"block\" slim>\n    <slot />\n  </BaseBlock>\n  <div v-else>\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BaseMarkdown.vue",
    "content": "<script setup lang=\"ts\">\nimport { Remarkable } from 'remarkable';\nimport { linkify } from 'remarkable/linkify';\n// import sanitizeHtml from 'sanitize-html';\nimport { getIpfsUrl } from '@/helpers/utils';\nimport { isSnapshotUrl } from '@/helpers/utils';\n\nconst props = defineProps<{\n  body: string;\n}>();\nconst { copyToClipboard } = useCopy();\n\nconst showModal = ref(false);\nconst clickedUrl = ref('');\n\nconst remarkable = new Remarkable({\n  html: false,\n  breaks: true,\n  typographer: false,\n  linkTarget: '_blank'\n}).use(linkify);\n\nconst markdown = computed(() => {\n  let body = props.body;\n\n  // Add the ipfs gateway to markdown images that start with ipfs://\n  function replaceIpfsUrl(match, p1) {\n    return match.replace(p1, getIpfsUrl(p1));\n  }\n  body = body.replace(/!\\[.*?\\]\\((ipfs:\\/\\/[a-zA-Z0-9]+?)\\)/g, replaceIpfsUrl);\n\n  // if body contains a link that contain `_` , replace it with `\\_` to escape it\n  body = body.replace(/(http.*?)(?=_)/g, '$1\\\\');\n  return remarkable.render(body);\n});\n\nfunction handleLinkClick(e, url) {\n  e.preventDefault();\n  clickedUrl.value = url;\n\n  if (isSnapshotUrl(url)) {\n    return handleConfirm();\n  }\n\n  showModal.value = true;\n}\n\nfunction handleConfirm() {\n  window.open(clickedUrl.value, '_blank', 'noopener,noreferrer');\n}\n\nonMounted(() => {\n  const body = document.querySelector('.markdown-body');\n  if (body !== null) {\n    body.querySelectorAll('pre>code').forEach(function (code) {\n      const parent = code.parentElement;\n      if (parent !== null) parent.classList.add('rounded-lg');\n      const copyButton = document.createElement('a');\n      const icon = document.createElement('i');\n      icon.classList.add('copy');\n      icon.classList.add('text-skin-text');\n      icon.classList.add('iconcopy');\n      icon.classList.add('iconfont');\n      copyButton.appendChild(icon);\n      copyButton.addEventListener('click', function () {\n        if (parent !== null) copyToClipboard(parent.innerText.trim());\n      });\n      code.appendChild(copyButton);\n    });\n    body.querySelectorAll('a[href]').forEach(function (link) {\n      link.addEventListener('click', function (e) {\n        handleLinkClick(e, link.getAttribute('href'));\n      });\n    });\n  }\n});\n</script>\n\n<template>\n  <!-- eslint-disable vue/no-v-html -->\n  <div\n    v-viewer\n    v-bind=\"$attrs\"\n    class=\"markdown-body break-words\"\n    v-html=\"markdown\"\n  />\n  <Teleport to=\"#modal\">\n    <ModalLinkPreview\n      :open=\"showModal\"\n      :clicked-url=\"clickedUrl\"\n      @close=\"showModal = false\"\n      @confirm=\"handleConfirm\"\n    />\n  </Teleport>\n</template>\n\n<style lang=\"scss\">\n.markdown-body {\n  font-size: 19px;\n  line-height: 1.3;\n  word-wrap: break-word;\n}\n\n.markdown-body::before {\n  display: table;\n  content: '';\n}\n\n.markdown-body blockquote {\n  color: var(--text-color);\n  border-left-color: var(--text-color);\n}\n\n.markdown-body::after {\n  display: table;\n  clear: both;\n  content: '';\n}\n\n.markdown-body > *:first-child {\n  margin-top: 0 !important;\n}\n\n.markdown-body > *:last-child {\n  margin-bottom: 0 !important;\n}\n\n.markdown-body a:not([href]) {\n  color: inherit;\n  text-decoration: none;\n}\n\n.markdown-body .absent {\n  color: #cb2431;\n}\n\n.markdown-body .anchor {\n  float: left;\n  padding-right: 4px;\n  margin-left: -20px;\n  line-height: 1;\n}\n\n.markdown-body .anchor:focus {\n  outline: none;\n}\n\n.markdown-body p,\n.markdown-body blockquote,\n.markdown-body ul,\n.markdown-body ol,\n.markdown-body dl,\n.markdown-body table,\n.markdown-body pre {\n  margin-top: 0;\n  margin-bottom: 16px;\n}\n\n.markdown-body hr {\n  height: 0.25em;\n  padding: 0;\n  margin: 24px 0;\n  background-color: #e1e4e8;\n  border: 0;\n}\n\n.markdown-body blockquote {\n  padding: 0 1em;\n  color: #6a737d;\n  border-left: 0.25em solid #dfe2e5;\n}\n\n.markdown-body blockquote > :first-child {\n  margin-top: 0;\n}\n\n.markdown-body blockquote > :last-child {\n  margin-bottom: 0;\n}\n\n.markdown-body kbd {\n  display: inline-block;\n  padding: 3px 5px;\n  font-size: 11px;\n  line-height: 10px;\n  color: #444d56;\n  vertical-align: middle;\n  background-color: #fafbfc;\n  border: solid 1px #c6cbd1;\n  border-bottom-color: #959da5;\n  border-radius: 3px;\n  box-shadow: inset 0 -1px 0 #959da5;\n}\n\n.markdown-body h1,\n.markdown-body h2,\n.markdown-body h3,\n.markdown-body h4,\n.markdown-body h5,\n.markdown-body h6 {\n  margin-top: 24px;\n  margin-bottom: 16px;\n  font-weight: 600;\n  line-height: 1.4 !important;\n}\n\n.markdown-body h1 .octicon-link,\n.markdown-body h2 .octicon-link,\n.markdown-body h3 .octicon-link,\n.markdown-body h4 .octicon-link,\n.markdown-body h5 .octicon-link,\n.markdown-body h6 .octicon-link {\n  color: #1b1f23;\n  vertical-align: middle;\n  visibility: hidden;\n}\n\n.markdown-body h1:hover .anchor,\n.markdown-body h2:hover .anchor,\n.markdown-body h3:hover .anchor,\n.markdown-body h4:hover .anchor,\n.markdown-body h5:hover .anchor,\n.markdown-body h6:hover .anchor {\n  text-decoration: none;\n}\n\n.markdown-body h1:hover .anchor .octicon-link,\n.markdown-body h2:hover .anchor .octicon-link,\n.markdown-body h3:hover .anchor .octicon-link,\n.markdown-body h4:hover .anchor .octicon-link,\n.markdown-body h5:hover .anchor .octicon-link,\n.markdown-body h6:hover .anchor .octicon-link {\n  visibility: visible;\n}\n\n.markdown-body h1 tt,\n.markdown-body h1 code,\n.markdown-body h2 tt,\n.markdown-body h2 code,\n.markdown-body h3 tt,\n.markdown-body h3 code,\n.markdown-body h4 tt,\n.markdown-body h4 code,\n.markdown-body h5 tt,\n.markdown-body h5 code,\n.markdown-body h6 tt,\n.markdown-body h6 code {\n  font-size: inherit;\n}\n\n.markdown-body h1 {\n  font-size: 1.5em;\n}\n\n.markdown-body h2 {\n  font-size: 1.25em;\n}\n\n.markdown-body h3 {\n  font-size: 1em;\n}\n\n.markdown-body h4 {\n  font-size: 0.875em;\n}\n\n.markdown-body h5 {\n  font-size: 0.85em;\n}\n\n.markdown-body h6 {\n  font-size: 0.8em;\n}\n\n.markdown-body ul,\n.markdown-body ol {\n  padding-left: 2em;\n}\n\n.markdown-body ul.no-list,\n.markdown-body ol.no-list {\n  padding: 0;\n  list-style-type: none;\n}\n\n.markdown-body ul {\n  list-style-type: disc;\n}\n\n.markdown-body ol {\n  list-style-type: decimal;\n}\n\n.markdown-body ul ul,\n.markdown-body ul ol,\n.markdown-body ol ol,\n.markdown-body ol ul {\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.markdown-body li {\n  word-wrap: break-all;\n}\n\n.markdown-body li > p {\n  margin-top: 16px;\n}\n\n.markdown-body li + li {\n  margin-top: 0.25em;\n}\n\n.markdown-body dl {\n  padding: 0;\n}\n\n.markdown-body dl dt {\n  padding: 0;\n  margin-top: 16px;\n  font-size: 1em;\n  font-style: italic;\n  font-weight: 600;\n}\n\n.markdown-body dl dd {\n  padding: 0 16px;\n  margin-bottom: 16px;\n}\n\n.markdown-body table {\n  display: block;\n  width: 100%;\n  overflow: auto;\n}\n\n.markdown-body table th {\n  font-weight: 600;\n}\n\n.markdown-body table th,\n.markdown-body table td {\n  padding: 6px 13px;\n  border: 1px solid var(--border-color);\n}\n\n.markdown-body table thead tr,\n.markdown-body table tbody tr:nth-child(2n) {\n  background-color: var(--bg-color);\n  border-top: 1px solid #c6cbd1;\n}\n\n.markdown-body table tbody tr {\n  background-color: var(--bg-color);\n}\n\n.markdown-body table img {\n  background-color: transparent;\n}\n\n.markdown-body img {\n  max-width: 100%;\n  box-sizing: content-box;\n  background-color: #fff;\n  cursor: pointer;\n}\n\n.markdown-body img[align='right'] {\n  padding-left: 20px;\n}\n\n.markdown-body img[align='left'] {\n  padding-right: 20px;\n}\n\n.markdown-body .emoji {\n  max-width: none;\n  vertical-align: text-top;\n  background-color: transparent;\n}\n\n.markdown-body span.frame {\n  display: block;\n  overflow: hidden;\n}\n\n.markdown-body span.frame > span {\n  display: block;\n  float: left;\n  width: auto;\n  padding: 7px;\n  margin: 13px 0 0;\n  overflow: hidden;\n  border: 1px solid #dfe2e5;\n}\n\n.markdown-body span.frame span img {\n  display: block;\n  float: left;\n}\n\n.markdown-body span.frame span span {\n  display: block;\n  padding: 5px 0 0;\n  clear: both;\n  color: #24292e;\n}\n\n.markdown-body span.align-center {\n  display: block;\n  overflow: hidden;\n  clear: both;\n}\n\n.markdown-body span.align-center > span {\n  display: block;\n  margin: 13px auto 0;\n  overflow: hidden;\n  text-align: center;\n}\n\n.markdown-body span.align-center span img {\n  margin: 0 auto;\n  text-align: center;\n}\n\n.markdown-body span.align-right {\n  display: block;\n  overflow: hidden;\n  clear: both;\n}\n\n.markdown-body span.align-right > span {\n  display: block;\n  margin: 13px 0 0;\n  overflow: hidden;\n  text-align: right;\n}\n\n.markdown-body span.align-right span img {\n  margin: 0;\n  text-align: right;\n}\n\n.markdown-body span.float-left {\n  display: block;\n  float: left;\n  margin-right: 13px;\n  overflow: hidden;\n}\n\n.markdown-body span.float-left span {\n  margin: 13px 0 0;\n}\n\n.markdown-body span.float-right {\n  display: block;\n  float: right;\n  margin-left: 13px;\n  overflow: hidden;\n}\n\n.markdown-body span.float-right > span {\n  display: block;\n  margin: 13px auto 0;\n  overflow: hidden;\n  text-align: right;\n}\n\n.markdown-body code,\n.markdown-body tt {\n  padding: 0.2em 0.4em;\n  margin: 0;\n  background-color: rgba(27, 31, 35, 0.05);\n  border-radius: 3px;\n  font-size: 16px;\n}\n\n.markdown-body code br,\n.markdown-body tt br {\n  display: none;\n}\n\n.markdown-body del code {\n  text-decoration: inherit;\n}\n\n.markdown-body pre {\n  font-size: 16px;\n  word-wrap: normal;\n}\n\n.markdown-body pre {\n  color: var(--link-color);\n  background-color: var(--border-color);\n  position: relative;\n}\n\n.markdown-body pre > code {\n  .copy {\n    font-size: 24px;\n    line-height: 24px;\n    position: absolute;\n    right: 1.2rem;\n    top: 1.2rem;\n  }\n}\n\n.markdown-body pre > code {\n  padding: 0;\n  margin: 0;\n  word-break: normal;\n  white-space: pre;\n  background: transparent;\n  border: 0;\n}\n\n.markdown-body .highlight {\n  margin-bottom: 16px;\n}\n\n.markdown-body .highlight pre {\n  margin-bottom: 0;\n  word-break: normal;\n}\n\n.markdown-body .highlight pre,\n.markdown-body pre {\n  padding: 16px;\n  overflow: auto;\n}\n\n.markdown-body pre code,\n.markdown-body pre tt {\n  display: inline;\n  max-width: auto;\n  padding: 0;\n  margin: 0;\n  overflow: visible;\n  line-height: inherit;\n  word-wrap: normal;\n  background-color: transparent;\n  border: 0;\n}\n\n.markdown-body .csv-data td,\n.markdown-body .csv-data th {\n  padding: 5px;\n  overflow: hidden;\n  font-size: 12px;\n  line-height: 1;\n  text-align: left;\n  white-space: nowrap;\n}\n\n.markdown-body .csv-data .blob-num {\n  padding: 10px 8px 9px;\n  text-align: right;\n  background: #fff;\n  border: 0;\n}\n\n.markdown-body .csv-data tr {\n  border-top: 0;\n}\n\n.markdown-body .csv-data th {\n  font-weight: 600;\n  background: #f6f8fa;\n  border-top: 0;\n}\n\n@media (min-width: 544px) {\n  .markdown-body {\n    font-size: 22px;\n    line-height: 1.4;\n  }\n\n  .markdown-body h1 {\n    font-size: 2em;\n  }\n\n  .markdown-body h2 {\n    font-size: 1.5em;\n  }\n\n  .markdown-body h3 {\n    font-size: 1.25em;\n  }\n\n  .markdown-body h4 {\n    font-size: 1em;\n  }\n\n  .markdown-body h5 {\n    font-size: 0.875em;\n  }\n\n  .markdown-body h6 {\n    font-size: 0.85em;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/BaseMenu.vue",
    "content": "<script setup lang=\"ts\">\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';\nimport { Float } from '@headlessui-float/vue';\nimport type { Placement } from '@floating-ui/dom';\n\ntype Item = {\n  text: string;\n  action: string;\n  extras?: any;\n};\n\nwithDefaults(\n  defineProps<{\n    items: Item[];\n    selected?: string;\n    placement?: Placement;\n  }>(),\n  {\n    selected: '',\n    placement: 'bottom-end'\n  }\n);\n\nconst emit = defineEmits(['select']);\n</script>\n\n<template>\n  <Menu as=\"div\" class=\"inline-block h-full text-left\">\n    <Float\n      portal\n      enter=\"transition ease-out duration-100\"\n      enter-from=\"transform opacity-0 scale-95\"\n      enter-to=\"transform opacity-100 scale-100\"\n      leave=\"transition ease-in duration-75\"\n      leave-from=\"transform opacity-100 scale-100\"\n      leave-to=\"transform opacity-0 scale-95\"\n      :placement=\"placement\"\n      :offset=\"8\"\n      :shift=\"16\"\n      :flip=\"16\"\n      :z-index=\"50\"\n    >\n      <MenuButton as=\"template\">\n        <slot v-if=\"$slots.button\" name=\"button\" />\n\n        <TuneButton v-else class=\"flex items-center\">\n          {{ selected }}\n          <i-ho-chevron-down\n            class=\"-mr-1 ml-1 text-xs text-skin-link\"\n            aria-hidden=\"true\"\n          />\n        </TuneButton>\n      </MenuButton>\n\n      <MenuItems\n        class=\"overflow-hidden rounded-2xl border bg-skin-header-bg shadow-lg outline-none\"\n      >\n        <div class=\"no-scrollbar max-h-[300px] overflow-auto\">\n          <MenuItem v-for=\"item in items\" :key=\"item.text\" v-slot=\"{ active }\">\n            <div\n              :class=\"[\n                active\n                  ? 'bg-skin-border text-skin-link'\n                  : 'bg-skin-header-bg text-skin-text',\n                'cursor-pointer whitespace-nowrap px-3 py-2'\n              ]\"\n              @click=\"emit('select', item.action)\"\n            >\n              <slot :key=\"item\" name=\"item\" :item=\"item\">\n                {{ item.text }}\n              </slot>\n            </div>\n          </MenuItem>\n        </div>\n      </MenuItems>\n    </Float>\n  </Menu>\n</template>\n"
  },
  {
    "path": "src/components/BaseMessage.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  level: 'info' | 'warning' | 'warning-red';\n}>();\n</script>\n\n<template>\n  <div>\n    <i-ho-information-circle\n      v-if=\"level === 'info'\"\n      class=\"float-left mr-1 text-sm\"\n    />\n    <i-ho-exclamation-circle\n      v-else\n      class=\"float-left mr-1 text-sm\"\n      :class=\"{ 'text-red': level === 'warning-red' }\"\n    />\n    <div class=\"leading-5\" :class=\"{ 'text-red': level === 'warning-red' }\">\n      <slot />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BaseMessageBlock.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  level: 'info' | 'warning' | 'warning-red';\n  isResponsive?: boolean;\n}>();\n</script>\n\n<template>\n  <BaseBlock\n    :class=\"[\n      'rounded-xl border text-skin-text',\n      { '!border-skin-text': level === 'warning' },\n      { '!border-red': level === 'warning-red' },\n      {\n        '!rounded-none border-x-0 md:!rounded-xl': isResponsive\n      }\n    ]\"\n  >\n    <BaseMessage :level=\"level\">\n      <slot />\n    </BaseMessage>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/BaseModal.vue",
    "content": "<script setup lang=\"ts\">\nimport { useWindowSize } from '@vueuse/core';\n\nconst props = withDefaults(\n  defineProps<{\n    open: boolean;\n    hideClose?: boolean;\n    maxHeight?: string;\n  }>(),\n  {\n    hideClose: false,\n    maxHeight: '420px'\n  }\n);\n\nconst emit = defineEmits(['close']);\n\nconst { open } = toRefs(props);\n\nconst { height } = useWindowSize();\n\nconst heightStyle = computed(() => {\n  return `${height.value}px !important`;\n});\n\nfunction onKeydown(e: KeyboardEvent) {\n  if (e.key === 'Escape') emit('close');\n}\n\nwatch(open, isOpen => {\n  document.body.classList[isOpen ? 'add' : 'remove']('overflow-hidden');\n  if (isOpen) document.addEventListener('keydown', onKeydown);\n  else document.removeEventListener('keydown', onKeydown);\n});\n\nonBeforeUnmount(() => {\n  document.body.classList.remove('overflow-hidden');\n});\n</script>\n\n<template>\n  <Transition name=\"fade\">\n    <div v-if=\"open\" class=\"modal z-50 mx-auto w-screen\">\n      <div class=\"backdrop\" @click=\"emit('close')\" />\n      <div class=\"shell relative overflow-hidden rounded-none md:rounded-3xl\">\n        <div v-if=\"$slots.header\" class=\"pt-3 text-center\">\n          <slot name=\"header\" />\n        </div>\n        <div class=\"modal-body\">\n          <slot :max-height=\"maxHeight\" />\n        </div>\n        <div v-if=\"$slots.footer\" class=\"border-t p-4 text-center\">\n          <slot name=\"footer\" />\n        </div>\n        <BaseButtonIcon\n          v-if=\"!hideClose\"\n          class=\"absolute right-[20px] top-[20px]\"\n          @click=\"emit('close')\"\n        >\n          <i-ho-x class=\"text-[17px]\" />\n        </BaseButtonIcon>\n      </div>\n    </div>\n  </Transition>\n</template>\n\n<style lang=\"scss\">\n.modal {\n  position: fixed;\n  display: flex;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  align-items: center;\n  justify-content: center;\n  z-index: 40;\n\n  .backdrop {\n    position: fixed;\n    top: 0;\n    bottom: 0;\n    left: 0;\n    right: 0;\n    z-index: 99;\n    background: rgba(0, 0, 0, 0.4);\n  }\n\n  .shell {\n    background-color: var(--bg-color);\n    padding-left: 0 !important;\n    padding-right: 0 !important;\n    max-width: 440px;\n    overflow-y: auto !important;\n    max-height: calc(100vh - 120px);\n    display: flex;\n    flex-direction: column;\n    z-index: 999;\n    margin: 0 auto;\n    width: 100%;\n\n    @media (max-width: 767px) {\n      border: 0;\n      width: 100% !important;\n      max-width: 100% !important;\n      height: 100% !important;\n      max-height: v-bind(heightStyle);\n      min-height: v-bind(heightStyle);\n      margin-bottom: 0 !important;\n\n      .modal-body {\n        max-height: 100% !important;\n      }\n    }\n\n    .modal-body {\n      max-height: v-bind(maxHeight);\n      flex: auto;\n      text-align: initial;\n      overflow-y: auto;\n      overflow-x: hidden;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/BaseModalSelectItem.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  selected?: boolean;\n  title: string;\n  tag?: string;\n  description?: string;\n  disabled?: boolean;\n}>();\n</script>\n\n<template>\n  <BaseBlock\n    :class=\"[\n      'transition-colors hover:border-skin-text cursor-pointer',\n      {\n        '!border-skin-link': selected,\n        'hover:!border-skin-border hover:!cursor-not-allowed': disabled\n      }\n    ]\"\n  >\n    <div class=\"relative inset-y-0 flex items-center\">\n      <div :class=\"['w-full text-left', { 'pr-[44px]': selected }]\">\n        <div class=\"mb-2 flex items-center gap-2\">\n          <h3\n            :class=\"[\n              'mb-0 truncate',\n              { 'mt-0': description, 'text-skin-text': disabled }\n            ]\"\n            v-text=\"title\"\n          />\n          <BasePill>{{ tag }}</BasePill>\n        </div>\n        <span class=\"break-all text-skin-text\" v-text=\"description\" />\n      </div>\n      <i-ho-check v-if=\"selected\" class=\"absolute right-0 text-md\" />\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/BaseNetworkItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { getIpfsUrl } from '@/helpers/utils';\n\nconst { formatCompactNumber } = useIntl();\n\nconst { networksSpacesCount } = useNetworksFilter();\n\ndefineProps<{\n  network: { logo: string; name: string; key: string };\n}>();\n</script>\n\n<template>\n  <BaseBlock>\n    <div class=\"mb-3 flex items-start\">\n      <BaseAvatar class=\"mr-2\" :src=\"getIpfsUrl(network.logo)\" size=\"28\" />\n      <div class=\"overflow-hidden\">\n        <h3 class=\"my-0 truncate leading-5\" v-text=\"network.name\" />\n        <div\n          class=\"text-xs leading-4 text-skin-text\"\n          v-text=\"'Chain #' + network.key\"\n        />\n      </div>\n    </div>\n    <div class=\"text-skin-text\">\n      {{\n        $tc('inSpaces', [\n          formatCompactNumber(networksSpacesCount?.[network.key] ?? 0)\n        ])\n      }}\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/BaseNoResults.vue",
    "content": "<template>\n  <div class=\"py-[20px] text-center text-skin-link md:py-5\">\n    <i-ho-emoji-sad class=\"mx-auto\" />\n    <div class=\"mt-2 text-base\">\n      {{ $t('noResultsFound') }}\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BasePill.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <span\n    class=\"rounded-full bg-skin-text px-2 text-center text-xs leading-5 text-white\"\n  >\n    <slot />\n  </span>\n</template>\n"
  },
  {
    "path": "src/components/BasePluginItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { getIpfsUrl } from '@/helpers/utils';\nimport { PluginIndex } from '@/helpers/interfaces';\n\nconst { formatCompactNumber } = useIntl();\nconst { pluginsSpacesCount } = usePlugins();\n\ndefineProps<{\n  plugin: PluginIndex;\n}>();\n</script>\n\n<template>\n  <BaseBlock>\n    <div class=\"mb-2 flex items-center\">\n      <BaseAvatar\n        v-if=\"plugin?.icon\"\n        class=\"mr-2\"\n        :src=\"getIpfsUrl(plugin.icon)\"\n        size=\"28\"\n      />\n      <h3 class=\"m-0 truncate\" v-text=\"plugin.name\" />\n      <div class=\"ml-1\">v{{ plugin.version }}</div>\n    </div>\n    <div class=\"flex items-end justify-between text-skin-text\">\n      <div class=\"flex flex-col\">\n        <div class=\"text-skin-text\">\n          <BaseIcon name=\"github\" class=\"mr-1\" />\n          {{ plugin.author }}\n        </div>\n        {{\n          $tc('inSpaces', [\n            formatCompactNumber(pluginsSpacesCount?.[plugin.key] ?? 0)\n          ])\n        }}\n      </div>\n\n      <BaseLink\n        :link=\"`https://github.com/snapshot-labs/snapshot/tree/master/src/plugins/${plugin.key}`\"\n        @click.stop\n      >\n        {{ $t('learnMore') }}\n      </BaseLink>\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/BasePopover.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  Popover,\n  PopoverButton,\n  PopoverPanel,\n  FocusTrap\n} from '@headlessui/vue';\nimport { Float } from '@headlessui-float/vue';\nimport type { Placement } from '@floating-ui/dom';\n\nwithDefaults(\n  defineProps<{\n    label?: string;\n    placement?: Placement;\n    disabled?: boolean;\n  }>(),\n  {\n    label: '',\n    placement: 'bottom-end',\n    disabled: false\n  }\n);\n</script>\n\n<template>\n  <Popover>\n    <Float\n      enter=\"transition ease-out duration-100\"\n      enter-from=\"transform opacity-0 scale-95\"\n      enter-to=\"transform opacity-100 scale-100\"\n      leave=\"transition ease-in duration-75\"\n      leave-from=\"transform opacity-100 scale-100\"\n      leave-to=\"transform opacity-0 scale-95\"\n      :placement=\"placement\"\n      :offset=\"4\"\n      :shift=\"16\"\n      :flip=\"16\"\n      :z-index=\"50\"\n      portal\n    >\n      <PopoverButton\n        as=\"template\"\n        :disabled=\"disabled\"\n        :class=\"[{ 'cursor-not-allowed': disabled }]\"\n      >\n        <slot v-if=\"$slots.button\" name=\"button\" />\n        <TuneButton v-else class=\"flex items-center\">\n          <span>{{ label }}</span>\n          <i-ho-chevron-down\n            class=\"ml-2 h-5 w-5 text-skin-link\"\n            aria-hidden=\"true\"\n          />\n        </TuneButton>\n      </PopoverButton>\n\n      <PopoverPanel\n        v-slot=\"{ close }\"\n        class=\"w-screen max-w-xs outline-none sm:max-w-sm\"\n      >\n        <div\n          class=\"overflow-hidden rounded-2xl border bg-skin-header-bg shadow-lg\"\n        >\n          <div\n            class=\"no-scrollbar max-h-[85vh] overflow-y-auto overscroll-contain\"\n          >\n            <FocusTrap>\n              <span tabindex=\"0\"></span>\n              <slot name=\"content\" :close=\"close\" />\n            </FocusTrap>\n          </div>\n        </div>\n      </PopoverPanel>\n    </Float>\n  </Popover>\n</template>\n"
  },
  {
    "path": "src/components/BasePopoverHover.vue",
    "content": "<script setup lang=\"ts\">\nimport { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue';\nimport { Float } from '@headlessui-float/vue';\nimport type { Placement } from '@floating-ui/dom';\n\nwithDefaults(\n  defineProps<{\n    placement?: Placement;\n  }>(),\n  {\n    placement: 'bottom-end'\n  }\n);\n\nconst show = ref(false);\nconst timerOpen = ref<any>(null);\nconst timerClose = ref<any>(null);\n\nconst open = () => {\n  if (timerClose.value !== null) {\n    clearTimeout(timerClose.value);\n    timerClose.value = null;\n  }\n  timerOpen.value = setTimeout(() => {\n    show.value = true;\n  }, 200);\n};\n\nconst delayClose = () => {\n  if (timerOpen.value !== null) {\n    clearTimeout(timerOpen.value);\n    timerOpen.value = null;\n  }\n  timerClose.value = setTimeout(() => {\n    show.value = false;\n  }, 150);\n};\n</script>\n\n<template>\n  <Popover>\n    <Float\n      enter=\"transition ease-out duration-100\"\n      enter-from=\"transform opacity-0 scale-95\"\n      enter-to=\"transform opacity-100 scale-100\"\n      leave=\"transition ease-in duration-75\"\n      leave-from=\"transform opacity-100 scale-100\"\n      leave-to=\"transform opacity-0 scale-95\"\n      :show=\"show\"\n      :placement=\"placement\"\n      :offset=\"10\"\n      :shift=\"16\"\n      :flip=\"16\"\n      :z-index=\"50\"\n      portal\n    >\n      <PopoverButton\n        @mouseenter=\"open\"\n        @mouseleave=\"delayClose\"\n        @focus=\"open\"\n        @focusout=\"delayClose\"\n      >\n        <slot name=\"button\" />\n      </PopoverButton>\n\n      <PopoverPanel\n        class=\"w-screen outline-none sm:max-w-sm\"\n        static\n        @mouseenter=\"open\"\n        @mouseleave=\"delayClose\"\n      >\n        <div\n          class=\"overflow-hidden rounded-2xl border bg-skin-header-bg shadow-lg\"\n        >\n          <div\n            class=\"no-scrollbar max-h-[85vh] overflow-y-auto overscroll-contain\"\n          >\n            <slot name=\"content\" />\n          </div>\n        </div>\n      </PopoverPanel>\n    </Float>\n  </Popover>\n</template>\n"
  },
  {
    "path": "src/components/BaseProgressBar.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{ value: number }>();\n</script>\n\n<template>\n  <div class=\"relative mt-1 flex h-2 overflow-hidden rounded-full\">\n    <div class=\"z-5 absolute h-full w-full bg-[color:var(--border-color)]\" />\n    <div\n      :style=\"`width: ${value.toFixed(3)}%;`\"\n      class=\"z-10 h-full bg-skin-primary opacity-80\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BaseProgressRadial.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{ value: number }>();\n</script>\n\n<template>\n  <div\n    :style=\"{\n      backgroundImage: `conic-gradient(var(--text-color) ${value}%, transparent 0)`\n    }\"\n    class=\"circle\"\n  ></div>\n</template>\n\n<style scoped>\n.circle {\n  width: 18px;\n  height: 18px;\n  border-radius: 50%;\n  border: 1px solid var(--text-color);\n}\n</style>\n"
  },
  {
    "path": "src/components/BaseSearch.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  modelValue: string;\n  placeholder?: string;\n  modal?: boolean;\n  focusOnMount?: boolean;\n  isDisabled?: boolean;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst input = ref(props.modelValue || '');\nconst BaseInputEL = ref<HTMLDivElement | undefined>(undefined);\n\nfunction handleInput(e) {\n  input.value = e.target.value;\n  emit('update:modelValue', e.target.value);\n}\n\nfunction clearInput() {\n  input.value = '';\n  emit('update:modelValue', '');\n}\n\nonMounted(() => {\n  if (props.focusOnMount) {\n    BaseInputEL?.value?.focus();\n  }\n});\n\nwatch(\n  () => props.modelValue,\n  () => {\n    input.value = props.modelValue;\n  }\n);\n</script>\n\n<template>\n  <div\n    class=\"flex h-[44px] items-center\"\n    :class=\"{ 'border-b bg-skin-bg py-3 pl-4': modal }\"\n  >\n    <i-ho-search class=\"mr-2 flex-shrink-0 text-[19px] text-skin-link\" />\n    <input\n      ref=\"BaseInputEL\"\n      :value=\"input\"\n      :placeholder=\"placeholder\"\n      type=\"text\"\n      autocorrect=\"off\"\n      autocapitalize=\"none\"\n      class=\"input w-full border-none\"\n      :class=\"{ '!cursor-not-allowed': isDisabled }\"\n      :disabled=\"isDisabled\"\n      @input=\"handleInput\"\n    />\n    <i-ho-x-circle\n      v-if=\"modelValue\"\n      class=\"mr-[6px] flex-shrink-0 cursor-pointer text-[16px] ml-3 text-skin-link\"\n      @click=\"clearInput\"\n    />\n    <slot name=\"after\" class=\"flex-shrink-0\" />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BaseSidebarNavigationItem.vue",
    "content": "<script setup lang=\"ts\">\nexport interface Props {\n  isActive?: boolean;\n}\n\nwithDefaults(defineProps<Props>(), {\n  isActive: false\n});\n</script>\n\n<template>\n  <div\n    :class=\"[\n      'group relative block cursor-pointer whitespace-nowrap px-[20px] py-2 text-skin-text  hover:bg-skin-bg lg:px-3',\n      { ' !text-skin-heading': isActive }\n    ]\"\n  >\n    <slot />\n    <div class=\"absolute left-0 top-0 flex h-full w-full justify-center\">\n      <div\n        class=\"lg:nav-left-border max-lg:nav-bottom-border lg:group-hover:nav-left-border-hovered\"\n        :class=\"[\n          {\n            selected: isActive\n          }\n        ]\"\n      />\n    </div>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n@tailwind components;\n\n@layer components {\n  .nav-left-border {\n    &.selected {\n      @apply absolute left-0 top-[4px] h-[32px] w-[4px] rounded-br rounded-tr bg-skin-text;\n    }\n  }\n\n  .nav-left-border-hovered {\n    &:not(.selected) {\n      @apply absolute left-0 top-[16px] h-[8px] w-[4px] rounded-br rounded-tr bg-skin-text;\n    }\n  }\n\n  .nav-bottom-border {\n    &.selected {\n      @apply absolute bottom-[0px] h-[4px] w-4/6 rounded-tl rounded-tr bg-skin-text;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/BaseSkinItem.vue",
    "content": "<script setup lang=\"ts\">\nconst { skinsSpacesCount } = useSkinsFilter();\n\nconst { formatCompactNumber } = useIntl();\n\ndefineProps<{\n  skin: string;\n}>();\n</script>\n\n<template>\n  <BaseBlock class=\"cursor-pointer hover:border-skin-text\">\n    <TuneButton :class=\"['mb-2', skin]\" primary use-white-text>{{\n      skin\n    }}</TuneButton>\n    <div class=\"text-skin-text\">\n      {{ $tc('inSpaces', [formatCompactNumber(skinsSpacesCount[skin] ?? 0)]) }}\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/BaseStrategyItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { Strategy } from '@/helpers/interfaces';\n\nconst { formatCompactNumber } = useIntl();\n\ndefineProps<{\n  strategy: Strategy;\n}>();\n</script>\n\n<template>\n  <BaseBlock class=\"cursor-pointer hover:border-skin-text\">\n    <div class=\"flex items-baseline\">\n      <h3 v-tippy=\"{ content: strategy.id }\" class=\"mt-0 truncate\">\n        {{ strategy.id }}\n      </h3>\n      <div class=\"ml-1\">v{{ strategy.version }}</div>\n    </div>\n    <div class=\"text-skin-text\">\n      <BaseIcon name=\"github\" class=\"mr-1\" />\n      {{ strategy.author }}\n    </div>\n    <div>\n      {{ $tc('inSpaces', [formatCompactNumber(strategy.spacesCount)]) }}\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/BaseUser.vue",
    "content": "<script setup lang=\"ts\">\nimport { Profile, ExtendedSpace, Proposal } from '@/helpers/interfaces';\n\nconst { domain } = useApp();\n\nconst props = defineProps<{\n  address: string;\n  space?: Partial<ExtendedSpace>;\n  proposal?: Proposal;\n  profile?: Profile;\n  hideAvatar?: boolean;\n  hideUsername?: boolean;\n  widthClass?: string;\n  textClass?: string;\n}>();\n\nconst { getUsername } = useUsername();\n\nconst spaceMembers = computed(() => {\n  if (!props.space) return [];\n  return [\n    ...(props.space.members || []),\n    ...(props.space.moderators || []),\n    ...(props.space.admins || [])\n  ];\n});\n</script>\n\n<template>\n  <PopoverHoverProfile\n    :address=\"address\"\n    :profile=\"profile\"\n    :proposal=\"proposal\"\n    :space=\"space\"\n    class=\"flex\"\n  >\n    <BaseLink\n      :link=\"\n        domain\n          ? `https://snapshot.org/#/profile/${address}`\n          : { name: 'profileActivity', params: { address } }\n      \"\n      hide-external-icon\n      tabindex=\"-1\"\n      @click.stop=\"\"\n    >\n      <div :class=\"[widthClass, 'flex flex-nowrap items-center space-x-1']\">\n        <AvatarUser v-if=\"!hideAvatar\" :address=\"address\" size=\"20\" />\n        <span\n          v-if=\"!hideUsername\"\n          class=\"w-full cursor-pointer truncate text-skin-link\"\n          :class=\"textClass\"\n        >\n          {{ getUsername(address, profile) }}\n        </span>\n        <BaseBadge\n          v-if=\"getUsername(address, profile) !== 'You'\"\n          :address=\"address\"\n          :members=\"spaceMembers\"\n        />\n      </div>\n    </BaseLink>\n  </PopoverHoverProfile>\n</template>\n"
  },
  {
    "path": "src/components/BlockLink.vue",
    "content": "<script setup lang=\"ts\">\nimport { debouncedWatch } from '@vueuse/core';\n\nconst props = withDefaults(\n  defineProps<{\n    link: string;\n    safeLinkPreview?: boolean;\n  }>(),\n  {\n    safeLinkPreview: true\n  }\n);\n\nconst preview = ref<null | {\n  meta: {\n    title: string;\n    description: string;\n  };\n  links: {\n    icon: {\n      href: string;\n    }[];\n  };\n}>(null);\n\nconst showModal = ref(false);\nconst loaded = ref(false);\nconst error = ref(false);\n\nfunction handleConfirm() {\n  window.open(props.link, '_blank', 'noopener,noreferrer');\n}\n\nfunction handleClickLink() {\n  if (props.safeLinkPreview) {\n    showModal.value = true;\n  } else {\n    window.open(props.link, '_blank', 'noopener,noreferrer');\n  }\n}\n\nasync function update(val: string) {\n  try {\n    error.value = false;\n    loaded.value = false;\n    preview.value = null;\n    new URL(val);\n    const IFRAMELY_API_KEY = 'd155718c86be7d5305ccb6';\n    const url = `https://cdn.iframe.ly/api/iframely?url=${encodeURI(\n      val\n    )}&api_key=${IFRAMELY_API_KEY}`;\n    const result = await fetch(url);\n    const json = await result.json();\n    if (json.status === 404) throw new Error('Error fetching link preview');\n    preview.value = json;\n  } catch (e) {\n    console.log(e);\n    error.value = true;\n  } finally {\n    loaded.value = true;\n  }\n}\n\ndebouncedWatch(\n  () => props.link,\n  newLink => update(newLink),\n  { debounce: 500, immediate: true }\n);\n</script>\n\n<template>\n  <div v-if=\"!error\">\n    <slot name=\"title\" />\n    <button\n      type=\"button\"\n      class=\"flex w-full items-center rounded-xl border hover:cursor-pointer hover:border-skin-text\"\n      @click=\"handleClickLink\"\n    >\n      <div class=\"shrink-0 px-4\">\n        <div v-if=\"!loaded\">\n          <div class=\"lazy-loading h-[32px] w-[32px] rounded-lg\" />\n        </div>\n        <div v-else>\n          <div class=\"w-[32px]\">\n            <IconDiscord\n              v-if=\"preview?.links.icon[0].href.includes('discord.com')\"\n            />\n            <img\n              v-else\n              :src=\"preview?.links.icon[0].href\"\n              alt=\"logo\"\n              width=\"32\"\n              height=\"32\"\n              class=\"rounded bg-white\"\n            />\n          </div>\n        </div>\n      </div>\n      <div class=\"overflow-hidden py-3 pr-3\">\n        <div v-if=\"!loaded\" class=\"flex h-[48px] flex-col justify-center\">\n          <div class=\"lazy-loading h-[10px] w-[90px] rounded\" />\n          <div class=\"lazy-loading mt-2 h-[10px] w-[160px] rounded\" />\n        </div>\n        <div v-else>\n          <div class=\"line-clamp-1 text-left text-skin-link\">\n            {{ preview?.meta.title }}\n          </div>\n          <div\n            v-if=\"preview?.meta.description\"\n            class=\"line-clamp-1 text-left text-sm text-skin-text\"\n          >\n            {{ preview?.meta.description }}\n          </div>\n        </div>\n      </div>\n    </button>\n    <Teleport to=\"#modal\">\n      <ModalLinkPreview\n        :open=\"showModal\"\n        :clicked-url=\"props.link\"\n        @close=\"showModal = false\"\n        @confirm=\"handleConfirm\"\n      />\n    </Teleport>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BlockSpacesList.vue",
    "content": "<script setup lang=\"ts\">\nimport { useMediaQuery } from '@vueuse/core';\nimport { Space } from '@/helpers/interfaces';\n\ndefineProps<{\n  spaces: Space[];\n  title: string;\n  message?: string;\n  loading?: boolean;\n}>();\n\nconst modalSpacesOpen = ref(false);\n\nconst isXSmallScreen = useMediaQuery('(max-width: 420px)');\nconst isSmallScreen = useMediaQuery('(max-width: 544px)');\nconst isMediumScreen = useMediaQuery('(max-width: 768px)');\n\nconst numberOfSpacesByScreenSize = computed(() => {\n  if (isXSmallScreen.value) {\n    return 3;\n  }\n  if (isSmallScreen.value) {\n    return 4;\n  }\n  if (isMediumScreen.value) {\n    return 5;\n  }\n  return 7;\n});\n</script>\n\n<template>\n  <div>\n    <BaseBlock :title=\"title\" :counter=\"spaces.length\" hide-bottom-border slim>\n      <div v-if=\"loading || spaces.length\" class=\"border-t px-4 py-4\">\n        <BlockSpacesListSkeleton\n          v-if=\"loading\"\n          :number-of-spaces=\"numberOfSpacesByScreenSize\"\n        />\n\n        <div v-else class=\"flex justify-between\">\n          <div class=\"flex w-full overflow-x-hidden\">\n            <div\n              v-for=\"space in spaces\"\n              :key=\"space.id\"\n              class=\"mx-2 min-w-[66px] max-w-[66px] text-center first:ml-0\"\n            >\n              <BlockSpacesListItem :space=\"space\" />\n            </div>\n          </div>\n          <BlockSpacesListButtonMore\n            v-if=\"numberOfSpacesByScreenSize < spaces.length\"\n            @click=\"modalSpacesOpen = true\"\n          />\n        </div>\n      </div>\n      <div v-else class=\"border-t p-4\">\n        {{ message || $t('noResultsFound') }}\n      </div>\n    </BaseBlock>\n    <teleport to=\"#modal\">\n      <ModalSpaces\n        :spaces=\"spaces\"\n        :open=\"modalSpacesOpen\"\n        @close=\"modalSpacesOpen = false\"\n      />\n    </teleport>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/BlockSpacesListButtonMore.vue",
    "content": "<template>\n  <button class=\"ml-4 h-full cursor-pointer bg-skin-bg text-skin-link\">\n    <div class=\"flex justify-center\">\n      <div\n        class=\"flex h-[54px] w-[54px] items-center justify-center rounded-full border hover:border-skin-text\"\n      >\n        <i-ho-dots-horizontal class=\"text-sm\" />\n      </div>\n    </div>\n    <div class=\"text-center text-xs\">{{ $t('seeAll') }}</div>\n  </button>\n</template>\n"
  },
  {
    "path": "src/components/BlockSpacesListItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { Space } from '@/helpers/interfaces';\n\ndefineProps<{\n  space: Space;\n}>();\n</script>\n\n<template>\n  <router-link\n    :to=\"{\n      name: 'spaceProposals',\n      params: { key: space.id }\n    }\"\n  >\n    <div class=\"flex justify-center\">\n      <div\n        class=\"flex justify-center rounded-full !border-[1px] !border-skin-text p-[2px]\"\n      >\n        <AvatarSpace :space=\"space\" symbol-index=\"space\" size=\"48\" />\n      </div>\n    </div>\n    <div class=\"flex items-center justify-center\">\n      <div class=\"truncate text-xs\">\n        {{ space.name }}\n      </div>\n      <IconVerifiedSpace\n        v-if=\"space.verified\"\n        :turbo=\"space.turbo\"\n        size=\"14\"\n        class=\"pl-1 text-skin-primary\"\n      />\n    </div>\n  </router-link>\n</template>\n"
  },
  {
    "path": "src/components/BlockSpacesListSkeleton.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  numberOfSpaces: number;\n}>();\n</script>\n\n<template>\n  <div class=\"flex justify-between\">\n    <div v-for=\"n in numberOfSpaces + 1\" :key=\"n\" class=\"animate-pulse\">\n      <div class=\"flex justify-center\">\n        <div class=\"flex rounded-full border p-[2px]\">\n          <div class=\"h-[48px] w-[48px] rounded-full bg-skin-border\" />\n        </div>\n      </div>\n      <div class=\"mt-[6px] h-[14px] w-[66px] rounded bg-skin-border\" />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ButtonBack.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  name?: string;\n}>();\n</script>\n\n<template>\n  <button type=\"button\">\n    <div\n      class=\"inline-flex items-center gap-1 text-skin-text hover:text-skin-link\"\n    >\n      <i-ho-arrow-sm-left />\n      {{ name ? name : $t('back') }}\n    </div>\n  </button>\n</template>\n"
  },
  {
    "path": "src/components/ButtonCard.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  title?: string;\n}>();\n</script>\n\n<template>\n  <button\n    class=\"relative w-full border-y border-skin-border p-4 py-[18px] pr-[80px] text-left hover:border-skin-text md:rounded-xl md:border-x\"\n  >\n    <h4 class=\"leading-2 mb-1 mt-0\">{{ title }}</h4>\n    <slot />\n    <i-ho-chevron-right class=\"absolute right-4 top-[calc(50%-17px)] text-xl\" />\n  </button>\n</template>\n"
  },
  {
    "path": "src/components/ButtonFollow.vue",
    "content": "<script setup lang=\"ts\">\nimport { Space, RankedSpace, ExtendedSpace } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: Space | RankedSpace | ExtendedSpace;\n  primary?: boolean;\n}>();\n\nconst { domain } = useApp();\nconst { isGnosisSafe } = useClient();\nconst { web3Account } = useWeb3();\nconst { modalTermsOpen, termsAccepted, acceptTerms } = useTerms(props.space.id);\nconst { clickFollow, loadingFollow, isFollowing, loadFollows } = useFollowSpace(\n  props.space.id\n);\n\nconst canFollow = computed(() =>\n  props.space.terms ? termsAccepted.value || isFollowing.value : true\n);\n\nwatch(\n  web3Account,\n  () => {\n    // Only for custom domain, else follows are loaded on sidebar\n    domain && loadFollows(props.space.id);\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <div\n    v-tippy=\"{\n      content: isGnosisSafe ? $t('walletNotSupported') : null\n    }\"\n    v-bind=\"$attrs\"\n  >\n    <TuneButton\n      v-bind=\"$attrs\"\n      :loading=\"loadingFollow === space.id\"\n      :disabled=\"isGnosisSafe\"\n      class=\"group min-w-[125px]\"\n      :class=\"{\n        'flex items-center justify-center hover:!border-red hover:!bg-red hover:!bg-opacity-5 hover:!text-red':\n          isFollowing\n      }\"\n      :primary=\"primary\"\n      @click.stop.prevent=\"\n        loadingFollow !== ''\n          ? null\n          : canFollow\n            ? clickFollow(space.id)\n            : (modalTermsOpen = true)\n      \"\n    >\n      <span v-if=\"!isFollowing\"> {{ $t('join') }} </span>\n      <span v-else>\n        <span class=\"flex items-center gap-2 group-hover:hidden\">\n          <i-ho-check class=\"text-green\" /> {{ $t('joined') }}\n        </span>\n        <span class=\"hidden group-hover:block\">\n          {{ $t('leave') }}\n        </span>\n      </span>\n    </TuneButton>\n  </div>\n  <teleport to=\"#modal\">\n    <ModalTerms\n      v-if=\"space\"\n      :open=\"modalTermsOpen\"\n      :space=\"space\"\n      :action=\"$t('modalTerms.actionJoin')\"\n      @close=\"modalTermsOpen = false\"\n      @accept=\"acceptTerms(), clickFollow(space.id)\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/components/ButtonPlayground.vue",
    "content": "<script setup lang=\"ts\">\nimport { encodeJson } from '@/helpers/b64';\n\nconst props = withDefaults(\n  defineProps<{\n    name: string;\n    network?: string;\n    params?: any;\n    snapshot?: string;\n    big?: boolean;\n  }>(),\n  {\n    name: '',\n    network: '',\n    params: {},\n    snapshot: '',\n    big: false\n  }\n);\n\nconst router = useRouter();\nconst { domain } = useApp();\n\nfunction clickPlayground() {\n  if (domain) {\n    return window.open(\n      `https://snapshot.org/#/playground/${props.name}?query=${encodeJson({\n        params: props.params,\n        network: props.network,\n        snapshot: props.snapshot\n      })}`,\n      '_blank'\n    );\n  }\n  const playgroundRoute = router.resolve({\n    name: 'playground',\n    query: {\n      query: encodeJson({\n        params: props.params,\n        network: props.network,\n        snapshot: props.snapshot\n      })\n    },\n    params: { name: props.name }\n  });\n  window.open(playgroundRoute.href, '_blank');\n}\n</script>\n\n<template>\n  <TuneButton v-if=\"big\" class=\"w-full\" @click=\"clickPlayground\">\n    {{ $t('settings.testInPlayground') }}\n    <i-ho-external-link class=\"mb-[2px] inline-block text-xs\" />\n  </TuneButton>\n\n  <BaseButtonIcon\n    v-else\n    v-tippy=\"{ content: $t('playground') }\"\n    @click=\"clickPlayground\"\n  >\n    <i-ho-play />\n  </BaseButtonIcon>\n</template>\n"
  },
  {
    "path": "src/components/ButtonShare.vue",
    "content": "<template>\n  <button\n    class=\"flex cursor-pointer h-full select-none items-center pr-1 hover:text-skin-link\"\n  >\n    <i-ho-upload class=\"text-base\" />\n    <span class=\"ml-1 hidden md:block\">{{ $t('share') }}</span>\n  </button>\n</template>\n"
  },
  {
    "path": "src/components/ButtonSwitch.vue",
    "content": "<script setup lang=\"ts\">\ninterface State {\n  value: string | number | boolean;\n  name: string;\n}\n\ndefineProps<{\n  modelValue: string | number | boolean;\n  state1: State;\n  state2: State;\n}>();\n\ndefineEmits(['update:modelValue']);\n</script>\n\n<template>\n  <div class=\"inline-flex items-center rounded-full border p-1\">\n    <div\n      :class=\"[\n        'w-full cursor-pointer px-3 py-1 text-center',\n        {\n          'rounded-full bg-skin-border text-skin-heading':\n            state1.value === modelValue\n        }\n      ]\"\n      @click=\"$emit('update:modelValue', state1.value)\"\n    >\n      {{ state1.name }}\n    </div>\n    <div\n      :class=\"[\n        'w-full cursor-pointer px-3 py-1 text-center',\n        {\n          'rounded-full bg-skin-border text-skin-heading':\n            state2.value === modelValue\n        }\n      ]\"\n      @click=\"$emit('update:modelValue', state2.value)\"\n    >\n      {{ state2.name }}\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ButtonTheme.vue",
    "content": "<script setup lang=\"ts\">\nconst { toggleUserTheme, getThemeIcon } = useSkin();\n</script>\n\n<template>\n  <BaseButtonRound\n    :aria-label=\"$t('toggleSkin')\"\n    class=\"text-skin-text hover:text-skin-link\"\n    @click=\"toggleUserTheme\"\n  >\n    <i-ho-moon v-if=\"getThemeIcon() === 'moon'\" />\n    <i-ho-sun v-if=\"getThemeIcon() === 'sun'\" />\n  </BaseButtonRound>\n</template>\n"
  },
  {
    "path": "src/components/ComboboxNetwork.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  network: string;\n  hint?: string;\n  disabled?: boolean;\n  error?: string;\n  showErrors?: boolean;\n}>();\n\nconst emit = defineEmits(['select']);\n\nconst { filterNetworks } = useNetworksFilter();\nconst { env } = useApp();\n\nconst networks = computed((): { id: string; name: string }[] => {\n  const filteredNetworks = filterNetworks().map(_n => ({\n    id: _n.key,\n    name: _n.name,\n    extras: {\n      hidden: env === 'production' ? _n.testnet : false\n    }\n  }));\n\n  return filteredNetworks;\n});\n</script>\n\n<template>\n  <TuneCombobox\n    :label=\"$t('settings.network.label')\"\n    :items=\"networks\"\n    :model-value=\"network\"\n    :hint=\"hint\"\n    :disabled=\"disabled\"\n    :error=\"error\"\n    :show-errors=\"showErrors\"\n    @update:model-value=\"value => emit('select', value)\"\n  >\n    <template #item=\"{ item }\">\n      <div class=\"flex items-center\">\n        <div class=\"truncate pr-2\">\n          {{ item.name }}\n        </div>\n\n        <BasePill class=\"leading-4\"> #{{ item.id }} </BasePill>\n      </div>\n    </template>\n  </TuneCombobox>\n</template>\n"
  },
  {
    "path": "src/components/ContainerParallelInput.vue",
    "content": "<template>\n  <div class=\"space-y-2 sm:flex sm:space-x-4 sm:space-y-0\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ExploreMenuCategories.vue",
    "content": "<script setup lang=\"ts\">\nimport { SPACE_CATEGORIES } from '@/helpers/constants';\n\nconst props = defineProps<{\n  metrics: Record<string, number>;\n}>();\n\nconst emit = defineEmits(['update:category']);\n\nconst { tc } = useI18n();\nconst route = useRoute();\nconst router = useRouter();\n\nconst selected = ref((route.query.category as string) || undefined);\n\nconst routeQuery = computed(() => route.query || undefined);\n\nconst categoryItems = computed(() => {\n  return [\n    {\n      text: tc('explore.categories.all'),\n      action: 'all',\n      extras: {\n        count: props.metrics?.all || 0,\n        selected: !selected.value\n      }\n    },\n    ...SPACE_CATEGORIES.map(c => ({\n      text: tc(`explore.categories.${c}`),\n      action: c,\n      extras: {\n        count: props.metrics?.[c] || 0,\n        selected: selected.value === c\n      }\n    })).sort((a, b) => b.extras.count - a.extras.count)\n  ];\n});\n\nfunction selectCategory(c: string) {\n  selected.value = c;\n  emit('update:category', c);\n  router.push({\n    query: { ...routeQuery.value, category: c }\n  });\n}\n</script>\n\n<template>\n  <BaseMenu\n    class=\"mt-2 w-full xs:w-auto sm:mr-2 md:ml-2 md:mt-0\"\n    :items=\"categoryItems\"\n    @select=\"selectCategory\"\n  >\n    <template #button>\n      <TuneButton class=\"w-full whitespace-nowrap pr-3\">\n        <div class=\"leading-2 flex items-center leading-3\">\n          <i-ho-view-grid class=\"mr-2 text-xs\" />\n          <span v-if=\"selected\">\n            {{ $tc('explore.categories.' + selected) }}\n          </span>\n          <span v-else>\n            {{ $tc('explore.category') }}\n          </span>\n          <i-ho-chevron-down class=\"ml-1 text-xs text-skin-link\" />\n        </div>\n      </TuneButton>\n    </template>\n    <template #item=\"{ item }\">\n      <div class=\"flex\">\n        <span class=\"mr-3\">{{ item.text }}</span>\n        <span class=\"ml-auto mt-[-3px] flex\">\n          <BaseCounter :counter=\"item.extras.count\" class=\"my-auto\" />\n        </span>\n      </div>\n    </template>\n  </BaseMenu>\n</template>\n"
  },
  {
    "path": "src/components/ExploreSkeletonLoading.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  isSpaces?: boolean;\n}>();\n\nconst CARD_COUNT = 12;\n</script>\n\n<template>\n  <div\n    v-if=\"isSpaces\"\n    class=\"grid gap-4 opacity-40 md:grid-cols-3 lg:grid-cols-4\"\n  >\n    <div\n      v-for=\"i in CARD_COUNT\"\n      :key=\"i\"\n      class=\"min-h-[266px] animate-pulse bg-skin-border md:rounded-xl\"\n    />\n  </div>\n  <div v-else class=\"grid gap-4 opacity-40 md:grid-cols-2 lg:grid-cols-3\">\n    <div\n      v-for=\"i in CARD_COUNT\"\n      :key=\"i\"\n      class=\"min-h-[124px] animate-pulse bg-skin-border md:rounded-xl\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ExploreSpaces.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\nimport { useInfiniteScroll, watchDebounced } from '@vueuse/core';\n\nconst route = useRoute();\nconst { validEnsTlds } = useEns();\nconst { formatCompactNumber } = useIntl();\nconst { loadExtendedSpace, extendedSpaces, spaceLoading } = useExtendedSpaces();\nconst {\n  loadSpacesHome,\n  loadMoreSpacesHome,\n  loadingSpacesHome,\n  loadingMoreSpacesHome,\n  enableSpaceHomeScroll,\n  spacesHome,\n  spacesHomeMetrics\n} = useSpaces();\n\nconst queryInput = ref({\n  search: (route.query.q as string) || '',\n  category: route.query.category || undefined\n});\n\nconst isSearchInputTld = computed(() => {\n  if (!queryInput.value.search) return false;\n  return validEnsTlds.includes(queryInput.value.search.split('.').pop() ?? '');\n});\n\nconst spaces = computed(() => {\n  if (isSearchInputTld.value) {\n    const space = extendedSpaces.value.find(\n      s => s.id === queryInput.value.search\n    );\n    return space ? [space] : [];\n  }\n  return spacesHome.value;\n});\n\nfunction handleClickMore() {\n  loadMoreSpacesHome(queryInput.value);\n  enableSpaceHomeScroll.value = true;\n}\n\nfunction loadSpaces() {\n  if (isSearchInputTld.value) return loadExtendedSpace(queryInput.value.search);\n  loadSpacesHome(queryInput.value);\n}\n\nuseInfiniteScroll(\n  document,\n  () => {\n    if (enableSpaceHomeScroll.value) {\n      loadMoreSpacesHome(queryInput.value);\n    }\n  },\n  { distance: 500 }\n);\n\nwatchDebounced(\n  queryInput,\n  () => {\n    loadSpaces();\n  },\n  { deep: true, debounce: 300 }\n);\n\nonMounted(() => {\n  loadSpaces();\n});\n</script>\n\n<template>\n  <div class=\"relative\">\n    <BaseContainer\n      class=\"mb-4 flex flex-col flex-wrap items-center xs:flex-row md:flex-nowrap\"\n    >\n      <div tabindex=\"-1\" class=\"w-full md:max-w-[420px]\">\n        <TheSearchBar @update:input-search=\"queryInput.search = $event\" />\n      </div>\n\n      <ExploreMenuCategories\n        :metrics=\"spacesHomeMetrics.categories\"\n        @update:category=\"queryInput.category = $event\"\n      />\n\n      <div\n        v-if=\"spacesHomeMetrics.total\"\n        class=\"mt-2 whitespace-nowrap text-right text-skin-text xs:ml-auto xs:mt-0\"\n      >\n        {{ $tc('spaceCount', [formatCompactNumber(spacesHomeMetrics.total)]) }}\n      </div>\n    </BaseContainer>\n\n    <BaseContainer slim>\n      <ExploreSkeletonLoading\n        v-if=\"loadingSpacesHome || spaceLoading\"\n        is-spaces\n      />\n      <BaseNoResults v-else-if=\"spaces.length < 1\" use-block />\n\n      <TransitionGroup\n        v-else-if=\"!loadingSpacesHome && !spaceLoading\"\n        name=\"fade\"\n        tag=\"div\"\n        class=\"grid gap-4 md:grid-cols-3 lg:grid-cols-4\"\n      >\n        <div v-for=\"space in spaces\" :key=\"space.id\">\n          <router-link\n            :to=\"{ name: 'spaceProposals', params: { key: space.id } }\"\n          >\n            <BaseBlock\n              class=\"mb-0 flex items-center justify-center text-center transition-all hover:border-skin-text\"\n              style=\"height: 266px\"\n            >\n              <div class=\"relative mb-2 inline-block\">\n                <AvatarSpace\n                  :space=\"space\"\n                  symbol-index=\"space\"\n                  size=\"82\"\n                  class=\"mb-1\"\n                />\n              </div>\n              <div class=\"flex items-center justify-center gap-1 truncate\">\n                <h3\n                  class=\"mb-0 mt-0 !h-[32px] overflow-hidden pb-0 text-[22px]\"\n                  v-text=\"shorten(space.name, 16)\"\n                />\n                <IconVerifiedSpace\n                  v-if=\"space.verified\"\n                  :turbo=\"space.turbo\"\n                  class=\"pt-[1px]\"\n                />\n              </div>\n              <div class=\"mb-[12px] text-skin-text\">\n                {{\n                  $tc('members', space.followersCount, {\n                    count: formatCompactNumber(space.followersCount)\n                  })\n                }}\n              </div>\n              <ButtonFollow :space=\"space\" class=\"mx-auto\" />\n            </BaseBlock>\n          </router-link>\n        </div>\n      </TransitionGroup>\n      <div\n        v-if=\"\n          !enableSpaceHomeScroll &&\n          spacesHomeMetrics.total > spacesHome.length &&\n          spaces.length >= 12\n        \"\n        class=\"px-3 text-center md:px-0\"\n      >\n        <TuneButton class=\"mt-4 w-full\" @click=\"handleClickMore\">\n          {{ $t('homeLoadmore') }}\n        </TuneButton>\n      </div>\n      <div v-else-if=\"loadingMoreSpacesHome\" class=\"mt-4 flex h-[46px]\">\n        <LoadingSpinner class=\"mx-auto\" big />\n      </div>\n    </BaseContainer>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/FooterLinks.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div class=\"space-y-[12px]\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/FooterLinksItem.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  link: any;\n}>();\n</script>\n\n<template>\n  <BaseLink\n    :link=\"link\"\n    hide-external-icon\n    class=\"flex items-center justify-center text-skin-text hover:text-skin-link md:justify-start\"\n  >\n    <slot />\n  </BaseLink>\n</template>\n"
  },
  {
    "path": "src/components/FooterSocials.vue",
    "content": "<script setup lang=\"ts\">\nconst socials = [\n  {\n    icon: 'x',\n    link: 'https://x.com/SnapshotLabs'\n  },\n  {\n    icon: 'discord',\n    link: 'https://discord.snapshot.org/'\n  }\n];\n</script>\n\n<template>\n  <div\n    class=\"flex items-center justify-center space-x-3 pt-2 md:mt-4 md:justify-start lg:mt-0 lg:justify-end\"\n  >\n    <span v-for=\"social in socials\" :key=\"social.icon\">\n      <BaseLink :link=\"social.link\" hide-external-icon>\n        <FooterSocialsItem v-if=\"social.icon === 'x'\">\n          <i-s-x class=\"text-[23px]\" />\n        </FooterSocialsItem>\n        <FooterSocialsItem v-else-if=\"social.icon === 'discord'\">\n          <i-s-discord class=\"text-[23px]\" />\n        </FooterSocialsItem>\n      </BaseLink>\n    </span>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/FooterSocialsItem.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div class=\"text-skin-text hover:text-skin-link\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/FooterTitle.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <h4 class=\"font-medium\">\n    <slot />\n  </h4>\n</template>\n"
  },
  {
    "path": "src/components/FormArrayStrategies.vue",
    "content": "<script setup lang=\"ts\">\nimport { SpaceStrategy } from '@/helpers/interfaces';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst props = defineProps<{\n  modelValue?: { name: string; network: string; params: any }[];\n  votingStrategies: SpaceStrategy[];\n}>();\n\nconst emit = defineEmits(['update:modelValue', 'update:isValid']);\n\nconst updateIndex = ref(0);\n\nconst { filterStrategies, getStrategies, strategies } = useStrategies();\n\nconst input = ref<{ name: string; network: string; params: any }[]>(\n  props.modelValue || []\n);\n\nconst strategyValidationStates = ref<boolean[]>([]);\nconst strategiesRef = ref();\n\nfunction handleDelete(index: number) {\n  input.value.splice(index, 1);\n  strategyValidationStates.value.splice(index, 1);\n}\n\nfunction handleCopyStrategies() {\n  updateIndex.value++;\n  input.value = clone(props.votingStrategies);\n}\n\nfunction forceShowError() {\n  strategiesRef?.value?.forEach((ref: any) => {\n    if (ref?.forceShowError) {\n      ref?.forceShowError();\n    }\n  });\n}\n\ndefineExpose({\n  forceShowError\n});\n\nwatch(\n  strategyValidationStates,\n  () => {\n    const isValid = Object.values(strategyValidationStates.value).every(\n      v => v === true\n    );\n    emit('update:isValid', isValid);\n  },\n  { immediate: true, deep: true }\n);\n\nwatch(\n  input.value,\n  () => {\n    emit('update:modelValue', input.value);\n  },\n  { deep: true }\n);\n\nonMounted(() => {\n  if (!strategies.value.length) getStrategies();\n  if (!input.value.length)\n    input.value.push({ name: 'ticket', network: '1', params: {} });\n});\n</script>\n\n<template>\n  <div>\n    <div v-if=\"!strategies.length\" class=\"mt-3 flex justify-center\">\n      <LoadingSpinner />\n    </div>\n    <div v-else class=\"mt-3\">\n      <LabelInput>\n        {{ $t('strategies') }}\n      </LabelInput>\n      <div class=\"space-y-3\">\n        <div\n          v-for=\"(property, i) in input\"\n          :key=\"i\"\n          class=\"space-y-2 rounded-md border p-3\"\n        >\n          <div class=\"mb-3 flex items-center justify-between\">\n            <BasePill class=\"text-[16px]\">\n              {{ i + 1 }}\n            </BasePill>\n            <BaseButtonIcon v-if=\"input.length > 1\" @click=\"handleDelete(i)\">\n              <i-ho-trash class=\"text-[17px]\" />\n            </BaseButtonIcon>\n          </div>\n          <TuneCombobox\n            :label=\"$t('strategy')\"\n            :items=\"filterStrategies().map(s => ({ id: s.id, name: s.id }))\"\n            :model-value=\"input[i].name\"\n            @update:model-value=\"value => (input[i].name = value)\"\n          />\n\n          <ComboboxNetwork\n            :network=\"input[i].network\"\n            @select=\"value => (input[i].network = value)\"\n          />\n\n          <FormObjectStrategyParams\n            :key=\"updateIndex\"\n            ref=\"strategiesRef\"\n            v-model=\"input[i].params\"\n            :strategy-name=\"input[i].name\"\n            @update:is-valid=\"strategyValidationStates[i] = $event\"\n          />\n        </div>\n\n        <TuneButton\n          class=\"flex w-full items-center justify-center gap-2\"\n          @click=\"input.push({ name: 'ticket', network: '1', params: {} })\"\n        >\n          <i-ho-plus class=\"text-sm\" />\n          {{ $t('addStrategy') }}\n        </TuneButton>\n\n        <TuneButton\n          v-if=\"votingStrategies.length\"\n          class=\"flex w-full items-center justify-center gap-2\"\n          @click=\"handleCopyStrategies\"\n        >\n          <i-ho-duplicate />\n          {{ $t('copyVotingStrategies') }}\n        </TuneButton>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/FormObjectStrategyParams.vue",
    "content": "<script setup lang=\"ts\">\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { validateForm } from '@/helpers/validation';\n\nconst props = defineProps<{\n  strategyName: string;\n  modelValue: any;\n}>();\n\nconst emit = defineEmits(['update:modelValue', 'update:isValid']);\n\nconst {\n  getExtendedStrategy,\n  strategyDefinition,\n  loadingExtendedStrategy,\n  extendedStrategy\n} = useStrategies();\n\nconst isValidJson = ref(true);\nconst formRef = ref();\n\nconst paramsComputed = computed({\n  get: () => props.modelValue,\n  set: value => {\n    emit('update:modelValue', value);\n  }\n});\n\nconst validationErrors = computed(() => {\n  return validateForm(strategyDefinition.value || {}, paramsComputed.value);\n});\n\nconst isValid = computed(() => {\n  return Object.values(validationErrors.value).length === 0;\n});\n\nfunction forceShowError() {\n  formRef?.value?.forceShowError();\n}\n\ndefineExpose({\n  forceShowError\n});\n\nwatch(\n  [isValidJson, isValid],\n  () => {\n    if (isValidJson.value && isValid.value) {\n      emit('update:isValid', true);\n    } else {\n      emit('update:isValid', false);\n    }\n  },\n  { immediate: true }\n);\n\nwatch(\n  () => props.strategyName,\n  async () => {\n    paramsComputed.value = {};\n    await getExtendedStrategy(props.strategyName);\n    if (\n      !strategyDefinition.value &&\n      extendedStrategy.value?.examples?.[0]?.strategy?.params\n    ) {\n      paramsComputed.value = clone(\n        extendedStrategy.value.examples[0].strategy.params\n      );\n    }\n  }\n);\n\nonMounted(() => {\n  getExtendedStrategy(props.strategyName);\n});\n</script>\n\n<template>\n  <div>\n    <div v-if=\"loadingExtendedStrategy\" class=\"flex justify-center\">\n      <LoadingSpinner />\n    </div>\n\n    <TuneForm\n      v-else-if=\"strategyDefinition\"\n      ref=\"formRef\"\n      v-model=\"paramsComputed\"\n      :definition=\"strategyDefinition\"\n      :error=\"validationErrors\"\n    />\n\n    <TuneTextareaJson\n      v-else\n      v-model=\"paramsComputed\"\n      v-model:is-valid=\"isValidJson\"\n      label=\"Params\"\n      :placeholder=\"$t('strategyParameters')\"\n      class=\"input text-left\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/IconDiscord.vue",
    "content": "<template>\n  <i-s-discord class=\"h-[32px] w-[32px] text-[#5865F2]\" />\n</template>\n"
  },
  {
    "path": "src/components/IconInformationTooltip.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  information?: string;\n}>();\n</script>\n\n<template>\n  <span\n    v-if=\"!!information\"\n    v-tippy=\"{ content: information }\"\n    class=\"text-xs hover:text-skin-link\"\n  >\n    <i-ho-question-mark-circle />\n  </span>\n</template>\n"
  },
  {
    "path": "src/components/IconVerifiedSpace.vue",
    "content": "<script setup lang=\"ts\">\nwithDefaults(\n  defineProps<{\n    size?: string;\n    turbo: boolean;\n  }>(),\n  {\n    size: '20',\n    turbo: false\n  }\n);\n</script>\n\n<template>\n  <div class=\"cursor-help\">\n    <BaseIcon\n      v-tippy=\"{\n        content: $t('verifiedSpace'),\n        placement: 'right'\n      }\"\n      name=\"check\"\n      :size=\"size\"\n      :class=\"[{ 'text-[#ffb503]': turbo }]\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/IndicatorAssetsChange.spec.js",
    "content": "import { describe, expect, it, afterEach } from 'vitest';\nimport { shallowMount } from '@vue/test-utils';\nimport IndicatorAssetsChange from './IndicatorAssetsChange.vue';\n\ndescribe('IndicatorAssetsChange', () => {\n  let wrapper;\n\n  const findAssetChange = () =>\n    wrapper.find('[data-testid=\"asset-quote-change\"]');\n\n  function createComponent(params = {}) {\n    wrapper = shallowMount(IndicatorAssetsChange, params);\n  }\n\n  afterEach(() => {\n    wrapper.unmount();\n  });\n\n  it('should render red if quote_24h is greater than quote', () => {\n    createComponent({\n      props: {\n        quote: {\n          quote: 2700,\n          quote_24h: 2800\n        }\n      }\n    });\n\n    expect(findAssetChange().classes()).toContain('text-red');\n  });\n\n  it('should render green if quote_24h is lower than quote', async () => {\n    createComponent({\n      props: {\n        quote: {\n          quote: 2900,\n          quote_24h: 2800\n        }\n      }\n    });\n\n    expect(findAssetChange().classes()).toContain('text-green');\n  });\n\n  it('should render correct 24h % change', () => {\n    const quote = {\n      quote: 2800,\n      quote_24h: 2800\n    };\n\n    createComponent({\n      props: {\n        quote\n      }\n    });\n\n    expect(\n      wrapper.find('[data-testid=\"asset-quote-change-percent\"]').text()\n    ).toContain('0%');\n  });\n\n  it('should render no indicator when both are zero', () => {\n    const quote = {\n      quote: 0,\n      quote_24h: 0\n    };\n\n    createComponent({\n      props: {\n        quote\n      }\n    });\n\n    expect(wrapper.find('[asset-quote-change\"]').exists()).toBe(false);\n  });\n\n  it('should render the change in $', async () => {\n    const quote = {\n      quote: 2800,\n      quote_24h: 2800\n    };\n\n    createComponent({\n      props: {\n        quote\n      }\n    });\n\n    expect(\n      wrapper.find('[data-testid=\"asset-quote-change-usd\"]').text()\n    ).toContain(`$0`);\n  });\n});\n"
  },
  {
    "path": "src/components/IndicatorAssetsChange.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  quote: {\n    quote: number;\n    quote_24h: number;\n  };\n}>();\n\nconst { formatPercentNumber, formatNumber } = useIntl();\n</script>\n\n<template>\n  <div v-if=\"quote.quote_24h || quote.quote\">\n    <span\n      data-testid=\"asset-quote-change\"\n      :class=\"[quote.quote_24h > quote.quote ? 'text-red' : 'text-green']\"\n    >\n      <span class=\"pr-1\" data-testid=\"asset-quote-change-percent\">\n        {{\n          `${quote.quote_24h > quote.quote ? '' : '+'}${formatPercentNumber(\n            (quote.quote - quote.quote_24h) / quote.quote_24h\n          )}`\n        }}\n      </span>\n      <span data-testid=\"asset-quote-change-usd\">\n        {{ `($${formatNumber(quote.quote - quote.quote_24h)})` }}\n      </span>\n    </span>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/InputCheckbox.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  modelValue: boolean;\n  name: string;\n  label: string;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n</script>\n\n<template>\n  <div class=\"flex items-center gap-[12px]\">\n    <input\n      :checked=\"modelValue\"\n      :name=\"name\"\n      type=\"checkbox\"\n      class=\"form-checkbox h-[19px] w-[19px] rounded-lg border-skin-text bg-skin-bg text-skin-primary !outline-none !ring-0\"\n      @input=\"\n        emit('update:modelValue', ($event.target as HTMLInputElement).checked)\n      \"\n    />\n    <div class=\"text-skin-text\">\n      {{ label }}\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/InputComboboxToken.vue",
    "content": "<script setup lang=\"ts\">\nimport { Token } from '@/helpers/alchemy';\n\ndefineProps<{\n  label: string;\n  network: string;\n  tokens: Token[];\n  amount: string;\n  selectedToken?: Token;\n  loading?: boolean;\n  error?: string;\n}>();\n\ndefineEmits(['update:selectedToken', 'update:amount']);\n\nconst { web3Account } = useWeb3();\nconst { modalAccountOpen } = useModal();\n\nconst isTokenModalOpen = ref(false);\n\nfunction handleOpenTokenModal() {\n  if (!web3Account.value) return (modalAccountOpen.value = true);\n  isTokenModalOpen.value = true;\n}\n</script>\n\n<template>\n  <TuneInput\n    :model-value=\"amount\"\n    :label=\"label\"\n    :error=\"error\"\n    always-show-error\n    placeholder=\"0.0\"\n    type=\"number\"\n    class=\"pr-[142px]\"\n    @update:model-value=\"$emit('update:amount', $event)\"\n  >\n    <template #after>\n      <button\n        type=\"button\"\n        label=\"Token\"\n        class=\"-mr-[23px] h-[40px] hover:bg-[--border-color-subtle] rounded-r-full border-l\"\n        @click=\"handleOpenTokenModal\"\n      >\n        <div\n          class=\"flex flex-row space-x-2 items-center pr-[12px] pl-3 max-w-[150px]\"\n        >\n          <template v-if=\"loading\">\n            <TuneLoadingSpinner />\n          </template>\n          <template v-else-if=\"selectedToken?.contractAddress\">\n            <AvatarToken :address=\"selectedToken.contractAddress\" size=\"20\" />\n            <span class=\"text-skin-link truncate\">{{\n              selectedToken.symbol\n            }}</span>\n          </template>\n          <template v-else>\n            <div>Select token</div>\n          </template>\n          <i-ho-chevron-down class=\"text-sm text-skin-link shrink-0\" />\n        </div>\n      </button>\n    </template>\n  </TuneInput>\n\n  <teleport to=\"#modal\">\n    <ModalTokens\n      :selected-token=\"selectedToken\"\n      :tokens=\"tokens\"\n      :open=\"isTokenModalOpen\"\n      :network=\"network\"\n      @update:selected-token=\"$emit('update:selectedToken', $event)\"\n      @close=\"isTokenModalOpen = false\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/components/InputDate.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  title?: string;\n  information?: string;\n  dateString?: string;\n  date: number;\n  disabled?: boolean;\n  type?: string;\n  tooltip: string | null;\n}>();\n\ndefineEmits(['update:date']);\n\nconst modalDateSelectOpen = ref(false);\n</script>\n\n<template>\n  <div class=\"w-full\">\n    <LabelInput :information=\"information\">{{ title }}</LabelInput>\n    <TuneButton\n      v-tippy=\"{ content: tooltip }\"\n      class=\"relative inset-y-0 flex !h-[42px] w-full items-center truncate pl-[44px] pr-3 text-left\"\n      :class=\"[disabled ? 'cursor-not-allowed' : 'cursor-pointer']\"\n      @click=\"disabled ? null : (modalDateSelectOpen = true)\"\n    >\n      <span :class=\"{ 'text-skin-text opacity-60': disabled }\">\n        {{ dateString }}\n      </span>\n      <i-ho-calendar\n        class=\"absolute left-[16px] -mt-[1px] text-sm text-skin-text\"\n      />\n    </TuneButton>\n  </div>\n  <teleport to=\"#modal\">\n    <ModalSelectDate\n      :type=\"type\"\n      :open=\"modalDateSelectOpen\"\n      :value=\"date\"\n      @close=\"modalDateSelectOpen = false\"\n      @input=\"$emit('update:date', $event)\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/components/InputEmail.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  modelValue?: string;\n  focusOnMount?: boolean;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n</script>\n\n<template>\n  <BaseInput\n    v-bind=\"$attrs\"\n    :model-value=\"modelValue\"\n    :focus-on-mount=\"focusOnMount\"\n    type=\"email\"\n    :placeholder=\"$t('newsletter.yourEmail')\"\n    class=\"!pr-[66px]\"\n    @update:model-value=\"emit('update:modelValue', $event)\"\n  >\n    <template #after>\n      <slot />\n    </template>\n  </BaseInput>\n</template>\n"
  },
  {
    "path": "src/components/InputNewsletter.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  tag: string;\n}>();\n\ndefineEmits(['close']);\n\nconst action =\n  'https://snapshot.us17.list-manage.com/subscribe/post?u=1c820b717ffe37c1703e33f4b&amp;id=f11d6e5df6';\n</script>\n\n<template>\n  <form\n    :action=\"action\"\n    method=\"post\"\n    target=\"_blank\"\n    autocomplete=\"off\"\n    class=\"flex\"\n  >\n    <input type=\"hidden\" name=\"tags\" :value=\"tag\" />\n\n    <InputEmail name=\"EMAIL\" required>\n      <button\n        type=\"submit\"\n        name=\"subscribe\"\n        class=\"absolute right-0 h-[42px] rounded-r-full px-3\"\n      >\n        <i-ho-paper-airplane class=\"rotate-90 text-skin-link\" />\n      </button>\n    </InputEmail>\n  </form>\n</template>\n"
  },
  {
    "path": "src/components/InputSelect.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  modelValue: string;\n  title?: string;\n  information?: string;\n  isDisabled?: boolean;\n  tooltip?: string | null;\n}>();\n\nconst emit = defineEmits(['select']);\n</script>\n\n<template>\n  <div class=\"w-full\">\n    <LabelInput :information=\"information\">{{ title }}</LabelInput>\n    <TuneButton\n      v-tippy=\"{ content: tooltip }\"\n      :class=\"[\n        $attrs.class,\n        { 'cursor-not-allowed !border-skin-border': isDisabled }\n      ]\"\n      class=\"relative !h-[42px] w-full truncate pl-3 pr-5 text-left\"\n      :disabled=\"isDisabled\"\n      @click=\"isDisabled ? null : emit('select')\"\n    >\n      <span :class=\"{ 'text-skin-text ': isDisabled }\">\n        {{ modelValue }}\n      </span>\n      <i-ho-chevron-down\n        class=\"absolute inset-y-[12px] right-[14px] text-xs text-skin-link\"\n      />\n    </TuneButton>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/InputSelectPrivacy.vue",
    "content": "<script setup lang=\"ts\">\nwithDefaults(\n  defineProps<{\n    privacy?: string;\n    hint?: string;\n    allowAny?: boolean;\n    disabled?: boolean;\n  }>(),\n  {\n    privacy: '',\n    hint: '',\n    allowAny: false,\n    disabled: false\n  }\n);\n\nconst emit = defineEmits(['update:privacy']);\n\nconst modalVotingPrivacyOpen = ref(false);\n</script>\n\n<template>\n  <div>\n    <TuneButtonSelect\n      :label=\"$t(`privacy.label`)\"\n      :hint=\"hint\"\n      :model-value=\"\n        privacy ? $t(`privacy.${privacy}.label`) : $t('privacy.any')\n      \"\n      :disabled=\"disabled\"\n      @select=\"modalVotingPrivacyOpen = true\"\n    />\n    <teleport to=\"#modal\">\n      <ModalVotingPrivacy\n        :selected=\"privacy\"\n        :open=\"modalVotingPrivacyOpen\"\n        :allow-any=\"allowAny\"\n        @update:selected=\"emit('update:privacy', $event)\"\n        @close=\"modalVotingPrivacyOpen = false\"\n      />\n    </teleport>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/InputSelectVoteType.vue",
    "content": "<script setup lang=\"ts\">\nwithDefaults(\n  defineProps<{\n    type?: string;\n    hint?: string;\n    allowAny?: boolean;\n    disabled?: boolean;\n    isDisabledSettings?: boolean;\n  }>(),\n  {\n    type: '',\n    hint: '',\n    allowAny: false,\n    disabled: false,\n    isDisabledSettings: false\n  }\n);\n\nconst emit = defineEmits(['update:type']);\n\nconst modalVotingTypeOpen = ref(false);\n</script>\n\n<template>\n  <TuneButtonSelect\n    :label=\"$t(`settings.type.label`)\"\n    :hint=\"hint\"\n    :model-value=\"type ? $t(`voting.${type}.label`) : $t('settings.anyType')\"\n    :disabled=\"disabled || isDisabledSettings\"\n    :tooltip=\"\n      disabled\n        ? $t('create.typeEnforced', { type: $t(`voting.${type}.label`) })\n        : null\n    \"\n    @select=\"modalVotingTypeOpen = true\"\n  />\n  <teleport to=\"#modal\">\n    <ModalVotingType\n      :selected=\"type\"\n      :open=\"modalVotingTypeOpen\"\n      :allow-any=\"allowAny\"\n      @update:selected=\"emit('update:type', $event)\"\n      @close=\"modalVotingTypeOpen = false\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/components/InputSelectVoteValidation.vue",
    "content": "<script setup lang=\"ts\">\nimport { VoteValidation, SpaceStrategy } from '@/helpers/interfaces';\n\ndefineProps<{\n  validation: VoteValidation;\n  votingStrategies: SpaceStrategy[];\n  disabled?: boolean;\n}>();\n\nconst emit = defineEmits(['add']);\n\nconst isModalOpen = ref(false);\n</script>\n\n<template>\n  <TuneButtonSelect\n    class=\"w-full\"\n    :label=\"$t(`votingValidation.label`)\"\n    :hint=\"$t(`votingValidation.information`)\"\n    :model-value=\"$t(`votingValidation.${validation.name}.label`)\"\n    :disabled=\"disabled\"\n    @select=\"isModalOpen = true\"\n  />\n\n  <teleport to=\"#modal\">\n    <ModalVoteValidation\n      :open=\"isModalOpen\"\n      :validation=\"validation\"\n      :voting-strategies=\"votingStrategies\"\n      @close=\"isModalOpen = false\"\n      @add=\"emit('add', $event)\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/components/InputSwitch.vue",
    "content": "<script setup lang=\"ts\">\nimport { Switch } from '@headlessui/vue';\n\ndefineProps<{\n  modelValue?: boolean;\n  label?: string;\n  textRight?: string;\n  definition?: any;\n  information?: string;\n  isDisabled?: boolean;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n</script>\n\n<template>\n  <div class=\"flex items-center space-x-2 pr-2 pt-1\">\n    <Switch\n      :model-value=\"modelValue\"\n      :class=\"[\n        modelValue ? 'bg-green' : 'bg-skin-border',\n        { '!cursor-not-allowed': isDisabled }\n      ]\"\n      :disabled=\"isDisabled\"\n      class=\"relative inline-flex h-[22px] w-[38px] flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent outline-offset-2 transition-colors duration-200 ease-in-out\"\n      @update:model-value=\"value => emit('update:modelValue', value)\"\n    >\n      <span v-if=\"label\" class=\"sr-only\">{{ label }}</span>\n      <span\n        :class=\"[\n          modelValue ? 'translate-x-[16px]' : 'translate-x-0',\n          'shadow pointer-events-none inline-block h-[18px] w-[18px] transform rounded-full bg-skin-bg transition duration-200 ease-in-out'\n        ]\"\n      >\n        <span\n          :class=\"[\n            modelValue\n              ? 'opacity-0 duration-100 ease-out'\n              : 'opacity-100 duration-200 ease-in',\n            'absolute inset-0 flex h-full w-full items-center justify-center text-skin-text transition-opacity'\n          ]\"\n          aria-hidden=\"true\"\n        >\n          <svg class=\"h-[10px] w-[10px]\" fill=\"none\" viewBox=\"0 0 12 12\">\n            <path\n              d=\"M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2\"\n              stroke=\"currentColor\"\n              stroke-width=\"2\"\n              stroke-linecap=\"round\"\n              stroke-linejoin=\"round\"\n            />\n          </svg>\n        </span>\n        <span\n          :class=\"[\n            modelValue\n              ? 'opacity-100 duration-200 ease-in'\n              : 'opacity-0 duration-100 ease-out',\n            'absolute inset-0 flex h-full w-full items-center justify-center text-green transition-opacity'\n          ]\"\n          aria-hidden=\"true\"\n        >\n          <svg\n            class=\"h-[10px] w-[10px]\"\n            fill=\"currentColor\"\n            viewBox=\"0 0 12 12\"\n          >\n            <path\n              d=\"M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z\"\n            />\n          </svg>\n        </span>\n      </span>\n    </Switch>\n    <LabelInput :information=\"information || definition?.description\">\n      {{ textRight || definition?.title }}\n    </LabelInput>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/InputUploadAvatar.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  isViewOnly?: boolean;\n}>();\n\nconst emit = defineEmits(['image-uploaded', 'image-remove']);\n\nconst fileInput = ref<HTMLInputElement | null>(null);\n\nfunction openFilePicker() {\n  if (props.isViewOnly) return;\n  fileInput.value?.click();\n}\n\nconst uploadSuccess = ref(false);\nconst previewFile = ref<File | undefined>(undefined);\n\nconst { upload, isUploadingImage, imageUploadError } = useImageUpload();\nconst { notify } = useFlashNotification();\n\nfunction onFileChange(e) {\n  uploadSuccess.value = false;\n  if ((e.target as HTMLInputElement).files?.[0])\n    previewFile.value = (e.target as HTMLInputElement).files?.[0];\n  upload(previewFile.value, image => {\n    uploadSuccess.value = true;\n    emit('image-uploaded', image.url);\n  });\n}\n\nwatch(\n  () => imageUploadError.value,\n  error => {\n    if (error !== '') {\n      notify(['red', error]);\n    }\n  }\n);\n</script>\n\n<template>\n  <div @click=\"openFilePicker()\">\n    <slot\n      name=\"avatar\"\n      :uploading=\"isUploadingImage\"\n      :preview-file=\"uploadSuccess ? previewFile : undefined\"\n    />\n  </div>\n  <input\n    v-bind=\"$attrs\"\n    ref=\"fileInput\"\n    type=\"file\"\n    accept=\"image/jpg, image/jpeg, image/png\"\n    style=\"display: none\"\n    @change=\"onFileChange\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/LabelInput.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  information?: string;\n}>();\n</script>\n\n<template>\n  <span class=\"mb-[2px] flex items-center gap-1 text-skin-text\">\n    <slot />\n    <IconInformationTooltip :information=\"information\" />\n  </span>\n</template>\n"
  },
  {
    "path": "src/components/LabelProposalState.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  state: string;\n}>();\n\nconst stateClass = computed(() => {\n  if (props.state === 'closed') return 'bg-[#BB6BD9]';\n  if (props.state === 'active') return 'bg-green';\n  return 'bg-gray-500';\n});\n</script>\n\n<template>\n  <div\n    class=\"text-white rounded-full px-[12px] text-sm h-[24px] w-fit leading-[23px]\"\n    :class=\"stateClass\"\n  >\n    {{ $t(`proposals.states.${state}`) }}\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/LabelProposalVoted.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <span\n    class=\"absolute inline-flex items-center gap-1 whitespace-nowrap py-[1px] text-sm text-skin-text\"\n  >\n    <i-s-voted class=\"text-green\" />\n    {{ $t('voted') }}\n  </span>\n</template>\n"
  },
  {
    "path": "src/components/LinkSpace.vue",
    "content": "<script setup lang=\"ts\">\nimport domains from '@/../snapshot-spaces/spaces/domains.json';\n\nconst props = defineProps<{\n  spaceId: string;\n}>();\n\nconst { domain } = useApp();\n\nconst spaceLink = computed(() => {\n  // Check if proposal space id is a value in the domains.json file\n  if (domain && Object.values(domains).includes(props.spaceId)) {\n    // If so, find the key that matches the value\n    const key = Object.keys(domains).find(\n      key => domains[key] === props.spaceId\n    );\n    return `https://${key}`;\n  }\n\n  if (domain) return `https://snapshot.org/#/${props.spaceId}`;\n\n  return {\n    name: 'spaceProposals',\n    params: { key: props.spaceId }\n  };\n});\n</script>\n\n<template>\n  <BaseLink :link=\"spaceLink\" hide-external-icon>\n    <slot />\n  </BaseLink>\n</template>\n"
  },
  {
    "path": "src/components/ListboxNetwork.vue",
    "content": "<script setup lang=\"ts\">\nimport { getUrl } from '@snapshot-labs/snapshot.js/src/utils';\n\ndefineProps<{\n  modelValue: string;\n  networks: {\n    value: string;\n    name: string;\n    extras?: { icon?: string };\n  }[];\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n</script>\n\n<template>\n  <TuneListbox\n    :model-value=\"modelValue\"\n    label=\"Network\"\n    :items=\"networks\"\n    class=\"w-full\"\n    @update:model-value=\"value => emit('update:modelValue', value)\"\n  >\n    <template #item=\"{ item }\">\n      <div class=\"flex items-center\">\n        <BaseAvatar\n          v-if=\"item.extras?.icon\"\n          :src=\"getUrl(item.extras.icon)\"\n          class=\"mr-2\"\n        />\n        <div class=\"truncate pr-2\">\n          {{ item.name }}\n        </div>\n\n        <BasePill class=\"leading-4\"> #{{ item.value }} </BasePill>\n      </div>\n    </template>\n    <template #selected=\"{ selectedItem }\">\n      <div class=\"flex items-center\">\n        <BaseAvatar :src=\"getUrl(selectedItem.extras?.icon)\" class=\"mr-2\" />\n        <div class=\"truncate pr-2\">\n          {{ selectedItem.name }}\n        </div>\n      </div>\n    </template>\n  </TuneListbox>\n</template>\n"
  },
  {
    "path": "src/components/LoadingList.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div>\n    <div\n      class=\"lazy-loading mb-2 rounded-md\"\n      style=\"width: 80%; height: 20px\"\n    />\n    <div class=\"lazy-loading rounded-md\" style=\"width: 50%; height: 20px\" />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/LoadingPage.vue",
    "content": "<template>\n  <BaseLoading>\n    <div class=\"space-y-3\">\n      <div class=\"lazy-loading rounded-md\" style=\"width: 100%; height: 34px\" />\n      <div class=\"lazy-loading rounded-md\" style=\"width: 40%; height: 34px\" />\n      <div class=\"lazy-loading rounded-md\" style=\"width: 65px; height: 28px\" />\n    </div>\n  </BaseLoading>\n</template>\n"
  },
  {
    "path": "src/components/LoadingRow.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{ block?: boolean }>();\n</script>\n\n<template>\n  <BaseLoading :block=\"block\">\n    <div class=\"block px-4 py-4\">\n      <div\n        class=\"lazy-loading mb-2 rounded-md\"\n        style=\"width: 60%; height: 28px\"\n      />\n      <div class=\"lazy-loading rounded-md\" style=\"width: 50%; height: 28px\" />\n    </div>\n  </BaseLoading>\n</template>\n"
  },
  {
    "path": "src/components/LoadingSpinner.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps({\n  fillWhite: {\n    type: Boolean,\n    default: false\n  },\n  small: {\n    type: Boolean,\n    default: false\n  },\n  big: {\n    type: Boolean,\n    default: false\n  }\n});\n</script>\n\n<template>\n  <BaseLoading class=\"loading\" :class=\"{ small, big }\">\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"50\"\n      height=\"50\"\n      viewBox=\"0 0 50 50\"\n    >\n      <path\n        :class=\"{ '!fill-white': fillWhite }\"\n        d=\"M25,5A20.14,20.14,0,0,1,45,22.88a2.51,2.51,0,0,0,2.49,2.26h0A2.52,2.52,0,0,0,50,22.33a25.14,25.14,0,0,0-50,0,2.52,2.52,0,0,0,2.5,2.81h0A2.51,2.51,0,0,0,5,22.88,20.14,20.14,0,0,1,25,5Z\"\n      >\n        <animateTransform\n          attributeName=\"transform\"\n          type=\"rotate\"\n          from=\"0 25 25\"\n          to=\"360 25 25\"\n          dur=\"0.5s\"\n          repeatCount=\"indefinite\"\n        />\n      </path>\n    </svg>\n  </BaseLoading>\n</template>\n\n<style lang=\"scss\">\n.loading {\n  span {\n    width: 100%;\n  }\n\n  &.small {\n    svg {\n      width: 18px;\n      height: 18px;\n    }\n  }\n\n  &.big {\n    svg {\n      width: 24px;\n      height: 24px;\n    }\n  }\n\n  svg {\n    display: inline-block;\n    vertical-align: middle;\n    width: 20px;\n    height: 20px;\n\n    path {\n      fill: var(--link-color);\n    }\n  }\n\n  &.overlay {\n    position: fixed;\n    text-align: center;\n    display: flex;\n    align-items: center;\n    align-content: center;\n    justify-content: center;\n    top: 0;\n    bottom: 80px;\n    left: 0;\n    right: 0;\n    width: 100%;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/MenuAccount.vue",
    "content": "<script setup lang=\"ts\">\nimport { openProfile } from '@/helpers/utils';\n\nconst props = defineProps<{\n  address: string;\n}>();\n\nconst emit = defineEmits(['switchWallet']);\nconst { domain } = useApp();\nconst { logout } = useWeb3();\nconst { modalEmailOpen } = useModal();\nconst router = useRouter();\n\nfunction handleAction(e) {\n  if (e === 'viewProfile') return openProfile(props.address, domain, router);\n  if (e === 'switchWallet') return emit('switchWallet');\n  if (e === 'subscribeEmail') {\n    modalEmailOpen.value = true;\n    return true;\n  }\n\n  return logout();\n}\n</script>\n\n<template>\n  <div>\n    <BaseMenu\n      :items=\"[\n        {\n          text: 'View profile',\n          action: 'viewProfile',\n          extras: { icon: 'profile' }\n        },\n        {\n          text: 'Switch wallet',\n          action: 'switchWallet',\n          extras: { icon: 'switch' }\n        },\n        {\n          text: 'Email notifications',\n          action: 'subscribeEmail',\n          extras: { icon: 'mail' }\n        },\n        { text: 'Log out', action: 'logout', extras: { icon: 'logout' } }\n      ]\"\n      @select=\"handleAction($event)\"\n    >\n      <template #button>\n        <slot />\n      </template>\n      <template #item=\"{ item }\">\n        <div class=\"flex items-center space-x-2\">\n          <div class=\"w-[24px]\">\n            <i-ho-user-circle\n              v-if=\"item.extras.icon === 'profile'\"\n              class=\"text-[19px]\"\n            />\n            <i-ho-user-add\n              v-if=\"item.extras.icon === 'user-add'\"\n              class=\"ml-[2px]\"\n            />\n            <i-ho-refresh v-if=\"item.extras.icon === 'switch'\" />\n            <i-ho-mail v-if=\"item.extras.icon === 'mail'\" />\n            <i-ho-logout\n              v-if=\"item.extras.icon === 'logout'\"\n              class=\"ml-[2px]\"\n            />\n          </div>\n          <div>\n            {{ item.text }}\n          </div>\n        </div>\n      </template>\n    </BaseMenu>\n  </div>\n\n  <teleport to=\"#modal\">\n    <ModalEmail :open=\"modalEmailOpen\" @close=\"modalEmailOpen = false\" />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/components/MenuLanguages.vue",
    "content": "<script setup lang=\"ts\">\nimport languages from '@/locales/languages.json';\n\nconst { setLocale } = useI18n();\n\nfunction selectLang(locale) {\n  setLocale(locale);\n}\n\nconst localeItems = computed<{ text: string; action: string }[]>(() => {\n  return Object.keys(languages).map(locale => ({\n    text:\n      locale === 'en-US'\n        ? languages[locale].name\n        : languages[locale].nativeName,\n    action: locale\n  }));\n});\n</script>\n\n<template>\n  <BaseMenu :items=\"localeItems\" @select=\"selectLang($event)\">\n    <template #button>\n      <TuneButton\n        class=\"flex !h-[44px] w-full items-center !text-skin-text hover:!text-skin-link\"\n      >\n        <i-ho-globe class=\"mr-2\" />\n        {{\n          languages[$i18n.locale]?.nativeName ?? languages[$i18n.locale]?.name\n        }}\n      </TuneButton>\n    </template>\n  </BaseMenu>\n</template>\n"
  },
  {
    "path": "src/components/MessageWarningFlagged.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  type: string;\n  responsive?: boolean;\n}>();\n\nconst emit = defineEmits(['forceShow']);\n\nconst { t } = useI18n();\n\nconst warningText = computed(() => {\n  if (props.type === 'proposal') {\n    return t('warningFlaggedProposal');\n  }\n  return t('warningFlaggedSpace');\n});\n</script>\n\n<template>\n  <div\n    class=\"flex justify-between py-3 pl-4\"\n    :class=\"[\n      { 'rounded-xl border': !responsive },\n      { 'rounded-none border-y md:rounded-xl md:border': responsive }\n    ]\"\n  >\n    <div class=\"flex items-center\">\n      {{ warningText }}\n    </div>\n    <div>\n      <button @click.prevent=\"emit('forceShow')\">\n        <div class=\"px-4 py-3 hover:text-skin-link\">\n          {{ $t('warningFlaggedActionShow') }}\n        </div>\n      </button>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/MessageWarningGnosisNetwork.vue",
    "content": "<script setup lang=\"ts\">\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\nconst defaultNetwork = import.meta.env.VITE_DEFAULT_NETWORK;\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  action: 'vote' | 'create' | 'settings';\n  isResponsive?: boolean;\n}>();\n\nconst networkKey = computed(() =>\n  props.action === 'settings' ? defaultNetwork : props.space.network\n);\n</script>\n\n<template>\n  <BaseMessageBlock level=\"warning\" :is-responsive=\"isResponsive\">\n    {{\n      $t('settings.gnosisWrongNetwork.base', {\n        network: networks?.[networkKey]?.name,\n        action: $t(`settings.gnosisWrongNetwork.${action}`)\n      })\n    }}\n  </BaseMessageBlock>\n</template>\n"
  },
  {
    "path": "src/components/MessageWarningHibernated.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n}>();\n\nconst { web3Account } = useWeb3();\nconst { loadSpaceController, isSpaceController } = useSpaceController();\n\nconst isAuthorized = computed(() => {\n  const admins = (props.space.admins || []).map(admin => admin.toLowerCase());\n  return (\n    admins.includes(web3Account.value?.toLowerCase()) || isSpaceController.value\n  );\n});\n\nonMounted(async () => {\n  await loadSpaceController();\n});\n</script>\n\n<template>\n  <BaseMessageBlock v-if=\"space.hibernated\" level=\"warning-red\" is-responsive>\n    {{\n      isAuthorized\n        ? $t('create.errorSpaceHibernatedAdmin')\n        : $t('create.errorSpaceHibernatedUsers')\n    }}\n    <BaseLink\n      v-if=\"isAuthorized\"\n      link=\"https://docs.snapshot.org/user-guides/spaces/space-hibernation\"\n    >\n      {{ $t('learnMore') }}\n    </BaseLink>\n\n    <p v-if=\"isAuthorized\" class=\"mt-3\">\n      <router-link :to=\"{ name: 'spaceSettings' }\">\n        <TuneButton> Go to Settings </TuneButton>\n      </router-link>\n    </p>\n  </BaseMessageBlock>\n</template>\n"
  },
  {
    "path": "src/components/MessageWarningTestnet.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  context: 'Treasury' | 'Strategy';\n  error?: string | Record<string, any>;\n}>();\n\nconst strategyTestnetErrors = computed(() => {\n  if (typeof props.error === 'object') {\n    const entries = Object.entries(props.error).filter(e => e[1].network);\n    return entries.filter(e => e[1].network === 'Testnet not allowed.');\n  }\n});\n</script>\n\n<template>\n  <BaseMessageBlock\n    v-if=\"strategyTestnetErrors\"\n    level=\"warning-red\"\n    class=\"mt-3\"\n  >\n    {{ context }}\n    #{{ strategyTestnetErrors.map(e => Number(e[0]) + 1).join(', ') }}\n    is using a test network which is no longer supported. If you are looking to\n    setup a space for testing, please checkout\n    <BaseLink link=\"https://testnet.snapshot.org\">\n      testnet.snapshot.org</BaseLink\n    >\n  </BaseMessageBlock>\n</template>\n"
  },
  {
    "path": "src/components/MessageWarningValidation.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  context: 'voting' | 'proposal';\n  spaceId: string;\n  validationName: string;\n  validationParams: Record<string, any>;\n  minScore: number;\n  symbol: string;\n}>();\n\nconst { formatCompactNumber } = useIntl();\n\nconst tPath = computed(() => {\n  if (props.context === 'voting') {\n    return 'votingValidation';\n  }\n  return 'proposalValidation';\n});\n</script>\n\n<template>\n  <BaseMessageBlock level=\"warning\">\n    <template v-if=\"validationName === 'basic'\">\n      {{\n        $t(`${tPath}.basic.invalidMessage`, {\n          amount: formatCompactNumber(minScore),\n          symbol\n        })\n      }}\n\n      <BaseLink :link=\"{ name: 'spaceAbout', params: { key: spaceId } }\">\n        {{ $t('learnMore') }}\n      </BaseLink>\n    </template>\n\n    <template v-else-if=\"validationName === 'passport-gated'\">\n      {{\n        $t(`${tPath}.passport-gated.invalidMessage`, {\n          scoreThreshold: validationParams?.scoreThreshold || 0\n        })\n      }}<span v-if=\"props.validationParams?.operator === 'NONE'\">. </span>\n\n      <template v-else>\n        {{\n          $t(`${tPath}.passport-gated.invalidMessageStamps`, {\n            operator:\n              props.validationParams?.operator === 'AND' ? 'all' : 'one',\n            stamps:\n              validationParams.stamps && validationParams.stamps.join(', ')\n          })\n        }}\n      </template>\n\n      <BaseLink link=\"https://passport.gitcoin.co/#/dashboard\">\n        Gitcoin Passport</BaseLink\n      >\n    </template>\n\n    <template v-else>\n      {{ $t(`${tPath}.notValidMessage`) }}\n      <BaseLink :link=\"{ name: 'spaceAbout', params: { key: spaceId } }\">\n        {{ $t('learnMore') }}\n      </BaseLink>\n    </template>\n  </BaseMessageBlock>\n</template>\n"
  },
  {
    "path": "src/components/ModalAccount.vue",
    "content": "<script setup lang=\"ts\">\nimport { getInjected } from '@snapshot-labs/lock/src/utils';\nimport connectors from '@/helpers/connectors';\nimport { getIpfsUrl } from '@/helpers/utils';\n\nconst props = defineProps<{\n  open: boolean;\n}>();\n\ndefineEmits(['login', 'close', 'openTerms']);\n\nconst { open } = toRefs(props);\n\nconst isShowingAllConnectors = ref(false);\n\nconst injected = computed(() => getInjected());\n\nconst filteredConnectors = computed(() => {\n  const baseConnectors = ['injected', 'walletconnect', 'walletlink'];\n  // If injected is Coinbase, hide WalletLink\n  if (injected.value?.name === 'Coinbase') connectors.walletlink.hidden = true;\n  if (isShowingAllConnectors.value) return Object.keys(connectors);\n  return Object.keys(connectors).filter(cId => baseConnectors.includes(cId));\n});\n\nwatch(open, () => {\n  isShowingAllConnectors.value = false;\n});\n</script>\n\n<template>\n  <TuneModal :open=\"open\" @close=\"$emit('close')\">\n    <TuneModalTitle as=\"h4\" class=\"mx-3 mt-3\">\n      Connect to Snapshot\n    </TuneModalTitle>\n    <!-- TODO: Enable when TOS ready and remember to enable disconnect in useApp -->\n    <!-- <TuneModalDescription class=\"mx-3 pb-3\">\n      By connecting, you agree to\n      <a\n        role=\"button\"\n        tabindex=\"0\"\n        class=\"font-semibold\"\n        @click=\"$emit('openTerms')\"\n        @keyup.enter=\"$emit('openTerms')\"\n      >\n        Snapshot Labs' Terms of Service</a\n      >.\n    </TuneModalDescription> -->\n    <div>\n      <div class=\"m-3 space-y-2\">\n        <div\n          v-for=\"cId in filteredConnectors\"\n          :key=\"cId\"\n          class=\"block\"\n          @click=\"$emit('login', connectors[cId].id)\"\n        >\n          <TuneButton\n            v-if=\"cId === 'injected' && injected\"\n            class=\"flex w-full items-center justify-center\"\n            data-testid=\"button-connnect-wallet-injected\"\n          >\n            <img\n              :src=\"getIpfsUrl(injected.icon)\"\n              height=\"28\"\n              width=\"28\"\n              class=\"-mt-1 mr-2\"\n              :alt=\"injected.name\"\n            />\n            {{ injected.name }}\n          </TuneButton>\n          <TuneButton\n            v-else-if=\"cId !== 'injected' && !connectors[cId].hidden\"\n            class=\"flex w-full items-center justify-center gap-2\"\n          >\n            <img\n              :src=\"getIpfsUrl(connectors[cId].icon)\"\n              height=\"25\"\n              width=\"25\"\n              :alt=\"connectors[cId].name\"\n            />\n            <span>{{ connectors[cId].name }}</span>\n          </TuneButton>\n        </div>\n        <TuneButton\n          v-if=\"!isShowingAllConnectors\"\n          class=\"flex w-full items-center justify-center gap-1\"\n          @click=\"isShowingAllConnectors = true\"\n        >\n          {{ $t('showMore') }}\n          <i-ho-chevron-down class=\"text-sm text-skin-link\" />\n        </TuneButton>\n      </div>\n    </div>\n  </TuneModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalConfirmAction.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  open: boolean;\n  title?: string;\n  showCancel?: boolean;\n  disabled?: boolean;\n}>();\n\ndefineEmits(['close', 'confirm']);\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <div class=\"flex flex-row items-center justify-center\">\n        <h3>{{ title ? title : $t('confirmAction') }}</h3>\n      </div>\n    </template>\n\n    <slot />\n\n    <template #footer>\n      <div class=\"flex gap-3\">\n        <TuneButton v-if=\"showCancel\" class=\"w-full\" @click=\"$emit('close')\">\n          {{ $t('cancel') }}\n        </TuneButton>\n        <TuneButton\n          class=\"w-full\"\n          primary\n          :disabled=\"disabled\"\n          @click=\"$emit('confirm'), $emit('close')\"\n        >\n          {{ $t('confirm') }}\n        </TuneButton>\n      </div>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalConfirmLeave.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  open: boolean;\n  title?: string;\n  disabled?: boolean;\n}>();\n\ndefineEmits(['close', 'save', 'leave']);\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <div class=\"flex flex-row items-center justify-center\">\n        <h3>{{ title ? title : 'Unsaved changes' }}</h3>\n      </div>\n    </template>\n\n    <BaseMessageBlock level=\"warning\" class=\"m-4\">\n      You have unsaved changes. Would you like to save them before leaving?\n    </BaseMessageBlock>\n\n    <template #footer>\n      <div class=\"flex gap-3\">\n        <TuneButton class=\"w-full\" @click=\"$emit('leave'), $emit('close')\">\n          Leave\n        </TuneButton>\n        <TuneButton\n          class=\"w-full\"\n          primary\n          :disabled=\"disabled\"\n          @click=\"$emit('save'), $emit('close')\"\n        >\n          Save\n        </TuneButton>\n      </div>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalControllerEdit.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\nimport { isAddress } from '@ethersproject/address';\n\nconst {\n  spaceControllerInput,\n  settingENSRecord,\n  spaceController,\n  confirmSetRecord\n} = useSpaceController();\n\ndefineProps<{\n  open: boolean;\n  ensAddress: string;\n}>();\n\nconst controllerInputIsValid = computed(() =>\n  isAddress(spaceControllerInput.value)\n);\n\ndefineEmits(['close']);\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <div class=\"flex flex-row items-center justify-center\">\n        <h3>{{ $t('settings.editController') }}</h3>\n      </div>\n    </template>\n\n    <div class=\"p-4\">\n      <BaseMessageBlock v-if=\"spaceController\" level=\"info\" class=\"mb-3\">\n        {{\n          $tc('settings.currentSpaceControllerIs', {\n            address: shorten(spaceController)\n          })\n        }}\n        <div>\n          <BaseLink :link=\"`https://app.ens.domains/name/${ensAddress}`\">\n            {{ $t('setup.seeOnEns') }}\n          </BaseLink>\n        </div>\n      </BaseMessageBlock>\n\n      <BaseInput\n        v-model.trim=\"spaceControllerInput\"\n        :title=\"$t('settings.newController')\"\n        :placeholder=\"\n          $t('setup.spaceOwnerAddressPlaceHolder', {\n            address:\n              spaceController ?? '0x3901D0fDe202aF1427216b79f5243f8A022d68cf'\n          })\n        \"\n        focus-on-mount\n      >\n      </BaseInput>\n    </div>\n    <template #footer>\n      <TuneButton\n        class=\"my-2 w-full\"\n        primary\n        :disabled=\"!controllerInputIsValid\"\n        :loading=\"settingENSRecord\"\n        @click=\"confirmSetRecord(), $emit('close')\"\n      >\n        {{ $t('settings.set') }}\n      </TuneButton>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalDelegate.vue",
    "content": "<script setup lang=\"ts\">\nimport { sleep } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst props = defineProps<{\n  open: boolean;\n  userAddress: string;\n  spaceId: string;\n}>();\n\nconst emit = defineEmits(['close', 'reload']);\n\nconst { delegateTo, delegationLoading } = useDelegate();\n\nasync function handleDelegate() {\n  emit('close');\n  await delegateTo(props.userAddress, props.spaceId);\n  await sleep(5000);\n  emit('reload');\n}\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <div class=\"flex flex-row items-center justify-center\">\n        <h3>{{ $t('profile.about.delegate') }}</h3>\n      </div>\n    </template>\n    <div class=\"space-y-3 p-4\">\n      <BaseInput\n        :model-value=\"userAddress\"\n        :title=\"$t('profile.about.delegateTo')\"\n        readonly\n      >\n        <template #label>{{ $t('delegate.to') }}</template>\n      </BaseInput>\n      <BaseInput :model-value=\"spaceId\" :title=\"$t('space')\" readonly>\n        <template #label>{{ $t('space') }}</template>\n      </BaseInput>\n    </div>\n    <div class=\"p-4\">\n      <TuneButton\n        primary\n        :loading=\"delegationLoading\"\n        class=\"w-full\"\n        @click=\"handleDelegate\"\n      >\n        {{ $t('confirm') }}\n      </TuneButton>\n    </div>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalEmail.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  open: boolean;\n}>();\nconst emit = defineEmits(['close']);\n\nconst { initialized, userState, loadEmailSubscriptions } =\n  useEmailSubscription();\n\nwatch(\n  () => props.open,\n  () => {\n    if (\n      (props.open && !initialized.value) ||\n      userState.value === 'UNVERIFIED'\n    ) {\n      loadEmailSubscriptions();\n    }\n  }\n);\n</script>\n\n<template>\n  <BaseModal max-height=\"510px\" :open=\"open\" @close=\"emit('close')\">\n    <template #header>\n      <h3 v-if=\"userState === 'NOT_SUBSCRIBED'\">\n        {{ $t('emailSubscription.title') }}\n      </h3>\n      <h3 v-else-if=\"userState === 'UNVERIFIED'\">\n        {{ $t('emailResend.title') }}\n      </h3>\n      <h3 v-else-if=\"userState === 'VERIFIED'\">\n        {{ $t('emailManagement.title') }}\n      </h3>\n    </template>\n\n    <LoadingRow v-if=\"!initialized\" />\n    <ModalEmailSubscription\n      v-else-if=\"userState === 'NOT_SUBSCRIBED'\"\n      @close=\"emit('close')\"\n    />\n    <ModalEmailResend\n      v-else-if=\"userState === 'UNVERIFIED'\"\n      @close=\"emit('close')\"\n    />\n    <ModalEmailManagement\n      v-else-if=\"userState === 'VERIFIED'\"\n      @close=\"emit('close')\"\n    />\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalEmailManagement.vue",
    "content": "<script setup lang=\"ts\">\nconst emit = defineEmits(['close']);\n\nconst { loading, error, clientSubscriptions, updateSubscriptions } =\n  useEmailSubscription();\nconst { t } = useI18n();\nconst { notify } = useFlashNotification();\n\nconst updateSubscriptionKeys = (key, value) => {\n  clientSubscriptions.value = { ...clientSubscriptions.value, [key]: value };\n};\n\nwatchEffect(() => {\n  if (error.value) {\n    notify(['red', t('notify.somethingWentWrong')]);\n  }\n});\n\nconst submit = async () => {\n  await updateSubscriptions();\n  if (error.value) {\n    error.value = null;\n  } else {\n    notify(['green', t('notify.emailPreferencesUpdated')]);\n    emit('close');\n  }\n};\n</script>\n\n<template>\n  <div class=\"m-4 flex flex-col gap-4\">\n    <p class=\"text-sm text-skin-text opacity-60\">\n      {{ t('emailManagement.subtitle') }}\n    </p>\n\n    <form class=\"flex flex-col space-y-4\" @submit.prevent=\"submit\">\n      <TuneSwitch\n        :model-value=\"clientSubscriptions.summary\"\n        :label=\"t('emailManagement.optionSummary')\"\n        :sublabel=\"t('emailManagement.optionSummaryDescription')\"\n        @update:model-value=\"updateSubscriptionKeys('summary', $event)\"\n      />\n\n      <TuneSwitch\n        :model-value=\"clientSubscriptions.newProposal\"\n        :label=\"t('emailManagement.optionNewProposal')\"\n        :sublabel=\"t('emailManagement.optionNewProposalDescription')\"\n        @update:model-value=\"updateSubscriptionKeys('newProposal', $event)\"\n      />\n\n      <TuneSwitch\n        :model-value=\"clientSubscriptions.closedProposal\"\n        :label=\"t('emailManagement.optionClosedProposal')\"\n        :sublabel=\"t('emailManagement.optionClosedProposalDescription')\"\n        @update:model-value=\"updateSubscriptionKeys('closedProposal', $event)\"\n      />\n\n      <TuneButton class=\"w-full\" primary type=\"submit\" :loading=\"loading\">\n        {{ t('emailManagement.updatePreferences') }}\n      </TuneButton>\n    </form>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ModalEmailResend.vue",
    "content": "<script setup lang=\"ts\">\nconst emit = defineEmits(['close']);\n</script>\n\n<template>\n  <div class=\"m-4 flex flex-col gap-4 text-center\">\n    {{ $t('emailResend.description') }}\n\n    <TuneButton class=\"w-full\" primary @click=\"emit('close')\">\n      {{ $t('close') }}\n    </TuneButton>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ModalEmailSubscription.vue",
    "content": "<script setup lang=\"ts\">\nconst emit = defineEmits(['close']);\n\ntype ModalView = 'SUBSCRIBE' | 'SUCCESS';\n\nconst { web3Account } = useWeb3();\nconst { subscribe, error, loading, loadEmailSubscriptions } =\n  useEmailSubscription();\nconst { t } = useI18n();\nconst { notify } = useFlashNotification();\n\nconst email = ref('');\nconst modalView = ref<ModalView>('SUBSCRIBE');\n\nwatchEffect(() => {\n  if (error.value) {\n    notify(['red', t('notify.somethingWentWrong')]);\n    error.value = null;\n  }\n});\n\nfunction close() {\n  email.value = '';\n  emit('close');\n  modalView.value = 'SUBSCRIBE';\n  loadEmailSubscriptions();\n}\n\nasync function submit() {\n  const isSucceed = await subscribe(email.value, web3Account.value);\n\n  if (isSucceed) {\n    modalView.value = 'SUCCESS';\n  }\n}\n</script>\n\n<template>\n  <template v-if=\"modalView === 'SUBSCRIBE'\">\n    <div class=\"m-4 flex flex-col gap-4\">\n      <p>\n        {{ $t('emailSubscription.description') }}\n      </p>\n\n      <form @submit.prevent=\"submit\">\n        <BaseInput\n          v-model=\"email\"\n          :placeholder=\"$t('emailSubscription.inputPlaceholder')\"\n          class=\"!pl-[40px]\"\n          type=\"email\"\n          autocomplete=\"off\"\n          required\n          focus-on-mount\n        >\n          <template #before>\n            <i-ho-mail class=\"text-[16px]\" />\n          </template>\n        </BaseInput>\n\n        <small>{{ $t('emailSubscription.inputCaption') }}</small>\n\n        <TuneButton\n          class=\"mt-3 w-full\"\n          primary\n          :type=\"'submit'\"\n          :loading=\"loading\"\n        >\n          {{ $t('emailSubscription.subscribe') }}\n        </TuneButton>\n      </form>\n    </div>\n  </template>\n\n  <template v-if=\"modalView === 'SUCCESS'\">\n    <div class=\"m-4 gap-4 flex flex-col text-center\">\n      <i-ho-check-circle class=\"mx-auto text-center text-[3em] text-green\" />\n      <div>\n        <h3>\n          {{ $t('emailSubscription.postSubscribeMessage.successThanks') }}\n        </h3>\n        <p class=\"mt-3 italic\">\n          {{ $t('emailSubscription.postSubscribeMessage.successConfirmation') }}\n        </p>\n      </div>\n      <TuneButton class=\"w-full\" primary @click=\"close\">\n        {{ $t('close') }}\n      </TuneButton>\n    </div>\n  </template>\n</template>\n"
  },
  {
    "path": "src/components/ModalLinkPreview.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  open: boolean;\n  clickedUrl: string;\n}>();\n\ndefineEmits<{\n  (event: 'close'): void;\n  (event: 'confirm'): void;\n}>();\n</script>\n\n<template>\n  <!-- eslint-disable vue/no-v-html -->\n  <ModalConfirmAction\n    :open=\"open\"\n    title=\"Proceed with caution!\"\n    show-cancel\n    @close=\"$emit('close')\"\n    @confirm=\"$emit('confirm')\"\n  >\n    <div\n      class=\"p-4 text-center\"\n      v-html=\"\n        $t('linkPreview', {\n          url: `<span class='text-skin-link font-semibold break-words'>${clickedUrl}</span>`\n        })\n      \"\n    />\n  </ModalConfirmAction>\n</template>\n"
  },
  {
    "path": "src/components/ModalMessage.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  open: boolean;\n  title: string;\n  level: 'info' | 'warning' | 'warning-red';\n}>();\n\ndefineEmits(['close']);\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <div class=\"flex flex-row items-center justify-center\">\n        <h3>{{ title }}</h3>\n      </div>\n    </template>\n\n    <BaseMessageBlock :level=\"level\" class=\"m-4 whitespace-pre-line\">\n      <slot name=\"message\" />\n    </BaseMessageBlock>\n    <template #footer>\n      <TuneButton class=\"w-full\" primary @click=\"$emit('close')\">\n        {{ $t('continue') }}\n      </TuneButton>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalNotice.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{ open: boolean; title: string }>();\ndefineEmits(['close']);\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>{{ title }}</h3>\n    </template>\n    <div class=\"my-2 flex h-full flex-col p-4 text-center\">\n      <slot />\n    </div>\n    <template #footer>\n      <div>\n        <TuneButton class=\"w-full\" primary @click=\"$emit('close')\">\n          {{ $t('continue') }}\n        </TuneButton>\n      </div>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalOsnap.vue",
    "content": "<script setup lang=\"ts\">\nimport { TreasuryWallet } from '@/helpers/interfaces';\nimport { Network } from '@/plugins/oSnap/types';\nimport { makeConfigureOsnapUrl } from '@/plugins/oSnap/utils/getters';\n\nconst props = defineProps<{\n  open: boolean;\n  isOsnapEnabled: boolean;\n  treasury: TreasuryWallet;\n  spaceName: string;\n}>();\ndefineEmits<{\n  (e: 'close'): void;\n}>();\nconst spaceUrl = window.location.href.replace('/settings', '');\nconst href = computed(() =>\n  makeConfigureOsnapUrl({\n    spaceUrl,\n    spaceName: props.spaceName,\n    safeAddress: props.treasury.address,\n    network: props.treasury.network as Network\n  })\n);\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3 v-text=\"'Configure oSnap'\" />\n    </template>\n    <div class=\"m-4 grid grid-cols-[auto,auto] gap-2\">\n      <i-ho-information-circle class=\"mt-1 text-sm\" />\n      <p class=\"text-sm\">\n        oSnap seamlessly integrates with Snapshot and your treasury,\n        automatically executing governance votes on-chain. Bypass the need for\n        privileged signers to create a DAO that's more efficient and truly\n        decentralized.\n        <BaseLink class=\"mt-1 block text-skin-link\" link=\"https://uma.xyz/osnap\"\n          >Learn more</BaseLink\n        >\n      </p>\n    </div>\n\n    <template #footer>\n      <a\n        :href=\"href\"\n        target=\"_blank\"\n        class=\"block w-full rounded-full py-[12px] text-white\"\n        :class=\"\n          isOsnapEnabled ? 'bg-[hsl(349,65%,52%)]' : 'bg-[hsl(240,83%,58%)]'\n        \"\n        @click.stop=\"$emit('close')\"\n      >\n        {{ isOsnapEnabled ? 'Deactivate' : 'Activate' }} oSnap\n        <i-ho-external-link class=\"mb-[2px] ml-1 inline-block text-xs\" />\n      </a>\n      <p class=\"mt-2 text-xs text-skin-text\">\n        Note that the deactivation process takes place in the Safe app\n      </p>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalPendingTransactions.vue",
    "content": "<script setup lang=\"ts\">\nimport { explorerUrl, shorten } from '@/helpers/utils';\n\ndefineProps<{\n  open: boolean;\n}>();\n\ndefineEmits(['close']);\n\nconst { pendingTransactionsWithHash } = useTxStatus();\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>{{ $t('setup.pendingTransactions') }}</h3>\n    </template>\n    <div class=\"flex w-full flex-col space-y-2 p-4\">\n      <BaseLink\n        v-for=\"tx in pendingTransactionsWithHash\"\n        :key=\"tx.id\"\n        :link=\"explorerUrl(tx.network, tx.hash as string, 'tx')\"\n        class=\"flex w-full flex-row items-center truncate rounded-lg border p-2 !text-skin-text hover:!text-skin-link\"\n        @click.stop\n      >\n        {{ shorten(tx.hash as string) }}\n      </BaseLink>\n    </div>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalPlugins.vue",
    "content": "<script setup lang=\"ts\">\nimport { PluginIndex } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  open: boolean;\n  plugin: Record<string, any>;\n  usedPlugins: string[];\n}>();\nconst emit = defineEmits(['add', 'close']);\n\nconst { open } = toRefs(props);\nconst searchInput = ref('');\nconst input = ref({});\nconst isValid = ref(true);\nconst selectedPlugin = ref<PluginIndex | null>(null);\n\nconst {\n  filterPlugins,\n  pluginIndex,\n  loadingPluginsSpacesCount,\n  getPluginsSpacesCount\n} = usePlugins();\n\nfunction handleSubmit() {\n  const key = selectedPlugin.value?.key;\n  emit('add', { input: input.value, key });\n  emit('close');\n}\n\nfunction selectPlugin(plugin: PluginIndex) {\n  selectedPlugin.value = plugin;\n  if (!plugin?.defaults?.space) return handleSubmit();\n  input.value = selectedPlugin.value?.defaults?.space ?? {};\n}\n\nconst availablePlugins = computed(() => {\n  const filteredPlugins = filterPlugins(searchInput.value);\n  const availablePlugins = filteredPlugins.filter(\n    p => !props.usedPlugins.includes(p.key)\n  );\n  return availablePlugins;\n});\n\nwatch(open, () => {\n  if (props.open) getPluginsSpacesCount();\n  if (Object.keys(props.plugin).length > 0) {\n    const key = Object.keys(props.plugin)[0];\n    input.value = props.plugin[key];\n    selectedPlugin.value =\n      Object.values(pluginIndex).find(p => p.key === key) ?? null;\n  } else {\n    input.value = {};\n    selectedPlugin.value = null;\n  }\n});\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>\n        {{\n          usedPlugins.includes(selectedPlugin?.key || '')\n            ? $t('settings.editPlugin')\n            : $t('settings.addPlugin')\n        }}\n      </h3>\n    </template>\n    <BaseSearch\n      v-if=\"!selectedPlugin?.key\"\n      v-model=\"searchInput\"\n      :placeholder=\"$t('searchPlaceholder')\"\n      focus-on-mount\n      modal\n    />\n    <div class=\"mx-4 my-4 min-h-[300px]\">\n      <div v-if=\"selectedPlugin?.key\">\n        <TextareaJson\n          v-model=\"input\"\n          v-model:is-valid=\"isValid\"\n          :placeholder=\"$t('settings.pluginParameters')\"\n          class=\"input text-left\"\n        />\n      </div>\n      <div v-if=\"!selectedPlugin?.key\">\n        <LoadingRow v-if=\"loadingPluginsSpacesCount\" block />\n        <div v-else class=\"space-y-3\">\n          <BasePluginItem\n            v-for=\"(pluginItem, i) in availablePlugins\"\n            :key=\"i\"\n            :plugin=\"pluginItem\"\n            @click=\"selectPlugin(pluginItem)\"\n          />\n\n          <BaseNoResults v-if=\"Object.keys(availablePlugins).length < 1\" />\n        </div>\n      </div>\n    </div>\n    <template v-if=\"selectedPlugin?.key\" #footer>\n      <TuneButton\n        :disabled=\"!isValid\"\n        class=\"w-full\"\n        primary\n        @click=\"handleSubmit\"\n      >\n        {{ Object.keys(plugin).length ? $t('applyChanges') : $t('add') }}\n      </TuneButton>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalPostPayment.vue",
    "content": "<script setup lang=\"ts\">\nimport { explorerUrl } from '@/helpers/utils';\n\ndefineProps<{\n  open: boolean;\n  tx: any;\n}>();\nconst { copyToClipboard } = useCopy();\n\ndefineEmits(['close']);\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>Transaction submitted</h3>\n    </template>\n    <div class=\"mx-4 mb-4\">\n      <p class=\"mt-2 text-center\">\n        Copy the transaction hash below for use on the network request form as\n        proof of payment.\n      </p>\n\n      <div class=\"p-3 my-2 text-sm text-center border rounded-xl\">\n        <b class=\"font-mono break-all text-skin-link\">{{ tx.hash }}</b>\n      </div>\n\n      <div class=\"flex\">\n        <BaseLink\n          v-tippy=\"{ content: 'View transaction in explorer' }\"\n          :link=\"explorerUrl(tx.network.toString(), tx.hash as string, 'tx')\"\n          class=\"text-skin-text\"\n        >\n          View in explorer\n        </BaseLink>\n        <div class=\"flex-grow\"></div>\n\n        <button\n          v-tippy=\"{ content: 'Copy transaction hash' }\"\n          class=\"text-skin-text\"\n          @click=\"copyToClipboard(tx.hash)\"\n        >\n          <i-ho-duplicate class=\"inline-block text-xs\" />\n          Copy tx hash\n        </button>\n      </div>\n    </div>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalPostVote.vue",
    "content": "<script setup lang=\"ts\">\nimport { getChoiceString } from '@/helpers/utils';\nimport { ExtendedSpace, Proposal } from '@/helpers/interfaces';\nimport { getSafeAppLink } from '@/plugins/oSnap/utils';\n\nconst { shareVote, shareProposalX, shareProposalHey } = useSharing();\nconst { web3, web3Account } = useWeb3();\nconst { userState, loadEmailSubscriptions, initialized } =\n  useEmailSubscription();\n\nconst props = defineProps<{\n  open: boolean;\n  space: ExtendedSpace;\n  proposal: Proposal;\n  selectedChoices: any;\n  waitingForSigners: boolean;\n}>();\n\nconst emit = defineEmits(['close', 'subscribeEmail']);\n\nconst subscribeEmail = () => {\n  emit('subscribeEmail');\n  emit('close');\n};\n\nconst imgPath = computed(() => {\n  return props.waitingForSigners\n    ? '/stickers/just_signed.png'\n    : '/stickers/hooray.png';\n});\n\nfunction share(shareTo: 'x' | 'hey') {\n  shareVote(shareTo, {\n    space: props.space,\n    proposal: props.proposal,\n    choices: getChoiceString(props.proposal, props.selectedChoices)\n  });\n}\n\nonMounted(() => {\n  if (!initialized.value) {\n    loadEmailSubscriptions();\n  }\n});\n</script>\n\n<template>\n  <TuneModal :open=\"open\" max-height=\"550px\" @close=\"emit('close')\">\n    <div class=\"flex flex-col justify-between p-4 md:h-auto\">\n      <div>\n        <img\n          :src=\"imgPath\"\n          class=\"mx-auto mt-5 h-[220px] sm:h-[300px] md:h-[220px]\"\n          alt=\"hooray sticker\"\n        />\n        <div class=\"mt-4 text-center\">\n          <template v-if=\"props.waitingForSigners\">\n            <h3 v-text=\"$t('proposal.postVoteModal.gnosisSafeTitle')\" />\n            <p\n              class=\"italic\"\n              v-text=\"$t('proposal.postVoteModal.gnosisSafeDescription')\"\n            />\n          </template>\n\n          <template v-else>\n            <h3 v-text=\"$t('proposal.postVoteModal.defaultTitle')\" />\n            <p>Thank you for your participation!</p>\n            <p>Votes can be changed while the proposal is active.</p>\n          </template>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"space-y-2 p-3\">\n      <TuneButton\n        class=\"flex !h-[42px] w-full items-center justify-center gap-2\"\n        @click=\"\n          props.waitingForSigners ? shareProposalX(space, proposal) : share('x')\n        \"\n      >\n        <i-s-x class=\"text-md\" />\n        Share on X\n      </TuneButton>\n      <TuneButton\n        class=\"flex !h-[42px] w-full items-center justify-center gap-2\"\n        @click=\"\n          props.waitingForSigners\n            ? shareProposalHey(space, proposal)\n            : share('hey')\n        \"\n      >\n        <i-s-hey class=\"text-[#FB3A5D]\" />\n        {{ $t('shareOnHey') }}\n      </TuneButton>\n\n      <TuneButton\n        v-if=\"userState !== 'VERIFIED' && initialized\"\n        class=\"flex !h-[42px] w-full items-center justify-center gap-2\"\n        @click=\"subscribeEmail\"\n      >\n        <i-ho-mail class=\"text-skin-link\" />\n        {{ $t('proposal.postVoteModal.subscribe') }}\n      </TuneButton>\n      <div v-if=\"props.waitingForSigners\">\n        <BaseLink\n          :link=\"\n            getSafeAppLink(web3.network.chainId, web3Account, {\n              path: 'transactions/queue'\n            })\n          \"\n          hide-external-icon\n        >\n          <TuneButton tabindex=\"-1\" class=\"w-full\">\n            <div class=\"flex flex-grow items-center justify-center gap-1\">\n              {{ $t('proposal.postVoteModal.seeQueue') }}\n              <i-ho-external-link class=\"text-sm\" />\n            </div>\n          </TuneButton>\n        </BaseLink>\n      </div>\n\n      <TuneButton\n        primary\n        class=\"!h-[42px] w-full\"\n        data-testid=\"post-vote-modal-close\"\n        @click=\"emit('close')\"\n      >\n        {{ $t('close') }}\n      </TuneButton>\n    </div>\n  </TuneModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalProfileForm.vue",
    "content": "<script setup lang=\"ts\">\nimport schemas from '@snapshot-labs/snapshot.js/src/schemas';\nimport client from '@/helpers/clientEIP712';\nimport { clearStampCache } from '@/helpers/utils';\n\nconst props = defineProps<{\n  address: string;\n  profile?: { name?: string; about?: string; avatar?: string };\n  open: boolean;\n}>();\n\nconst emit = defineEmits(['close']);\n\nconst { aliasWallet, actionWithAlias, actionLoading } = useAliasAction();\nconst { web3Account } = useWeb3();\nconst { notify } = useFlashNotification();\nconst { t } = useI18n();\nconst { reloadProfile } = useProfiles();\n\nconst properties = schemas.profile.properties;\n\nconst form = ref({\n  name: '',\n  avatar: '',\n  about: ''\n});\n\nasync function save() {\n  await client.profile(aliasWallet.value, aliasWallet.value.address, {\n    from: web3Account.value,\n    timestamp: Number((Date.now() / 1e3).toFixed()),\n    profile: JSON.stringify(form.value)\n  });\n  await clearStampCache(props.address, 'avatar');\n  reloadProfile(props.address);\n  emit('close');\n  return notify(['green', t('notify.saved')]);\n}\n\nwatch(\n  () => props.open,\n  () => {\n    form.value = {\n      name: props.profile?.name ?? '',\n      avatar: props.profile?.avatar ?? '',\n      about: props.profile?.about ?? ''\n    };\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <div class=\"flex flex-row items-center justify-center\">\n        <h3>{{ $t('profile.settings.header') }}</h3>\n      </div>\n    </template>\n\n    <div class=\"space-y-2 p-4\">\n      <div class=\"flex justify-center\">\n        <InputUploadAvatar\n          @image-uploaded=\"url => (form.avatar = url)\"\n          @image-remove=\"form.avatar = ''\"\n        >\n          <template #avatar=\"{ uploading, previewFile }\">\n            <div class=\"relative\">\n              <AvatarUser\n                :address=\"address\"\n                :preview-file=\"previewFile\"\n                size=\"80\"\n              />\n              <AvatarOverlayEdit :loading=\"uploading\" :avatar=\"form?.avatar\" />\n              <div\n                class=\"absolute bottom-[2px] right-0 rounded-full bg-skin-heading p-1\"\n              >\n                <i-ho-pencil class=\"text-[12px] text-skin-bg\" />\n              </div>\n            </div>\n          </template>\n        </InputUploadAvatar>\n      </div>\n\n      <BaseInput\n        v-model=\"form.name\"\n        :title=\"$t('profile.settings.name')\"\n        type=\"text\"\n        :placeholder=\"$t('profile.settings.namePlaceholder')\"\n        :max-length=\"properties.name.maxLength\"\n        focus-on-mount\n      />\n      <div>\n        <LabelInput> {{ $t('profile.settings.biography') }} </LabelInput>\n        <TextareaAutosize\n          v-model=\"form.about\"\n          class=\"s-input !rounded-3xl\"\n          :max-length=\"properties.about.maxLength\"\n          :placeholder=\"$t('profile.settings.bioPlaceholder')\"\n        />\n      </div>\n    </div>\n    <div class=\"p-4\">\n      <TuneButton\n        :loading=\"actionLoading\"\n        class=\"w-full\"\n        primary\n        @click=\"actionWithAlias(save)\"\n      >\n        {{ $t('save') }}\n      </TuneButton>\n    </div>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalRevokeDelegate.vue",
    "content": "<script setup lang=\"ts\">\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { sendTransaction, sleep } from '@snapshot-labs/snapshot.js/src/utils';\nimport { formatBytes32String } from '@ethersproject/strings';\nimport { contractAddress } from '@/helpers/delegation';\nimport { Profile } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  open: boolean;\n  id: string;\n  profile: Profile;\n  delegate: string;\n}>();\n\nconst abi = ['function clearDelegate(bytes32 id)'];\n\nconst emit = defineEmits(['close', 'reload']);\n\nconst auth = getInstance();\nconst { t } = useI18n();\nconst { notify } = useFlashNotification();\n\nconst { getUsername } = useUsername();\n\nconst loading = ref(false);\nconst {\n  createPendingTransaction,\n  updatePendingTransaction,\n  removePendingTransaction\n} = useTxStatus();\n\nasync function handleSubmit() {\n  loading.value = true;\n  const txPendingId = createPendingTransaction();\n  try {\n    const tx = await sendTransaction(\n      auth.web3,\n      contractAddress,\n      abi,\n      'clearDelegate',\n      [formatBytes32String(props.id || '')]\n    );\n    updatePendingTransaction(txPendingId, { hash: tx.hash });\n    emit('close');\n    loading.value = false;\n    const receipt = await tx.wait();\n    console.log('Receipt', receipt);\n    await sleep(3e3);\n    notify(t('notify.delegationRemoved'));\n    removePendingTransaction(txPendingId);\n    emit('reload');\n  } catch (e) {\n    removePendingTransaction(txPendingId);\n    console.log(e);\n  }\n  loading.value = false;\n}\n</script>\n\n<template>\n  <BaseModal v-if=\"open\" :open=\"open\" class=\"flex\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>{{ $t('removeDelegation') }}</h3>\n    </template>\n    <form class=\"flex flex-auto flex-col\" @submit.prevent=\"handleSubmit\">\n      <h4 class=\"m-4 text-center\">\n        {{ $t('confirmRemove') }}\n        {{ getUsername(delegate, profile) }}\n        <template v-if=\"id\">{{ $tc('removeSpace', [id]) }}</template\n        >?\n      </h4>\n      <div class=\"overflow-hidden border-t p-4 text-center\">\n        <div class=\"float-left w-2/4 pr-2\">\n          <TuneButton type=\"button\" class=\"w-full\" @click=\"$emit('close')\">\n            {{ $t('cancel') }}\n          </TuneButton>\n        </div>\n        <div class=\"float-left w-2/4 pl-2\">\n          <TuneButton\n            :disabled=\"loading\"\n            :loading=\"loading\"\n            type=\"submit\"\n            class=\"w-full\"\n            primary\n          >\n            {{ $t('confirm') }}\n          </TuneButton>\n        </div>\n      </div>\n    </form>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalSelectDate.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  open: boolean;\n  value?: number;\n  type?: string;\n}>();\n\nconst emit = defineEmits(['input', 'close']);\n\nconst { open } = toRefs(props);\nconst step = ref(0);\nconst input = ref('');\nconst time = ref('12:00');\nconst isTimeValid = computed(() => {\n  const isTimeEnteringStep = step.value === 1;\n  if (!isTimeEnteringStep) return true;\n  if (!input.value) return false;\n  const startDateString = `${input.value} ${time.value}:59`;\n  const startTimestamp = new Date(startDateString).getTime();\n  return startTimestamp >= Date.now();\n});\n\nfunction formatDate(date) {\n  const output = { h: '12', m: '00', dateString: '' };\n  if (!date) return output;\n  const dateObject = new Date(date * 1000);\n  const offset = dateObject.getTimezoneOffset();\n  const data = new Date(dateObject.getTime() - offset * 60 * 1000);\n  output.dateString = data.toISOString().split('T')[0];\n  output.h = `0${dateObject.getHours().toString()}`.slice(-2);\n  output.m = `0${dateObject.getMinutes().toString()}`.slice(-2);\n  return output;\n}\n\nfunction combineDateAndTime(date, time) {\n  const dateString = `${date} ${time}:00`;\n  return new Date(dateString).getTime() / 1000;\n}\n\nfunction handleSubmit() {\n  if (step.value === 0) return (step.value = 1);\n  const timestamp = combineDateAndTime(input.value, time.value);\n  const now = parseInt((Date.now() / 1e3).toFixed());\n  emit('input', Math.max(timestamp, now));\n  emit('close');\n}\n\nwatch(open, () => {\n  step.value = 0;\n  if (!props.value) return;\n  const { dateString, h, m } = formatDate(props.value);\n  time.value = `${h}:${m}`;\n  input.value = dateString;\n});\n\nwatch(step, () => {\n  if (step.value === 0) return;\n  const selectedDateTimestamp = combineDateAndTime(input.value, time.value);\n  const timestamp = Math.max(\n    selectedDateTimestamp,\n    parseInt((Date.now() / 1e3 + 10).toFixed())\n  );\n  const { dateString, h, m } = formatDate(timestamp);\n  time.value = `${h}:${m}`;\n  input.value = dateString;\n});\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3 v-if=\"step === 0\">\n        {{ type === 'start' ? $t('create.startDate') : $t('create.endDate') }}\n      </h3>\n      <h3 v-else>\n        {{ type === 'start' ? $t('create.startTime') : $t('create.endTime') }}\n      </h3>\n    </template>\n    <div v-if=\"step === 0\">\n      <div class=\"m-4\">\n        <BaseCalendar v-model=\"input\" class=\"mx-auto mb-2\" />\n      </div>\n    </div>\n    <div v-else class=\"m-4\">\n      <input\n        v-model=\"time\"\n        type=\"time\"\n        class=\"s-input form-input mx-auto max-w-[140px] text-center text-lg\"\n      />\n      <TuneErrorInput\n        v-if=\"!isTimeValid\"\n        class=\"mx-auto mt-2 text-center\"\n        :error=\"$t('create.errorTimeInPast')\"\n      />\n    </div>\n    <template #footer>\n      <div class=\"float-left w-2/4 pr-2\">\n        <TuneButton type=\"button\" class=\"w-full\" @click=\"$emit('close')\">\n          {{ $t('cancel') }}\n        </TuneButton>\n      </div>\n      <div class=\"float-left w-2/4 pl-2\">\n        <TuneButton\n          :disabled=\"!isTimeValid\"\n          class=\"w-full\"\n          primary\n          @click=\"handleSubmit\"\n        >\n          <span v-if=\"step === 0\">{{ $t('next') }}</span>\n          <span v-else>{{ $t('select') }}</span>\n        </TuneButton>\n      </div>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalSkins.vue",
    "content": "<script setup>\nconst props = defineProps({\n  open: {\n    type: Boolean,\n    required: true\n  }\n});\n\nconst emit = defineEmits(['close', 'update:modelValue']);\n\nconst searchInput = ref('');\nconst { filterSkins, getSkinsSpacesCount, loadingSkins } = useSkinsFilter();\nconst filteredSkins = computed(() => filterSkins(searchInput.value));\n\nwatch(\n  () => props.open,\n  () => {\n    if (props.open) getSkinsSpacesCount();\n  }\n);\n\nfunction select(key) {\n  emit('update:modelValue', key);\n  emit('close');\n}\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>{{ $t('skins') }}</h3>\n    </template>\n    <BaseSearch\n      v-model=\"searchInput\"\n      :placeholder=\"$t('searchPlaceholder')\"\n      focus-on-mount\n      modal\n    />\n    <div class=\"mx-0 my-4 min-h-[339px] md:mx-4\">\n      <LoadingRow v-if=\"loadingSkins\" block />\n      <div v-else class=\"space-y-3\">\n        <div\n          v-if=\"!searchInput\"\n          key=\"\"\n          class=\"default cursor-pointer rounded-none md:rounded-md\"\n          @click=\"select(undefined)\"\n        >\n          <BaseBlock>\n            <TuneButton class=\"mb-2\" primary>{{\n              $t('defaultSkin')\n            }}</TuneButton>\n          </BaseBlock>\n        </div>\n\n        <BaseSkinItem\n          v-for=\"skin in filteredSkins\"\n          :key=\"skin\"\n          :skin=\"skin\"\n          @click=\"select(skin)\"\n        />\n\n        <BaseNoResults v-if=\"Object.keys(filteredSkins).length < 1\" />\n      </div>\n    </div>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalSnapshotTerms.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  open: boolean;\n}>();\n\ndefineEmits(['close']);\n// TODO: Add correct TOS text\n</script>\n\n<template>\n  <TuneModal size=\"medium\" :open=\"open\" @close=\"$emit('close')\">\n    <TuneModalTitle as=\"h4\" class=\"m-3\"> Term of use </TuneModalTitle>\n    <div\n      class=\"p-3 pt-0 space-y-2 max-h-[calc(100vh-130px)] md:max-h-[488px] overflow-y-auto\"\n    >\n      Lorem ipsum dolor, sit amet consectetur adipisicing elit. Alias maxime\n      ipsum reiciendis delectus laboriosam consequuntur ipsa, exercitationem,\n      ex, quam quaerat nisi dignissimos aperiam quisquam odio totam aliquam\n      necessitatibus! Natus, itaque.\n      <br />\n      <br />\n      Lorem ipsum dolor, sit amet consectetur adipisicing elit. Alias maxime\n      ipsum reiciendis delectus laboriosam consequuntur ipsa, exercitationem,\n      ex, quam quaerat nisi dignissimos aperiam quisquam odio totam aliquam\n      necessitatibus! Natus, itaque. Lorem ipsum dolor, sit amet consectetur\n      adipisicing elit. Alias maxime ipsum reiciendis delectus laboriosam\n      consequuntur ipsa, exercitationem, ex, quam quaerat nisi dignissimos\n      aperiam quisquam odio totam aliquam necessitatibus! Natus, itaque.\n      <br />\n      <br />\n      Lorem ipsum dolor, sit amet consectetur adipisicing elit. Alias maxime\n      ipsum reiciendis delectus laboriosam consequuntur ipsa, exercitationem,\n      ex, quam quaerat nisi dignissimos aperiam quisquam odio totam aliquam\n      necessitatibus! Natus, itaque.\n      <br />\n      <br />\n      Lorem ipsum dolor, sit amet consectetur adipisicing elit. Alias maxime\n      ipsum reiciendis delectus laboriosam consequuntur ipsa, exercitationem,\n      ex, quam quaerat nisi dignissimos aperiam quisquam odio totam aliquam\n      necessitatibus! Natus, itaque.\n      <br />\n      <br />\n      Lorem ipsum dolor, sit amet consectetur adipisicing elit. Alias maxime\n      ipsum reiciendis delectus laboriosam consequuntur ipsa, exercitationem,\n      ex, quam quaerat nisi dignissimos aperiam quisquam odio totam aliquam\n      necessitatibus! Natus, itaque.\n      <br />\n      <br />\n      Lorem ipsum dolor, sit amet consectetur adipisicing elit. Alias maxime\n      ipsum reiciendis delectus laboriosam consequuntur ipsa, exercitationem,\n      ex, quam quaerat nisi dignissimos aperiam quisquam odio totam aliquam\n      necessitatibus! Natus, itaque. Lorem ipsum dolor, sit amet consectetur\n      adipisicing elit. Alias maxime ipsum reiciendis delectus laboriosam\n      consequuntur ipsa, exercitationem, ex, quam quaerat nisi dignissimos\n      aperiam quisquam odio totam aliquam necessitatibus! Natus, itaque.\n      <br />\n      <br />\n      Lorem ipsum dolor, sit amet consectetur adipisicing elit. Alias maxime\n      ipsum reiciendis delectus laboriosam consequuntur ipsa, exercitationem,\n      ex, quam quaerat nisi dignissimos aperiam quisquam odio totam aliquam\n      necessitatibus! Natus, itaque.\n    </div>\n    <div class=\"p-3 flex gap-3\">\n      <TuneButton class=\"w-full\" @click=\"$emit('close')\"> Close </TuneButton>\n    </div>\n  </TuneModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalSpaces.vue",
    "content": "<script setup lang=\"ts\">\nimport { Space } from '@/helpers/interfaces';\n\ndefineProps<{\n  open: boolean;\n  spaces: Space[];\n}>();\n\ndefineEmits(['close']);\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <div class=\"flex flex-row items-center justify-center\">\n        <h3>{{ $t('spaces') }}</h3>\n      </div>\n    </template>\n    <div class=\"space-y-3 py-4 md:px-4\">\n      <div v-for=\"space in spaces\" :key=\"space.id\">\n        <ModalSpacesListItem :space=\"space\" />\n      </div>\n    </div>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalSpacesListItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { Space } from '@/helpers/interfaces';\n\nconst { formatCompactNumber } = useIntl();\n\ndefineProps<{\n  space: Space;\n}>();\n</script>\n\n<template>\n  <router-link :to=\"{ name: 'spaceProposals', params: { key: space.id } }\">\n    <BaseBlock class=\"\">\n      <div class=\"flex justify-between\">\n        <div class=\"flex min-w-0\">\n          <AvatarSpace :space=\"space\" size=\"44\" />\n          <div class=\"ml-3 mr-3 truncate\">\n            <div class=\"flex items-center\">\n              <div class=\"truncate\">\n                {{ space.name }}\n              </div>\n              <IconVerifiedSpace\n                v-if=\"space.verified\"\n                :turbo=\"space.turbo\"\n                class=\"ml-1 flex text-skin-primary\"\n                size=\"18\"\n              />\n            </div>\n            <div class=\"text-xs leading-5 text-skin-text\">\n              {{\n                $tc('members', {\n                  count: formatCompactNumber(space.followersCount || 0)\n                })\n              }}\n            </div>\n          </div>\n        </div>\n        <ButtonFollow :space=\"space\" />\n      </div>\n    </BaseBlock>\n  </router-link>\n</template>\n"
  },
  {
    "path": "src/components/ModalStrategies.vue",
    "content": "<script setup lang=\"ts\">\nimport { Proposal, SpaceStrategy } from '@/helpers/interfaces';\n\ndefineProps<{\n  open: boolean;\n  strategies: SpaceStrategy[];\n  proposal?: Proposal;\n}>();\n\ndefineEmits(['close']);\n</script>\n\n<template>\n  <TuneModal :open=\"open\" @close=\"$emit('close')\">\n    <TuneModalTitle class=\"my-3 px-3\">\n      {{ $t('strategiesPage') }}\n    </TuneModalTitle>\n    <div\n      class=\"max-h-[calc(100vh-100px)] md:max-h-[488px] overflow-y-auto space-y-3 px-3 pb-3\"\n    >\n      <StrategiesListItem\n        v-for=\"(strategy, i) in strategies\"\n        :key=\"i\"\n        :strategy=\"strategy\"\n        :proposal=\"proposal\"\n      />\n    </div>\n  </TuneModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalStrategy.vue",
    "content": "<script setup lang=\"ts\">\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { validateForm } from '@/helpers/validation';\n\nconst defaultParams = {\n  symbol: 'DAI',\n  address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',\n  decimals: 18\n};\n\nconst props = defineProps<{\n  open: boolean;\n  strategy: {\n    name: string;\n    network: string;\n    params: Record<string, any>;\n  };\n  defaultNetwork?: string;\n}>();\n\nconst emit = defineEmits(['add', 'close']);\nconst { open } = toRefs(props);\nconst searchInput = ref('');\nconst isValidJson = ref(true);\nconst loading = ref(false);\nconst input = ref({\n  name: '',\n  network: '1',\n  params: {} as Record<string, any>\n});\nconst formRef = ref();\n\nconst {\n  filterStrategies,\n  getStrategies,\n  isLoadingStrategies,\n  getExtendedStrategy,\n  extendedStrategy,\n  strategyDefinition\n} = useStrategies();\nconst strategiesResults = computed(() => filterStrategies(searchInput.value));\n\nconst { getNetworksSpacesCount } = useNetworksFilter();\n\nconst validationErrors = computed(() => {\n  return validateForm(strategyDefinition.value || {}, input.value.params);\n});\n\nconst isValid = computed(() => {\n  return Object.values(validationErrors.value).length === 0;\n});\n\nfunction handleSubmit() {\n  if (!isValid.value || !isValidJson.value)\n    return formRef?.value?.forceShowError();\n\n  const strategyObj = clone(input.value);\n  emit('add', strategyObj);\n  emit('close');\n}\nasync function initStrategy(strategyName) {\n  loading.value = true;\n  await getExtendedStrategy(strategyName);\n}\nasync function selectStrategy(strategyName) {\n  input.value.name = strategyName;\n  await initStrategy(strategyName);\n  const params =\n    extendedStrategy.value?.examples?.[0]?.strategy?.params || defaultParams;\n\n  input.value.params = strategyDefinition.value ? {} : params;\n  loading.value = false;\n}\nasync function editStrategy(strategyName) {\n  input.value = props.strategy;\n  await initStrategy(strategyName);\n  loading.value = false;\n}\n\nwatch(open, () => {\n  input.value.network = props.defaultNetwork ?? '';\n  // compute the spaces count for network ordering.\n  getNetworksSpacesCount();\n  if (props.open && !props.strategy?.name) getStrategies();\n  if (props.strategy?.name) {\n    editStrategy(props.strategy.name);\n  } else {\n    input.value = {\n      name: '',\n      network: props.defaultNetwork ?? '1',\n      params: defaultParams\n    };\n  }\n});\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3 v-text=\"input.name ? input.name : $t('settings.addStrategy')\" />\n    </template>\n    <BaseSearch\n      v-if=\"!strategy.name && !input.name\"\n      v-model=\"searchInput\"\n      :placeholder=\"$t('searchPlaceholder')\"\n      focus-on-mount\n      modal\n    />\n    <div v-if=\"input.name\" class=\"m-4\">\n      <LoadingRow v-if=\"loading\" class=\"px-0\" />\n      <div v-else>\n        <div class=\"min-h-[250px] space-y-2\">\n          <ComboboxNetwork\n            :network=\"input.network\"\n            @select=\"value => (input.network = value)\"\n          />\n          <div>\n            <TuneForm\n              v-if=\"strategyDefinition\"\n              ref=\"formRef\"\n              v-model=\"input.params\"\n              :definition=\"strategyDefinition\"\n              :error=\"validationErrors\"\n            />\n            <TuneTextareaJson\n              v-else\n              v-model=\"input.params\"\n              :placeholder=\"$t('strategyParameters')\"\n              @update:is-valid=\"value => (isValidJson = value)\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <div v-else class=\"mx-0 my-4 min-h-[300px] md:mx-4\">\n      <LoadingRow v-if=\"isLoadingStrategies\" block />\n      <div v-else class=\"space-y-3\">\n        <BaseStrategyItem\n          v-for=\"str in strategiesResults\"\n          :key=\"str.id\"\n          :strategy=\"str\"\n          @click=\"selectStrategy(str.id)\"\n        />\n        <BaseNoResults v-if=\"strategiesResults.length < 1\" />\n      </div>\n    </div>\n    <template v-if=\"input.name\" #footer>\n      <ButtonPlayground\n        big\n        :name=\"strategy.name\"\n        :network=\"strategy.network\"\n        :params=\"strategy.params\"\n      />\n      <TuneButton\n        :disabled=\"loading\"\n        class=\"mt-2 w-full\"\n        primary\n        @click=\"handleSubmit\"\n      >\n        {{ strategy.name ? $t('save') : $t('add') }}\n      </TuneButton>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalTerms.vue",
    "content": "<script setup lang=\"ts\">\nimport { getUrl } from '@snapshot-labs/snapshot.js/src/utils';\nimport { Space, RankedSpace, ExtendedSpace } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  open: boolean;\n  space: Space | RankedSpace | ExtendedSpace;\n  action: string;\n}>();\n\nconst emit = defineEmits(['accept', 'close']);\n\nconst getIpfsUrl = getUrl(props.space.terms ?? '');\n\nfunction accept() {\n  emit('accept');\n  emit('close');\n}\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>{{ $t('settings.terms.label') }}</h3>\n    </template>\n    <div class=\"py-4 text-center md:p-4\">\n      <BaseMessageBlock is-responsive level=\"info\" class=\"mb-4 text-left\">\n        {{\n          $tc('modalTerms.mustAgreeTo', {\n            action,\n            spaceName: space.name || 'spaces'\n          })\n        }}\n      </BaseMessageBlock>\n\n      <BaseLink :link=\"space.terms!\">\n        <TextAutolinker :text=\"getIpfsUrl\" :truncate=\"35\" />\n      </BaseLink>\n    </div>\n    <template #footer>\n      <div class=\"float-left w-2/4 pr-2\">\n        <TuneButton type=\"button\" class=\"w-full\" @click=\"$emit('close')\">\n          {{ $t('cancel') }}\n        </TuneButton>\n      </div>\n      <div class=\"float-left w-2/4 pl-2\">\n        <TuneButton class=\"w-full\" primary @click=\"accept\">\n          {{ $t('agree') }}\n        </TuneButton>\n      </div>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalTokens.vue",
    "content": "<script setup lang=\"ts\">\nimport { isAddress } from '@ethersproject/address';\nimport { ERC20ABI } from '@/helpers/constants';\nimport { Token } from '@/helpers/alchemy';\nimport snapshot from '@snapshot-labs/snapshot.js';\n\nconst props = defineProps<{\n  selectedToken?: Token;\n  open: boolean;\n  tokens: Token[];\n  network: string;\n}>();\n\nconst emit = defineEmits(['close', 'update:selectedToken']);\n\nconst { web3Account } = useWeb3();\n\nconst searchInput = ref('');\nconst customTokenLoading = ref(false);\nconst customToken: Ref<Token | null> = ref(null);\nconst isSearchValueValidToken = ref(false);\n\nconst tokensFiltered = computed(() => {\n  if (customToken.value && isSearchValueValidToken.value) {\n    return [customToken.value];\n  }\n\n  return props.tokens.filter(filterToken);\n});\n\nfunction filterToken(token: Token) {\n  const searchQuery = searchInput.value.toLowerCase();\n  return isTokenMatchingSearch(token, searchQuery) || !searchInput.value;\n}\n\nfunction isTokenMatchingSearch(token: Token, searchQuery: string) {\n  return [token.symbol, token.name, token.contractAddress].some(\n    property => property?.toLowerCase().includes(searchQuery)\n  );\n}\n\nfunction handleTokenClick(token: Token) {\n  emit('update:selectedToken', token);\n  emit('close');\n}\n\nasync function fetchCustomToken(address: string) {\n  if (props.tokens.find(asset => asset.contractAddress === address)) return;\n\n  customTokenLoading.value = true;\n\n  const provider = snapshot.utils.getProvider(props.network);\n  const tokens = [address];\n\n  try {\n    const multi = new snapshot.utils.Multicaller(\n      props.network,\n      provider,\n      ERC20ABI\n    );\n    tokens.forEach(token => {\n      multi.call(`${token}.name`, token, 'name');\n      multi.call(`${token}.symbol`, token, 'symbol');\n      multi.call(`${token}.decimals`, token, 'decimals');\n      if (web3Account.value)\n        multi.call(`${token}.balance`, token, 'balanceOf', [web3Account.value]);\n    });\n\n    const result = await multi.execute();\n\n    const fetchedToken = result[address];\n\n    customToken.value = {\n      contractAddress: address,\n      symbol: fetchedToken.symbol,\n      name: fetchedToken.name,\n      tokenBalance: web3Account.value ? fetchedToken.balance._hex : 0,\n      decimals: fetchedToken.decimals,\n      price: 0,\n      change: 0,\n      value: 0\n    };\n\n    isSearchValueValidToken.value = true;\n  } catch (e) {\n    isSearchValueValidToken.value = false;\n  } finally {\n    customTokenLoading.value = false;\n  }\n}\n\nwatch(searchInput, value => {\n  if (!isAddress(value)) {\n    customToken.value = null;\n    return;\n  }\n\n  fetchCustomToken(value);\n});\n\nwatch(\n  () => props.open,\n  () => {\n    searchInput.value = '';\n  }\n);\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <div\n        class=\"flex flex-col content-center items-center justify-center gap-x-4\"\n      >\n        <h3>Assets</h3>\n        <BaseSearch\n          v-model=\"searchInput\"\n          placeholder=\"Search or add token address\"\n          modal\n          focus-on-mount\n          class=\"min-h-[60px] w-full flex-auto !px-3 pb-3 sm:!px-4\"\n        />\n      </div>\n    </template>\n\n    <template #default=\"{ maxHeight }\">\n      <div\n        class=\"flex w-full flex-col overflow-auto\"\n        :style=\"{ minHeight: maxHeight }\"\n      >\n        <LoadingList v-if=\"customTokenLoading\" class=\"p-4\" />\n        <template v-else>\n          <ModalTokensItem\n            v-for=\"token in tokensFiltered\"\n            :key=\"token.contractAddress\"\n            :token=\"token\"\n            :is-selected=\"\n              token.contractAddress === selectedToken?.contractAddress\n            \"\n            :network=\"network\"\n            @select=\"handleTokenClick\"\n          />\n\n          <div\n            v-if=\"!tokensFiltered.length\"\n            class=\"py-[20px] text-center text-skin-link md:py-5\"\n          >\n            <i-ho-emoji-sad class=\"mx-auto\" />\n            <div class=\"mt-2\">\n              <span v-if=\"searchInput.length && !tokensFiltered.length\">{{\n                $t('noResultsFound')\n              }}</span>\n              <span v-else-if=\"!tokensFiltered.length\">\n                No tokens found.\n                <br />\n                Add a token by searching the contract address.\n              </span>\n            </div>\n          </div>\n        </template>\n      </div>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalTokensItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten, explorerUrl } from '@/helpers/utils';\nimport { Token } from '@/helpers/alchemy';\nimport { formatUnits } from '@ethersproject/units';\n\nconst props = defineProps<{\n  token: Token;\n  isSelected: boolean;\n  network: string;\n}>();\n\nconst emit = defineEmits(['select']);\nconst { formatNumber, getNumberFormatter } = useIntl();\n\nconst exploreUrl = computed(() => {\n  return explorerUrl(props.network, props.token.contractAddress);\n});\n</script>\n\n<template>\n  <button\n    class=\"flex h-[64px] w-full cursor-pointer items-center justify-between border-b border-skin-border px-3 py-2 hover:bg-skin-border sm:px-4\"\n    :class=\"{\n      '!bg-skin-border': isSelected\n    }\"\n    @click=\"emit('select', token)\"\n  >\n    <div class=\"flex items-center\">\n      <div class=\"mr-3 flex\">\n        <AvatarToken :address=\"token.contractAddress\" size=\"38\" />\n      </div>\n\n      <div class=\"pr-4\">\n        <div class=\"flex w-full items-center text-skin-link\">\n          <div class=\"text-left text-skin-text line-clamp-1\">\n            {{ token.symbol }}\n          </div>\n        </div>\n        <span class=\"line-clamp-1 text-left text-skin-text\">\n          {{ token.name }}\n        </span>\n      </div>\n    </div>\n\n    <div class=\"h-full text-right\">\n      <span class=\"text-skin-link\">\n        {{\n          formatNumber(\n            Number(formatUnits(token.tokenBalance, token.decimals)),\n            getNumberFormatter({ maximumFractionDigits: 6 }).value\n          )\n        }}\n      </span>\n      <div>\n        <BaseLink v-if=\"exploreUrl\" :link=\"exploreUrl\" @click.stop>\n          {{ shorten(token.contractAddress) }}</BaseLink\n        >\n      </div>\n    </div>\n  </button>\n</template>\n"
  },
  {
    "path": "src/components/ModalTransactionStatus.vue",
    "content": "<script setup lang=\"ts\">\nimport { explorerUrl } from '@/helpers/utils';\n\nconst props = defineProps<{\n  open: boolean;\n  title: string;\n  subtitle: string;\n  network?: string;\n  variant: 'success' | 'loading' | 'error';\n}>();\n\nconst emit = defineEmits(['close', 'tryAgain']);\n\nconst closeButtonText = computed(() => {\n  switch (props.variant) {\n    case 'success':\n      return 'Done!';\n    default:\n      return 'Close';\n  }\n});\n\nconst closeButtonVariant = computed(() => {\n  switch (props.variant) {\n    case 'loading':\n      return 'danger';\n    default:\n      return undefined;\n  }\n});\n</script>\n\n<template>\n  <TuneModal :open=\"open\" hide-close @close=\"emit('close')\">\n    <div class=\"pt-[40px] h-full flex flex-col justify-between\">\n      <div>\n        <TuneModalIndicator :variant=\"variant\" />\n        <div class=\"my-[20px] text-center\">\n          <TuneModalTitle class=\"m-0 leading-6\">\n            {{ title }}\n          </TuneModalTitle>\n          <TuneModalDescription class=\"text-md leading-5 mt-1\">\n            <span v-if=\"subtitle.startsWith('0x') && network\">\n              <BaseLink :link=\"explorerUrl(network, subtitle, 'tx')\">\n                View on Etherscan</BaseLink\n              >\n            </span>\n            <span v-else>\n              {{ subtitle }}\n            </span>\n          </TuneModalDescription>\n        </div>\n      </div>\n      <div class=\"m-3 flex gap-[12px]\">\n        <TuneButton\n          class=\"w-full\"\n          :variant=\"closeButtonVariant\"\n          @click=\"emit('close')\"\n          >{{ closeButtonText }}</TuneButton\n        >\n        <TuneButton\n          v-if=\"variant === 'error'\"\n          primary\n          class=\"w-full\"\n          @click=\"emit('tryAgain')\"\n        >\n          Try again\n        </TuneButton>\n      </div>\n    </div>\n  </TuneModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalTreasury.vue",
    "content": "<script setup lang=\"ts\">\nimport { clone, validateSchema } from '@snapshot-labs/snapshot.js/src/utils';\nimport schemas from '@snapshot-labs/snapshot.js/src/schemas';\nimport { TreasuryWallet } from '@/helpers/interfaces';\nimport { toChecksumAddress } from '@/helpers/utils';\n\nconst props = defineProps<{\n  open: boolean;\n  treasury: TreasuryWallet;\n}>();\n\nconst emit = defineEmits(['add', 'close']);\nconst { open } = toRefs(props);\n\nconst input = ref({\n  name: '',\n  address: '',\n  network: ''\n});\n\nconst { getValidationMessage } = useFormValidation(\n  schemas.space.properties.treasuries.items,\n  input\n);\n\nconst treasuryProperties = computed(\n  () => schemas.space.properties.treasuries.items.properties\n);\n\nconst treasuryValidationErrors = computed(\n  () =>\n    validateSchema(schemas.space.properties.treasuries.items, input.value) ?? []\n);\nconst isValid = computed(() =>\n  treasuryValidationErrors.value === true ? true : false\n);\n\nfunction handleSubmit() {\n  const checksumAddress = toChecksumAddress(input.value.address);\n  const treasuryObj = clone(input.value);\n  treasuryObj.address = checksumAddress;\n  emit('add', treasuryObj);\n  emit('close');\n}\n\nwatch(open, () => {\n  if (props.treasury.name) {\n    input.value = props.treasury;\n  } else {\n    input.value = {\n      name: '',\n      address: '',\n      network: '1'\n    };\n  }\n});\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3\n        v-text=\"\n          treasury.name\n            ? $t('settings.treasuries.edit')\n            : $t('settings.treasuries.add')\n        \"\n      />\n    </template>\n    <div class=\"m-4\">\n      <div>\n        <div class=\"space-y-3\">\n          <ComboboxNetwork\n            :network=\"input.network\"\n            @select=\"value => (input.network = value)\"\n          />\n          <BaseInput\n            v-model=\"input.name\"\n            :title=\"treasuryProperties.name?.title\"\n            :placeholder=\"treasuryProperties?.name.examples[0]\"\n            :error=\"{ message: getValidationMessage('name') }\"\n            focus-on-mount\n          />\n          <BaseInput\n            v-model=\"input.address\"\n            :title=\"treasuryProperties.address?.title\"\n            :placeholder=\"treasuryProperties.address?.examples[0]\"\n            :error=\"{ message: getValidationMessage('address') }\"\n          />\n        </div>\n      </div>\n    </div>\n\n    <template #footer>\n      <TuneButton\n        :disabled=\"!isValid\"\n        class=\"w-full\"\n        primary\n        @click=\"handleSubmit\"\n      >\n        {{ treasury.name ? $t('applyChanges') : $t('add') }}\n      </TuneButton>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalValidation.vue",
    "content": "<script setup lang=\"ts\">\nimport { SpaceValidation, SpaceStrategy } from '@/helpers/interfaces';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { validateForm } from '@/helpers/validation';\n\nconst props = defineProps<{\n  open: boolean;\n  validation: SpaceValidation;\n  votingStrategies: SpaceStrategy[];\n  filterMinScore: number;\n}>();\n\nconst DEFAULT_PARAMS: Record<string, any> = {};\n\nconst emit = defineEmits(['add', 'close', 'resetMinScore']);\n\nconst { open } = toRefs(props);\nconst { t } = useI18n();\n\nconst isValidParams = ref(true);\nconst validations = ref<Validations | null>(null);\nconst isValidationsLoaded = ref(false);\nconst formRef = ref();\nconst strategiesFormRef = ref();\nconst showStrategies = ref(false);\n\nconst input = ref({\n  name: '',\n  params: clone(DEFAULT_PARAMS)\n});\n\ntype Validations = Record<\n  string,\n  {\n    key: string;\n    example?: Record<string, any>[];\n    schema?: Record<string, any>;\n    about?: string;\n    voteValidationOnly?: boolean;\n  }\n>;\n\nconst validationDefinition = computed(() => {\n  const definition =\n    validations.value?.[input.value.name]?.schema?.definitions?.Validation ||\n    null;\n\n  if (input.value.name === 'passport-gated')\n    return definePassportGated(clone(definition));\n\n  return definition;\n});\n\nfunction definePassportGated(definition: any) {\n  if (input.value.params.operator === 'NONE')\n    delete definition.properties.stamps;\n\n  return definition;\n}\n\nconst validationErrors = computed(() => {\n  return validateForm(validationDefinition.value || {}, input.value.params);\n});\n\nconst isValid = computed(() => {\n  return Object.values(validationErrors.value).length === 0;\n});\n\nfunction handleSelect(n: string) {\n  input.value.name = n;\n\n  if (props.validation.name !== n) {\n    input.value.params = clone(DEFAULT_PARAMS);\n  }\n\n  if (n === 'any') {\n    handleSubmit();\n    emit('resetMinScore');\n  }\n\n  if (n === 'basic') {\n    input.value.params.minScore =\n      input.value.params.minScore || props.filterMinScore || undefined;\n\n    if (input.value.params.strategies) {\n      showStrategies.value = true;\n    }\n  }\n\n  if (n === 'passport-gated') {\n    if (!input.value.params.operator) input.value.params.operator = 'NONE';\n  }\n}\n\nfunction handleSubmit() {\n  if (input.value.name === 'passport-gated') handlePassportGated();\n\n  if (!isValid.value || !isValidParams.value) {\n    strategiesFormRef.value?.forceShowError();\n    formRef?.value?.forceShowError();\n    return;\n  }\n\n  emit('add', clone(input.value));\n  emit('close');\n}\n\nfunction handlePassportGated() {\n  if (!input.value.params.stamps?.[0]) {\n    input.value.params.stamps = undefined;\n    input.value.params.operator = 'NONE';\n  }\n  if (input.value.params.operator === 'NONE') {\n    input.value.params.stamps = undefined;\n  }\n}\n\nfunction removeVoteValidationOnly(validations: Validations) {\n  Object.keys(validations).forEach(key => {\n    if (validations[key]?.voteValidationOnly) {\n      delete validations[key];\n    }\n  });\n}\n\nasync function getValidations() {\n  if (validations.value) return;\n  const fetchedValidations: Validations = await fetch(\n    `${import.meta.env.VITE_SCORES_URL}/api/validations`\n  ).then(res => res.json());\n  const validationsWithAny: Validations = {\n    any: {\n      key: 'any'\n    },\n    ...fetchedValidations\n  };\n\n  if (validationsWithAny.basic.schema)\n    validationsWithAny.basic.schema.definitions.Validation.properties.minScore.description =\n      t('proposalValidation.basic.minScoreHint');\n\n  removeVoteValidationOnly(validationsWithAny);\n\n  validations.value = validationsWithAny || null;\n  isValidationsLoaded.value = true;\n}\n\nwatch(open, () => {\n  getValidations();\n  input.value.name = '';\n  if (props.validation?.params) {\n    input.value.params = props.validation.params;\n  } else {\n    input.value = {\n      name: '',\n      params: clone(DEFAULT_PARAMS)\n    };\n  }\n});\n\nwatch(showStrategies, () => {\n  if (!showStrategies.value) {\n    delete input.value.params.strategies;\n    isValidParams.value = true;\n  }\n});\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>\n        {{\n          input.name\n            ? $t('proposalValidation.settingsTitle')\n            : $t('proposalValidation.title')\n        }}\n      </h3>\n    </template>\n\n    <div class=\"mx-0 my-4 min-h-[250px] md:mx-4\">\n      <div v-if=\"input.name\" class=\"mx-4 text-skin-link md:mx-0\">\n        <TuneForm\n          v-if=\"validationDefinition\"\n          ref=\"formRef\"\n          v-model=\"input.params\"\n          :definition=\"validationDefinition\"\n          :error=\"validationErrors\"\n        />\n\n        <TuneTextareaJson\n          v-else\n          v-model=\"input.params\"\n          :placeholder=\"$t('proposalValidation.paramPlaceholder')\"\n          @update:is-valid=\"value => (isValidParams = value)\"\n        />\n\n        <TuneSwitch\n          v-if=\"input.name === 'basic'\"\n          v-model=\"showStrategies\"\n          :label=\"$t('useCustomStrategies')\"\n          :hint=\"$t('proposalValidation.basic.customStrategiesHint')\"\n        />\n\n        <FormArrayStrategies\n          v-if=\"input.name === 'basic' && showStrategies\"\n          ref=\"strategiesFormRef\"\n          v-model=\"input.params.strategies\"\n          :voting-strategies=\"votingStrategies\"\n          @update:is-valid=\"value => (isValidParams = value)\"\n        />\n      </div>\n      <div v-if=\"!input.name\">\n        <LoadingRow v-if=\"!isValidationsLoaded\" block class=\"px-0\" />\n        <div v-else class=\"space-y-3\">\n          <BaseModalSelectItem\n            v-for=\"v in validations\"\n            :key=\"v.key\"\n            :title=\"$t(`proposalValidation.${v.key}.label`)\"\n            :description=\"$t(`proposalValidation.${v.key}.description`)\"\n            :selected=\"validation.name === v.key\"\n            :tag=\"v.key === 'passport-gated' ? 'Beta' : ''\"\n            @click=\"handleSelect(v.key)\"\n          />\n        </div>\n      </div>\n    </div>\n    <template v-if=\"input.name\" #footer>\n      <TuneButton class=\"w-full\" primary @click=\"handleSubmit\">\n        {{ $t('applyChanges') }}\n      </TuneButton>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalVote.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten, getChoiceString, explorerUrl } from '@/helpers/utils';\nimport { getPower, voteValidation } from '@/helpers/snapshot';\nimport { ExtendedSpace, Proposal } from '@/helpers/interfaces';\nimport shutterEncryptChoice from '@/helpers/shutter';\nimport { SNAPSHOT_HELP_LINK } from '@/helpers/constants';\n\nconst { web3Account } = useWeb3();\n\nconst votingPower = ref(0);\nconst votingPowerByStrategy = ref([]);\nconst isValidVoter = ref(false);\nconst reason = ref('');\n\nconst isValidationAndPowerLoading = ref(false);\nconst isValidationAndPowerLoaded = ref(false);\nconst hasVotingPowerFailed = ref(false);\nconst hasVotingValidationFailed = ref(false);\n\nconst props = defineProps<{\n  open: boolean;\n  space: ExtendedSpace;\n  proposal: Proposal;\n  selectedChoices: number | number[] | Record<string, any> | null;\n  strategies: { name: string; network: string; params: Record<string, any> }[];\n}>();\n\nconst emit = defineEmits(['reload', 'close', 'openPostVoteModal']);\n\nconst { send, isSending } = useClient();\nconst format = getChoiceString;\nconst { formatNumber, formatCompactNumber } = useIntl();\nconst { addVotedProposalId } = useProposals();\nconst { isGnosisAndNotSpaceNetwork } = useGnosis(props.space);\n\nconst isLoadingShutter = ref(false);\n\nconst symbols = computed(() =>\n  props.strategies.map(strategy => strategy.params.symbol || '')\n);\n\nconst validationStrategySymbolsString = computed(() => {\n  let symbols = props.proposal.validation?.params?.strategies\n    ?.map(strategy => strategy.params.symbol)\n    .filter(symbol => symbol);\n\n  if (!symbols) return '';\n\n  symbols = symbols.map(symbol => `$${symbol}`);\n\n  if (symbols.length === 1) return `${symbols[0]}`;\n\n  return `(${symbols.join(', ')})`;\n});\n\nasync function voteShutter() {\n  isLoadingShutter.value = true;\n  const encryptedChoice = await shutterEncryptChoice(\n    JSON.stringify(props.selectedChoices),\n    props.proposal.id\n  );\n  isLoadingShutter.value = false;\n\n  if (!encryptedChoice) return null;\n  return vote({\n    proposal: props.proposal,\n    choice: encryptedChoice,\n    privacy: 'shutter',\n    reason: reason.value\n  });\n}\n\nasync function vote(payload) {\n  return send(props.space, 'vote', payload);\n}\n\nasync function handleSubmit() {\n  let result: { id: string; ipfs?: string } | null = null;\n  if (props.proposal.privacy === 'shutter') result = await voteShutter();\n  else\n    result = await vote({\n      proposal: props.proposal,\n      choice: props.selectedChoices,\n      reason: reason.value\n    });\n\n  console.log('Result', result);\n\n  if (result?.id) {\n    const waitingForSigners = !result.ipfs;\n    emit('openPostVoteModal', waitingForSigners);\n    if (!waitingForSigners) emit('reload');\n    addVotedProposalId(props.proposal.id);\n  }\n  emit('close');\n}\n\nasync function loadVotingValidation() {\n  if (props.proposal.validation.name === 'any') {\n    isValidVoter.value = true;\n    return;\n  }\n  hasVotingValidationFailed.value = false;\n  try {\n    const validationRes = await voteValidation(\n      props.space,\n      web3Account.value,\n      props.proposal\n    );\n    isValidVoter.value = validationRes;\n  } catch (e) {\n    hasVotingValidationFailed.value = true;\n    console.log(e);\n  }\n}\n\nasync function loadVotingPower() {\n  hasVotingPowerFailed.value = false;\n  try {\n    const powerRes = await getPower(\n      props.space,\n      web3Account.value,\n      props.proposal\n    );\n    votingPower.value = powerRes.vp;\n    votingPowerByStrategy.value = powerRes.vp_by_strategy;\n  } catch (e) {\n    hasVotingPowerFailed.value = true;\n    console.log(e);\n  }\n}\n\nasync function loadValidationAndPower() {\n  try {\n    isValidationAndPowerLoading.value = true;\n    await Promise.all([loadVotingPower(), loadVotingValidation()]);\n  } catch (e) {\n    console.log(e);\n    isValidationAndPowerLoading.value = false;\n  } finally {\n    isValidationAndPowerLoading.value = false;\n    isValidationAndPowerLoaded.value = true;\n  }\n}\n\nwatch(\n  () => [props.open, web3Account.value],\n  async () => {\n    if (props.open === false) return;\n    loadValidationAndPower();\n  },\n  {\n    immediate: true\n  }\n);\n</script>\n\n<template>\n  <TuneModal :open=\"open\" hide-close @close=\"$emit('close')\">\n    <div class=\"mx-3\">\n      <TuneModalTitle class=\"mt-3 mx-1\">\n        {{ $tc('proposal.castVote') }}\n      </TuneModalTitle>\n\n      <div class=\"space-y-3 text-skin-link\">\n        <div class=\"mx-1\">\n          <div class=\"flex\">\n            <span class=\"mr-1 flex-auto text-skin-text\" v-text=\"$t('choice')\" />\n            <span\n              v-if=\"\n                proposal.type === 'approval' &&\n                Array.isArray(selectedChoices) &&\n                selectedChoices?.length === 0\n              \"\n              class=\"text-right\"\n            >\n              Blank vote\n            </span>\n            <span\n              v-else\n              v-tippy=\"{\n                content:\n                  format(proposal, selectedChoices).length > 30\n                    ? format(proposal, selectedChoices)\n                    : null\n              }\"\n              class=\"ml-4 truncate text-right\"\n            >\n              {{ format(proposal, selectedChoices) }}\n            </span>\n          </div>\n\n          <div class=\"flex\">\n            <span\n              class=\"mr-1 flex-auto text-skin-text\"\n              v-text=\"$t('snapshot')\"\n            />\n            <BaseLink\n              :link=\"explorerUrl(proposal.network, proposal.snapshot, 'block')\"\n              class=\"float-right\"\n            >\n              {{ formatNumber(Number(proposal.snapshot)) }}\n            </BaseLink>\n          </div>\n\n          <div\n            v-if=\"\n              proposal.validation?.name !== 'any' &&\n              isValidationAndPowerLoaded &&\n              !isValidationAndPowerLoading\n            \"\n            class=\"flex\"\n          >\n            <span\n              class=\"mr-1 flex-auto text-skin-text\"\n              v-text=\"$t('votingValidation.label')\"\n            />\n            <div class=\"flex items-center gap-1\">\n              <span\n                v-if=\"hasVotingValidationFailed\"\n                class=\"flex items-center gap-1\"\n              >\n                <i-ho-exclamation-circle class=\"text-sm text-red\" />\n                {{ $t('failed') }}\n              </span>\n              <span v-else class=\"flex items-center\">\n                <i-ho-check v-if=\"isValidVoter\" class=\"text-green\" />\n                <i-ho-x v-else class=\"text-red\" />\n                {{ $t(`votingValidation.${proposal.validation.name}.label`) }}\n              </span>\n            </div>\n          </div>\n\n          <div class=\"flex\">\n            <span\n              class=\"mr-1 flex-auto text-skin-text\"\n              v-text=\"$t('votingPower')\"\n            />\n            <span v-if=\"hasVotingPowerFailed\" class=\"flex items-center gap-1\">\n              <i-ho-exclamation-circle class=\"text-sm text-red\" />\n              {{ $t('failed') }}\n            </span>\n            <span\n              v-else-if=\"\n                isValidationAndPowerLoaded && !isValidationAndPowerLoading\n              \"\n              v-tippy=\"{\n                content: votingPowerByStrategy\n                  .map(\n                    (score, index) =>\n                      `${formatCompactNumber(votingPower === 0 ? 0 : score)} ${\n                        symbols[index]\n                      }`\n                  )\n                  .join(' + ')\n              }\"\n            >\n              {{ formatCompactNumber(votingPower) }}\n              {{ shorten(proposal.symbol || space.symbol, 'symbol') }}\n            </span>\n            <LoadingSpinner v-else />\n          </div>\n        </div>\n\n        <MessageWarningGnosisNetwork\n          v-if=\"isGnosisAndNotSpaceNetwork\"\n          :space=\"space\"\n          action=\"vote\"\n        />\n        <template\n          v-else-if=\"isValidationAndPowerLoaded && !isValidationAndPowerLoading\"\n        >\n          <!-- Voting power messages -->\n          <BaseMessageBlock v-if=\"hasVotingPowerFailed\" level=\"warning\">\n            <i18n-t\n              keypath=\"votingPowerFailedMessage\"\n              tag=\"span\"\n              scope=\"global\"\n            >\n              <template #help>\n                <BaseLink :link=\"SNAPSHOT_HELP_LINK\">Help Center</BaseLink>\n              </template>\n            </i18n-t>\n          </BaseMessageBlock>\n\n          <!-- Voting validation messages -->\n          <BaseMessageBlock\n            v-else-if=\"hasVotingValidationFailed\"\n            level=\"warning\"\n          >\n            <!-- {{ t('votingValidationFailedMessage') }} -->\n\n            <i18n-t\n              keypath=\"votingValidationFailedMessage\"\n              tag=\"span\"\n              scope=\"global\"\n            >\n              <template #help>\n                <BaseLink :link=\"SNAPSHOT_HELP_LINK\">Help Center</BaseLink>\n              </template>\n            </i18n-t>\n          </BaseMessageBlock>\n          <MessageWarningValidation\n            v-else-if=\"!isValidVoter && proposal.validation?.name\"\n            context=\"voting\"\n            :space-id=\"proposal.space.id\"\n            :validation-name=\"proposal.validation.name\"\n            :validation-params=\"proposal.validation?.params || {}\"\n            :min-score=\"proposal.validation?.params?.minScore || 0\"\n            :symbol=\"validationStrategySymbolsString\"\n          />\n          <!-- No voting power -->\n          <BaseMessageBlock v-else-if=\"votingPower === 0\" level=\"warning\">\n            {{\n              $t('noVotingPower', {\n                blockNumber: formatNumber(Number(proposal.snapshot))\n              })\n            }}\n            <BaseLink\n              link=\"https://github.com/snapshot-labs/snapshot/discussions/767\"\n            >\n              {{ $t('learnMore') }}</BaseLink\n            >\n          </BaseMessageBlock>\n          <!-- Reason field -->\n          <div v-else-if=\"props.proposal.privacy !== 'shutter'\" class=\"flex\">\n            <TextareaAutosize\n              v-model=\"reason\"\n              :max-length=\"140\"\n              class=\"s-input !rounded-3xl\"\n              :placeholder=\"$t('comment.placeholder')\"\n            />\n          </div>\n        </template>\n      </div>\n\n      <div class=\"mb-3 mt-5 flex gap-x-[12px]\">\n        <TuneButton type=\"button\" class=\"w-full\" @click=\"$emit('close')\">\n          {{ $t('cancel') }}\n        </TuneButton>\n        <TuneButton\n          :disabled=\"\n            votingPower === 0 ||\n            !isValidVoter ||\n            isSending ||\n            isLoadingShutter ||\n            isGnosisAndNotSpaceNetwork ||\n            isValidationAndPowerLoading\n          \"\n          :loading=\"isSending || isLoadingShutter\"\n          class=\"w-full\"\n          primary\n          data-testid=\"confirm-vote-button\"\n          @click=\"handleSubmit\"\n        >\n          {{ $t('confirm') }}\n        </TuneButton>\n      </div>\n    </div>\n  </TuneModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalVoteValidation.vue",
    "content": "<script setup lang=\"ts\">\nimport { VoteValidation, SpaceStrategy } from '@/helpers/interfaces';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { validateForm } from '@/helpers/validation';\n\nconst DEFAULT_PARAMS: Record<string, any> = {};\n\nconst props = defineProps<{\n  open: boolean;\n  validation: VoteValidation;\n  votingStrategies: SpaceStrategy[];\n}>();\n\nconst emit = defineEmits(['add', 'close']);\n\nconst { open } = toRefs(props);\nconst { t } = useI18n();\n\nconst isValidParams = ref(true);\nconst formRef = ref();\nconst strategiesFormRef = ref();\nconst showStrategies = ref(false);\n\nconst input = ref({\n  name: '',\n  params: clone(DEFAULT_PARAMS)\n});\n\ntype Validations = Record<\n  string,\n  {\n    key: string;\n    example?: Record<string, any>[];\n    schema?: Record<string, any>;\n    about?: string;\n    proposalValidationOnly?: boolean;\n  }\n>;\n\nconst validations = ref<Validations | null>(null);\nconst isValidationsLoaded = ref(false);\nconst updateIndex = ref(0);\n\nconst validationDefinition = computed(() => {\n  const definition =\n    validations.value?.[input.value.name]?.schema?.definitions?.Validation ||\n    null;\n\n  if (input.value.name === 'passport-gated')\n    return definePassportGated(clone(definition));\n\n  return definition;\n});\n\nfunction definePassportGated(definition: any) {\n  if (input.value.params.operator === 'NONE')\n    delete definition.properties.stamps;\n\n  return definition;\n}\n\nconst validationErrors = computed(() => {\n  return validateForm(validationDefinition.value || {}, input.value.params);\n});\n\nconst isValid = computed(() => {\n  return Object.values(validationErrors.value).length === 0;\n});\n\nfunction select(n: string) {\n  input.value.name = n;\n\n  if (props.validation.name !== n) {\n    input.value.params = clone(DEFAULT_PARAMS);\n  }\n\n  if (n === 'any') {\n    handleSubmit();\n  }\n\n  if (n === 'basic') {\n    if (input.value.params.strategies) {\n      showStrategies.value = true;\n    }\n  }\n\n  if (n === 'passport-gated') {\n    if (!input.value.params.operator) input.value.params.operator = 'NONE';\n  }\n}\n\nfunction handleSubmit() {\n  if (input.value.name === 'passport-gated') handlePassportGatedNoneOperator();\n\n  if (!isValid.value || !isValidParams.value) {\n    strategiesFormRef.value?.forceShowError();\n    formRef?.value?.forceShowError();\n    return;\n  }\n\n  emit('add', clone(input.value));\n  emit('close');\n}\n\nfunction handlePassportGatedNoneOperator() {\n  if (!input.value.params.stamps?.[0]) {\n    input.value.params.stamps = undefined;\n    input.value.params.operator = 'NONE';\n  }\n  if (input.value.params.operator === 'NONE') {\n    input.value.params.stamps = undefined;\n  }\n}\n\nfunction removeProposalValidationOnly(validations: Validations) {\n  Object.keys(validations).forEach(key => {\n    if (validations[key]?.proposalValidationOnly) {\n      delete validations[key];\n    }\n  });\n}\n\nasync function getValidations() {\n  if (validations.value) return;\n  const fetchedValidations: Validations = await fetch(\n    `${import.meta.env.VITE_SCORES_URL}/api/validations`\n  ).then(res => res.json());\n  const validationsWithAny: Validations = {\n    any: {\n      key: 'any'\n    },\n    ...fetchedValidations\n  };\n\n  if (validationsWithAny.basic.schema)\n    validationsWithAny.basic.schema.definitions.Validation.properties.minScore.description =\n      t('votingValidation.basic.minScoreHint');\n\n  removeProposalValidationOnly(validationsWithAny);\n\n  validations.value = validationsWithAny || null;\n  isValidationsLoaded.value = true;\n}\n\nwatch(open, () => {\n  getValidations();\n  input.value.name = '';\n  if (props.validation?.params) {\n    input.value.params = props.validation.params;\n  } else {\n    input.value = {\n      name: '',\n      params: clone(DEFAULT_PARAMS)\n    };\n  }\n});\n\nwatch(showStrategies, () => {\n  if (!showStrategies.value) {\n    delete input.value.params.strategies;\n    isValidParams.value = true;\n  }\n});\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>\n        {{\n          input.name\n            ? $t('votingValidation.settingsTitle')\n            : $t('votingValidation.title')\n        }}\n      </h3>\n    </template>\n\n    <div class=\"mx-0 my-4 min-h-[250px] md:mx-4\">\n      <div v-if=\"input.name\" class=\"mx-4 text-skin-link md:mx-0\">\n        <TuneForm\n          v-if=\"validationDefinition\"\n          ref=\"formRef\"\n          :key=\"updateIndex\"\n          v-model=\"input.params\"\n          :definition=\"validationDefinition\"\n          :error=\"validationErrors\"\n        />\n\n        <TuneTextareaJson\n          v-else\n          v-model=\"input.params\"\n          :placeholder=\"$t('votingValidation.paramPlaceholder')\"\n          @update:is-valid=\"value => (isValidParams = value)\"\n        />\n\n        <TuneSwitch\n          v-if=\"input.name === 'basic'\"\n          v-model=\"showStrategies\"\n          :label=\"$t('useCustomStrategies')\"\n          :hint=\"$t('votingValidation.basic.customStrategiesHint')\"\n        />\n\n        <FormArrayStrategies\n          v-if=\"input.name === 'basic' && showStrategies\"\n          ref=\"strategiesFormRef\"\n          v-model=\"input.params.strategies\"\n          :voting-strategies=\"votingStrategies\"\n          @update:is-valid=\"value => (isValidParams = value)\"\n        />\n      </div>\n      <div v-if=\"!input.name\">\n        <LoadingRow v-if=\"!isValidationsLoaded\" block class=\"px-0\" />\n        <div v-else class=\"space-y-3\">\n          <BaseModalSelectItem\n            v-for=\"v in validations\"\n            :key=\"v.key\"\n            :title=\"$t(`votingValidation.${v.key}.label`)\"\n            :description=\"$t(`votingValidation.${v.key}.description`)\"\n            :selected=\"validation.name === v.key\"\n            :tag=\"v.key === 'passport-gated' ? 'Beta' : ''\"\n            @click=\"select(v.key)\"\n          />\n        </div>\n      </div>\n    </div>\n    <template v-if=\"input.name\" #footer>\n      <TuneButton class=\"w-full\" primary @click=\"handleSubmit\">\n        {{ $t('applyChanges') }}\n      </TuneButton>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalVotingPrivacy.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  open: boolean;\n  selected?: string;\n  allowAny?: boolean;\n  allowNone?: boolean;\n}>();\n\nconst emit = defineEmits(['close', 'update:selected']);\n\nconst { env } = useApp();\n\nconst types = ['shutter'];\n\nfunction select(id) {\n  emit('update:selected', id);\n  emit('close');\n}\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>{{ $t('privacy.title') }}</h3>\n    </template>\n    <div class=\"mx-0 my-4 flex flex-col space-y-3 md:mx-4\">\n      <a v-if=\"allowAny\" @click=\"select(undefined)\">\n        <BaseModalSelectItem :selected=\"!selected\" :title=\"$t('privacy.any')\" />\n      </a>\n      <a v-else-if=\"allowNone\" @click=\"select('')\">\n        <BaseModalSelectItem\n          :selected=\"!selected\"\n          :title=\"$t('privacy.none')\"\n        />\n      </a>\n      <a\n        v-for=\"(type, key) in types\"\n        :key=\"key\"\n        @click=\"env === 'demo' ? null : select(type)\"\n      >\n        <BaseModalSelectItem\n          :disabled=\"env === 'demo'\"\n          :selected=\"type === selected\"\n          :title=\"$t(`privacy.${type}.label`)\"\n          :description=\"$t(`privacy.${type}.description`)\"\n        />\n      </a>\n    </div>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalVotingType.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  open: boolean;\n  selected: string;\n  allowAny: boolean;\n}>();\n\nconst emit = defineEmits(['close', 'update:selected']);\n\nconst types = [\n  'single-choice',\n  'approval',\n  'quadratic',\n  'ranked-choice',\n  'weighted',\n  'basic'\n];\n\nfunction select(id) {\n  emit('update:selected', id);\n  emit('close');\n}\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>{{ $t('voting.selectVoting') }}</h3>\n    </template>\n    <div class=\"mx-0 my-4 flex flex-col space-y-3 md:mx-4\">\n      <button v-if=\"allowAny\" @click=\"select(undefined)\">\n        <BaseModalSelectItem\n          :selected=\"!selected\"\n          :title=\"$t('settings.anyType')\"\n        />\n      </button>\n      <button v-for=\"(type, key) in types\" :key=\"key\" @click=\"select(type)\">\n        <BaseModalSelectItem\n          :selected=\"type === selected\"\n          :title=\"$t(`voting.${type}.label`)\"\n          :description=\"$t(`voting.${type}.description`)\"\n        />\n      </button>\n    </div>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/ModalWrongNetwork.vue",
    "content": "<script setup lang=\"ts\">\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\n\nconst props = defineProps<{\n  open: boolean;\n  network?: string;\n  showDemoButton?: boolean;\n}>();\nconst emit = defineEmits(['close', 'networkChanged']);\n\nconst { notify } = useFlashNotification();\nconst { t } = useI18n();\nconst { changeNetwork } = useChangeNetwork();\n\nconst switchingChain = ref(false);\n\nconst usingMetaMask = computed(() => {\n  return window.ethereum && getInstance().provider.value?.isMetaMask;\n});\n\nconst desiredNetwork = computed(() => {\n  return props.network ?? import.meta.env.VITE_DEFAULT_NETWORK;\n});\n\nasync function handleChange() {\n  try {\n    await changeNetwork(desiredNetwork.value);\n    emit('close');\n    emit('networkChanged');\n  } catch (e) {\n    notify(['red', t('notify.somethingWentWrong')]);\n    console.error(e);\n    switchingChain.value = false;\n  }\n}\n</script>\n\n<template>\n  <TuneModal :open=\"open\" hide-close @close=\"$emit('close')\">\n    <div class=\"pt-[40px] h-full\">\n      <div class=\"mx-4\">\n        <TuneModalIndicator variant=\"error\" />\n\n        <div class=\"my-[20px] text-center\">\n          <TuneModalTitle class=\"m-0 leading-6\"> Wrong network </TuneModalTitle>\n          <TuneModalDescription class=\"text-md leading-5 mt-1\">\n            To continue, you need to change the network in your wallet to\n            <span class=\"font-semibold\">{{\n              networks[desiredNetwork].name\n            }}</span\n            >.\n          </TuneModalDescription>\n        </div>\n      </div>\n\n      <div v-if=\"usingMetaMask\" class=\"m-4 space-y-2\">\n        <TuneButton\n          :loading=\"switchingChain\"\n          class=\"w-full\"\n          primary\n          @click=\"handleChange\"\n        >\n          {{\n            $t('unsupportedNetwork.switchToNetwork', {\n              network: networks[desiredNetwork].name\n            })\n          }}\n        </TuneButton>\n      </div>\n      <div v-else-if=\"desiredNetwork === '1' && showDemoButton\">\n        <BaseLink link=\"https://testnet.snapshot.org\" hide-external-icon>\n          <TuneButton tabindex=\"-1\" class=\"w-full\">\n            {{ $t('unsupportedNetwork.goToDemoSite') }}\n          </TuneButton>\n        </BaseLink>\n      </div>\n      <div v-else class=\"m-4\">\n        <TuneButton class=\"w-full\" @click=\"$emit('close')\"> Close </TuneButton>\n      </div>\n    </div>\n  </TuneModal>\n</template>\n"
  },
  {
    "path": "src/components/NavbarAccount.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { useStorage } from '@vueuse/core';\n\nconst { login, web3, web3Account } = useWeb3();\nconst { profiles, loadProfiles } = useProfiles();\nconst { modalAccountOpen } = useModal();\nconst auth = getInstance();\n\nconst loading = ref(false);\nconst modalTermsOpen = ref(false);\n\nconst termsAccepted = useStorage('snapshot.termsAccepted', false);\n\nasync function handleLogin(connector) {\n  modalAccountOpen.value = false;\n  loading.value = true;\n  termsAccepted.value = true;\n  await login(connector);\n  loading.value = false;\n}\n\nconst profile = computed(() => profiles.value[web3Account.value]);\nwatch(\n  () => web3Account,\n  () => loadProfiles([web3Account.value]),\n  { immediate: true }\n);\n</script>\n\n<template>\n  <template v-if=\"auth.isAuthenticated && web3Account\">\n    <MenuAccount\n      :address=\"web3Account\"\n      @switch-wallet=\"modalAccountOpen = true\"\n    >\n      <TuneButton\n        :loading=\"web3.authLoading\"\n        class=\"flex items-center\"\n        data-testid=\"button-account-menu\"\n      >\n        <AvatarUser\n          :address=\"web3Account\"\n          size=\"20\"\n          class=\"-ml-1 -mr-1 sm:mr-2 md:mr-2 lg:mr-2 xl:mr-2\"\n        />\n        <span\n          v-if=\"profile?.name || profile?.ens\"\n          class=\"hidden sm:block max-w-[120px] truncate\"\n          v-text=\"profile.name || profile.ens\"\n        />\n        <span v-else class=\"hidden sm:block\" v-text=\"shorten(web3Account)\" />\n      </TuneButton>\n    </MenuAccount>\n  </template>\n\n  <TuneButton\n    v-if=\"!auth.isAuthenticated.value\"\n    :loading=\"loading || web3.authLoading\"\n    :aria-label=\"$t('connectWallet')\"\n    data-testid=\"button-connect-wallet\"\n    @click=\"modalAccountOpen = true\"\n  >\n    <span class=\"hidden sm:block\" v-text=\"$t('connectWallet')\" />\n    <i-ho-login class=\"-ml-2 -mr-[11px] block align-text-bottom sm:hidden\" />\n  </TuneButton>\n\n  <teleport to=\"#modal\">\n    <ModalAccount\n      :open=\"modalAccountOpen\"\n      :profile=\"profile\"\n      @close=\"modalAccountOpen = false\"\n      @login=\"handleLogin\"\n      @open-terms=\"modalTermsOpen = true\"\n    />\n  </teleport>\n  <ModalSnapshotTerms :open=\"modalTermsOpen\" @close=\"modalTermsOpen = false\" />\n</template>\n"
  },
  {
    "path": "src/components/NavbarExtras.vue",
    "content": "<script setup lang=\"ts\">\nimport pkg from '@/../package.json';\nimport { PopoverButton } from '@headlessui/vue';\n\nconst { domain } = useApp();\nconst router = useRouter();\n\nconst commitSha =\n  import.meta.env.VITE_COMMIT_SHA || import.meta.env.VITE_VERCEL_GIT_COMMIT_SHA;\n\nconst navigationItems = [\n  {\n    name: 'Explore',\n    link: 'home'\n  },\n  {\n    name: 'Timeline',\n    link: 'timeline'\n  },\n  {\n    name: 'Create a space',\n    link: 'setup'\n  }\n];\n\nfunction clickNavigationItem(item: any) {\n  if (domain) window.open(`https://snapshot.org/#/${item.link}`, '_blank');\n  else router.push({ name: item.link });\n}\n</script>\n\n<template>\n  <BasePopover>\n    <template #button>\n      <BaseButtonRound class=\"relative\">\n        <i-ho-dots-horizontal class=\"text-skin-link\" />\n      </BaseButtonRound>\n    </template>\n    <template #content>\n      <div>\n        <div class=\"m-4 flex justify-between\">\n          <div>\n            <ButtonTheme v-if=\"!domain\" />\n          </div>\n          <MenuLanguages class=\"!h-[42px]\" />\n        </div>\n        <div class=\"group m-4 my-[30px]\">\n          <PopoverButton\n            v-for=\"item in navigationItems\"\n            :key=\"item.name\"\n            as=\"button\"\n            class=\"block w-full cursor-pointer py-1 text-left text-xl text-skin-link hover:!text-skin-link hover:!opacity-100 group-hover:text-skin-text group-hover:opacity-70\"\n            @click=\"clickNavigationItem(item)\"\n          >\n            {{ item.name }}\n          </PopoverButton>\n        </div>\n\n        <div class=\"mt-4 border-t\">\n          <div class=\"m-4 flex items-center justify-between\">\n            <FooterSocials class=\"inline-flex justify-start !pt-0\" />\n            <div class=\"text-sm leading-4 opacity-40\">\n              <BaseLink\n                v-if=\"commitSha\"\n                :link=\"`https://github.com/${pkg.repository}/tree/${commitSha}`\"\n              >\n                v{{ pkg.version }}#{{ commitSha.slice(0, 7) }}\n              </BaseLink>\n              <span v-else v-text=\"`v${pkg.version}`\" />\n            </div>\n          </div>\n        </div>\n      </div>\n    </template>\n  </BasePopover>\n</template>\n"
  },
  {
    "path": "src/components/NavbarNotifications.vue",
    "content": "<script setup lang=\"ts\">\nimport { PopoverButton } from '@headlessui/vue';\n\nconst {\n  notificationsLoading,\n  NotificationEvents,\n  notificationsSortedByTime,\n  selectedFilter,\n  filters,\n  selectNotification,\n  markAllAsRead,\n  loadNotifications\n} = useNotifications();\n\nconst { formatRelativeTime, longRelativeTimeFormatter } = useIntl();\n\nfunction selectThreedotItem(e) {\n  if (e === 'markAllAsRead') markAllAsRead();\n}\n\nonMounted(() => loadNotifications());\n</script>\n\n<template>\n  <BasePopover>\n    <template #button>\n      <BaseButtonRound class=\"relative\">\n        <i-ho-bell class=\"text-skin-link\" />\n        <BaseIndicator\n          v-if=\"notificationsSortedByTime.some(n => n.seen === false)\"\n          class=\"absolute bottom-0 right-0 !bg-red\"\n        />\n      </BaseButtonRound>\n    </template>\n    <template #content>\n      <div class=\"my-2 w-full\">\n        <div class=\"mb-3 flex items-center justify-between px-3\">\n          <h4>{{ $t('notifications.header') }}</h4>\n          <BaseMenu\n            :items=\"[\n              {\n                text: $t('notifications.markAllAsRead'),\n                action: 'markAllAsRead'\n              }\n            ]\"\n            @select=\"selectThreedotItem\"\n          >\n            <template #button>\n              <button>\n                <i-ho-dots-horizontal\n                  class=\"cursor-pointer text-[22px] hover:text-skin-link\"\n                />\n              </button>\n            </template>\n            <template #item=\"{ item }\">\n              <div class=\"flex items-center\">\n                <i-ho-check class=\"mr-2 text-sm\" />\n\n                {{ item.text }}\n              </div>\n            </template>\n          </BaseMenu>\n        </div>\n        <div class=\"mb-3 space-x-2 px-3\">\n          <TuneButton\n            v-for=\"filter in filters\"\n            :key=\"filter\"\n            class=\"!h-[44px]\"\n            :class=\"{ '!border-skin-link': selectedFilter === filter }\"\n            @click=\"selectedFilter = filter\"\n          >\n            {{ $t(`notifications.${filter}`) }}\n          </TuneButton>\n        </div>\n        <div v-if=\"!notificationsSortedByTime.length && notificationsLoading\">\n          <LoadingRow class=\"!px-0\" />\n        </div>\n        <div\n          v-else-if=\"!notificationsSortedByTime.length\"\n          class=\"pb-3 pt-4 text-center\"\n        >\n          <h4 class=\"text-skin-text\">\n            {{ $t('notifications.noNotifications') }}\n          </h4>\n        </div>\n\n        <PopoverButton\n          v-for=\"item in notificationsSortedByTime\"\n          :key=\"item.id\"\n          as=\"div\"\n        >\n          <template v-if=\"item.space\">\n            <a\n              tabindex=\"0\"\n              class=\"flex w-full cursor-pointer px-3 pb-2 pt-3 hover:bg-skin-border\"\n              @click=\"selectNotification(item.id, item.space!.id)\"\n            >\n              <div class=\"hidden w-[78px] sm:block\">\n                <AvatarSpace :space=\"item.space\" size=\"44\" class=\"-ml-2\" />\n              </div>\n              <div class=\"w-full\">\n                <div class=\"flex leading-tight\">\n                  <div class=\"max-w-[110px] truncate text-skin-link\">\n                    {{ item.space.name }}\n                  </div>\n                  <div class=\"ml-1 text-skin-text\">\n                    <span\n                      v-if=\"item.event === NotificationEvents.ProposalStart\"\n                    >\n                      {{ $t('notifications.proposalStarted') }}\n                    </span>\n                    <span v-if=\"item.event === NotificationEvents.ProposalEnd\">\n                      {{ $t('notifications.proposalEnded') }}\n                    </span>\n                  </div>\n                </div>\n                <div\n                  class=\"line-clamp-2 whitespace-normal leading-tight text-skin-link\"\n                >\n                  \"{{ item.text }}\"\n                </div>\n                <div class=\"leading-normal text-skin-text\">\n                  <span>\n                    {{\n                      formatRelativeTime(item.time, longRelativeTimeFormatter)\n                    }}\n                  </span>\n                </div>\n              </div>\n              <div class=\"ml-2 flex w-[12px] items-center\">\n                <BaseIndicator v-if=\"!item.seen\" />\n              </div>\n            </a>\n          </template>\n        </PopoverButton>\n      </div>\n    </template>\n  </BasePopover>\n</template>\n"
  },
  {
    "path": "src/components/PopoverHoverProfile.vue",
    "content": "<script setup lang=\"ts\">\nimport { explorerUrl } from '@/helpers/utils';\nimport { useMediaQuery } from '@vueuse/core';\nimport { Profile, ExtendedSpace, Proposal } from '@/helpers/interfaces';\n\nconst isXLargeScreen = useMediaQuery('(min-width: 1280px)');\n\ndefineProps<{\n  address: string;\n  profile?: Profile;\n  proposal?: Proposal;\n  space?: Partial<ExtendedSpace>;\n}>();\n\nconst { domain } = useApp();\n</script>\n\n<template>\n  <BasePopoverHover :placement=\"isXLargeScreen ? 'bottom' : 'bottom-start'\">\n    <template #button>\n      <slot />\n    </template>\n    <template #content>\n      <div class=\"p-4\">\n        <div class=\"flex\">\n          <div>\n            <AvatarUser :address=\"address\" size=\"69\" />\n          </div>\n          <div class=\"px-3\">\n            <ProfileName :profile=\"profile\" :address=\"address\" />\n            <ProfileAddressCopy :profile=\"profile\" :user-address=\"address\" />\n          </div>\n        </div>\n        <p v-if=\"profile?.about\" class=\"mt-4\">\n          {{ profile.about }}\n        </p>\n\n        <div class=\"mt-4 flex w-full\">\n          <div class=\"w-1/2 pr-2\">\n            <BaseLink\n              :link=\"\n                domain\n                  ? `https://snapshot.org/#/profile/${address}`\n                  : { name: 'profileActivity', params: { address } }\n              \"\n              hide-external-icon\n            >\n              <TuneButton primary class=\"w-full\" tabindex=\"-1\">\n                {{ $t('profile.viewProfile') }}\n              </TuneButton>\n            </BaseLink>\n          </div>\n          <div class=\"w-1/2 pl-2\">\n            <BaseLink\n              :link=\"\n                explorerUrl(proposal?.network || space?.network || '1', address)\n              \"\n              hide-external-icon\n            >\n              <TuneButton class=\"w-full\" tabindex=\"-1\">\n                {{ $t('seeInExplorer') }}\n                <i-ho-external-link class=\"mb-[2px] inline-block text-xs\" />\n              </TuneButton>\n            </BaseLink>\n          </div>\n        </div>\n      </div>\n    </template>\n  </BasePopoverHover>\n</template>\n"
  },
  {
    "path": "src/components/ProfileAboutBiography.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{ about: string }>();\n</script>\n\n<template>\n  <BaseBlock :title=\"$t('profile.about.biography')\">\n    <TextAutolinker :text=\"about\" />\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/ProfileAboutDelegate.vue",
    "content": "<script setup lang=\"ts\">\nimport { getDelegators } from '@/helpers/delegation';\nimport uniqBy from 'lodash/uniqBy';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { SNAPSHOT_SUBGRAPH_URL } from '@snapshot-labs/snapshot.js/src/utils';\nimport { Space } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  userAddress: string;\n  followingSpaces: Space[];\n  loading: boolean;\n}>();\n\nconst { web3Account } = useWeb3();\nconst { networkKey } = useDelegate();\nconst { t } = useI18n();\n\nconst delegators = ref<{ delegator: string; space: string }[] | null>(null);\n\nconst followingDelegatorSpaceIds = computed(() => {\n  if (!delegators.value) return [];\n\n  const followingSpaceIds = props.followingSpaces.map(s => s.id);\n  const delegatorSpaceIds = delegators.value\n    .map(d => d.space)\n    .filter(d => d !== '');\n\n  return uniqBy(delegatorSpaceIds.filter(d => followingSpaceIds.includes(d)));\n});\n\nconst loadingDelegators = ref<boolean | 'notSupportedNetwork'>(false);\n\nasync function loadDelegatorsByNetwork() {\n  loadingDelegators.value = true;\n  if (SNAPSHOT_SUBGRAPH_URL[networkKey.value]) {\n    const res = await getDelegators(networkKey.value, props.userAddress);\n    delegators.value = res.delegations ?? [];\n    return (loadingDelegators.value = false);\n  }\n  loadingDelegators.value = 'notSupportedNetwork';\n}\n\nwatch(\n  networkKey,\n  async () => {\n    if (!web3Account.value) return;\n    loadDelegatorsByNetwork();\n  },\n  { immediate: true }\n);\n\n// Delegate modal\nconst modalDelegateOpen = ref(false);\nconst delegateSpaceId = ref('');\n\nfunction clickDelegate(id) {\n  delegateSpaceId.value = id;\n  modalDelegateOpen.value = true;\n}\n\nconst networkString = computed(() => {\n  return (\n    networks?.[networkKey.value]?.shortName ??\n    networks?.[networkKey.value]?.name ??\n    t('theCurrentNetwork')\n  );\n});\n</script>\n\n<template>\n  <div>\n    <BaseBlock\n      :loading=\"loading || loadingDelegators === true\"\n      :title=\"$t('profile.about.delegateFor')\"\n      :counter=\"followingDelegatorSpaceIds.length\"\n      :label=\"networkString\"\n      :label-tooltip=\"$t('profile.about.delegatorNetworkInfo')\"\n      hide-bottom-border\n      slim\n    >\n      <ProfileAboutDelegateListItem\n        v-if=\"followingDelegatorSpaceIds.length && delegators\"\n        :spaces=\"followingDelegatorSpaceIds\"\n        :delegators=\"delegators\"\n        :user-address=\"userAddress\"\n        :web3-account=\"web3Account\"\n        @delegate=\"clickDelegate\"\n      />\n      <div v-else class=\"border-t p-4\">\n        {{\n          loadingDelegators === 'notSupportedNetwork'\n            ? $t('profile.about.notSupportedNetwork', {\n                network: networkString\n              })\n            : $t('profile.about.noDelegatorsMessage', {\n                network: networkString\n              })\n        }}\n      </div>\n    </BaseBlock>\n  </div>\n  <Teleport to=\"#modal\">\n    <ModalDelegate\n      :open=\"modalDelegateOpen\"\n      :user-address=\"userAddress\"\n      :space-id=\"delegateSpaceId\"\n      @close=\"modalDelegateOpen = false\"\n      @reload=\"loadDelegatorsByNetwork\"\n    />\n  </Teleport>\n</template>\n"
  },
  {
    "path": "src/components/ProfileAboutDelegateListItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { Space } from '@/helpers/interfaces';\n\ndefineProps<{\n  spaces: Space[];\n  delegators: { delegator: string; space: string }[];\n  userAddress: string;\n  web3Account: string;\n}>();\n\ndefineEmits(['delegate']);\n</script>\n\n<template>\n  <div>\n    <div\n      v-for=\"space in spaces\"\n      :key=\"space.id\"\n      class=\"border-b px-4 py-2 first:border-t last:border-b-0\"\n    >\n      <div class=\"flex justify-between\">\n        <router-link\n          class=\"flex items-center\"\n          :to=\"{ name: 'spaceProposals', params: { key: space.id } }\"\n        >\n          <AvatarSpace :space=\"space\" size=\"35\" />\n          <div class=\"flex items-center\">\n            <h4 class=\"ml-3\">\n              {{ space.name }}\n            </h4>\n            <IconVerifiedSpace\n              v-if=\"space.verified\"\n              :turbo=\"space.turbo\"\n              size=\"19\"\n              class=\"ml-1 flex text-skin-primary\"\n            />\n          </div>\n        </router-link>\n\n        <div\n          v-if=\"\n            delegators.find(\n              d =>\n                d.delegator === web3Account.toLowerCase() &&\n                d.space === space.id\n            )\n          \"\n          class=\"flex h-[44px] items-center space-x-2 rounded-full border px-4\"\n        >\n          <i-ho-check />\n          <div>{{ $t('profile.about.delegated') }}</div>\n        </div>\n\n        <TuneButton\n          v-else-if=\"userAddress !== web3Account\"\n          class=\"!h-[44px]\"\n          primary\n          @click=\"$emit('delegate', space)\"\n        >\n          {{ $t('profile.about.delegate') }}\n        </TuneButton>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ProfileActivityList.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  title: string;\n}>();\n</script>\n\n<template>\n  <div>\n    <span class=\"px-4 text-xs text-skin-link md:px-0\">{{\n      title.toUpperCase()\n    }}</span>\n    <BaseBlock slim class=\"my-1\">\n      <slot />\n    </BaseBlock>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ProfileActivityListItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { ProfileActivity } from '@/helpers/interfaces';\n\nconst { formatRelativeTime, longRelativeTimeFormatter } = useIntl();\n\ndefineProps<{ activity: ProfileActivity }>();\n</script>\n\n<template>\n  <div class=\"border-b last:border-b-0\">\n    <!-- Vote activities -->\n    <router-link\n      v-if=\"activity.type === 'vote'\"\n      :to=\"{\n        name: 'spaceProposal',\n        params: { key: activity.space.id, id: activity.vote?.proposalId }\n      }\"\n    >\n      <div class=\"flex w-full px-4 py-4\">\n        <div class=\"relative min-w-[52px]\">\n          <AvatarSpace size=\"44\" :space=\"activity.space\" />\n          <div\n            class=\"absolute right-0 top-[24px] rounded-full bg-skin-primary p-[6px] pr-[5px] text-[9px] text-skin-bg\"\n          >\n            <i-s-signature />\n          </div>\n        </div>\n        <div class=\"ml-4 w-[calc(100%-64px)]\">\n          <div class=\"flex text-xs leading-5 text-skin-text\">\n            <div class=\"flex-grow\">\n              {{\n                $t('profile.activity.votedFor', {\n                  choice: activity.vote?.choice\n                    ? `\"${activity.vote?.choice}\"`\n                    : ''\n                })\n              }}\n            </div>\n            <div\n              v-tippy=\"{\n                content: new Date(activity.created * 1000).toUTCString()\n              }\"\n              class=\"cursor-help\"\n            >\n              {{\n                formatRelativeTime(activity.created, longRelativeTimeFormatter)\n              }}\n            </div>\n          </div>\n          <div class=\"truncate pr-2\">\n            {{ activity.title }}\n          </div>\n        </div>\n      </div>\n    </router-link>\n    <!-- Other activities -->\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ProfileAddressCopy.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\n\ndefineProps<{\n  profile?: {\n    ens?: string;\n    name?: string;\n  };\n  userAddress: string;\n}>();\n\nconst { copyToClipboard } = useCopy();\n</script>\n\n<template>\n  <div class=\"flex space-x-2 leading-5\">\n    <button\n      v-if=\"profile?.ens && profile?.name\"\n      class=\"flex min-w-0 cursor-pointer items-center rounded-full text-xs\"\n      @click=\"copyToClipboard(profile.ens)\"\n    >\n      <div class=\"truncate\">\n        {{ profile.ens }}\n      </div>\n    </button>\n    <button\n      class=\"flex cursor-pointer items-center rounded border px-1 text-xs shrink-0\"\n      @click=\"copyToClipboard(userAddress)\"\n    >\n      {{ shorten(userAddress) }}\n      <i-ho-duplicate class=\"ml-1 text-xs\" />\n    </button>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ProfileName.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\n\ndefineProps<{\n  address: string;\n  profile?: {\n    ens?: string;\n    name?: string;\n  };\n}>();\n</script>\n\n<template>\n  <div\n    v-tippy=\"{\n      content: profile?.name || profile?.ens || address\n    }\"\n    class=\"truncate text-lg font-semibold leading-10 text-skin-heading\"\n  >\n    {{ profile?.name || profile?.ens || shorten(address) }}\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ProfileSidebar.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  userAddress: string;\n  profiles: {\n    [address: string]: {\n      ens: string;\n      name?: string;\n      about?: string;\n    };\n  };\n}>();\n\nconst emit = defineEmits(['edit']);\n\nconst { web3Account } = useWeb3();\n</script>\n\n<template>\n  <div class=\"lg:fixed lg:w-[240px]\">\n    <BaseBlock\n      slim\n      class=\"-mt-1 overflow-hidden !border-t-0 md:mt-0 md:!border-t\"\n    >\n      <div class=\"flex px-[20px] md:px-3 md:pt-3 lg:block\">\n        <ProfileSidebarHeader\n          v-if=\"profiles[userAddress]\"\n          :user-address=\"userAddress\"\n          :profile=\"profiles[userAddress]\"\n        />\n        <ProfileSidebarHeaderSkeleton v-else />\n\n        <div\n          v-if=\"userAddress === web3Account\"\n          class=\"flex flex-grow justify-end lg:mt-3 lg:flex-auto lg:justify-center\"\n        >\n          <TuneButton\n            :disabled=\"!profiles[userAddress]\"\n            class=\"whitespace-nowrap lg:w-full\"\n            @click=\"emit('edit')\"\n          >\n            <span>\n              {{ $t('profile.buttonEdit') }}\n            </span>\n          </TuneButton>\n        </div>\n      </div>\n\n      <ProfileSidebarNavigation />\n    </BaseBlock>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ProfileSidebarHeader.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  userAddress: string;\n  profile: { ens: string; name?: string; avatar?: string };\n}>();\n</script>\n\n<template>\n  <div>\n    <AvatarUser size=\"48\" :address=\"userAddress\" />\n\n    <div class=\"text-left\">\n      <ProfileName :profile=\"profile\" :address=\"userAddress\" />\n      <ProfileAddressCopy :profile=\"profile\" :user-address=\"userAddress\" />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ProfileSidebarHeaderSkeleton.vue",
    "content": "<template>\n  <div>\n    <div class=\"lazy-loading h-[48px] w-[48px] rounded-full\" />\n    <div class=\"mt-2 flex flex-col items-start justify-center\">\n      <div\n        class=\"lazy-loading mb-2 h-[24px] w-[130px] rounded-md bg-skin-text\"\n      />\n      <div class=\"lazy-loading h-[22px] w-[100px] rounded-md bg-skin-text\" />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ProfileSidebarNavigation.vue",
    "content": "<template>\n  <div class=\"no-scrollbar flex pb-0 pt-4 lg:block lg:pb-4\">\n    <router-link v-slot=\"{ isExactActive }\" :to=\"{ name: 'profileActivity' }\">\n      <BaseSidebarNavigationItem :is-active=\"isExactActive\">\n        {{ $t('profile.activity.header') }}\n      </BaseSidebarNavigationItem>\n    </router-link>\n    <router-link v-slot=\"{ isExactActive }\" :to=\"{ name: 'profileAbout' }\">\n      <BaseSidebarNavigationItem :is-active=\"isExactActive\">\n        {{ $t('profile.about.header') }}\n      </BaseSidebarNavigationItem>\n    </router-link>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ProposalsItem.vue",
    "content": "<script setup lang=\"ts\">\nimport removeMd from 'remove-markdown';\nimport { Proposal, ExtendedSpace, Profile } from '@/helpers/interfaces';\nimport { BoostSubgraph } from '@/helpers/boost/types';\n\nconst props = defineProps<{\n  proposal: Proposal;\n  profiles: { [key: string]: Profile };\n  space: ExtendedSpace;\n  voted: boolean;\n  to: Record<string, unknown>;\n  hideSpaceAvatar?: boolean;\n  showVerifiedIcon?: boolean;\n  boosts?: BoostSubgraph[];\n}>();\n\nconst { isMessageVisible, setMessageVisibility } = useFlaggedMessageStatus(\n  props.proposal.id\n);\n\nconst body = computed(() => removeMd(props.proposal.body));\n\nconst boostsCount = computed(() => {\n  if (!props.boosts) return 0;\n  return props.boosts.filter(\n    boost => boost.strategy.proposal === props.proposal.id\n  ).length;\n});\n\nonMounted(() => setMessageVisibility(props.proposal.flagged));\n</script>\n\n<template>\n  <div>\n    <div class=\"block p-3 text-skin-text sm:p-4\">\n      <div>\n        <MessageWarningFlagged\n          v-if=\"isMessageVisible\"\n          type=\"proposal\"\n          @force-show=\"setMessageVisibility(false)\"\n        />\n        <template v-else>\n          <div class=\"flex h-[26px] items-start justify-between\">\n            <div class=\"flex items-center gap-1\">\n              <template v-if=\"!hideSpaceAvatar\">\n                <LinkSpace class=\"text-skin-text\" :space-id=\"proposal.space.id\">\n                  <div class=\"flex items-center\">\n                    <AvatarSpace :space=\"proposal.space\" size=\"20\" />\n                    <span\n                      class=\"ml-1 text-skin-link\"\n                      v-text=\"proposal.space.name\"\n                    />\n                    <IconVerifiedSpace\n                      v-if=\"showVerifiedIcon && space.verified\"\n                      :turbo=\"space.turbo\"\n                      class=\"mt-[2px] pl-[2px]\"\n                      size=\"18\"\n                    />\n                  </div>\n                </LinkSpace>\n                <span v-text=\"$tc('proposalBy')\" />\n              </template>\n              <BaseUser\n                :address=\"proposal.author\"\n                :profile=\"profiles[proposal.author]\"\n                :space=\"space\"\n                :proposal=\"proposal\"\n                :hide-avatar=\"!hideSpaceAvatar\"\n              />\n            </div>\n            <LabelProposalState :state=\"proposal.state\" />\n          </div>\n\n          <router-link :to=\"to\" class=\"cursor-pointer\">\n            <ProposalsItemTitle :proposal=\"proposal\" :voted=\"voted\" />\n\n            <ProposalsItemBody v-if=\"body\">\n              {{ body }}\n            </ProposalsItemBody>\n\n            <ProposalsItemResults\n              v-if=\"\n                proposal.scores_state === 'final' && proposal.scores_total > 0\n              \"\n              :proposal=\"proposal\"\n            />\n          </router-link>\n\n          <ProposalsItemFooter :proposal=\"proposal\" />\n          <div\n            v-if=\"boostsCount > 0 && proposal.state === 'active'\"\n            class=\"bg-boost/5 border border-boost/20 px-[12px] py-2 rounded-xl text-boost flex items-center justify-center gap-1 mt-2\"\n          >\n            <i-ho-fire class=\"text-sm\" />\n            {{ boostsCount }}\n            <div>boost<span v-if=\"boostsCount > 1\">s</span></div>\n            active\n          </div>\n        </template>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ProposalsItemBody.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <p class=\"line-clamp-2 break-words text-md font-semibold\">\n    <slot />\n  </p>\n</template>\n"
  },
  {
    "path": "src/components/ProposalsItemFooter.vue",
    "content": "<script setup lang=\"ts\">\nimport capitalize from 'lodash/capitalize';\nimport { Proposal } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  proposal: Proposal;\n}>();\n\nconst { getRelativeProposalPeriod, formatPercentNumber } = useIntl();\nconst quorumText = computed(() => {\n  if (!props.proposal.quorum || !props.proposal.scores_total) {\n    return '';\n  }\n\n  const quorumOfRejection = props.proposal.quorumType === 'rejection';\n  const percentage = formatPercentNumber(\n    quorumOfRejection\n      ? Number(\n          props.proposal.scores\n            .filter((c, i) => i === 1)\n            .reduce((a, b) => a + b, 0) / props.proposal.quorum\n        )\n      : Number(props.proposal.scores_total / props.proposal.quorum)\n  );\n  return quorumOfRejection\n    ? `${percentage} quorum rejection`\n    : `${percentage} quorum reached`;\n});\n</script>\n\n<template>\n  <div class=\"mt-3\">\n    <span\n      v-tippy=\"{\n        content: new Date(\n          (proposal.state === 'pending' ? proposal.start : proposal.end) * 1000\n        ).toUTCString()\n      }\"\n      class=\"cursor-help\"\n    >\n      {{\n        capitalize(\n          getRelativeProposalPeriod(\n            proposal.state,\n            proposal.start,\n            proposal.end\n          )\n        )\n      }}\n    </span>\n    <template v-if=\"proposal.quorum && proposal.scores_total\">\n      -\n      {{ quorumText }}\n    </template>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ProposalsItemResults.vue",
    "content": "<script setup lang=\"ts\">\nimport { Proposal } from '@/helpers/interfaces';\nimport { shorten } from '@/helpers/utils';\n\nconst { formatCompactNumber, formatPercentNumber } = useIntl();\n\nconst props = defineProps<{\n  proposal: Proposal;\n}>();\n\nconst winningChoice = computed(() =>\n  props.proposal.scores.indexOf(Math.max(...props.proposal.scores))\n);\n</script>\n\n<template>\n  <div :class=\"{ 'mt-3': !proposal?.body }\">\n    <div\n      v-for=\"(choice, i) in proposal.choices\"\n      :key=\"i\"\n      class=\"relative mt-1 w-full\"\n    >\n      <template v-if=\"proposal.choices.length <= 3 || i === winningChoice\">\n        <div\n          class=\"absolute ml-3 flex items-center leading-[43px] text-skin-link\"\n        >\n          <i-ho-check v-if=\"i === winningChoice\" class=\"-ml-1 mr-2 text-sm\" />\n          {{ shorten(choice, 32) }}\n          <span class=\"ml-1 text-skin-text\">\n            {{ formatCompactNumber(proposal.scores[i]) }}\n            {{ proposal.symbol || proposal.space.symbol }}\n          </span>\n        </div>\n        <div\n          class=\"absolute right-0 mr-3 leading-[40px] text-skin-link\"\n          v-text=\"\n            formatPercentNumber(\n              (1 / proposal.scores_total) * proposal.scores[i]\n            )\n          \"\n        />\n        <div\n          :style=\"`width: ${\n            (100 / proposal.scores_total) * proposal.scores[i]\n          }%;`\"\n          class=\"h-[40px] rounded-md bg-skin-border\"\n        />\n      </template>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/ProposalsItemTitle.vue",
    "content": "<script setup lang=\"ts\">\nimport { Proposal } from '@/helpers/interfaces';\n\ndefineProps<{\n  proposal: Proposal;\n  voted: boolean;\n}>();\n</script>\n\n<template>\n  <div class=\"relative mb-1 mt-3 break-words pr-[80px] leading-[32px]\">\n    <h3 class=\"inline pr-2\">{{ proposal.title }}</h3>\n    <LabelProposalVoted v-if=\"voted\" />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SettingsBoostBlock.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  isViewOnly?: boolean;\n}>();\n\nconst { form } = useFormSpaceSettings(props.context);\n</script>\n\n<template>\n  <BaseBlock title=\"Boost\">\n    <div class=\"space-y-2\">\n      <TuneSwitch\n        v-model=\"form.boost.enabled\"\n        label=\"Enable Boost\"\n        sublabel=\"Allow users to set up rewards for voters.\"\n        :disabled=\"props.isViewOnly\"\n      />\n      <TuneSwitch\n        v-model=\"form.boost.bribeEnabled\"\n        label=\"Enable strategic incentivization\"\n        sublabel=\"Allow users to set up rewards for voting on a specific choice.\"\n        :disabled=\"props.isViewOnly\"\n      />\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SettingsDangerzoneBlock.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\n\nconst props = defineProps<{\n  isController: boolean;\n  ensOwner: string | null;\n  isOwner: boolean;\n  isSettingEnsRecord: boolean;\n  isDeleting: boolean;\n}>();\n\nconst emit = defineEmits(['changeController', 'deleteSpace']);\n\nconst { t } = useI18n();\n\nconst items = computed(() => [\n  {\n    title: t('settings.dangerZone.changeController.title'),\n    description: t('settings.dangerZone.changeController.information'),\n    button: t('settings.dangerZone.changeController.button'),\n    disabled: !props.isOwner,\n    disabledInformation: t(\n      'settings.dangerZone.changeController.disabledInformation',\n      { owner: shorten(props.ensOwner || '') }\n    ),\n    action: () => emit('changeController'),\n    loading: props.isSettingEnsRecord\n  },\n  {\n    title: t('settings.dangerZone.deleteSpace.title'),\n    description: t('settings.dangerZone.deleteSpace.information'),\n    button: t('settings.dangerZone.deleteSpace.button'),\n    disabled: !props.isController,\n    disabledInformation: t(\n      'settings.dangerZone.deleteSpace.disabledInformation'\n    ),\n    action: () => emit('deleteSpace'),\n    loading: props.isDeleting\n  }\n]);\n</script>\n\n<template>\n  <BaseBlock slim :title=\"$t('settings.dangerZone.title')\">\n    <div class=\"\">\n      <div\n        v-for=\"item in items\"\n        :key=\"item.title\"\n        class=\"flex items-center justify-between border-b px-4 py-3 last:border-b-0\"\n      >\n        <div>\n          <div class=\"font-semibold text-skin-heading\">\n            {{ item.title }}\n          </div>\n          <div>\n            {{ item.description }}\n          </div>\n        </div>\n        <div\n          v-tippy=\"{ content: item.disabled ? item.disabledInformation : '' }\"\n        >\n          <TuneButton\n            variant=\"danger\"\n            class=\"ml-4 whitespace-nowrap\"\n            :disabled=\"item.disabled\"\n            :loading=\"item.loading\"\n            @click=\"item.disabled ? null : item.action()\"\n          >\n            {{ item.button }}\n          </TuneButton>\n        </div>\n      </div>\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SettingsDelegationBlock.vue",
    "content": "<script setup lang=\"ts\">\nimport schemas from '@snapshot-labs/snapshot.js/src/schemas';\n\nconst delegationDefinition =\n  schemas.space.properties.delegationPortal.properties;\n\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  isViewOnly?: boolean;\n}>();\n\nconst { form, validationErrors, addRef } = useFormSpaceSettings(props.context);\n\nconst delegationTypes = computed(() => {\n  return delegationDefinition.delegationType.anyOf.map((item: any) => ({\n    value: item.const,\n    name: item.title\n  }));\n});\n</script>\n\n<template>\n  <BaseBlock :title=\"$t('settings.delegationPortal.title')\">\n    <BaseMessageBlock level=\"info\" class=\"mb-3\">\n      {{ $t('settings.delegationPortal.information') }}\n    </BaseMessageBlock>\n\n    <div class=\"space-y-2\">\n      <TuneListbox\n        :ref=\"addRef\"\n        :items=\"delegationTypes\"\n        :model-value=\"form.delegationPortal.delegationType\"\n        :definition=\"delegationDefinition.delegationType\"\n        @update:model-value=\"\n          value => (form.delegationPortal.delegationType = value)\n        \"\n      />\n      <TuneInput\n        :ref=\"addRef\"\n        v-model=\"form.delegationPortal.delegationContract\"\n        :definition=\"delegationDefinition.delegationContract\"\n        :error=\"validationErrors.delegationPortal?.delegationContract\"\n      />\n\n      <ComboboxNetwork\n        :ref=\"addRef\"\n        label=\"Delegation network\"\n        :hint=\"delegationDefinition.delegationContract.description\"\n        :network=\"form.delegationPortal.delegationNetwork\"\n        @select=\"value => (form.delegationPortal.delegationNetwork = value)\"\n      />\n      <TuneInput\n        :ref=\"addRef\"\n        v-model=\"form.delegationPortal.delegationApi\"\n        :definition=\"delegationDefinition.delegationApi\"\n        :error=\"validationErrors.delegationPortal?.delegationApi\"\n      />\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SettingsDomainBlock.vue",
    "content": "<script setup lang=\"ts\">\nimport schemas from '@snapshot-labs/snapshot.js/src/schemas';\n\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  isViewOnly?: boolean;\n}>();\n\nconst { form, validationErrors } = useFormSpaceSettings(props.context);\n\nconst modalSkinsOpen = ref(false);\n</script>\n\n<template>\n  <BaseBlock :title=\"$t('settings.customDomain')\">\n    <BaseMessageBlock level=\"info\" class=\"mb-4\">\n      <i18n-t keypath=\"settings.domain.info\" tag=\"span\" scope=\"global\">\n        <template #docs>\n          <BaseLink link=\"https://docs.snapshot.org/spaces/add-custom-domain\">\n            {{ $t('learnMore') }}\n          </BaseLink>\n        </template>\n      </i18n-t>\n    </BaseMessageBlock>\n\n    <ContainerParallelInput>\n      <TuneInput\n        v-model=\"form.domain\"\n        :label=\"$t('settings.domain.label')\"\n        :error=\"validationErrors?.domain\"\n        :max-length=\"schemas.space.properties.domain.maxLength\"\n        :disabled=\"isViewOnly\"\n        placeholder=\"e.g. vote.balancer.fi\"\n      />\n\n      <TuneButtonSelect\n        :label=\"$t(`settings.skin`)\"\n        :model-value=\"form.skin ? form.skin : $t('defaultSkin')\"\n        :disabled=\"isViewOnly\"\n        @select=\"modalSkinsOpen = true\"\n      />\n    </ContainerParallelInput>\n\n    <teleport to=\"#modal\">\n      <ModalSkins\n        v-model=\"form.skin\"\n        :open=\"modalSkinsOpen\"\n        @close=\"modalSkinsOpen = false\"\n      />\n    </teleport>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SettingsLinkBlock.vue",
    "content": "<script setup lang=\"ts\">\nimport schemas from '@snapshot-labs/snapshot.js/src/schemas';\n\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  isViewOnly?: boolean;\n}>();\n\nconst { form, validationErrors, addRef } = useFormSpaceSettings(props.context);\n\ntype SocialIcon = 'x' | 'github' | 'coingecko';\n\nconst socialInputs: Array<{\n  label: string;\n  icon: SocialIcon;\n  placeholder: string;\n  key: string;\n}> = [\n  {\n    label: 'X (Twitter)',\n    icon: 'x',\n    placeholder: 'e.g. elonmusk',\n    key: 'twitter'\n  },\n  {\n    label: 'Github',\n    icon: 'github',\n    placeholder: 'e.g. vbuterin',\n    key: 'github'\n  },\n  {\n    label: 'CoinGecko',\n    icon: 'coingecko',\n    placeholder: 'e.g. uniswap',\n    key: 'coingecko'\n  }\n];\n</script>\n\n<template>\n  <BaseBlock :title=\"$t('settings.links')\">\n    <div class=\"space-y-3\">\n      <ContainerParallelInput>\n        <TuneInputSocial\n          v-for=\"item in socialInputs\"\n          :key=\"item.key\"\n          :ref=\"addRef\"\n          v-model=\"form[item.key]\"\n          :label=\"item.label\"\n          :error=\"validationErrors?.[item.key]\"\n          :max-length=\"schemas.space.properties[item.key].maxLength\"\n          :disabled=\"isViewOnly\"\n          :icon=\"item.icon\"\n          :placeholder=\"item.placeholder\"\n        />\n      </ContainerParallelInput>\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SettingsMembersBlock.vue",
    "content": "<script setup lang=\"ts\">\nimport { isAddress } from '@ethersproject/address';\nimport capitalize from 'lodash/capitalize';\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  space?: ExtendedSpace;\n  isSpaceController?: boolean;\n  isSpaceAdmin?: boolean;\n}>();\n\nconst { form, validationErrors } = useFormSpaceSettings(props.context);\nconst { notify } = useFlashNotification();\nconst { t } = useI18n();\n\nconst inputAddMembers = ref('');\nconst inputAddRole = ref('author');\n\ntype Member = {\n  address: string;\n  role: string;\n};\n\nconst members = computed(() => {\n  const members: Member[] = [];\n  form.value.admins.forEach((admin: string) => {\n    members.push({\n      address: admin,\n      role: 'admin'\n    });\n  });\n\n  form.value.moderators.forEach((member: string) => {\n    if (form.value.admins?.includes(member)) {\n      form.value.moderators = form.value.moderators?.filter(m => m !== member);\n      return;\n    }\n    members.push({\n      address: member,\n      role: 'moderator'\n    });\n  });\n\n  form.value.members.forEach((member: string) => {\n    if (\n      form.value.admins?.includes(member) ||\n      form.value.moderators?.includes(member)\n    ) {\n      form.value.members = form.value.members?.filter(m => m !== member);\n      return;\n    }\n    members.push({\n      address: member,\n      role: 'author'\n    });\n  });\n\n  return members;\n});\n\nconst isAbleToChangeMembers = computed(() => {\n  if (props.context === 'setup') return true;\n  if (props.context === 'settings') {\n    if (props.isSpaceAdmin || props.isSpaceController) return true;\n  }\n  return false;\n});\n\nconst isAbleToChangeAdmins = computed(() => {\n  if (props.context === 'setup') return true;\n  if (props.context === 'settings') {\n    if (props.isSpaceController) return true;\n  }\n  return false;\n});\n\nfunction changeMemberRole(address: string, role: string, close: () => void) {\n  if (\n    role === 'admin'\n      ? !isAbleToChangeAdmins.value\n      : !isAbleToChangeMembers.value\n  )\n    return;\n\n  if (role === 'admin' && !form.value.admins?.includes(address)) {\n    form.value.admins = [...form.value.admins, address];\n    form.value.moderators = form.value.moderators?.filter(\n      member => member !== address\n    );\n    form.value.members = form.value.members?.filter(\n      member => member !== address\n    );\n  } else if (\n    role === 'moderator' &&\n    !form.value.moderators?.includes(address)\n  ) {\n    form.value.moderators = [...form.value.moderators, address];\n    form.value.admins = form.value.admins?.filter(member => member !== address);\n    form.value.members = form.value.members?.filter(\n      member => member !== address\n    );\n  } else if (role === 'author' && !form.value.members?.includes(address)) {\n    form.value.members = [...form.value.members, address];\n    form.value.admins = form.value.admins?.filter(member => member !== address);\n    form.value.moderators = form.value.moderators?.filter(\n      member => member !== address\n    );\n  }\n  close();\n}\n\nfunction deleteMember(member: Member) {\n  if (\n    member.role === 'admin'\n      ? !isAbleToChangeAdmins.value\n      : !isAbleToChangeMembers.value\n  )\n    return;\n\n  if (member.role === 'admin') {\n    form.value.admins = form.value.admins?.filter(m => m !== member.address);\n  } else if (member.role === 'moderator') {\n    form.value.moderators = form.value.moderators?.filter(\n      m => m !== member.address\n    );\n  } else if (member.role === 'author') {\n    form.value.members = form.value.members?.filter(m => m !== member.address);\n  }\n}\n\nfunction addMembers(addresses: string) {\n  inputAddMembers.value = addresses;\n\n  const addressesArray = addresses\n    .split(',')\n    .map(address => address.trim().toLowerCase())\n    .filter(address => isAddress(address))\n    .filter(address => {\n      const isNotMember =\n        !form.value.admins?.includes(address) &&\n        !form.value.moderators?.includes(address) &&\n        !form.value.members?.includes(address);\n      return isNotMember;\n    });\n\n  if (addressesArray.length === 0) return;\n\n  addressesArray.forEach(address => {\n    if (inputAddRole.value === 'admin') {\n      form.value.admins = [...form.value.admins, address];\n    }\n    if (inputAddRole.value === 'moderator') {\n      form.value.moderators = [...form.value.moderators, address];\n    }\n    if (inputAddRole.value === 'author') {\n      form.value.members = [...form.value.members, address];\n    }\n  });\n\n  nextTick(() => {\n    inputAddMembers.value = '';\n  });\n  notify(['green', t('settings.members.membersAdded')]);\n}\n\nconst errorMessage = computed(() => {\n  if (inputAddMembers.value === '') return '';\n\n  const membersArray = inputAddMembers.value\n    .split(',')\n    .map(address => address.trim().toLowerCase());\n\n  let message = '';\n\n  membersArray.forEach(address => {\n    if (!isAddress(address)) {\n      message = t('settings.members.invalidAddress');\n      return;\n    }\n    const isMember =\n      form.value.admins?.includes(address) ||\n      form.value.moderators?.includes(address) ||\n      form.value.members?.includes(address);\n    if (isMember) {\n      message = t('settings.members.alreadyExists');\n      return;\n    }\n  });\n\n  return message;\n});\n\nfunction changeInputAddRole(role: string, close: () => void) {\n  if (role === 'admin' && isAbleToChangeAdmins.value) {\n    inputAddRole.value = 'admin';\n    close();\n  }\n  if (role === 'moderator' || role === 'author') {\n    inputAddRole.value = role;\n    close();\n  }\n}\n</script>\n\n<template>\n  <BaseBlock\n    :title=\"$t('settings.members.title')\"\n    :information=\"$t('settings.members.information')\"\n  >\n    <div class=\"space-y-1\">\n      <div\n        v-for=\"member in members\"\n        :key=\"member.address\"\n        class=\"flex items-center justify-between\"\n      >\n        <BaseUser :address=\"member.address\" />\n\n        <div class=\"flex items-center gap-1\">\n          <BasePopover\n            :disabled=\"\n              member.role === 'admin'\n                ? !isAbleToChangeAdmins\n                : !isAbleToChangeMembers\n            \"\n          >\n            <template #button>\n              <InputSelect :model-value=\"capitalize(member.role)\" />\n            </template>\n            <template #content=\"{ close }\">\n              <SettingsMembersPopoverContent\n                :current-role=\"member.role\"\n                :is-able-to-change-admins=\"isAbleToChangeAdmins\"\n                :is-able-to-change-members=\"isAbleToChangeMembers\"\n                @change=\"changeMemberRole(member.address, $event, close)\"\n              />\n            </template>\n          </BasePopover>\n          <BaseButtonIcon\n            :class=\"{\n              'cursor-not-allowed':\n                member.role === 'admin'\n                  ? !isAbleToChangeAdmins\n                  : !isAbleToChangeMembers\n            }\"\n            @click=\"deleteMember(member)\"\n          >\n            <i-ho-x class=\"text-base\" />\n          </BaseButtonIcon>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"mt-3\">\n      <div class=\"flex gap-2\">\n        <TuneInput\n          :model-value=\"inputAddMembers\"\n          :error=\"errorMessage\"\n          :disabled=\"!isAbleToChangeMembers\"\n          :label=\"$t('settings.members.addMembers')\"\n          :hint=\"$t('settings.members.addMembersInformation')\"\n          placeholder=\"0x3901D0fDe202aF1427216b79f5243f8A022d68cf, 0x3901D0fDe202aF1427216b79f5243f8A022d68cf\"\n          class=\"w-full\"\n          @update:model-value=\"addMembers\"\n        />\n\n        <BasePopover\n          :disabled=\"!isAbleToChangeMembers && !isAbleToChangeAdmins\"\n        >\n          <template #button>\n            <InputSelect\n              :model-value=\"capitalize(inputAddRole)\"\n              class=\"mt-[9px] sm:mt-[11px]\"\n            />\n          </template>\n          <template #content=\"{ close }\">\n            <SettingsMembersPopoverContent\n              :current-role=\"inputAddRole\"\n              :is-able-to-change-admins=\"isAbleToChangeAdmins\"\n              :is-able-to-change-members=\"isAbleToChangeMembers\"\n              @change=\"changeInputAddRole($event, close)\"\n            />\n          </template>\n        </BasePopover>\n      </div>\n      <BaseBlock\n        v-if=\"\n          validationErrors?.admins ||\n          validationErrors?.moderators ||\n          validationErrors?.members\n        \"\n        class=\"mt-2 !border-red\"\n      >\n        <BaseIcon name=\"warning\" class=\"mr-2 !text-red\" />\n        <span class=\"!text-red\">\n          {{\n            validationErrors?.admins ||\n            validationErrors?.moderators ||\n            validationErrors?.members\n          }}\n        </span>\n      </BaseBlock>\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SettingsMembersPopoverContent.vue",
    "content": "<script setup lang=\"ts\">\nimport capitalize from 'lodash/capitalize';\n\ndefineProps<{\n  isAbleToChangeAdmins: boolean;\n  isAbleToChangeMembers: boolean;\n  currentRole: string;\n}>();\n\nconst emit = defineEmits(['change']);\n</script>\n\n<template>\n  <div class=\"my-2\">\n    <div v-for=\"role in ['admin', 'moderator', 'author']\" :key=\"role\">\n      <button\n        class=\"flex items-center px-3 py-2 text-left\"\n        :class=\"[\n          (isAbleToChangeMembers &&\n            (role === 'author' || role === 'moderator')) ||\n          (isAbleToChangeAdmins && role === 'admin')\n            ? 'cursor-pointer hover:bg-skin-border'\n            : 'hover:bg-skin-background cursor-not-allowed'\n        ]\"\n        @click=\"emit('change', role)\"\n      >\n        <div class=\"\">\n          <div class=\"font-semibold text-skin-heading\">\n            {{ capitalize(role) }}\n          </div>\n          <span class=\"opacity-80\">\n            {{ $t(`settings.members.${role}.description`) }}\n          </span>\n        </div>\n        <div class=\"px-3\">\n          <i-ho-check v-if=\"currentRole === role\" />\n        </div>\n      </button>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SettingsPluginsBlock.vue",
    "content": "<script setup lang=\"ts\">\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  isViewOnly?: boolean;\n}>();\n\nconst { form } = useFormSpaceSettings(props.context);\n\nconst { pluginIndex } = usePlugins();\nconst currentPlugin = ref({});\nconst modalPluginsOpen = ref(false);\n\nfunction handleEditPlugins(name: string) {\n  if (props.isViewOnly) return;\n  // the oSnap plugin does not require any configuration\n  // so we don't need to open the modal\n  if (name === 'oSnap') return;\n  currentPlugin.value = {};\n  currentPlugin.value[name] = clone(form.value.plugins[name]);\n  modalPluginsOpen.value = true;\n}\n\nfunction handleRemovePlugins(name: string) {\n  const pluginsObj = clone(form.value.plugins);\n  delete pluginsObj[name];\n  form.value.plugins = pluginsObj;\n}\n\nfunction handleAddPlugins() {\n  currentPlugin.value = {};\n  modalPluginsOpen.value = true;\n}\n\nfunction handleSubmitPlugins(payload) {\n  const pluginsObj = clone(form.value.plugins);\n  pluginsObj[payload.key] = payload.input;\n  form.value.plugins = pluginsObj;\n}\n</script>\n\n<template>\n  <BaseBlock :title=\"$t('plugins')\">\n    <div v-if=\"form.plugins\">\n      <div\n        v-for=\"(name, index) in Object.keys(form.plugins).filter(\n          key => pluginIndex[key]\n        )\"\n        :key=\"index\"\n        class=\"mb-3\"\n      >\n        <button\n          v-if=\"pluginIndex[name].name\"\n          class=\"flex w-full items-center justify-between rounded-md border p-4\"\n          :class=\"{ ' cursor-default': isViewOnly }\"\n          @click=\"handleEditPlugins(name)\"\n        >\n          <div class=\"flex items-center gap-2 truncate pr-[20px] text-left\">\n            <h4 class=\"truncate\">{{ pluginIndex[name].name }}</h4>\n          </div>\n          <BaseButtonIcon\n            v-show=\"!isViewOnly\"\n            class=\"-mr-2\"\n            @click.stop=\"handleRemovePlugins(name)\"\n          >\n            <BaseIcon name=\"close\" size=\"14\" />\n          </BaseButtonIcon>\n        </button>\n      </div>\n    </div>\n\n    <TuneButton\n      :disabled=\"isViewOnly\"\n      class=\"block w-full\"\n      @click=\"handleAddPlugins\"\n    >\n      {{ $t('settings.addPlugin') }}\n    </TuneButton>\n    <teleport to=\"#modal\">\n      <ModalPlugins\n        :open=\"modalPluginsOpen\"\n        :plugin=\"currentPlugin\"\n        :used-plugins=\"Object.keys(form.plugins)\"\n        @close=\"modalPluginsOpen = false\"\n        @add=\"handleSubmitPlugins\"\n      />\n    </teleport>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SettingsProfileBlock.vue",
    "content": "<script setup lang=\"ts\">\nimport schemas from '@snapshot-labs/snapshot.js/src/schemas';\nimport { SPACE_CATEGORIES } from '@/helpers/constants';\n\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  isViewOnly?: boolean;\n}>();\n\nconst { form, validationErrors, addRef } = useFormSpaceSettings(props.context);\n\nconst avatarNotReactive = ref(form.value.avatar);\n</script>\n\n<template>\n  <BaseBlock :title=\"$t('settings.profile')\">\n    <div class=\"space-y-2\">\n      <div class=\"flex flex-col-reverse sm:flex-row\">\n        <div class=\"mt-3 w-full space-y-2 sm:mt-0\">\n          <div class=\"flex w-full\">\n            <div>\n              <LabelInput>\n                {{ $t('settings.avatar') }}\n              </LabelInput>\n              <InputUploadAvatar\n                :is-view-only=\"isViewOnly\"\n                class=\"h-[80px]\"\n                @image-uploaded=\"url => (form.avatar = url)\"\n                @image-remove=\"() => (form.avatar = '')\"\n              >\n                <template #avatar=\"{ uploading, previewFile }\">\n                  <div class=\"relative\">\n                    <AvatarSpace\n                      :preview-file=\"previewFile\"\n                      size=\"80\"\n                      :space=\"{\n                        id:\n                          ($route.params.ens as string) ??\n                          ($route.params.key as string),\n                        avatar: avatarNotReactive\n                      }\"\n                    />\n                    <AvatarOverlayEdit\n                      :loading=\"uploading\"\n                      :avatar=\"form?.avatar\"\n                      :is-view-only=\"isViewOnly\"\n                    />\n                    <div\n                      :class=\"{\n                        'cursor-not-allowed': isViewOnly\n                      }\"\n                      class=\"absolute bottom-[2px] right-0 rounded-full bg-skin-heading p-1\"\n                    >\n                      <i-ho-pencil class=\"text-[12px] text-skin-bg\" />\n                    </div>\n                  </div>\n                </template>\n              </InputUploadAvatar>\n            </div>\n          </div>\n\n          <TuneInput\n            :ref=\"addRef\"\n            v-model=\"form.name\"\n            :label=\"$t(`settings.name.label`)\"\n            :error=\"validationErrors?.name\"\n            :max-length=\"schemas.space.properties.name.maxLength\"\n            :placeholder=\"$t('settings.name.placeholder')\"\n            :disabled=\"isViewOnly\"\n            autofocus\n          />\n\n          <TuneTextarea\n            :ref=\"addRef\"\n            v-model=\"form.about\"\n            :label=\"$t(`settings.about.label`)\"\n            :max-length=\"schemas.space.properties.about.maxLength\"\n            :placeholder=\"$t('settings.about.placeholder')\"\n            :disabled=\"isViewOnly\"\n          />\n\n          <TuneListboxMultiple\n            :ref=\"addRef\"\n            v-model=\"form.categories\"\n            :placeholder=\"$t('settings.categories.select')\"\n            :label=\"$t(`settings.categories.label`)\"\n            :items=\"\n              SPACE_CATEGORIES.map(category => ({\n                value: category,\n                name: $t(`explore.categories.${category}`)\n              }))\n            \"\n            :limit=\"2\"\n            :disabled=\"isViewOnly\"\n          />\n\n          <TuneInputUrl\n            :ref=\"addRef\"\n            v-model=\"form.website\"\n            :label=\"$t('settings.website')\"\n            :error=\"validationErrors?.website\"\n            :max-length=\"schemas.space.properties.website.maxLength\"\n            :disabled=\"isViewOnly\"\n            placeholder=\"e.g. https://www.example.com\"\n          />\n\n          <TuneInputUrl\n            :ref=\"addRef\"\n            v-model=\"form.terms\"\n            :label=\"$t(`settings.terms.label`)\"\n            :hint=\"$t('settings.terms.information')\"\n            :error=\"validationErrors?.terms\"\n            :disabled=\"isViewOnly\"\n            placeholder=\"e.g. https://example.com/terms\"\n          />\n\n          <TuneSwitch\n            v-model=\"form.private\"\n            :disabled=\"isViewOnly\"\n            :label=\"$t('settings.hideSpace')\"\n          />\n        </div>\n      </div>\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SettingsProposalBlock.vue",
    "content": "<script setup lang=\"ts\">\nimport schemas from '@snapshot-labs/snapshot.js/src/schemas';\n\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  isViewOnly?: boolean;\n}>();\n\nconst { form, validationErrors, addRef } = useFormSpaceSettings(props.context);\n</script>\n\n<template>\n  <BaseBlock :title=\"$t('settings.proposal.title')\">\n    <div class=\"space-y-2\">\n      <TuneInputUrl\n        :ref=\"addRef\"\n        v-model=\"form.guidelines\"\n        :label=\"$t('settings.proposal.guidelines.title')\"\n        :hint=\"$t('settings.proposal.guidelines.information')\"\n        placeholder=\"e.g. https://example.com/guidelines\"\n        :error=\"validationErrors?.guidelines\"\n        :max-length=\"schemas.space.properties.guidelines.maxLength\"\n        :disabled=\"isViewOnly\"\n      />\n\n      <TuneTextarea\n        v-model=\"form.template\"\n        class=\"input\"\n        :label=\"$t('settings.proposal.template.title')\"\n        :hint=\"$t('settings.proposal.template.information')\"\n        :placeholder=\"`## Intro\\n## Body\\n## Conclusion`\"\n        :max-length=\"schemas.space.properties.template.maxLength\"\n        :disabled=\"isViewOnly\"\n      />\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SettingsStrategiesBlock.vue",
    "content": "<script setup lang=\"ts\">\nimport { SpaceStrategy } from '@/helpers/interfaces';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport schemas from '@snapshot-labs/snapshot.js/src/schemas';\nimport { STRATEGIES_LIMITS } from '@/helpers/constants';\n\nconst spaceSchema = schemas.space;\n\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  title?: string;\n  showErrors?: boolean;\n  isViewOnly?: boolean;\n  spaceType?: string;\n}>();\n\nconst { form, validationErrors } = useFormSpaceSettings(props.context, {\n  spaceType: props.spaceType\n});\n\nconst strategiesLimit = computed(\n  () => STRATEGIES_LIMITS[props.spaceType || 'default']\n);\nconst strategies = computed(() => form.value.strategies);\n\nconst strategyObj = {\n  name: '',\n  network: '',\n  params: {}\n};\n\nconst modalStrategyOpen = ref(false);\nconst currentStrategyIndex = ref<number | null>(null);\nconst currentStrategy = ref<SpaceStrategy>(clone(strategyObj));\n\nfunction handleRemoveStrategy(i) {\n  form.value.strategies = strategies.value.filter(\n    (strategy, index) => index !== i\n  );\n}\n\nfunction handleEditStrategy(i) {\n  currentStrategyIndex.value = i;\n  currentStrategy.value = clone(strategies.value[i]);\n  modalStrategyOpen.value = true;\n}\n\nfunction handleAddStrategy() {\n  if (props.isViewOnly) return;\n  currentStrategyIndex.value = null;\n  currentStrategy.value = strategyObj;\n  modalStrategyOpen.value = true;\n}\n\nfunction handleSubmitStrategy(strategy) {\n  if (currentStrategyIndex.value !== null) {\n    const strategiesClone = clone(strategies.value);\n    strategiesClone[currentStrategyIndex.value] = strategy;\n    form.value.strategies = strategiesClone;\n  } else {\n    form.value.strategies = strategies.value.concat(strategy);\n  }\n}\n</script>\n\n<template>\n  <div>\n    <BaseBlock :title=\"title || $t('settings.strategies.label')\">\n      <ContainerParallelInput class=\"mb-4 w-full\">\n        <ComboboxNetwork\n          :network=\"form.network\"\n          :hint=\"$t('settings.network.information')\"\n          :disabled=\"isViewOnly\"\n          :error=\"validationErrors?.network\"\n          :show-errors=\"showErrors\"\n          @select=\"value => (form.network = value)\"\n        />\n        <TuneInput\n          v-model=\"form.symbol\"\n          :label=\"$t(`settings.symbol.label`)\"\n          :hint=\"$t(`settings.symbol.information`)\"\n          placeholder=\"e.g. BAL\"\n          :error=\"validationErrors?.symbol\"\n          :max-length=\"schemas.space.properties.symbol.maxLength\"\n          :disabled=\"isViewOnly\"\n          autofocus\n        />\n      </ContainerParallelInput>\n\n      <div class=\"flex justify-between\">\n        <div>\n          <div class=\"flex items-center gap-1\">\n            <h4>\n              {{ $tc('settings.strategiesList', [strategiesLimit]) }}\n            </h4>\n            <IconInformationTooltip\n              class=\"text-sm\"\n              :information=\"$t('settings.strategies.information')\"\n            />\n          </div>\n          <div class=\"-mt-[3px] text-sm\">\n            ({{ $t('settings.votingPowerIsCumulative') }})\n          </div>\n        </div>\n        <div>\n          <TuneButton\n            class=\"flex w-full items-center gap-1\"\n            :disabled=\"isViewOnly\"\n            @click=\"handleAddStrategy\"\n          >\n            <i-ho-plus class=\"text-sm\" />\n            {{ $t('add') }}\n          </TuneButton>\n        </div>\n      </div>\n      <div class=\"mt-3 space-y-3\">\n        <StrategiesListItem\n          v-for=\"(strategy, i) in strategies\"\n          :key=\"i\"\n          :strategy=\"strategy\"\n          :show-delete=\"!isViewOnly\"\n          :show-edit=\"!isViewOnly\"\n          @edit=\"handleEditStrategy(i)\"\n          @delete=\"handleRemoveStrategy(i)\"\n        />\n      </div>\n\n      <StrategiesBlockWarning\n        v-if=\"showErrors\"\n        :error=\"validationErrors?.strategies\"\n        :context=\"context\"\n      />\n    </BaseBlock>\n\n    <teleport to=\"#modal\">\n      <ModalStrategy\n        :open=\"modalStrategyOpen\"\n        :strategy=\"currentStrategy\"\n        :default-network=\"form.network\"\n        @close=\"modalStrategyOpen = false\"\n        @add=\"handleSubmitStrategy\"\n      />\n    </teleport>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SettingsSubSpacesBlock.vue",
    "content": "<script setup lang=\"ts\">\nimport { watchDebounced } from '@vueuse/core';\n\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  isViewOnly?: boolean;\n}>();\n\nconst { form } = useFormSpaceSettings(props.context);\nconst { loadExtendedSpace, extendedSpaces } = useExtendedSpaces();\n\nconst lookingUpParent = ref(false);\nconst foundParent = ref(false);\nconst parentNotFound = ref(false);\nwatchDebounced(\n  () => form.value.parent,\n  async () => {\n    foundParent.value = false;\n    parentNotFound.value = false;\n\n    if (!form.value.parent) return;\n\n    lookingUpParent.value = true;\n    await loadExtendedSpace(form.value.parent);\n\n    const found = extendedSpaces.value?.some(\n      space => space.id === form.value.parent\n    );\n    if (found) {\n      foundParent.value = true;\n    } else {\n      parentNotFound.value = true;\n    }\n    lookingUpParent.value = false;\n  },\n  { debounce: 500, immediate: true }\n);\n\nconst childInput = ref('');\nconst lookingUpChild = ref(false);\nconst foundChild = ref(false);\nconst childNotFound = ref(false);\nwatchDebounced(\n  childInput,\n  async () => {\n    foundChild.value = false;\n    childNotFound.value = false;\n\n    if (!childInput.value) return;\n\n    lookingUpChild.value = true;\n    await loadExtendedSpace(childInput.value);\n\n    const found = extendedSpaces.value?.some(\n      space => space.id === childInput.value\n    );\n    if (found) {\n      foundChild.value = true;\n    } else {\n      childNotFound.value = true;\n    }\n    lookingUpChild.value = false;\n  },\n  { debounce: 500 }\n);\n\nconst addChild = () => {\n  if (foundChild.value) {\n    form.value.parent = '';\n    form.value.children.push(childInput.value);\n    childInput.value = '';\n    foundChild.value = false;\n    childNotFound.value = false;\n  }\n};\n\nconst removeChild = (child: string) => {\n  if (props.isViewOnly) return;\n  form.value.children = form.value.children.filter(c => c !== child);\n};\n</script>\n\n<template>\n  <BaseBlock :title=\"$t('settings.subspaces.label')\">\n    <div class=\"space-y-2\">\n      <BaseMessageBlock level=\"info\" class=\"mb-4\">\n        <i18n-t\n          keypath=\"settings.subspaces.information\"\n          tag=\"span\"\n          scope=\"global\"\n        >\n          <template #docs>\n            <BaseLink link=\"https://docs.snapshot.org/spaces/sub-spaces\">\n              {{ $t('learnMore') }}\n            </BaseLink>\n          </template>\n        </i18n-t>\n      </BaseMessageBlock>\n      <BaseInput\n        v-model=\"form.parent\"\n        :is-disabled=\"!!form.children?.length || isViewOnly\"\n        :title=\"$t(`settings.subspaces.parent.label`)\"\n        :information=\"$t(`settings.subspaces.parent.information`)\"\n        :placeholder=\"$t('settings.subspaces.parent.placeholder')\"\n        :loading=\"lookingUpParent\"\n        :success=\"foundParent && !lookingUpParent\"\n        :failed=\"parentNotFound\"\n      />\n\n      <div class=\"flex items-end space-x-2\">\n        <BaseInput\n          v-model=\"childInput\"\n          :is-disabled=\"!!form.parent || isViewOnly\"\n          :title=\"$t(`settings.subspaces.children.label`)\"\n          :information=\"$t(`settings.subspaces.children.information`)\"\n          :placeholder=\"$t('settings.subspaces.children.placeholder')\"\n          :loading=\"lookingUpChild\"\n          :failed=\"!!childInput && childNotFound\"\n        />\n        <div>\n          <BaseButtonRound\n            :is-disabled=\"isViewOnly\"\n            class=\"whitespace-nowrap text-skin-link\"\n            :class=\"{\n              'cursor-not-allowed !text-skin-text hover:border-skin-border':\n                !foundChild || lookingUpChild\n            }\"\n            size=\"42px\"\n            @click=\"addChild()\"\n          >\n            <i-ho-plus class=\"text-sm\" />\n          </BaseButtonRound>\n        </div>\n      </div>\n      <div class=\"flex flex-wrap gap-2\">\n        <BasePill\n          v-for=\"child in form.children\"\n          :key=\"child\"\n          class=\"flex gap-1 rounded-3xl py-1 pl-2 pr-1 text-sm text-white\"\n        >\n          {{ child }}\n          <BaseButtonIcon\n            :is-disabled=\"isViewOnly\"\n            class=\"!p-0\"\n            @click=\"removeChild(child)\"\n          >\n            <i-ho-x class=\"text-xs text-white\" />\n          </BaseButtonIcon>\n        </BasePill>\n      </div>\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SettingsTreasuriesBlock.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace, TreasuryWallet } from '@/helpers/interfaces';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  space: ExtendedSpace;\n  error: string | Record<string, any>;\n  isViewOnly?: boolean;\n}>();\n\nconst { form } = useFormSpaceSettings(props.context);\n\nconst treasuryObj = {\n  name: '',\n  address: '',\n  network: ''\n};\n\nconst modalTreasuryOpen = ref(false);\nconst modalOsnapOpen = ref(false);\nconst currentTreasuryIndex = ref<number | null>(null);\nconst currentTreasury = ref<TreasuryWallet>(clone(treasuryObj));\nconst hasOsnapPlugin = computed(() => {\n  return Object.keys(form.value.plugins).includes('oSnap');\n});\nconst isOsnapEnabledOnCurrentTreasury = ref(false);\n\nfunction handleRemoveTreasury(i: number) {\n  form.value.treasuries = form.value.treasuries.filter(\n    (treasury, index) => index !== i\n  );\n}\n\nfunction handleEditTreasury(i: number) {\n  if (props.isViewOnly) return;\n  currentTreasuryIndex.value = i;\n  currentTreasury.value = clone(form.value.treasuries[i]);\n  modalTreasuryOpen.value = true;\n}\n\nfunction handleAddTreasury() {\n  currentTreasuryIndex.value = null;\n  currentTreasury.value = treasuryObj;\n  modalTreasuryOpen.value = true;\n}\n\nfunction handleSubmitTreasury(treasury: TreasuryWallet) {\n  if (currentTreasuryIndex.value !== null) {\n    const treasuriesClone = clone(form.value.treasuries);\n    treasuriesClone[currentTreasuryIndex.value] = treasury;\n    form.value.treasuries = treasuriesClone;\n  } else {\n    form.value.treasuries = form.value.treasuries.concat(treasury);\n  }\n}\n\nfunction handleOpenConfigureOsnapModal(\n  treasuryIndex: number,\n  isEnabled: boolean\n) {\n  if (props.isViewOnly || !hasOsnapPlugin.value) return;\n  currentTreasuryIndex.value = treasuryIndex;\n  currentTreasury.value = clone(form.value.treasuries[treasuryIndex]);\n  isOsnapEnabledOnCurrentTreasury.value = isEnabled;\n  modalOsnapOpen.value = true;\n}\n\nfunction handleCloseConfigureOsnapModal() {\n  modalOsnapOpen.value = false;\n  isOsnapEnabledOnCurrentTreasury.value = false;\n}\n</script>\n\n<template>\n  <BaseBlock :title=\"$t('settings.treasuries.label')\">\n    <div v-if=\"hasOsnapPlugin && form.treasuries.length === 0\" class=\"mb-3\">\n      <h2>Warning: no treasuries</h2>\n      <p>\n        You have installed the oSnap plugin, but you don't have any treasuries.\n      </p>\n      <p>\n        Please add a Safe as a treasury and enable oSnap on it to use the oSnap\n        plugin.\n      </p>\n    </div>\n    <div v-if=\"form.treasuries.length\" class=\"mb-3 grid gap-3\">\n      <SettingsTreasuriesBlockItem\n        :treasuries=\"form.treasuries\"\n        :is-view-only=\"isViewOnly\"\n        :has-osnap-plugin=\"hasOsnapPlugin\"\n        @edit-treasury=\"i => handleEditTreasury(i)\"\n        @remove-treasury=\"i => handleRemoveTreasury(i)\"\n        @configure-osnap=\"handleOpenConfigureOsnapModal\"\n      />\n    </div>\n\n    <TuneButton\n      :disabled=\"isViewOnly\"\n      class=\"block w-full\"\n      @click=\"handleAddTreasury\"\n    >\n      {{ $t('settings.treasuries.add') }}\n    </TuneButton>\n\n    <MessageWarningTestnet context=\"Treasury\" :error=\"error\" />\n\n    <teleport to=\"#modal\">\n      <ModalTreasury\n        :open=\"modalTreasuryOpen\"\n        :treasury=\"currentTreasury\"\n        @close=\"modalTreasuryOpen = false\"\n        @add=\"handleSubmitTreasury\"\n      />\n      <ModalOsnap\n        v-if=\"hasOsnapPlugin\"\n        :open=\"modalOsnapOpen\"\n        :treasury=\"currentTreasury\"\n        :space-name=\"form.name\"\n        :is-osnap-enabled=\"isOsnapEnabledOnCurrentTreasury\"\n        @close=\"handleCloseConfigureOsnapModal\"\n      />\n    </teleport>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SettingsTreasuriesBlockItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { TreasuryWallet } from '@/helpers/interfaces';\n\ndefineProps<{\n  treasuries: TreasuryWallet[];\n  isViewOnly?: boolean;\n  hasOsnapPlugin: boolean;\n}>();\n\nconst emit = defineEmits<{\n  removeTreasury: [index: number];\n  editTreasury: [index: number];\n  configureOsnap: [index: number, isEnabled: boolean];\n}>();\n</script>\n\n<template>\n  <SettingsTreasuriesBlockItemButton\n    v-for=\"(treasury, i) in treasuries\"\n    :key=\"i\"\n    :treasury=\"treasury\"\n    :treasury-index=\"i\"\n    :is-view-only=\"!!isViewOnly\"\n    :has-osnap-plugin=\"hasOsnapPlugin\"\n    @edit-treasury=\"i => emit('editTreasury', i)\"\n    @remove-treasury=\"i => emit('removeTreasury', i)\"\n    @configure-osnap=\"(i, isEnabled) => emit('configureOsnap', i, isEnabled)\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/SettingsTreasuriesBlockItemButton.vue",
    "content": "<script setup lang=\"ts\">\nimport { TreasuryWallet } from '@/helpers/interfaces';\nimport { Network } from '@/plugins/oSnap/types';\nimport { ConfigError, getIsOsnapEnabled } from '@/plugins/oSnap/utils/getters';\n\nconst props = defineProps<{\n  treasury: TreasuryWallet;\n  treasuryIndex: number;\n  isViewOnly?: boolean;\n  hasOsnapPlugin: boolean;\n}>();\n\nconst emit = defineEmits<{\n  removeTreasury: [index: number];\n  editTreasury: [index: number];\n  configureOsnap: [index: number, isEnabled: boolean];\n}>();\n\nconst isOsnapEnabled = ref(false);\nconst isChainSupported = ref(true);\n\nasync function updateIsOsnapEnabled() {\n  if (!props.hasOsnapPlugin) return;\n  const isEnabled = await getIsOsnapEnabled(\n    props.treasury.network as Network,\n    props.treasury.address\n  ).catch(e => {\n    if (e instanceof ConfigError) {\n      isChainSupported.value = false;\n      return false;\n    }\n    return false;\n  });\n  isOsnapEnabled.value = isEnabled;\n}\n\nonMounted(async () => {\n  await updateIsOsnapEnabled();\n  window.addEventListener('focus', updateIsOsnapEnabled);\n});\n\nonUnmounted(() => {\n  window.removeEventListener('focus', updateIsOsnapEnabled);\n});\n</script>\n\n<template>\n  <button\n    class=\"flex h-full w-full items-center justify-between truncate rounded-md border p-4\"\n    :class=\"{ 'cursor-default': isViewOnly }\"\n    @click=\"emit('editTreasury', treasuryIndex)\"\n  >\n    <div class=\"flex items-center gap-2 truncate pr-[20px] text-left\">\n      <h4 class=\"truncate\">{{ treasury.name }}</h4>\n    </div>\n    <div v-if=\"hasOsnapPlugin\" class=\"ml-auto mr-3\">\n      <SettingsTreasuryActivateOsnapButton\n        v-if=\"hasOsnapPlugin && isChainSupported\"\n        :is-osnap-enabled=\"isOsnapEnabled\"\n        @click.stop=\"\n          !isViewOnly && emit('configureOsnap', treasuryIndex, isOsnapEnabled)\n        \"\n      />\n      <div v-else>oSnap unavailable</div>\n    </div>\n    <BaseButtonIcon\n      v-show=\"!isViewOnly\"\n      class=\"-mr-2\"\n      @click.stop=\"emit('removeTreasury', treasuryIndex)\"\n    >\n      <BaseIcon name=\"close\" size=\"14\" />\n    </BaseButtonIcon>\n  </button>\n</template>\n"
  },
  {
    "path": "src/components/SettingsTreasuryActivateOsnapButton.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  isOsnapEnabled: boolean;\n}>();\n</script>\n\n<template>\n  <button\n    v-if=\"isOsnapEnabled\"\n    class=\"flex items-center gap-2 rounded-full px-3 py-2 bg-skin-primary text-skin-bg\"\n  >\n    <span class=\"block h-[6px] w-[6px] rounded-full bg-green\" />oSnap activated\n  </button>\n  <button\n    v-else\n    class=\"bg-skin-link text-skin-bg flex items-center gap-2 rounded-full px-3 py-2\"\n  >\n    <span class=\"block h-[6px] w-[6px] rounded-full bg-skin-bg opacity-30\" />\n    Activate oSnap\n  </button>\n</template>\n"
  },
  {
    "path": "src/components/SettingsValidationBlock.vue",
    "content": "<script setup lang=\"ts\">\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  showErrors?: boolean;\n  isViewOnly?: boolean;\n}>();\n\nconst { form, validationErrors } = useFormSpaceSettings(props.context);\n\nconst modalValidationOpen = ref(false);\n\nfunction handleSubmitAddValidation(input) {\n  form.value.validation = clone(input);\n}\n\nfunction handleClickSelectValidation() {\n  if (form.value.filters.onlyMembers) return;\n  modalValidationOpen.value = true;\n}\n</script>\n\n<template>\n  <BaseBlock :title=\"$t('settings.proposalValidation')\">\n    <div class=\"space-y-2\">\n      <TuneButtonSelect\n        class=\"w-full\"\n        :label=\"$t(`proposalValidation.label`)\"\n        :hint=\"$t(`proposalValidation.information`)\"\n        :model-value=\"$t(`proposalValidation.${form.validation.name}.label`)\"\n        :disabled=\"form.filters.onlyMembers || isViewOnly\"\n        @select=\"handleClickSelectValidation\"\n      />\n\n      <TuneSwitch\n        v-model=\"form.filters.onlyMembers\"\n        :disabled=\"isViewOnly\"\n        :label=\"$t('settings.allowOnlyAuthors')\"\n      />\n    </div>\n    <BaseMessageBlock\n      v-if=\"validationErrors.validation && showErrors\"\n      level=\"warning-red\"\n      class=\"mt-4\"\n    >\n      {{ $t('missingProposalValidationError') }}\n    </BaseMessageBlock>\n    <teleport to=\"#modal\">\n      <ModalValidation\n        :open=\"modalValidationOpen\"\n        :validation=\"form.validation\"\n        :voting-strategies=\"form.strategies\"\n        :filter-min-score=\"form.filters.minScore\"\n        @close=\"modalValidationOpen = false\"\n        @add=\"handleSubmitAddValidation\"\n        @reset-min-score=\"form.filters.minScore = 0\"\n      />\n    </teleport>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SettingsVotingBlock.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  context: 'setup' | 'settings';\n  isViewOnly?: boolean;\n}>();\n\nconst { form, validationErrors } = useFormSpaceSettings(props.context);\n</script>\n\n<template>\n  <BaseBlock :title=\"$t('settings.voting')\">\n    <div class=\"space-y-2\">\n      <div class=\"space-y-2\">\n        <TuneInputDuration\n          v-model=\"form.voting.delay\"\n          :label=\"$t('settings.votingDelay')\"\n          :disabled=\"isViewOnly\"\n          :error=\"validationErrors?.voting?.delay\"\n          hide-minutes\n          block\n        />\n\n        <TuneInputDuration\n          v-model=\"form.voting.period\"\n          :label=\"$t('settings.votingPeriod')\"\n          :disabled=\"isViewOnly\"\n          :error=\"validationErrors?.voting?.period\"\n          hide-minutes\n          block\n        />\n\n        <TuneListbox\n          v-model=\"form.voting.quorumType\"\n          placeholder=\"Default\"\n          label=\"Quorum type\"\n          hint=\"The type of quorum used for this space.\"\n          :items=\"[\n            {\n              value: 'default',\n              name: 'Default'\n            },\n            {\n              value: 'rejection',\n              name: 'Quorum of rejection'\n            }\n          ]\"\n          :disabled=\"isViewOnly\"\n          :error=\"validationErrors?.voting?.quorumType\"\n        />\n\n        <TuneInput\n          :model-value=\"form.voting.quorum\"\n          :label=\"$t('settings.quorum.label')\"\n          :hint=\"$t('settings.quorum.information')\"\n          :disabled=\"isViewOnly\"\n          placeholder=\"e.g. 1000\"\n          type=\"number\"\n          @update:model-value=\"value => (form.voting.quorum = Number(value))\"\n        />\n\n        <InputSelectVoteType\n          :type=\"form.voting.type\"\n          :hint=\"$t(`settings.type.information`)\"\n          :is-disabled-settings=\"isViewOnly\"\n          allow-any\n          @update:type=\"value => (form.voting.type = value)\"\n        />\n\n        <InputSelectPrivacy\n          :privacy=\"form.voting.privacy\"\n          :hint=\"$t(`privacy.information`)\"\n          :is-disabled=\"isViewOnly\"\n          allow-any\n          @update:privacy=\"value => (form.voting.privacy = value)\"\n        />\n\n        <InputSelectVoteValidation\n          :validation=\"form.voteValidation\"\n          :voting-strategies=\"form.strategies\"\n          :disabled=\"isViewOnly\"\n          @add=\"value => (form.voteValidation = value)\"\n        />\n      </div>\n\n      <TuneSwitch\n        v-model=\"form.voting.hideAbstain\"\n        :label=\"$t('settings.hideAbstain')\"\n        :disabled=\"isViewOnly\"\n      />\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SetupButtonBack.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <TuneButton class=\"mt-4\">\n    {{ $t('back') }}\n  </TuneButton>\n</template>\n"
  },
  {
    "path": "src/components/SetupButtonCreate.vue",
    "content": "<script setup lang=\"ts\">\nconst emit = defineEmits(['create']);\n\ndefineProps<{\n  creatingSpace: boolean;\n}>();\n</script>\n\n<template>\n  <TuneButton\n    class=\"float-right\"\n    primary\n    :loading=\"creatingSpace\"\n    @click=\"emit('create')\"\n  >\n    {{ $t('createButton') }}\n  </TuneButton>\n</template>\n"
  },
  {
    "path": "src/components/SetupButtonNext.vue",
    "content": "<template>\n  <TuneButton primary class=\"float-right mt-4\">\n    {{ $t('next') }}\n  </TuneButton>\n</template>\n"
  },
  {
    "path": "src/components/SetupDomain.vue",
    "content": "<script setup lang=\"ts\">\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { useTippy } from 'vue-tippy';\nimport { shorten } from '@/helpers/utils';\n\nconst { env } = useApp();\nconst { t } = useI18n();\n\nconst defaultNetwork = import.meta.env.VITE_DEFAULT_NETWORK;\n\nconst { web3Account } = useWeb3();\nconst { loadOwnedEnsDomains, ownedEnsDomains } = useEns();\nconst { loadSpaces, spaces, getDeletedSpaces } = useSpaces();\n\nconst inputDomain = ref('');\nconst isLoading = ref(false);\nconst deletedSpaces = ref<string[]>([]);\nconst refEnsUnavailableTooltip = ref<Record<string, any>>({});\n\nwatch(\n  web3Account,\n  async () => {\n    ownedEnsDomains.value = [];\n    isLoading.value = true;\n    await loadOwnedEnsDomains(web3Account.value);\n\n    const ids = ownedEnsDomains.value.map(d => d.name);\n    if (ids.length) {\n      await loadSpaces(ids);\n      const spaceIds = spaces.value.map(space => space.id);\n      deletedSpaces.value = await getDeletedSpaces(\n        ids.filter(id => !spaceIds.includes(id))\n      );\n    }\n    isLoading.value = false;\n  },\n  { immediate: true }\n);\n\nconst triggerEnsUnavailableTooltip = (key: string, error_code: string) => {\n  let text;\n  switch (error_code) {\n    case 'deleted-space':\n      text =\n        'This ENS name is used by a previously deleted space, and can not be used anymore to create a new space. <a target=\"_blank\" href=\"https://docs.snapshot.org/faq#why-cant-i-create-a-new-space-with-my-previous-deleted-space-ens-name\">Learn more.</a>';\n      break;\n    case 'invalid-ens-length':\n      text = 'The ENS name is too long. It must be less than 64 characters.';\n      break;\n    default:\n      text = t('setup.domain.invalidEns');\n  }\n\n  useTippy(refEnsUnavailableTooltip.value[key], {\n    content: text,\n    interactive: true,\n    allowHTML: true,\n    theme: 'urlified'\n  });\n};\n\nconst availableDomains = computed(() => {\n  const spaceIds = spaces.value.map(space => space.id);\n  return ownedEnsDomains.value.filter(\n    d =>\n      !spaceIds.includes(d.name) &&\n      !d.isInvalid &&\n      !deletedSpaces.value.includes(d.name) &&\n      d.name.length <= 64\n  );\n});\n\nconst unavailableDomains = computed(() => {\n  const spaceIds = spaces.value.map(space => space.id);\n  return ownedEnsDomains.value.filter(\n    d =>\n      !spaceIds.includes(d.name) &&\n      (d.isInvalid ||\n        deletedSpaces.value.includes(d.name) ||\n        d.name.length > 64)\n  );\n});\n\nconst domainsWithExistingSpace = computed(() => {\n  const spaceIds = ownedEnsDomains.value.map(d => d.name);\n  return spaces.value.filter(d => spaceIds.includes(d.id));\n});\n\nconst emit = defineEmits(['next']);\n\n// handle periodic lookup (every 5s) while registering new domain\nlet waitingForRegistrationInterval;\nconst waitForRegistration = () => {\n  clearInterval(waitingForRegistrationInterval);\n  waitingForRegistrationInterval = setInterval(loadOwnedEnsDomains, 5000);\n};\n\nfunction shortenInvalidEns(ens: string) {\n  const [name, domain] = ens.split('.');\n  return `${shorten(name)}.${domain}`;\n}\n\n// stop lookup when leaving\nonUnmounted(() => clearInterval(waitingForRegistrationInterval));\n</script>\n\n<template>\n  <div>\n    <LoadingRow v-if=\"isLoading\" block />\n    <div v-else>\n      <h4 class=\"mb-2 px-4 md:px-0\">{{ $t('setup.domain.title') }}</h4>\n      <BaseMessageBlock\n        v-if=\"env !== 'demo'\"\n        class=\"mb-4\"\n        level=\"info\"\n        is-responsive\n      >\n        {{ $t('setup.domain.ensMessage') }}\n        <i18n-t\n          keypath=\"setup.domain.ensMessageTestnet\"\n          tag=\"span\"\n          scope=\"global\"\n        >\n          <template #link>\n            <BaseLink link=\"https://testnet.v1.snapshot.box\">\n              {{ $t('setup.domain.tryDemo') }}\n            </BaseLink>\n          </template>\n        </i18n-t>\n      </BaseMessageBlock>\n\n      <BlockSpacesList\n        v-if=\"domainsWithExistingSpace.length\"\n        :spaces=\"domainsWithExistingSpace\"\n        :title=\"$t('setup.domain.yourExistingSpaces')\"\n        class=\"mb-3\"\n      />\n\n      <BaseMessageBlock\n        v-if=\"defaultNetwork && env === 'demo'\"\n        level=\"info\"\n        class=\"mb-4\"\n        is-responsive\n      >\n        {{\n          $t('setup.demoTestnetEnsMessage', {\n            network: networks[defaultNetwork].name\n          })\n        }}\n      </BaseMessageBlock>\n\n      <BaseBlock>\n        <div class=\"flex flex-col space-y-4\">\n          <div v-if=\"availableDomains.length\">\n            <div class=\"mb-3\">\n              {{\n                $t(\n                  availableDomains.length > 1\n                    ? 'setup.chooseExistingEns'\n                    : 'setup.useSingleExistingEns'\n                )\n              }}\n            </div>\n            <div class=\"space-y-2 flex flex-col\">\n              <template v-for=\"(ens, i) in availableDomains\" :key=\"i\">\n                <TuneButton\n                  class=\"flex w-full items-center justify-between\"\n                  :primary=\"availableDomains.length === 1\"\n                  @click=\"emit('next', ens.name)\"\n                >\n                  {{ ens.name }}\n                  <i-ho-arrow-sm-right class=\"-mr-2\" />\n                </TuneButton>\n              </template>\n            </div>\n          </div>\n\n          <div v-if=\"unavailableDomains.length\">\n            <div class=\"mb-3\">Unavailable ENS domains:</div>\n            <div class=\"space-y-2 flex flex-col\">\n              <template v-for=\"(ens, i) in unavailableDomains\" :key=\"i\">\n                <template v-if=\"deletedSpaces.includes(ens.name)\">\n                  <TuneButton\n                    class=\"flex w-full items-center justify-between hover:cursor-default hover:border-skin-border\"\n                  >\n                    {{ ens.name }}\n                    <div\n                      @mouseenter=\"\n                        triggerEnsUnavailableTooltip(ens.name, 'deleted-space')\n                      \"\n                      @focus=\"\n                        triggerEnsUnavailableTooltip(ens.name, 'deleted-space')\n                      \"\n                    >\n                      <div :ref=\"v => (refEnsUnavailableTooltip[ens.name] = v)\">\n                        <i-ho-exclamation-circle\n                          class=\"text-red -mr-2 cursor-help\"\n                        />\n                      </div>\n                    </div>\n                  </TuneButton>\n                </template>\n                <template v-else-if=\"ens.name.length > 64\">\n                  <TuneButton class=\"flex w-full items-center justify-between\">\n                    {{ shortenInvalidEns(ens.name) }}\n                    <div\n                      @mouseenter=\"\n                        triggerEnsUnavailableTooltip(\n                          ens.name,\n                          'invalid-ens-length'\n                        )\n                      \"\n                      @focus=\"\n                        triggerEnsUnavailableTooltip(\n                          ens.name,\n                          'invalid-ens-length'\n                        )\n                      \"\n                    >\n                      <div\n                        :ref=\"\n                          v => {\n                            refEnsUnavailableTooltip[ens.name] = v;\n                          }\n                        \"\n                      >\n                        <i-ho-exclamation-circle\n                          class=\"-mr-2 text-red cursor-help\"\n                        />\n                      </div>\n                    </div>\n                  </TuneButton>\n                </template>\n                <template v-else-if=\"ens.isInvalid\">\n                  <TuneButton class=\"flex w-full items-center justify-between\">\n                    {{ shortenInvalidEns(ens.name) }}\n\n                    <div\n                      @mouseenter=\"\n                        triggerEnsUnavailableTooltip(ens.name, 'invalid-ens')\n                      \"\n                      @focus=\"\n                        triggerEnsUnavailableTooltip(ens.name, 'invalid-ens')\n                      \"\n                    >\n                      <div\n                        :ref=\"\n                          v => {\n                            refEnsUnavailableTooltip[ens.name] = v;\n                          }\n                        \"\n                      >\n                        <i-ho-exclamation-circle\n                          class=\"-mr-2 text-red cursor-help\"\n                        />\n                      </div>\n                    </div>\n                  </TuneButton>\n                </template>\n              </template>\n            </div>\n          </div>\n\n          <div>\n            <div class=\"mb-2\">\n              {{ $t('setup.orRegisterNewEns') }}\n            </div>\n\n            <div>\n              <div v-if=\"!availableDomains.length\" class=\"mb-3\">\n                {{ $t('setup.toCreateASpace') }}\n              </div>\n              <SetupDomainRegister\n                v-model.trim=\"inputDomain\"\n                @wait-for-registration=\"waitForRegistration\"\n              />\n            </div>\n          </div>\n        </div>\n      </BaseBlock>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SetupDomainRegister.vue",
    "content": "<script setup lang=\"ts\">\n/**\n * An input with ENS TLD validation and a register button, forwarding to app.ens.domains.\n * Emits waitForRegistration event when Register button is clicked, to trigger domain lookup in background.\n */\n\nconst { validEnsTlds } = useEns();\n\nconst props = defineProps<{\n  modelValue: string;\n}>();\n\ndefineEmits(['update:modelValue', 'waitForRegistration']);\n\nconst isValidDomain = computed(() => {\n  if (!props.modelValue.includes('.')) return false;\n  return validEnsTlds.includes(props.modelValue.split('.').pop() || '');\n});\n</script>\n\n<template>\n  <div>\n    <BaseInput\n      :model-value=\"modelValue\"\n      :placeholder=\"$t('setup.example')\"\n      class=\"!pr-[44px]\"\n      @input=\"$emit('update:modelValue', $event.target.value.toLowerCase())\"\n    >\n      <template #after>\n        <span\n          v-tippy=\"{\n            content: `${$t('setup.supportedEnsTLDs')}: ${validEnsTlds.join(\n              ', '\n            )}`\n          }\"\n          class=\"-mr-2 flex cursor-help items-center\"\n          target=\"_blank\"\n        >\n          <BaseIcon name=\"info\" size=\"24\" class=\"-mr-1 text-skin-text\" />\n        </span>\n      </template>\n    </BaseInput>\n    <BaseLink\n      :link=\"`https://app.ens.domains/name/${modelValue}/register`\"\n      hide-external-icon\n      @click=\"$emit('waitForRegistration')\"\n    >\n      <TuneButton\n        tabindex=\"-1\"\n        :disabled=\"!isValidDomain\"\n        class=\"mt-2 w-full\"\n        primary\n      >\n        {{ $t('setup.registerEnsButton') }}\n      </TuneButton>\n    </BaseLink>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SetupExtras.vue",
    "content": "<script setup lang=\"ts\">\nconst { isGnosisSafe } = useClient();\nconst { validationErrors } = useFormSpaceSettings('setup');\n\ndefineProps<{\n  creatingSpace: boolean;\n}>();\n\nconst emit = defineEmits(['submit', 'back']);\n\nconst showErrors = ref(false);\n\nfunction handleSubmit() {\n  if (validationErrors.value.validation) return (showErrors.value = true);\n  emit('submit');\n}\n</script>\n\n<template>\n  <div v-if=\"isGnosisSafe\" class=\"space-y-4\">\n    <SettingsMembersBlock context=\"setup\" />\n\n    <SettingsProposalBlock context=\"setup\" />\n\n    <SettingsVotingBlock context=\"setup\" />\n\n    <!-- <SettingsDomainBlock context=\"setup\" /> -->\n\n    <SettingsSubSpacesBlock context=\"setup\" />\n\n    <SettingsTreasuriesBlock context=\"setup\" />\n\n    <SettingsPluginsBlock context=\"setup\" />\n\n    <SettingsValidationBlock context=\"setup\" :show-errors=\"showErrors\" />\n  </div>\n  <div v-else class=\"space-y-4\">\n    <h4 class=\"-mb-2 px-4 md:px-0\">\n      {{ $t('setup.validationTitle') }}\n    </h4>\n    <SettingsMembersBlock context=\"setup\" />\n    <SettingsValidationBlock context=\"setup\" :show-errors=\"showErrors\" />\n  </div>\n\n  <div class=\"px-4 md:px-0\">\n    <SetupButtonCreate\n      :creating-space=\"creatingSpace\"\n      class=\"mt-4\"\n      @create=\"handleSubmit\"\n    />\n    <SetupButtonBack @click=\"emit('back')\" />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SetupIntro.vue",
    "content": "<script setup lang=\"ts\">\nconst { modalAccountOpen } = useModal();\nconst { web3Account } = useWeb3();\n\nconst emit = defineEmits(['next']);\n</script>\n\n<template>\n  <div>\n    <div class=\"px-4 md:px-0\">\n      {{ $t('header.description') }}\n    </div>\n\n    <SetupMessageHelp />\n\n    <div class=\"px-4 md:px-0\">\n      <TuneButton\n        primary\n        class=\"float-right mt-4 w-full\"\n        @click=\"!web3Account ? (modalAccountOpen = true) : emit('next')\"\n      >\n        {{ $t('getStarted') }}\n      </TuneButton>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SetupMessageHelp.vue",
    "content": "<script setup lang=\"ts\">\nimport { SNAPSHOT_HELP_LINK } from '@/helpers/constants';\n</script>\n\n<template>\n  <BaseBlock class=\"mt-4 text-skin-text\">\n    <BaseIcon\n      name=\"gitbook\"\n      size=\"24\"\n      class=\"pr-2 !align-middle text-skin-text\"\n    />\n    <i18n-t keypath=\"setup.helpDocsAndDiscordLinks\" tag=\"span\" scope=\"global\">\n      <template #docs>\n        <BaseLink link=\"https://docs.snapshot.org/spaces/create\">\n          documentation</BaseLink\n        >\n      </template>\n      <template #help>\n        <BaseLink :link=\"SNAPSHOT_HELP_LINK\">Help Center</BaseLink>\n      </template>\n    </i18n-t>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SetupProfile.vue",
    "content": "<script setup lang=\"ts\">\nconst emit = defineEmits(['next', 'back']);\n\nconst { validationErrors, forceShowError } = useFormSpaceSettings('setup');\n\nfunction nextStep() {\n  if (\n    ['name', 'website', 'terms', 'x', 'github', 'coingecko'].some(item =>\n      Object.keys(validationErrors.value).includes(item)\n    )\n  )\n    return forceShowError();\n  emit('next');\n}\n</script>\n\n<template>\n  <div class=\"space-y-4\">\n    <SettingsProfileBlock context=\"setup\" />\n\n    <SettingsLinkBlock context=\"setup\" />\n  </div>\n  <div class=\"px-4 md:px-0\">\n    <SetupButtonBack @click=\"emit('back')\" />\n    <SetupButtonNext @click=\"nextStep\" />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SetupSidebarStepper.vue",
    "content": "<script setup lang=\"ts\">\nconst { isGnosisSafe } = useClient();\n\ndefineProps<{\n  currentStep: number;\n}>();\n\nconst emit = defineEmits(['changeStep']);\n\nconst steps = [\n  { name: 'Getting started' },\n  { name: 'ENS' },\n  { name: 'Profile' },\n  { name: 'Strategy' },\n  { name: isGnosisSafe.value ? 'Extras' : 'Members' }\n];\n</script>\n\n<template>\n  <div>\n    <nav class=\"flex\" aria-label=\"Progress\">\n      <ol role=\"list\" class=\"space-y-4 pt-3\">\n        <li v-for=\"(step, i) in steps\" :key=\"step.name\">\n          <div v-if=\"currentStep > i\">\n            <span class=\"flex items-center\">\n              <span\n                class=\"relative flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-skin-primary\"\n              >\n                <i-ho-check\n                  class=\"text-[14px] text-skin-bg\"\n                  aria-hidden=\"true\"\n                />\n                <span\n                  v-if=\"i > 0\"\n                  class=\"absolute -top-4 h-4 w-[1px] bg-skin-primary\"\n                />\n              </span>\n\n              <button\n                class=\"ml-3 text-base font-medium text-skin-text\"\n                @click=\"emit('changeStep', i)\"\n              >\n                {{ step.name }}\n              </button>\n            </span>\n          </div>\n          <div\n            v-else-if=\"currentStep === i\"\n            class=\"flex items-center\"\n            aria-current=\"step\"\n          >\n            <span\n              class=\"relative flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full border !border-skin-primary\"\n              aria-hidden=\"true\"\n            >\n              <span class=\"absolute h-4 w-4 rounded-full\" />\n              <span\n                class=\"relative block h-2 w-2 rounded-full bg-skin-primary\"\n              />\n              <span\n                v-if=\"i > 0\"\n                class=\"absolute -top-[25px] h-4 w-[1px] bg-skin-primary\"\n              />\n            </span>\n            <span class=\"ml-3 text-base font-medium text-skin-link\">\n              {{ step.name }}\n            </span>\n          </div>\n          <div v-else class=\"flex items-center\">\n            <div\n              class=\"relative flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full border\"\n              aria-hidden=\"true\"\n            >\n              <span\n                v-if=\"i > 0\"\n                class=\"absolute -top-[25px] h-4 w-[1px] bg-skin-border\"\n              />\n            </div>\n            <span class=\"ml-3 text-base font-medium text-skin-text\">\n              {{ step.name }}\n            </span>\n          </div>\n        </li>\n      </ol>\n    </nav>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SetupStrategy.vue",
    "content": "<script setup lang=\"ts\">\nconst emit = defineEmits(['next', 'back']);\n\nenum Step {\n  CHOOSE,\n  TOKEN_VOTING,\n  ONE_PERSON_ONE_VOTE,\n  ADVANCED\n}\n\nconst votingStep = ref(Step.CHOOSE);\n</script>\n\n<template>\n  <div>\n    <div v-if=\"votingStep === Step.CHOOSE\">\n      <div class=\"px-4 md:px-0\">\n        <h4 class=\"mb-1\">{{ $t('setup.strategy.title') }}</h4>\n        <p class=\"mb-3\">\n          {{ $t('setup.strategy.subtitle') }}\n        </p>\n      </div>\n      <div class=\"space-y-3\">\n        <ButtonCard\n          :title=\"$t('setup.strategy.tokenVoting.title')\"\n          @click=\"votingStep = Step.TOKEN_VOTING\"\n        >\n          {{ $t('setup.strategy.tokenVoting.description') }}\n        </ButtonCard>\n        <ButtonCard\n          :title=\"$t('setup.strategy.onePersonOneVote.title')\"\n          @click=\"votingStep = Step.ONE_PERSON_ONE_VOTE\"\n        >\n          {{ $t('setup.strategy.onePersonOneVote.description') }}\n        </ButtonCard>\n        <ButtonCard\n          :title=\"$t('setup.strategy.advanced.title')\"\n          @click=\"votingStep = Step.ADVANCED\"\n        >\n          {{ $t('setup.strategy.advanced.description') }}\n        </ButtonCard>\n      </div>\n    </div>\n    <SetupStrategyVote\n      v-else-if=\"votingStep === Step.ONE_PERSON_ONE_VOTE\"\n      @next=\"emit('next')\"\n    />\n    <SetupStrategyBasic\n      v-else-if=\"votingStep === Step.TOKEN_VOTING\"\n      @next=\"emit('next')\"\n    />\n    <SetupStrategyAdvanced\n      v-else-if=\"votingStep === Step.ADVANCED\"\n      @next=\"emit('next')\"\n    />\n    <div class=\"px-4 md:px-0\">\n      <SetupButtonBack\n        @click=\"\n          votingStep !== Step.CHOOSE ? (votingStep = Step.CHOOSE) : emit('back')\n        \"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SetupStrategyAdvanced.vue",
    "content": "<script setup lang=\"ts\">\nconst { validationErrors } = useFormSpaceSettings('setup');\n\nconst emit = defineEmits(['next']);\n\nconst showErrors = ref(false);\n\nfunction nextStep() {\n  if (validationErrors.value.strategies || validationErrors.value.symbol)\n    return (showErrors.value = true);\n  emit('next');\n}\n</script>\n\n<template>\n  <div>\n    <SettingsStrategiesBlock\n      context=\"setup\"\n      :title=\"$t('setup.strategy.blockTitle')\"\n      :show-errors=\"showErrors\"\n    />\n    <div class=\"mx-4 md:mx-0\">\n      <SetupButtonNext @click=\"nextStep\" />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SetupStrategyBasic.vue",
    "content": "<script setup lang=\"ts\">\nimport { getTokenPrices } from '@/helpers/covalent';\nimport { call, clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { JsonRpcProvider } from '@ethersproject/providers';\nimport { ERC20ABI } from '@/helpers/constants';\nimport { isAddress } from '@ethersproject/address';\nimport { shorten } from '@/helpers/utils';\n\nconst DEFAULT_TOKEN = {\n  name: '',\n  logo: '',\n  standard: 'ERC-20',\n  symbol: '',\n  decimals: '18'\n};\n\nconst emit = defineEmits(['next']);\n\nconst { form } = useFormSpaceSettings('setup');\nconst { t } = useI18n();\n\nconst isTokenLoading = ref(false);\nconst tokenError = ref('');\nconst network = ref('1');\nconst contract = ref('');\nconst token = ref(clone(DEFAULT_TOKEN));\n\nconst strategy = computed(() => {\n  let name = 'erc20-balance-of';\n  if (token.value.standard === 'ERC-721') {\n    name = 'erc721';\n  } else if (token.value.standard === 'ERC-1155') {\n    name = 'erc1155-balance-of';\n  }\n\n  return {\n    name,\n    network: network.value,\n    params: {\n      network: network.value,\n      address: contract.value,\n      decimals: token.value.decimals,\n      symbol: token.value.symbol\n    }\n  };\n});\n\nconst tokenStandards = computed(() => {\n  return ['ERC-20', 'ERC-721', 'ERC-1155'].map(name => ({\n    value: name\n  }));\n});\n\nfunction nextStep() {\n  if (!token.value.symbol) return;\n\n  emit('next');\n\n  form.value.strategies = [];\n  form.value.strategies.push(strategy.value);\n  form.value.symbol = strategy.value.params.symbol;\n}\n\nasync function getTokenInfo() {\n  tokenError.value = '';\n\n  if (!contract.value || !isAddress(contract.value)) {\n    tokenError.value = t('errors.invalidAddress');\n    token.value = clone(DEFAULT_TOKEN);\n    return;\n  }\n\n  isTokenLoading.value = true;\n\n  const { data } = await getTokenPrices(contract.value, network.value);\n\n  if (data?.[0]?.contract_name) {\n    token.value.name = data[0].contract_name;\n    token.value.logo = data[0].logo_url;\n    token.value.symbol = data[0].contract_ticker_symbol;\n    token.value.decimals = data[0].contract_decimals;\n    isTokenLoading.value = false;\n  } else {\n    try {\n      const provider = new JsonRpcProvider(\n        `${import.meta.env.VITE_BROVIDER_URL}/${network.value}`\n      );\n      const tokenInfo = await Promise.all([\n        call(provider, ERC20ABI, [contract.value, 'name', []]),\n        call(provider, ERC20ABI, [contract.value, 'symbol', []]),\n        call(provider, ERC20ABI, [contract.value, 'decimals', []])\n      ]);\n      token.value.name = tokenInfo[0];\n      token.value.symbol = tokenInfo[1];\n      token.value.decimals = tokenInfo[2];\n    } catch (e) {\n      console.log(e);\n      tokenError.value = t('setup.strategy.tokenVoting.tokenNotFound');\n      if (\n        token.value.standard === 'ERC-721' ||\n        token.value.standard === 'ERC-1155'\n      ) {\n        token.value.decimals = '0';\n      }\n    } finally {\n      isTokenLoading.value = false;\n    }\n  }\n}\n\nwatch(\n  [contract, network],\n  async () => {\n    await getTokenInfo();\n  },\n  { deep: true }\n);\n</script>\n\n<template>\n  <div class=\"mt-4 space-y-4\">\n    <BaseBlock :title=\"$t('setup.strategy.blockTitle')\">\n      <div class=\"flex md:w-2/3\">\n        <div class=\"w-full space-y-3\">\n          <ComboboxNetwork\n            :network=\"network\"\n            @select=\"value => (network = value)\"\n          />\n          <BaseListbox\n            v-model=\"token.standard\"\n            :items=\"tokenStandards\"\n            label=\"Token standard\"\n          />\n          <div>\n            <BaseInput\n              v-model.trim=\"contract\"\n              title=\"Token contract\"\n              placeholder=\"Enter address\"\n              :error=\"{\n                message:\n                  tokenError !== $t('setup.strategy.tokenVoting.tokenNotFound')\n                    ? tokenError\n                    : '',\n                push: true\n              }\"\n              :loading=\"isTokenLoading\"\n              focus-on-mount\n            />\n          </div>\n          <div>\n            <BaseInput\n              v-if=\"\n                !isTokenLoading &&\n                tokenError === $t('setup.strategy.tokenVoting.tokenNotFound')\n              \"\n              v-model.trim=\"token.symbol\"\n              title=\"Symbol\"\n              placeholder=\"Enter Symbol\"\n              :error=\"{\n                message: !token.symbol ? 'Symbol is required' : '',\n                push: true\n              }\"\n              focus-on-mount\n            />\n          </div>\n          <div>\n            <BaseInput\n              v-if=\"\n                !isTokenLoading &&\n                tokenError === $t('setup.strategy.tokenVoting.tokenNotFound')\n              \"\n              v-model.trim=\"token.decimals\"\n              title=\"Decimals\"\n              placeholder=\"Enter Decimals\"\n              :error=\"{\n                message: !token.decimals ? 'Decimals is required' : '',\n                push: true\n              }\"\n            />\n          </div>\n        </div>\n      </div>\n    </BaseBlock>\n\n    <BaseBlock v-if=\"token.name\" class=\"!mt-3 space-x-1 text-left text-sm\">\n      <div class=\"flex justify-between\">\n        <div class=\"flex items-center gap-1 truncate\">\n          <AvatarToken :address=\"contract\" class=\"mr-1\" size=\"38\" />\n          <div class=\"truncate\">\n            <div class=\"mr-4 truncate whitespace-nowrap text-skin-link\">\n              {{ token.name }}\n            </div>\n            {{ token.symbol }}\n          </div>\n        </div>\n        <div class=\"flex items-end\">\n          <BaseLink\n            v-if=\"network == '1'\"\n            class=\"text-skin-text hover:text-skin-link\"\n            :link=\"`https://etherscan.io/token/${contract}`\"\n          >\n            {{ shorten(contract) }}\n          </BaseLink>\n        </div>\n      </div>\n    </BaseBlock>\n\n    <div class=\"float-right mx-4 md:mx-0\">\n      <SetupButtonNext\n        class=\"!mt-0\"\n        :disabled=\"isTokenLoading\"\n        @click=\"nextStep\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SetupStrategyVote.vue",
    "content": "<script setup lang=\"ts\">\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst emit = defineEmits(['next']);\n\nconst { form, DEFAULT_VOTE_VALIDATION } = useFormSpaceSettings('setup');\nconst { t } = useI18n();\n\nconst strategyName = ref('whitelist');\nconst symbol = ref('VOTE');\nconst whitelist = ref([]);\nconst validation = ref(clone(DEFAULT_VOTE_VALIDATION));\nconst showError = ref(false);\n\nconst strategy = computed(() => {\n  const strategy: {\n    name: string;\n    params: {\n      symbol: string;\n      addresses?: string[];\n    };\n  } = {\n    name: strategyName.value,\n    params: {\n      symbol: symbol.value\n    }\n  };\n\n  if (strategyName.value === 'whitelist') {\n    strategy.params.addresses = whitelist.value;\n  }\n\n  return strategy;\n});\n\nconst votingItems = computed(() => {\n  return ['whitelist', 'ticket'].map(name => ({\n    value: name,\n    extras: {\n      information:\n        name === 'whitelist'\n          ? t('setup.strategy.onePersonOneVote.whitelistInformation')\n          : t('setup.strategy.onePersonOneVote.ticketInformation')\n    }\n  }));\n});\n\nconst isValidTicket = computed(\n  () => !(strategyName.value === 'ticket' && validation.value.name === 'any')\n);\n\nconst isValidWhitelist = computed(\n  () => !(strategyName.value === 'whitelist' && whitelist.value.length === 0)\n);\n\nconst isValidSymbol = computed(() => symbol.value.length > 0);\n\nconst whitelistRef = ref();\nconst symbolRef = ref();\n\nfunction forceShowError() {\n  whitelistRef?.value?.forceShowError();\n  symbolRef?.value?.forceShowError();\n}\n\nfunction nextStep() {\n  if (!isValidWhitelist.value || !isValidSymbol.value) {\n    forceShowError();\n    return;\n  }\n  if (!isValidTicket.value) {\n    showError.value = true;\n    return;\n  }\n\n  form.value.voteValidation = clone(DEFAULT_VOTE_VALIDATION);\n  form.value.strategies = [strategy.value];\n  form.value.symbol = symbol.value;\n\n  if (strategyName.value === 'ticket') {\n    form.value.voteValidation = validation.value;\n  }\n\n  emit('next');\n}\n</script>\n\n<template>\n  <div>\n    <BaseMessageBlock level=\"info\" class=\"mb-3\" is-responsive>\n      {{ t('setup.strategy.onePersonOneVote.votesEqualInfo') }}\n    </BaseMessageBlock>\n    <BaseBlock :title=\"t('setup.strategy.blockTitle')\">\n      <div class=\"space-y-3\">\n        <div class=\"space-y-3 md:w-2/3\">\n          <TuneListbox\n            v-model=\"strategyName\"\n            :items=\"votingItems\"\n            label=\"Strategy\"\n            class=\"w-full\"\n          >\n            <template #selected=\"{ selectedItem }\">\n              <span>\n                {{\n                  selectedItem.value === 'whitelist'\n                    ? 'Whitelist voting'\n                    : 'Ticket voting'\n                }}\n              </span>\n            </template>\n            <template #item=\"{ item }\">\n              <span class=\"flex items-center gap-1\">\n                {{\n                  item.value === 'whitelist'\n                    ? 'Whitelist voting'\n                    : 'Ticket voting'\n                }}\n                <IconInformationTooltip\n                  :information=\"item.extras?.information\"\n                  class=\"text-skin-text\"\n                />\n              </span>\n            </template>\n          </TuneListbox>\n          <TuneInput\n            ref=\"symbolRef\"\n            v-model=\"symbol\"\n            label=\"Symbol\"\n            :error=\"!isValidSymbol ? 'Symbol is required' : ''\"\n          />\n        </div>\n        <div v-if=\"strategyName === 'ticket'\" class=\"md:w-2/3\">\n          <InputSelectVoteValidation\n            :validation=\"validation\"\n            :voting-strategies=\"form.strategies\"\n            @add=\"validation = $event\"\n          />\n\n          <BaseMessageBlock\n            v-if=\"!isValidTicket && showError\"\n            level=\"warning-red\"\n            class=\"mt-3\"\n          >\n            <i18n-t\n              keypath=\"ticketWithAnyOrBasicError\"\n              tag=\"span\"\n              scope=\"global\"\n            >\n              <template #article>\n                <BaseLink\n                  link=\"https://snapshot.mirror.xyz/-uSylOUP82hGAyWUlVn4lCg9ESzKX9QCvsUgvv-ng84\"\n                >\n                  {{ $t('learnMore') }}\n                </BaseLink>\n              </template>\n            </i18n-t>\n          </BaseMessageBlock>\n        </div>\n        <div v-if=\"strategyName === 'whitelist'\" class=\"md:w-2/3\">\n          <TuneTextareaArray\n            ref=\"whitelistRef\"\n            v-model=\"whitelist\"\n            label=\"Whitelisted addresses\"\n            :placeholder=\"`0x8C28Cf33d9Fd3D0293f963b1cd27e3FF422B425c\\n0xeF8305E140ac520225DAf050e2f71d5fBcC543e7`\"\n            :error=\"!isValidWhitelist ? 'Please add at least one address' : ''\"\n          />\n        </div>\n      </div>\n    </BaseBlock>\n    <div class=\"float-right mx-4 md:mx-0\">\n      <SetupButtonNext @click=\"nextStep\" />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SidebarSpacesSkeleton.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div class=\"mt-2 space-y-2 px-2\">\n    <div\n      v-for=\"n in 5\"\n      :key=\"n\"\n      class=\"h-[44px] w-[44px] animate-pulse rounded-full bg-skin-border\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SidebarUnreadIndicator.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{ space: string; hasUnseen: boolean }>();\n</script>\n\n<template>\n  <div\n    :class=\"[\n      $route.params.key === space\n        ? '!h-[20px] bg-skin-link'\n        : hasUnseen\n          ? 'bg-skin-text group-hover:h-[10px]'\n          : 'group-hover:h-[10px] '\n    ]\"\n    class=\"absolute left-[-4px] h-[8px] w-[8px] rounded-full transition-all duration-300 group-hover:bg-skin-link\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/SpaceBoostCardProposal.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace, Proposal } from '@/helpers/interfaces';\n\ndefineProps<{\n  proposal: Proposal;\n  space: ExtendedSpace;\n}>();\n</script>\n\n<template>\n  <TuneBlock class=\"!bg-[--border-color-faint]\">\n    <RouterLink :to=\"{ name: 'spaceProposal', params: { id: proposal.id } }\">\n      <h4 class=\"leading-5\">\n        {{ proposal.title }}\n      </h4>\n      <p class=\"line-clamp-2 mt-1 text-skin-text\">\n        {{ proposal.body }}\n      </p>\n    </RouterLink>\n    <div class=\"flex gap-3 items-center h-[20px] mt-[12px]\">\n      <LinkSpace\n        class=\"text-skin-text flex items-center\"\n        :space-id=\"proposal.space.id\"\n      >\n        <AvatarSpace :space=\"space\" size=\"20\" class=\"!text-skin-text\" />\n        <span class=\"ml-1 text-skin-text\" v-text=\"space.name\" />\n      </LinkSpace>\n\n      <BaseUser\n        :address=\"proposal.author\"\n        :space=\"space\"\n        text-class=\"text-skin-text\"\n      />\n    </div>\n  </TuneBlock>\n</template>\n"
  },
  {
    "path": "src/components/SpaceBoostDeposit.vue",
    "content": "<script setup lang=\"ts\">\nimport { formatUnits } from '@ethersproject/units';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { Token } from '@/helpers/alchemy';\nimport { SUPPORTED_NETWORKS } from '@/helpers/boost';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { isExcludedToken } from '@/helpers/boost/tokens';\n\nconst props = defineProps<{\n  formToken?: Token;\n  formNetwork: string;\n  formAmount: string;\n  tokens: Token[];\n  loadingBalances: boolean;\n  amountWithTokenFeeParsed: string;\n  formErrorMessages: Record<string, string>;\n  tokenFeePercent: string;\n  tokenFeeParsed: string | BigNumber;\n  ethFee: string;\n}>();\n\nconst emit = defineEmits([\n  'update:formNetwork',\n  'update:formAmount',\n  'update:formToken'\n]);\n\nconst { formatNumber, getNumberFormatter } = useIntl();\n\nconst tokensWithoutExcluded = computed(() => {\n  return props.tokens.filter(token => {\n    return !isExcludedToken(props.formNetwork, token.contractAddress);\n  });\n});\n\nconst amountWithTokenFeeFormatted = computed(\n  () =>\n    (props.formToken &&\n      formatUnits(props.amountWithTokenFeeParsed, props.formToken.decimals)) ||\n    '0'\n);\n\nconst tokenFeeFormatted = computed(() => {\n  return formatNumber(\n    Number(\n      formatUnits(props.tokenFeeParsed, props.formToken?.decimals ?? '18')\n    ),\n    getNumberFormatter({ maximumFractionDigits: 8 }).value\n  );\n});\n\nconst filteredNetworks = computed(() => {\n  return Object.values(networks)\n    .map((network: any) => {\n      return {\n        value: network.chainId.toString(),\n        name: network.name,\n        extras: {\n          icon: network.logo\n        },\n        testnet: network.testnet\n      };\n    })\n    .filter(network => SUPPORTED_NETWORKS.includes(network.value));\n});\n\nwatch(\n  tokensWithoutExcluded,\n  () => {\n    if (!props.formToken && tokensWithoutExcluded.value.length > 0) {\n      emit('update:formToken', tokensWithoutExcluded.value[0]);\n    }\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <TuneBlock>\n    <template #header>\n      <TuneBlockHeader\n        title=\"Deposit amount\"\n        sub-title=\"Select a token and specify the total amount for the reward pool.\"\n      />\n    </template>\n    <div class=\"flex flex-col md:flex-row gap-[12px]\">\n      <ListboxNetwork\n        :model-value=\"formNetwork\"\n        :networks=\"filteredNetworks\"\n        @update:model-value=\"$emit('update:formNetwork', $event)\"\n      />\n      <InputComboboxToken\n        :amount=\"formAmount\"\n        label=\"Amount\"\n        :selected-token=\"formToken\"\n        :network=\"formNetwork\"\n        :tokens=\"tokensWithoutExcluded\"\n        :loading=\"loadingBalances\"\n        :error=\"\n          formErrorMessages?.token ||\n          formErrorMessages?.amount ||\n          formErrorMessages?.balance\n        \"\n        @update:selected-token=\"$emit('update:formToken', $event)\"\n        @update:amount=\"$emit('update:formAmount', $event)\"\n      />\n    </div>\n    <TuneBlockFooter v-if=\"Number(tokenFeePercent) > 0 || Number(ethFee) > 0\">\n      <div v-if=\"Number(ethFee) > 0\" class=\"flex justify-between\">\n        <div class=\"flex items-center gap-1\">\n          ETH fee\n          <TuneIconHint\n            hint=\"This fee is required for additional gas costs on our end\"\n          />\n        </div>\n        <div class=\"text-skin-heading\">\n          {{ ethFee }}\n          ETH\n        </div>\n      </div>\n      <div v-if=\"Number(tokenFeePercent) > 0\" class=\"flex justify-between\">\n        <div class=\"flex items-center gap-1\">\n          Token fee\n          <TuneIconHint\n            hint=\"This is the fee we charge for the boost service\"\n          />\n        </div>\n        <div class=\"text-skin-heading\">\n          {{ tokenFeeFormatted }}\n          {{ formToken?.symbol }}\n          ({{ tokenFeePercent }}%)\n        </div>\n      </div>\n      <div\n        class=\"flex justify-between mt-2 border-t pt-2 border-[--border-color-soft]\"\n      >\n        Final cost\n        <div class=\"text-skin-heading\">\n          <span v-if=\"Number(tokenFeePercent) > 0\">\n            {{ amountWithTokenFeeFormatted }}\n            {{ formToken?.symbol }}\n          </span>\n          <span v-if=\"Number(ethFee) > 0\">\n            +\n\n            {{ ethFee }}\n            ETH\n          </span>\n        </div>\n      </div>\n    </TuneBlockFooter>\n  </TuneBlock>\n</template>\n"
  },
  {
    "path": "src/components/SpaceBreadcrumbs.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace, Proposal } from '@/helpers/interfaces';\nimport { shorten } from '@/helpers/utils';\n\nconst props = defineProps<{ space: ExtendedSpace; proposal?: Proposal }>();\n\nconst route = useRoute();\nconst { domain } = useApp();\n\nconst pages = computed(() => {\n  let pages: any = [];\n  const spaceRoute = `/${props.space.id}`;\n  const basePages = [\n    { name: domain ? 'Home' : props.space.name, to: spaceRoute, current: false }\n  ];\n\n  if (route.name === 'spaceProposal') {\n    const id = route.params.id as string;\n    pages = [\n      ...basePages,\n      {\n        id: 'proposal-title',\n        name: props.proposal?.title,\n        to: `${spaceRoute}/proposal/${id}`,\n        current: true\n      }\n    ];\n  }\n\n  if (route.name === 'spaceDelegate') {\n    const delegate = route.params.address as string;\n    pages = [\n      ...basePages,\n      {\n        name: 'Delegates',\n        to: `${spaceRoute}/delegates`,\n        current: false\n      },\n      {\n        name: shorten(delegate),\n        to: `${spaceRoute}/delegate/${delegate}`,\n        current: true\n      }\n    ];\n  }\n\n  if (route.name === 'spaceBoost') {\n    const id = route.params.proposalId as string;\n    pages = [\n      ...basePages,\n      {\n        name: props.proposal?.title,\n        to: `${spaceRoute}/proposal/${id}`,\n        current: false\n      },\n      { name: 'New boost', current: true }\n    ];\n  }\n\n  pages = pages.filter((page: any) => page.name);\n\n  return pages;\n});\n</script>\n\n<template>\n  <BaseBreadcrumbs\n    :pages=\"pages\"\n    class=\"px-[20px] md:px-4 -mt-1 pb-[16px] lg:pb-[20px]\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/SpaceCreateContent.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  preview: boolean;\n  bodyLimit: number;\n}>();\n\nconst { formatNumber } = useIntl();\nconst spaceType = computed(() => (props.space.turbo ? 'turbo' : 'default'));\nconst { form, formDraft, validationErrors } = useFormSpaceProposal({\n  spaceType: spaceType.value\n});\n\nconst imageDragging = ref(false);\nconst textAreaEl = ref<HTMLTextAreaElement | null>(null);\n\nconst inputName = computed({\n  get: () => form.value.name,\n  set: value => {\n    form.value.name = value;\n    formDraft.value.name = value;\n  }\n});\n\nconst inputBody = computed({\n  get: () => form.value.body,\n  set: value => {\n    form.value.body = value;\n    formDraft.value.body = value;\n    formDraft.value.isBodySet = true;\n  }\n});\n\nconst injectImageToBody = image => {\n  const cursorPosition = textAreaEl.value?.selectionStart;\n  const currentBody = textAreaEl.value?.value;\n  const currentBodyWithImage = `${currentBody?.substring(\n    0,\n    cursorPosition\n  )} \\n![${image.name}](${image.url})\n    ${currentBody?.substring(cursorPosition as number)}`;\n\n  form.value.body = currentBodyWithImage;\n};\n\nconst { upload, imageUploadError, isUploadingImage } = useImageUpload();\n\nconst handlePaste = e => {\n  for (const item of e.clipboardData.items) {\n    if (item.kind === 'file' && item.type.startsWith('image/')) {\n      const file = item.getAsFile();\n      upload(new File([file], 'image', { type: file.type }), injectImageToBody);\n    }\n  }\n};\n\nconst handleDrop = e => {\n  for (const item of e.dataTransfer.files) {\n    if (item.type.startsWith('image/')) {\n      upload(item, injectImageToBody);\n    }\n  }\n};\n</script>\n\n<template>\n  <div class=\"mb-5 px-4 md:px-0\">\n    <div class=\"flex flex-col space-y-3\">\n      <BlockLink v-if=\"space?.guidelines\" :link=\"space.guidelines\">\n        <template #title>\n          {{ $t('settings.proposal.guidelines.title') }}\n        </template>\n      </BlockLink>\n\n      <h1\n        v-if=\"preview\"\n        class=\"w-full break-all\"\n        v-text=\"form.name || $t('create.untitled')\"\n      />\n      <TuneInput\n        v-else\n        v-model=\"inputName\"\n        :label=\"$t('create.proposalTitle')\"\n        :error=\"validationErrors?.name\"\n        focus-on-mount\n        data-testid=\"input-proposal-title\"\n      />\n\n      <div v-if=\"!preview\">\n        <div class=\"flex justify-between\">\n          <LabelInput>\n            {{ $t('create.proposalDescription') }}\n          </LabelInput>\n          <div class=\"text-xs\">\n            {{ formatNumber(form.body.length) }} /\n            {{ formatNumber(bodyLimit) }}\n          </div>\n        </div>\n        <div\n          @drop.prevent=\"handleDrop\"\n          @dragover=\"imageDragging = true\"\n          @dragleave=\"imageDragging = false\"\n        >\n          <div\n            :class=\"[\n              'peer min-h-[240px] overflow-hidden rounded-t-xl border focus-within:border-skin-text',\n              { 'tune-error-border': validationErrors?.body }\n            ]\"\n          >\n            <textarea\n              ref=\"textAreaEl\"\n              v-model.trim=\"inputBody\"\n              class=\"s-input mt-0 h-full min-h-[240px] w-full !rounded-xl border-none pt-0 text-base\"\n              data-testid=\"input-proposal-body\"\n              @paste=\"handlePaste\"\n            />\n          </div>\n\n          <label\n            :class=\"[\n              'relative flex items-center justify-between rounded-b-xl border border-t-0 px-2 py-1 peer-focus-within:border-skin-text',\n              { 'tune-error-border': validationErrors?.body }\n            ]\"\n          >\n            <input\n              accept=\"image/jpg, image/jpeg, image/png\"\n              type=\"file\"\n              class=\"absolute bottom-0 left-0 right-0 top-0 ml-0 w-full p-[5px] opacity-0\"\n              @change=\"\n                e =>\n                  upload(\n                    (e.target as HTMLInputElement)?.files?.[0],\n                    injectImageToBody\n                  )\n              \"\n            />\n\n            <span class=\"pointer-events-none relative pl-1 text-sm\">\n              <span v-if=\"isUploadingImage\" class=\"flex\">\n                <LoadingSpinner small class=\"-mt-[2px] mr-2\" />\n                {{ $t('create.uploading') }}\n              </span>\n              <span v-else-if=\"imageUploadError !== ''\">\n                {{ imageUploadError }}</span\n              >\n              <span v-else>\n                {{ $t('create.uploadImageExplainer') }}\n              </span>\n            </span>\n            <BaseLink\n              v-tippy=\"{ content: $t('create.markdown') }\"\n              class=\"relative inline\"\n              link=\"https://docs.github.com/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax\"\n              hide-external-icon\n            >\n              <BaseIcon name=\"markdown\" class=\"text-skin-text\" />\n            </BaseLink>\n          </label>\n        </div>\n        <TuneErrorInput\n          v-if=\"validationErrors?.body\"\n          :error=\"validationErrors?.body\"\n        />\n      </div>\n\n      <div v-if=\"form.body && preview\" class=\"mb-4\">\n        <BaseMarkdown :body=\"form.body\" />\n      </div>\n\n      <TuneInputUrl\n        v-if=\"!preview\"\n        v-model.trim=\"form.discussion\"\n        placeholder=\"https://forum.balancer.fi/proposal\"\n        :label=\"$t('create.discussion')\"\n        :error=\"validationErrors?.discussion\"\n        data-testid=\"input-proposal-discussion\"\n      />\n      <BlockLink\n        v-if=\"form.discussion\"\n        :link=\"form.discussion\"\n        :safe-link-preview=\"false\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceCreateLegacyOsnap.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\ndefineProps<{\n  space: ExtendedSpace | undefined;\n  legacyOsnap: { selection: boolean; valid: boolean };\n}>();\n\ndefineEmits<{\n  (event: 'legacyOsnapToggle'): void;\n}>();\n</script>\n\n<template>\n  <div class=\"mb-4\">\n    <div v-if=\"space?.voting?.type && space.voting.type !== 'basic'\">\n      <h6>Where is oSnap?</h6>\n      <p class=\"mb-3\">\n        oSnap is currently disabled because your space's voting settings\n        disallow the basic voting type which is a requirement for oSnap to work\n        properly.\n      </p>\n      <p>\n        Have your admin visit your\n        <a :href=\"`#/${space.id}/settings`\">settings page</a> under Voting ->\n        Type, and make sure either \"Any\" or \"Basic Voting\" is selected. This\n        will allow you to create oSnap proposals.\n      </p>\n    </div>\n    <div v-else-if=\"legacyOsnap.valid\">\n      <h6>oSnap Proposal</h6>\n      <p>\n        Are you planning for this proposal to initiate a transaction that your\n        organizations Safe will execute if approved? (Remember, oSnap enables\n        trustless and permissionless execution)\n      </p>\n      <br />\n      <input\n        id=\"toggleOsnap\"\n        type=\"checkbox\"\n        :checked=\"legacyOsnap.selection\"\n        @change=\"$emit('legacyOsnapToggle')\"\n      />\n      <label for=\"toggleOsnap\">\n        Yes, use oSnap for transactions (this will restrict voting type to\n        Basic).\n      </label>\n    </div>\n    <div v-else>\n      <div\n        class=\"mb-4 border-y border-skin-border bg-skin-block-bg text-base md:rounded-xl md:border p-4\"\n      >\n        <h6>oSnap Error</h6>\n        <p>\n          Something is wrong with your SafeSnap settings and oSnap will not work\n          correctly. Have an admin check SafeSnap configuration or\n          <a\n            href=\"https://docs.uma.xyz/developers/osnap/osnap-configuration-parameters-1\"\n            target=\"_blank\"\n            >migrate to the dedicated oSnap plugin.</a\n          >\n        </p>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceCreateOsnap.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  shouldUseOsnap: boolean;\n  legacyOsnap: { enabled: boolean };\n}>();\n\ndefineEmits<{\n  (event: 'toggleShouldUseOsnap'): void;\n}>();\n</script>\n\n<template>\n  <div\n    class=\"mb-4 border-y border-skin-border bg-skin-block-bg text-base md:rounded-xl md:border p-4\"\n  >\n    <div v-if=\"legacyOsnap.enabled\">\n      <h6>oSnap Warning</h6>\n      <p class=\"mb-3\">\n        You currently have both the oSnap plugin and the SafeSnap plugin\n        installed in your space. You can continue using oSnap with SafeSnap\n        without any changes to your space. If you would like to use the oSnap\n        plugin instead, please see the\n        <a\n          target=\"_blank\"\n          href=\"https://docs.uma.xyz/developers/osnap/osnap-configuration-parameters-1\"\n          >oSnap plugin migration docs.</a\n        >\n        Otherwise please remove the oSnap plugin from your space settings for\n        the best experience with SafeSnap.\n      </p>\n    </div>\n    <div v-else>\n      <h6>oSnap Proposal</h6>\n      <p class=\"mb-4\">\n        Are you planning for this proposal to initiate a transaction that your\n        organizations Safe will execute if approved? (Remember, oSnap enables\n        trustless and permissionless execution)\n      </p>\n      <TuneSwitch\n        :model-value=\"shouldUseOsnap\"\n        label=\"Yes, use oSnap for transactions (this will restrict voting type to Basic).\"\n        :disabled=\"legacyOsnap.enabled\"\n        @update:model-value=\"$emit('toggleShouldUseOsnap')\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceCreatePlugins.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: any;\n  modelValue: any;\n}>();\n\nconst { getPluginComponents } = usePlugins();\nconst components = getPluginComponents(\n  'Create',\n  Object.keys(props.space.plugins)\n);\n\nconst emit = defineEmits(['update:modelValue']);\nconst update = data => {\n  const allConfig = props.modelValue;\n  allConfig[data.key] = data.form;\n  emit('update:modelValue', allConfig);\n};\n</script>\n\n<template>\n  <div class=\"space-y-3\">\n    <component\n      :is=\"component\"\n      v-for=\"(component, key) in components\"\n      :key=\"key\"\n      v-bind=\"props\"\n      @update=\"update\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceCreateVoting.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport draggable from 'vuedraggable';\nimport SpaceCreateLegacyOsnap from './SpaceCreateLegacyOsnap.vue';\nimport SpaceCreateOsnap from './SpaceCreateOsnap.vue';\nimport { BOOST_ENABLED_VOTING_TYPES } from '@/helpers/constants';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  dateStart: number;\n  dateEnd: number;\n  hasOsnapPlugin: boolean;\n  shouldUseOsnap: boolean;\n  legacyOsnap: { enabled: boolean; selection: boolean };\n  isEditing: boolean;\n}>();\n\nconst {\n  form,\n  sourceProposalLoaded,\n  userSelectedDateStart,\n  userSelectedDateEnd\n} = useFormSpaceProposal();\n\nconst disableChoiceEdit = computed(() => form.value.type === 'basic');\n\nfunction addChoices(num) {\n  for (let i = 1; i <= num; i++) {\n    form.value.choices.push({ key: form.value.choices.length, text: '' });\n  }\n}\n\nfunction setDateStart(ts) {\n  form.value.start = ts;\n  userSelectedDateStart.value = true;\n}\n\nfunction setDateEnd(ts) {\n  form.value.end = ts;\n  userSelectedDateEnd.value = true;\n}\n\nwatch(\n  () => form.value.type,\n  (newType, oldType) => {\n    if (newType !== 'basic' && oldType === 'basic') {\n      form.value.choices = [\n        { key: 0, text: '' },\n        { key: 1, text: '' }\n      ];\n    }\n    if (form.value.type === 'basic') {\n      form.value.choices = [\n        { key: 1, text: 'For' },\n        { key: 2, text: 'Against' },\n        { key: 3, text: 'Abstain' }\n      ];\n    }\n  },\n  { immediate: true }\n);\n\nwatch(\n  () => props.legacyOsnap.selection,\n  () => {\n    // If using osnap, we can only allow basic voting, for, against, abstain\n    if (props.legacyOsnap.selection) {\n      form.value.type = 'basic';\n    } else if (props.space?.voting?.type) {\n      form.value.type = props.space.voting.type;\n    }\n  }\n);\n\nwatch(\n  () => props.shouldUseOsnap,\n  () => {\n    if (props.shouldUseOsnap) {\n      form.value.type = 'basic';\n    }\n  }\n);\n\nconst { getSnapshot } = useSnapshot();\n\nonMounted(async () => {\n  // Initialize the start date to current\n  if (!sourceProposalLoaded.value && !userSelectedDateStart.value)\n    form.value.start = Number((Date.now() / 1e3).toFixed());\n  // Initialize the proposal type if set in space\n  if (props.space?.voting?.type) form.value.type = props.space.voting.type;\n  form.value.snapshot = await getSnapshot(props.space.network);\n});\n\ndefineEmits<{\n  (event: 'legacyOsnapToggle'): void;\n  (event: 'toggleShouldUseOsnap'): void;\n}>();\n</script>\n\n<template>\n  <SpaceCreateLegacyOsnap\n    v-if=\"legacyOsnap.enabled\"\n    :space=\"space\"\n    :legacy-osnap=\"legacyOsnap\"\n    @legacy-osnap-toggle=\"$emit('legacyOsnapToggle')\"\n  />\n  <SpaceCreateOsnap\n    v-if=\"hasOsnapPlugin\"\n    :should-use-osnap=\"shouldUseOsnap\"\n    :legacy-osnap=\"legacyOsnap\"\n    @toggle-should-use-osnap=\"$emit('toggleShouldUseOsnap')\"\n  />\n  <div class=\"mb-5 space-y-4\">\n    <BaseBlock :title=\"$t('create.voting')\">\n      <InputSelectVoteType\n        :type=\"space.voting?.type || form.type\"\n        :disabled=\"\n          !!space.voting?.type || legacyOsnap.selection || shouldUseOsnap\n        \"\n        @update:type=\"value => (form.type = value)\"\n      />\n      <template v-if=\"space.boost.enabled\">\n        <BaseMessage\n          v-if=\"!BOOST_ENABLED_VOTING_TYPES.includes(form.type)\"\n          level=\"info\"\n          class=\"mt-2 border bg-[--border-color-subtle] p-3 rounded-xl\"\n        >\n          Note that Boost is not available for this voting type.\n        </BaseMessage>\n      </template>\n\n      <h4 class=\"mb-1 mt-3\" v-text=\"$t('create.choices')\" />\n      <div class=\"flex\">\n        <div class=\"w-full overflow-hidden\">\n          <draggable\n            v-model=\"form.choices\"\n            v-bind=\"{ animation: 200 }\"\n            :disabled=\"disableChoiceEdit\"\n            item-key=\"id\"\n            handle=\".drag-handle\"\n            class=\"space-y-2\"\n          >\n            >\n            <template #item=\"{ element, index }\">\n              <UiInput\n                v-model=\"element.text\"\n                maxlength=\"32\"\n                :disabled=\"disableChoiceEdit\"\n                :placeholder=\"index > 0 ? $t('optional') : ''\"\n                class=\"group\"\n                :focus-on-mount=\"index === 0\"\n                :data-testid=\"`input-proposal-choice-${index}`\"\n              >\n                <template #label>\n                  <div\n                    class=\"drag-handle flex cursor-grab items-center active:cursor-grabbing\"\n                    :class=\"{\n                      'cursor-not-allowed active:cursor-not-allowed':\n                        disableChoiceEdit\n                    }\"\n                  >\n                    <BaseIcon name=\"draggable\" size=\"16\" class=\"mr-[12px]\" />\n                    {{ $tc('create.choice', [index + 1]) }}\n                  </div>\n                </template>\n                <template #info>\n                  <span\n                    class=\"hidden text-xs text-skin-text whitespace-nowrap group-focus-within:block\"\n                  >\n                    {{ `${element.text.length}/32` }}\n                  </span>\n                </template>\n              </UiInput>\n            </template>\n          </draggable>\n        </div>\n        <div v-if=\"!disableChoiceEdit\" class=\"ml-2 flex w-[48px] items-end\">\n          <BaseButtonRound\n            v-if=\"!disableChoiceEdit\"\n            size=\"42px\"\n            @click=\"addChoices(1)\"\n          >\n            <i-ho-plus-sm class=\"text-skin-link\" />\n          </BaseButtonRound>\n        </div>\n      </div>\n    </BaseBlock>\n\n    <BaseBlock\n      :title=\"$t('create.period')\"\n      :information=\"$t('create.votingPeriodExplainer')\"\n    >\n      <div class=\"space-y-2 md:flex md:space-x-3 md:space-y-0\">\n        <SpaceCreateVotingDateStart\n          :delay=\"space.voting?.delay\"\n          :date=\"dateStart\"\n          :is-editing=\"isEditing\"\n          @select=\"value => setDateStart(value)\"\n        />\n\n        <SpaceCreateVotingDateEnd\n          :period=\"space.voting?.period\"\n          :date=\"dateEnd\"\n          :is-editing=\"isEditing\"\n          @select=\"value => setDateEnd(value)\"\n        />\n      </div>\n    </BaseBlock>\n\n    <BaseBlock v-if=\"$route.query.snapshot\" :title=\"$t('snapshot')\">\n      <UiInput\n        v-model=\"form.snapshot\"\n        :number=\"true\"\n        :placeholder=\"$t('create.snapshotBlock')\"\n      >\n        <template #label>\n          {{ $t('snapshot') }}\n        </template>\n      </UiInput>\n    </BaseBlock>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceCreateVotingDateEnd.vue",
    "content": "<script setup lang=\"ts\">\nconst { d } = useI18n();\n\nconst props = withDefaults(\n  defineProps<{\n    period?: number | null;\n    isEditing?: boolean;\n    date: number;\n  }>(),\n  {\n    period: 0\n  }\n);\n\nconst dateString = computed(() => d(props.date * 1e3, 'short', 'en-US'));\nconst isDisabled = computed(() => !!props.period || props.isEditing);\n\nconst emit = defineEmits(['select']);\n</script>\n\n<template>\n  <InputDate\n    type=\"end\"\n    :title=\"$t(`create.end`)\"\n    :disabled=\"isDisabled\"\n    :date=\"date\"\n    :date-string=\"dateString\"\n    :tooltip=\"!!period && !isEditing ? $t('create.periodEnforced') : null\"\n    @update:date=\"emit('select', $event)\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/SpaceCreateVotingDateStart.vue",
    "content": "<script setup lang=\"ts\">\nconst { t, d } = useI18n();\n\nconst props = withDefaults(\n  defineProps<{\n    delay?: number | null;\n    isEditing?: boolean;\n    date: number;\n  }>(),\n  {\n    delay: 0\n  }\n);\n\nconst dateString = computed(() =>\n  Math.round(props.date / 10) ===\n  Math.round(Number((Date.now() / 1e3).toFixed()) / 10)\n    ? t('create.now')\n    : d(props.date * 1e3, 'short', 'en-US')\n);\nconst isDisabled = computed(() => !!props.delay || props.isEditing);\n\nconst emit = defineEmits(['select']);\n</script>\n\n<template>\n  <InputDate\n    type=\"start\"\n    :title=\"$t(`create.start`)\"\n    :disabled=\"isDisabled\"\n    :date=\"date\"\n    :date-string=\"dateString\"\n    :tooltip=\"!!delay && !isEditing ? $t('create.delayEnforced') : null\"\n    @update:date=\"emit('select', $event)\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/SpaceCreateWarnings.vue",
    "content": "<script setup lang=\"ts\">\nimport { SNAPSHOT_HELP_LINK } from '@/helpers/constants';\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  validationFailed: boolean;\n  isValidAuthor: boolean;\n  isValidSpace: boolean;\n  validationName: string;\n  containsShortUrl: boolean;\n}>();\n\nconst { web3, web3Account } = useWeb3();\nconst { isGnosisAndNotSpaceNetwork } = useGnosis(props.space);\nconst { errorFetchingSnapshot } = useSnapshot();\n\nconst minScore = computed(\n  () =>\n    props.space?.validation?.params?.minScore ||\n    props.space?.filters?.minScore ||\n    0\n);\n\nconst strategySymbolsString = computed(() => {\n  const strategies = props.space.validation?.params?.strategies\n    ? props.space.validation.params.strategies\n    : props.space.strategies;\n\n  let symbols = strategies\n    ?.map(strategy => strategy.params.symbol)\n    .filter(symbol => symbol);\n\n  if (symbols.length === 0) return '';\n\n  symbols = symbols.map(symbol => `$${symbol}`);\n\n  if (symbols.length === 1) return `${symbols[0]}`;\n\n  return `(${symbols.join(', ')})`;\n});\n</script>\n\n<template>\n  <div class=\"mb-4 space-y-2\">\n    <MessageWarningHibernated v-if=\"space.hibernated\" :space=\"space\" />\n\n    <BaseMessageBlock v-else-if=\"!isValidSpace\" level=\"warning\">\n      Proposal creation is blocked due to invalid space settings. Please contact\n      a space admin or if you are an admin head over to the settings page and\n      save them again.\n    </BaseMessageBlock>\n\n    <MessageWarningGnosisNetwork\n      v-else-if=\"isGnosisAndNotSpaceNetwork\"\n      :space=\"space\"\n      action=\"create\"\n      is-responsive\n    />\n\n    <BaseMessageBlock\n      v-else-if=\"errorFetchingSnapshot\"\n      level=\"warning\"\n      is-responsive\n    >\n      {{ $t('create.errorGettingSnapshot') }}\n      <BaseLink :link=\"SNAPSHOT_HELP_LINK\">\n        {{ $t('learnMore') }}\n      </BaseLink>\n    </BaseMessageBlock>\n\n    <BaseMessageBlock\n      v-else-if=\"\n        !web3Account &&\n        !web3.authLoading &&\n        (props.space.validation.name || space.filters.onlyMembers)\n      \"\n      level=\"warning\"\n      is-responsive\n    >\n      {{ $t('proposalValidation.notConnectedMessage') }}\n      <div>\n        <BaseLink :link=\"{ name: 'spaceAbout', params: { key: space.id } }\">{{\n          $t('learnMore')\n        }}</BaseLink>\n      </div>\n    </BaseMessageBlock>\n\n    <!-- Shows when wallet is connected and executing validation fails (e.g.\n      due to misconfigured strategy)  -->\n\n    <BaseMessageBlock\n      v-else-if=\"validationFailed\"\n      level=\"warning\"\n      is-responsive\n    >\n      {{ $t('proposalValidation.executionError') }}\n    </BaseMessageBlock>\n\n    <!-- Shows when wallet is connected and doesn't pass validation -->\n    <BaseMessageBlock\n      v-else-if=\"isValidAuthor === false && space?.filters.onlyMembers\"\n      level=\"warning\"\n    >\n      <span>\n        {{ $t('proposalValidation.onlyMemberMessage') }}\n      </span>\n    </BaseMessageBlock>\n    <MessageWarningValidation\n      v-else-if=\"isValidAuthor === false && validationName\"\n      context=\"proposal\"\n      :space-id=\"space.id\"\n      :validation-name=\"validationName\"\n      :validation-params=\"space.validation?.params || {}\"\n      :min-score=\"minScore\"\n      :symbol=\"strategySymbolsString || space.symbol\"\n    />\n\n    <BaseMessageBlock v-else-if=\"containsShortUrl\" level=\"warning\">\n      {{ $t('warningShortUrl') }}\n    </BaseMessageBlock>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceDelegateEdit.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport schemas from '@snapshot-labs/snapshot.js/src/schemas';\nimport { validateForm } from '@/helpers/validation';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  address: string;\n  statement: {\n    about: string;\n    statement: string;\n    discourse: string;\n    network: string;\n    status: string;\n  };\n  edited: boolean;\n  saving: boolean;\n}>();\n\nconst emit = defineEmits(['save', 'update:about', 'update:statement']);\n\nconst aboutRef = ref<any>(null);\n\nconst validationErrors = computed(() => {\n  return validateForm(schemas.statement, props.statement);\n});\n\nconst isValid = computed(() => {\n  return Object.values(validationErrors.value).length === 0;\n});\n\nasync function handleClickSave() {\n  if (!isValid.value) {\n    aboutRef.value?.forceShowError();\n    return;\n  }\n  emit('save');\n}\n</script>\n\n<template>\n  <TheLayout>\n    <template #content-left>\n      <div class=\"space-y-3 px-4 md:px-0\">\n        <TuneTextarea\n          ref=\"aboutRef\"\n          :model-value=\"statement.about\"\n          label=\"About\"\n          placeholder=\"Tell us about yourself\"\n          :max-length=\"schemas.statement.properties.about.maxLength\"\n          :error=\"validationErrors.about\"\n          class=\"min-h-[100px] text-skin-link\"\n          @update:model-value=\"emit('update:about', $event)\"\n        />\n        <TuneTextarea\n          :model-value=\"statement.statement\"\n          label=\"Statement\"\n          :max-length=\"schemas.statement.properties.statement.maxLength\"\n          :error=\"validationErrors.statement\"\n          placeholder=\"Why should people vote for you?\"\n          class=\"min-h-[220px] text-skin-link sm:min-h-[300px] lg:min-h-[400px]\"\n          @update:model-value=\"emit('update:statement', $event)\"\n        />\n      </div>\n    </template>\n\n    <template #sidebar-right>\n      <BaseBlock\n        class=\"mt-4 hidden p-4 md:block md:p-3 lg:sticky lg:top-[110px] lg:mt-0 lg:w-[320px]\"\n        slim\n      >\n        <div class=\"font-semibold text-skin-heading\">Save changes</div>\n\n        You can always come back and edit your profile at any time.\n\n        <TheActionbar break-point=\"md\">\n          <div class=\"px-4 md:px-0 py-[16px] md:pb-0\">\n            <TuneButton\n              class=\"w-full\"\n              :loading=\"saving\"\n              :disabled=\"!edited\"\n              primary\n              @click=\"handleClickSave\"\n            >\n              Save changes\n            </TuneButton>\n          </div>\n        </TheActionbar>\n      </BaseBlock>\n    </template>\n  </TheLayout>\n</template>\n"
  },
  {
    "path": "src/components/SpaceDelegatesAccount.vue",
    "content": "<script setup lang=\"ts\">\nimport { useStorage } from '@vueuse/core';\n\nconst { web3Account } = useWeb3();\n\nconst showOnboarding = useStorage('snapshot.showDelegatesOnboarding', {});\n\nconst showOnboardingDelegates = computed({\n  get: () => showOnboarding.value[web3Account.value],\n  set: value => {\n    showOnboarding.value[web3Account.value] = value;\n  }\n});\n</script>\n\n<template>\n  <div\n    ref=\"loggedAvatar\"\n    class=\"relative\"\n    @click=\"showOnboardingDelegates = false\"\n  >\n    <div\n      v-if=\"showOnboardingDelegates !== false\"\n      class=\"pointer-events-none absolute bottom-[46px] left-[22px] hidden md:flex\"\n    >\n      <i-s-line-arrow class=\"text-skin-text opacity-40\" />\n      <div\n        class=\"absolute bottom-[30px] left-[42px] w-[102px] text-xs leading-4 opacity-60\"\n      >\n        view and edit your delegator profile\n      </div>\n    </div>\n    <BaseButtonRound>\n      <AvatarUser :address=\"web3Account\" size=\"20\" class=\"cursor-pointer\" />\n    </BaseButtonRound>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceDelegatesCard.vue",
    "content": "<script setup lang=\"ts\">\nimport { explorerUrl } from '@/helpers/utils';\nimport {\n  DelegateWithPercent,\n  Profile,\n  ExtendedSpace\n} from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  delegate: DelegateWithPercent;\n  profiles: Record<string, Profile>;\n  space: ExtendedSpace;\n  stats?: {\n    votes: number;\n    proposals: number;\n  };\n  about?: string;\n}>();\n\nconst emit = defineEmits(['clickDelegate', 'clickUser']);\n\nconst { getUsername } = useUsername();\nconst { formatCompactNumber, formatNumber } = useIntl();\nconst { formatPercentageNumber } = useStatement();\nconst router = useRouter();\n\nconst dropdownItems = computed(() => [\n  {\n    text: 'View profile',\n    action: 'viewProfile'\n  },\n  {\n    text: 'See explorer',\n    action: 'seeExplorer'\n  }\n]);\n\nfunction handleDropdownAction(action: string) {\n  if (action === 'viewProfile') {\n    router.push({\n      name: 'profileActivity',\n      params: {\n        address: props.delegate.id\n      }\n    });\n  }\n  if (action === 'seeExplorer') {\n    window.open(\n      explorerUrl(props.space.network, props.delegate.id),\n      '_blank',\n      'noopener,noreferrer'\n    );\n  }\n}\n</script>\n\n<template>\n  <button\n    type=\"button\"\n    class=\"flex h-full w-full flex-col justify-between rounded-xl border border-skin-border px-3 pb-3 pt-[12px] md:px-3 md:pb-3 md:pt-[12px]\"\n    @click=\"emit('clickUser')\"\n  >\n    <div class=\"flex w-full\">\n      <div class=\"flex items-center text-left gap-3 flex-auto min-w-0\">\n        <AvatarUser :address=\"delegate.id\" size=\"40\" class=\"shrink-0\" />\n        <div class=\"flex flex-col truncate grow\">\n          <div class=\"flex truncate gap-1\">\n            <div class=\"font-semibold text-skin-heading truncate flex-auto\">\n              {{ getUsername(delegate.id, profiles[delegate.id]) }}\n            </div>\n            <BaseMenu\n              :items=\"dropdownItems\"\n              @select=\"handleDropdownAction($event)\"\n              @click.stop\n            >\n              <template #button>\n                <BaseButtonIcon class=\"-mr-[6px] !h-[24px]\">\n                  <i-ho-dots-horizontal class=\"text-[17px]\" />\n                </BaseButtonIcon>\n              </template>\n              <template #item=\"{ item }\">\n                <div class=\"w-[170px] text-skin-link\">\n                  <span class=\"flex items-center gap-1\">\n                    {{ item.text }}\n                    <i-ho-external-link\n                      v-if=\"item.action === 'seeExplorer'\"\n                      class=\"text-sm\"\n                    />\n                  </span>\n                </div>\n              </template>\n            </BaseMenu>\n          </div>\n          <div class=\"flex gap-x-[6px] flex-wrap\">\n            <div\n              v-tippy=\"{\n                content: `${formatNumber(\n                  Number(delegate.delegatedVotes)\n                )} (${formatPercentageNumber(delegate.votesPercentage)})`\n              }\"\n              class=\"cursor-help text-skin-text\"\n            >\n              {{ formatCompactNumber(Number(delegate.delegatedVotes)) }}\n              {{ space.symbol }}\n            </div>\n            ·\n            <div\n              v-tippy=\"{\n                content: formatPercentageNumber(delegate.delegatorsPercentage)\n              }\"\n              class=\"cursor-help\"\n            >\n              {{\n                formatCompactNumber(\n                  Number(delegate.tokenHoldersRepresentedAmount)\n                )\n              }}\n              delegators\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"mt-3 h-[48px] cursor-pointer\">\n      <template v-if=\"about\">\n        <span class=\"line-clamp-2 break-words text-left\">\n          {{ about }}\n        </span>\n      </template>\n    </div>\n\n    <div class=\"mt-3 flex gap-[6px]\">\n      <div>\n        {{ formatCompactNumber(stats?.votes || 0) }}\n        votes\n      </div>\n      ·\n      <div>\n        {{ formatCompactNumber(stats?.proposals || 0) }}\n        proposals\n      </div>\n    </div>\n\n    <TuneButton\n      class=\"mt-3 w-full text-skin-link\"\n      @click.stop=\"emit('clickDelegate')\"\n    >\n      Delegate\n    </TuneButton>\n  </button>\n</template>\n"
  },
  {
    "path": "src/components/SpaceDelegatesDelegateModal.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport { watchDebounced } from '@vueuse/core';\nimport { validateForm } from '@/helpers/validation';\nimport { clone, sleep } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst props = defineProps<{\n  open: boolean;\n  space: ExtendedSpace;\n  address: string;\n}>();\n\nconst emit = defineEmits(['close', 'reload']);\n\nconst {\n  createPendingTransaction,\n  updatePendingTransaction,\n  removePendingTransaction\n} = useTxStatus();\nconst { notify } = useFlashNotification();\nconst { t } = useI18n();\nconst { resolveName } = useResolveName();\nconst { setDelegates, loadDelegateBalance, isLoadingDelegateBalance } =\n  useDelegates(props.space);\nconst { formatCompactNumber } = useIntl();\nconst { web3Account } = useWeb3();\n\nconst form = ref({\n  to: ''\n});\nconst resolvedAddress = ref('');\nconst isResolvingName = ref(false);\nconst addressRef = ref();\nconst isAwaitingSignature = ref(false);\nconst accountBalance = ref('');\n\nconst definition = computed(() => {\n  return {\n    type: 'object',\n    properties: {\n      to: {\n        type: 'string',\n        format: 'address',\n        title: 'Delegate to',\n        description: 'The address, ENS or Lens of who you want to delegate to',\n        examples: ['Enter: Address, ENS or Lens']\n      }\n    },\n    required: ['to'],\n    additionalProperties: false\n  };\n});\n\nconst validationErrors = computed(() => {\n  return validateForm(\n    definition.value || {},\n    clone({\n      to: resolvedAddress.value\n    })\n  );\n});\n\nconst isValid = computed(() => {\n  return Object.values(validationErrors.value).length === 0;\n});\n\nasync function handleConfirm() {\n  if (!isValid.value) {\n    addressRef?.value?.forceShowError();\n    return;\n  }\n\n  const txPendingId = createPendingTransaction();\n  try {\n    isAwaitingSignature.value = true;\n    const tx = await setDelegates([resolvedAddress.value]);\n    isAwaitingSignature.value = false;\n    updatePendingTransaction(txPendingId, { hash: tx.hash });\n    emit('close');\n    notify(t('notify.transactionSent'));\n    const receipt = await tx.wait();\n    console.log('Receipt', receipt);\n    await sleep(3e3);\n    notify(t('notify.delegationAdded'));\n    removePendingTransaction(txPendingId);\n    emit('reload');\n  } catch (e) {\n    console.log(e);\n    isAwaitingSignature.value = false;\n    notify(['red', 'An error occurred while building the transaction.']);\n    removePendingTransaction(txPendingId);\n  }\n}\n\nasync function resolveToAddress(value: string) {\n  if (value) {\n    isResolvingName.value = true;\n    resolvedAddress.value = '';\n    resolvedAddress.value = (await resolveName(value)) || '';\n    isResolvingName.value = false;\n  }\n}\n\nasync function loadAccountBalance() {\n  const balance = await loadDelegateBalance(web3Account.value);\n  accountBalance.value = balance || '0';\n}\n\nwatchDebounced(\n  () => form.value.to,\n  async value => {\n    resolveToAddress(value);\n  },\n  { debounce: 300 }\n);\n\nwatch(\n  () => props.address,\n  () => {\n    form.value.to = props.address;\n    resolvedAddress.value = props.address;\n  },\n  { immediate: true }\n);\n\nwatch(\n  web3Account,\n  () => {\n    loadAccountBalance();\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"emit('close')\">\n    <template #header>\n      <div class=\"px-4 pt-1 text-left text-skin-heading\">\n        <h3 class=\"m-0\">{{ $t('delegates.delegateModal.title') }}</h3>\n        <span>{{ $t('delegates.delegateModal.sub') }}</span>\n      </div>\n    </template>\n\n    <div class=\"space-y-3 p-4\">\n      <div>\n        <LabelInput> Voting power </LabelInput>\n        <div class=\"mt-1 flex items-center gap-1 text-skin-heading\">\n          <LoadingSpinner\n            v-if=\"isLoadingDelegateBalance\"\n            class=\"inline-block\"\n            small\n          />\n          <span v-else>\n            {{ formatCompactNumber(Number(accountBalance)) }}\n            {{ space.symbol }}\n          </span>\n        </div>\n      </div>\n\n      <div>\n        <LabelInput> Delegation scope </LabelInput>\n        <div class=\"mt-1 flex items-center gap-1\">\n          <AvatarSpace :space=\"space\" />\n          <span class=\"text-skin-heading\"> {{ space.name }} </span>\n        </div>\n      </div>\n\n      <TuneInput\n        ref=\"addressRef\"\n        v-model=\"form.to\"\n        :label=\"definition.properties.to.title\"\n        :hint=\"definition.properties.to.description\"\n        :placeholder=\"definition.properties.to.examples[0]\"\n        :error=\"validationErrors?.to\"\n      />\n    </div>\n\n    <template #footer>\n      <TuneButton\n        :loading=\"isResolvingName || isAwaitingSignature\"\n        class=\"w-full\"\n        primary\n        @click=\"handleConfirm\"\n      >\n        {{ $t('confirm') }}\n      </TuneButton>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/SpaceDelegatesSkeleton.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div\n    v-for=\"n in 18\"\n    :key=\"n\"\n    class=\"mb-[1px] h-[240px] animate-pulse rounded-xl bg-skin-border md:mb-0\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/SpaceDelegatesSplitDelegationModal.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport { clone, sleep } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst defaultDelegate = {\n  address: '',\n  weight: 100\n};\n\nconst props = defineProps<{\n  open: boolean;\n  space: ExtendedSpace;\n  address: string;\n}>();\n\nconst emit = defineEmits(['close', 'reload', 'deleteDelegate']);\n\nconst {\n  createPendingTransaction,\n  updatePendingTransaction,\n  removePendingTransaction\n} = useTxStatus();\nconst route = useRoute();\nconst { notify } = useFlashNotification();\nconst { d, t } = useI18n();\nconst { setDelegates, fetchDelegatingTo, clearDelegations } = useDelegates(\n  props.space\n);\nconst { web3 } = useWeb3();\n\nconst currentDelegations = ref<{ address: string; weight: number }[]>([]);\nconst delegates = ref(\n  currentDelegations.value.length > 0\n    ? clone(currentDelegations.value)\n    : [defaultDelegate]\n);\nconst delegationWeightError = ref('');\nconst delegationAddressError = ref('');\nconst isAwaitingSignature = ref(false);\nconst expirationDate = ref<number>(calculateInitialDate());\n\nconst isSpaceDelegatesValid = computed(() => {\n  const allDelegatesHaveAddress = delegates.value.every(\n    delegate => delegate.address\n  );\n\n  const totalWeight = delegates.value.reduce(\n    (total, delegate) => total + delegate.weight,\n    0\n  );\n\n  return (\n    delegates.value.length > 0 &&\n    allDelegatesHaveAddress &&\n    Math.floor(totalWeight) <= 100 &&\n    totalWeight > 0\n  );\n});\n\nconst dateString = computed(() =>\n  d(expirationDate.value * 1e3, 'short', 'en-US')\n);\n\nconst handleExpirationDateUpdate = (date: number) => {\n  expirationDate.value = date;\n};\n\nasync function handleConfirm() {\n  if (web3.value.network.chainId !== 1 && web3.value.network.chainId !== 100) {\n    notify([\n      'red',\n      'Change your network to Mainnet or Gnosis Chain to delegate your voting power.'\n    ]);\n    return;\n  }\n  const txPendingId = createPendingTransaction();\n\n  try {\n    isAwaitingSignature.value = true;\n    const addresses = delegates.value.map(delegation => delegation.address);\n\n    const weights = delegates.value.map(delegation =>\n      Math.floor(delegation.weight)\n    );\n\n    const delegateToSelf =\n      delegates.value\n        .map(delegation => delegation.weight)\n        .reduce((acc, weight) => acc + weight, 0) < 100;\n\n    if (delegateToSelf) {\n      addresses.push(web3.value.account);\n      weights.push(100 - weights.reduce((acc, weight) => acc + weight, 0));\n    }\n\n    const expirationTime = expirationDate.value;\n    const tx = await setDelegates(addresses, weights, expirationTime);\n    isAwaitingSignature.value = false;\n    updatePendingTransaction(txPendingId, { hash: tx.hash });\n    emit('close');\n    notify(t('notify.transactionSent'));\n    const receipt = await tx.wait();\n    console.log('Receipt', receipt);\n    await sleep(3e3);\n    notify(t('notify.delegationSuccess'));\n    removePendingTransaction(txPendingId);\n    emit('reload');\n  } catch (e) {\n    console.log(e);\n    isAwaitingSignature.value = false;\n    removePendingTransaction(txPendingId);\n  }\n}\n\nconst handleCloseModal = () => {\n  delegates.value =\n    currentDelegations.value.length > 0\n      ? clone(currentDelegations.value)\n      : [defaultDelegate];\n  isAwaitingSignature.value = false;\n  expirationDate.value = calculateInitialDate();\n  emit('close');\n};\n\nfunction calculateInitialDate(): number {\n  const date = new Date();\n  date.setFullYear(date.getFullYear() + 1);\n  return Math.floor(date.getTime() / 1000);\n}\n\nfunction deleteDelegate(index: number) {\n  const newDelegates = clone(delegates.value);\n  newDelegates.splice(index, 1);\n  delegates.value = newDelegates;\n\n  validateDelegations();\n}\n\nfunction updateDelegate(index: number, form: { to: string; weight: number }) {\n  const newDelegates = clone(delegates.value);\n  newDelegates[index] = {\n    ...newDelegates[index],\n    address: form.to,\n    weight: form.weight\n  };\n  delegates.value = newDelegates;\n\n  validateDelegations();\n}\n\nfunction validateDelegations() {\n  // show error if weights add to more than 100\n  const totalWeight = delegates.value.reduce(\n    (total, delegate) => total + delegate.weight,\n    0\n  );\n  delegationWeightError.value =\n    totalWeight > 100 ? 'Total weight cannot exceed 100' : '';\n\n  // Show error if duplicate addresses\n  const nonEmptyAddresses = delegates.value.map(d => d.address).filter(Boolean);\n  const hasDuplicates =\n    new Set(nonEmptyAddresses).size !== nonEmptyAddresses.length;\n  delegationAddressError.value = hasDuplicates\n    ? 'Duplicate addresses not allowed'\n    : '';\n}\n\nasync function deleteAllDelegates() {\n  const txPendingId = createPendingTransaction();\n\n  try {\n    isAwaitingSignature.value = true;\n\n    const tx = await clearDelegations();\n    isAwaitingSignature.value = false;\n    updatePendingTransaction(txPendingId, { hash: tx.hash });\n    emit('close');\n    notify(t('notify.transactionSent'));\n    const receipt = await tx.wait();\n    console.log('Receipt', receipt);\n    await sleep(3e3);\n    notify('Your delegations have been cleared');\n    removePendingTransaction(txPendingId);\n    emit('reload');\n  } catch (e) {\n    console.log(e);\n    isAwaitingSignature.value = false;\n    removePendingTransaction(txPendingId);\n  }\n}\n\nfunction addDelegate() {\n  const newDelegate = {\n    address: '',\n    weight: 0\n  };\n\n  delegates.value.push(newDelegate);\n}\n\nfunction divideEqually() {\n  delegationWeightError.value = '';\n  const numDelegates = delegates.value.length;\n  if (numDelegates === 0) return;\n\n  const equalWeight = 100 / numDelegates;\n\n  const updatedDelegates = delegates.value.map(delegate => ({\n    ...delegate,\n    weight: equalWeight\n  }));\n\n  delegates.value = updatedDelegates;\n}\n\nasync function loadDelegatingTo() {\n  const delegatingTo = await fetchDelegatingTo(web3.value.account);\n  const delegations = delegatingTo?.delegateTree?.map(\n    ({ delegate, weight }) => ({\n      address: delegate,\n      weight: weight / 100 // delegate weight comes from api as BPS\n    })\n  );\n  currentDelegations.value = delegations ? clone(delegations) : [];\n\n  if (props.address) {\n    const newDelegate = {\n      address: props.address,\n      weight:\n        100 -\n        currentDelegations.value.reduce(\n          (acc, delegate) => acc + delegate.weight,\n          0\n        )\n    };\n    delegations?.push(newDelegate);\n  }\n  delegates.value =\n    delegations && delegations.length ? clone(delegations) : [defaultDelegate];\n}\n\nwatch(() => web3.value.account, loadDelegatingTo, { immediate: true });\n\nwatch(\n  route,\n  newDelegateAddress => {\n    const delegateAddress = newDelegateAddress.query.delegate;\n    if (delegateAddress) {\n      if (delegates.value.length === 1 && !delegates.value[0].address) {\n        // delegates has default value, replace with the passed address\n        const _delegates = clone(delegates.value);\n        _delegates[0].address = delegateAddress;\n        delegates.value = _delegates;\n      } else {\n        // if delegates already has a value, add to the end of the list,\n        // with the remaining weight of 100\n        const newDelegate = {\n          address: delegateAddress as string,\n          weight:\n            100 -\n            currentDelegations.value.reduce(\n              (acc, delegate) => acc + delegate.weight,\n              0\n            )\n        };\n        delegates.value.push(newDelegate);\n      }\n    }\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"handleCloseModal\">\n    <template #header>\n      <div class=\"px-4 pt-1 text-left text-skin-heading\">\n        <h3 class=\"m-0\" v-text=\"$t('delegates.delegateModal.title')\" />\n        <p class=\"text-gray-500\">\n          Delegate your voting power to multiple addresses.\n        </p>\n      </div>\n    </template>\n\n    <div class=\"space-y-3 p-4\">\n      <p>\n        Any unallocated power (100% - any delegations) will remain with you.\n      </p>\n      <div class=\"flex flex-col space-y-1\">\n        <LabelInput> Delegation scope </LabelInput>\n        <div class=\"flex items-center space-x-1\">\n          <AvatarSpace :space=\"space\" />\n          <span class=\"text-skin-heading\" v-text=\"space.name\" />\n        </div>\n      </div>\n      <div>\n        <TuneLabelInput hint=\"Your delegations will expire after this date\">\n          Set expiration\n        </TuneLabelInput>\n        <InputDate\n          :tooltip=\"$t('create.delayEnforced')\"\n          :date=\"expirationDate\"\n          :date-string=\"dateString\"\n          @update:date=\"handleExpirationDateUpdate\"\n        />\n      </div>\n      <div class=\"space-y-1\">\n        <div v-if=\"delegates.length > 0\" class=\"flex justify-between\">\n          <TuneLabelInput\n            hint=\"Add addresses and the percentage of your voting power you want to delegate to them\"\n          >\n            {{ 'Delegations' }}\n          </TuneLabelInput>\n          <button\n            class=\"text-gray-500 underline hover:opacity-50 text-xs bg-none\"\n            @click=\"divideEqually\"\n          >\n            Divide equally\n          </button>\n        </div>\n        <div\n          v-for=\"(delegate, index) in delegates\"\n          :key=\"`delegate-registry-row-${index}`\"\n        >\n          <SpaceSplitDelegationRow\n            :address=\"delegate.address\"\n            :weight=\"delegate.weight\"\n            @delete-delegate=\"deleteDelegate(index)\"\n            @update:model-value=\"form => updateDelegate(index, form)\"\n          />\n        </div>\n        <div class=\"flex justify-between\">\n          <TuneButton class=\"flex items-center space-x-2\" @click=\"addDelegate\">\n            <i-ho-plus class=\"text-xs\" />\n            <span>Add delegate</span>\n          </TuneButton>\n          <button\n            v-if=\"currentDelegations.length > 0\"\n            class=\"text-red underline hover:opacity-50 text-xs bg-none\"\n            @click=\"deleteAllDelegates\"\n          >\n            Clear all delegations\n          </button>\n        </div>\n        <TuneErrorInput\n          v-if=\"Boolean(delegationWeightError)\"\n          :error=\"delegationWeightError\"\n        />\n        <TuneErrorInput\n          v-if=\"Boolean(delegationAddressError)\"\n          :error=\"delegationAddressError\"\n        />\n      </div>\n    </div>\n    <template #footer>\n      <TuneButton\n        class=\"w-full\"\n        type=\"button\"\n        :disabled=\"\n          !isSpaceDelegatesValid ||\n          Boolean(delegationWeightError) ||\n          Boolean(delegationAddressError)\n        \"\n        :loading=\"isAwaitingSignature\"\n        @click=\"handleConfirm\"\n      >\n        {{ $t('confirm') }}\n      </TuneButton>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalBoost.vue",
    "content": "<script setup lang=\"ts\">\nimport { getClaims, getBoosts } from '@/helpers/boost/subgraph';\nimport { Proposal, ExtendedSpace } from '@/helpers/interfaces';\nimport { useStorage } from '@vueuse/core';\nimport { getRewards } from '@/helpers/boost/api';\nimport { toChecksumAddress } from '@/helpers/utils';\nimport { sleep } from '@snapshot-labs/snapshot.js/src/utils';\nimport { useIntervalFn } from '@vueuse/core';\nimport {\n  BoostClaimSubgraph,\n  BoostRewardGuard,\n  BoostSubgraph\n} from '@/helpers/boost/types';\n\nconst INITIAL_VISIBLE_BOOSTS = 3;\n\nconst props = defineProps<{\n  proposal: Proposal;\n  space: ExtendedSpace;\n}>();\n\nconst createModalOpen = ref(false);\nconst showAllBoosts = ref(false);\nconst boosts = ref<BoostSubgraph[]>([]);\nconst boostClaims = ref<BoostClaimSubgraph[]>([]);\nconst boostRewards = ref<BoostRewardGuard[]>([]);\nconst loading = ref(false);\nconst loadingRewards = ref(false);\nconst relativeEndTime = ref('');\n\nconst router = useRouter();\nconst { formatRelativeTime, longRelativeTimeFormatter } = useIntl();\nconst { userVote, loadUserVote } = useProposalVotes(props.proposal);\nconst { web3Account } = useWeb3();\nconst { sanitizeBoosts } = useBoost();\nconst dontShowModalAgain = useStorage(\n  'snapshot.boosts-modal-dont-show-again',\n  false\n);\n\nconst newBoostLink = computed(() => ({\n  name: 'spaceBoost',\n  params: { proposalId: props.proposal.id }\n}));\n\nconst isActive = computed(() => props.proposal.state === 'active');\nconst isFinal = computed(() => props.proposal.scores_state === 'final');\n\nconst winningChoice = computed(() => {\n  const maxScore = Math.max(...props.proposal.scores);\n  const maxScoreCount = props.proposal.scores.filter(\n    score => score === maxScore\n  ).length;\n  return maxScoreCount > 1 ? 0 : props.proposal.scores.indexOf(maxScore) + 1;\n});\n\nfunction isEligible(boost: BoostSubgraph) {\n  const type = boost.strategy.eligibility.type;\n  const choice = boost.strategy.eligibility.choice;\n\n  if (!web3Account.value) return false;\n  if (!isFinal.value) return false;\n  if (!userVote.value) return false;\n  if (type === 'prediction')\n    return userVote.value.choice.toString() === winningChoice.value.toString();\n  if (type === 'incentive' && choice === null) return true;\n  return userVote.value.choice.toString() === choice;\n}\n\nconst eligibleBoosts = computed(() => {\n  if (!boosts.value.length) return [];\n  return boosts.value.filter(boost => isEligible(boost));\n});\n\nconst boostsSorted = computed(() => {\n  if (!boosts.value.length) return [];\n\n  const boostsWithReward = boosts.value.map(boost => {\n    const reward = boostRewards.value.find(\n      reward =>\n        reward.boost_id === boost.id && reward.chain_id === boost.chainId\n    );\n    return {\n      ...boost,\n      rewardAmount: reward ? reward.reward : 0\n    };\n  });\n\n  const owned: BoostSubgraph[] = [];\n  const eligible: BoostSubgraph[] = [];\n  const claimed: BoostSubgraph[] = [];\n  const other: BoostSubgraph[] = [];\n\n  boostsWithReward.forEach(boost => {\n    const isClaimed = boostClaims.value.some(\n      claim => claim.boost.id === boost.id\n    );\n\n    if (\n      toChecksumAddress(boost.owner) === toChecksumAddress(web3Account.value)\n    ) {\n      owned.push(boost);\n    } else if (isEligible(boost) && !isClaimed) {\n      eligible.push(boost);\n    } else if (isClaimed) {\n      claimed.push(boost);\n    } else {\n      other.push(boost);\n    }\n  });\n\n  const sortFn = (a, b) => Number(b.rewardAmount) - Number(a.rewardAmount);\n  return [\n    ...owned.sort(sortFn),\n    ...eligible.sort(sortFn),\n    ...claimed.sort(sortFn),\n    ...other.sort(sortFn)\n  ];\n});\n\nfunction handleStart() {\n  router.push(newBoostLink.value);\n  createModalOpen.value = false;\n}\n\nasync function loadBoosts() {\n  try {\n    const response = await getBoosts([props.proposal.id]);\n    const sanitizedBoosts = sanitizeBoosts(\n      response,\n      [props.proposal],\n      props.space\n    );\n    boosts.value = sanitizedBoosts;\n  } catch (e) {\n    console.error('Load boosts error:', e);\n  }\n}\n\nasync function loadClaims() {\n  if (!isFinal.value || !web3Account.value) return;\n  try {\n    boostClaims.value = await getClaims(web3Account.value);\n  } catch (e) {\n    console.error('Load boosts error:', e);\n  }\n}\n\nfunction handleBoost() {\n  if (dontShowModalAgain.value) {\n    handleStart();\n  } else {\n    createModalOpen.value = true;\n  }\n}\n\nasync function loadRewards() {\n  if (\n    !web3Account.value ||\n    !isFinal.value ||\n    !userVote.value ||\n    !boosts.value.length\n  ) {\n    return;\n  }\n\n  loadingRewards.value = true;\n\n  try {\n    boostRewards.value = await getRewards(\n      props.proposal.id,\n      web3Account.value,\n      boosts.value\n    );\n  } catch (e) {\n    boostRewards.value = [];\n    console.log('Get boostRewards error:', e);\n  } finally {\n    loadingRewards.value = false;\n  }\n}\n\nwatch(\n  [() => props.proposal],\n  async () => {\n    loading.value = true;\n    await Promise.all([\n      loadBoosts(),\n      loadClaims(),\n      loadUserVote(web3Account.value)\n    ]);\n    await loadRewards();\n    loading.value = false;\n  },\n  { immediate: true, deep: true }\n);\n\nwatch(web3Account, async value => {\n  if (loading.value) return;\n  loadClaims();\n  await loadUserVote(value);\n  loadRewards();\n});\n\nconst { pause } = useIntervalFn(async () => {\n  if (!isActive.value) return;\n\n  relativeEndTime.value = formatRelativeTime(\n    props.proposal.end,\n    longRelativeTimeFormatter.value\n  );\n\n  const timestampNow = Math.floor(Date.now() / 1000);\n  const isClaimable = props.proposal.end < timestampNow;\n  if (isClaimable) {\n    pause();\n    await sleep(3000);\n    window.location.reload();\n  }\n}, 1000);\n</script>\n\n<template>\n  <div>\n    <SpaceProposalBoostClaim\n      v-if=\"eligibleBoosts.length && isFinal\"\n      :proposal=\"proposal\"\n      :boosts=\"boosts\"\n      :eligible-boosts=\"eligibleBoosts\"\n      :rewards=\"boostRewards\"\n      :claims=\"boostClaims\"\n      :loading-rewards=\"loadingRewards\"\n      @reload=\"loadClaims\"\n    />\n\n    <TuneBlock\n      v-if=\"isActive || boosts.length\"\n      slim\n      class=\"rounded-2xl overflow-hidden\"\n      :class=\"[\n        {\n          '!border-0 ring-1 ring-boost !bg-boost': isActive\n        }\n      ]\"\n    >\n      <div\n        v-if=\"isActive\"\n        class=\"text-white flex items-center justify-center h-[40px] bg-[url('@/assets/images/stars.svg')]\"\n      >\n        <template v-if=\"boosts.length\">\n          <i-ho-fire class=\"text-xs mr-1\" />\n          <span> {{ boosts.length }} boost active </span>\n        </template>\n        <template v-else> Try out this feature! </template>\n      </div>\n      <div class=\"bg-skin-bg rounded-2xl p-3\">\n        <div v-if=\"boosts.length\">\n          <div class=\"md:flex md:justify-between\">\n            <div>\n              <i-s-boost-logo class=\"text-xs text-skin-link\" />\n              <p v-if=\"isActive\" class=\"text-md leading-5 mt-2\">\n                Get rewards by voting on this proposal\n              </p>\n            </div>\n            <TuneButton\n              v-if=\"isActive\"\n              type=\"button\"\n              class=\"flex items-center pl-[18px] pr-[22px] w-full md:w-auto justify-center mt-[12px] md:mt-0\"\n              @click=\"handleBoost\"\n            >\n              <i-ho-plus class=\"mr-2 text-xs\" />\n              <span> New boost </span>\n            </TuneButton>\n          </div>\n          <LoadingList v-if=\"loading\" class=\"mt-3\" />\n          <div v-else>\n            <div class=\"mt-3 space-y-2\">\n              <div\n                v-for=\"boost in showAllBoosts\n                  ? boostsSorted\n                  : boostsSorted.slice(0, INITIAL_VISIBLE_BOOSTS)\"\n                :key=\"boost.id\"\n              >\n                <SpaceProposalBoostItem\n                  :boost=\"boost\"\n                  :claims=\"boostClaims\"\n                  :proposal=\"proposal\"\n                  :reward=\"\n                    boostRewards.find(\n                      reward =>\n                        reward.boost_id === boost.id &&\n                        reward.chain_id === boost.chainId\n                    )\n                  \"\n                  :is-eligible=\"isEligible(boost)\"\n                  @reload=\"loadBoosts()\"\n                />\n              </div>\n            </div>\n            <TuneButton\n              v-if=\"boostsSorted.length > INITIAL_VISIBLE_BOOSTS\"\n              class=\"w-full mt-3 flex items-center justify-center\"\n              @click=\"showAllBoosts = !showAllBoosts\"\n            >\n              <span v-if=\"showAllBoosts\"> View less </span>\n              <span v-else> View more </span>\n              <i-ho-arrow-sm-down\n                class=\"ml-2\"\n                :class=\"{ 'rotate-180': showAllBoosts }\"\n              />\n            </TuneButton>\n          </div>\n          <div\n            v-if=\"eligibleBoosts.length && isActive\"\n            class=\"bg-[--border-color-faint] border-t border-[--border-color-soft] -mx-3 -mb-3 mt-3 p-3\"\n          >\n            <div class=\"md:flex md:items-center gap-3\">\n              <div class=\"flex gap-[12px] items-center w-full\">\n                <div\n                  class=\"h-[44px] w-[44px] flex-shrink-0 rounded-2xl shadow-xl\"\n                >\n                  <div class=\"flex items-center justify-center h-full\">\n                    <i-ho-gift class=\"text-skin-heading text-sm\" />\n                  </div>\n                </div>\n                <div class=\"w-full\">\n                  <h5 class=\"leading-none\">Rewards</h5>\n                  <p class=\"leading-none\">\n                    You're eligible to {{ eligibleBoosts.length }} boost<span\n                      v-if=\"boosts.length > 1\"\n                      >s</span\n                    >\n                  </p>\n                </div>\n              </div>\n\n              <div\n                class=\"border text-border rounded-full py-1 px-[12px] justify-center flex md:mt-0 mt-3\"\n              >\n                <div class=\"text-skin-link flex items-center opacity-40\">\n                  <i-ho-fire class=\"text-sm mr-2\" />\n                  <div class=\"whitespace-nowrap\">\n                    Claim\n                    {{ relativeEndTime }}\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          v-else-if=\"isActive\"\n          class=\"md:flex items-center justify-between gap-3\"\n        >\n          <div class=\"flex items-center gap-3\">\n            <div class=\"h-[46px] w-[46px] flex-shrink-0 rounded-2xl shadow-xl\">\n              <div class=\"flex items-center justify-center h-full\">\n                <i-s-boost-icon class=\"text-skin-heading\" />\n              </div>\n            </div>\n            <div class=\"w-full\">\n              <h4 class=\"leading-5\">Boost proposal</h4>\n              <p>Incentivize people to vote with rewards.</p>\n            </div>\n          </div>\n\n          <TuneButton\n            class=\"flex items-center justify-center w-full md:w-auto mt-3 md:mt-0\"\n            :loading=\"loading\"\n            @click=\"handleBoost\"\n          >\n            <i-ho-fire class=\"text-sm mr-2\" />\n            <div>Boost</div>\n          </TuneButton>\n        </div>\n      </div>\n    </TuneBlock>\n    <SpaceProposalBoostModalCreate\n      :open=\"createModalOpen\"\n      @close=\"createModalOpen = false\"\n      @start=\"handleStart\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalBoostClaim.vue",
    "content": "<script setup lang=\"ts\">\nimport { Proposal } from '@/helpers/interfaces';\nimport { claimAllTokens } from '@/helpers/boost';\nimport { toChecksumAddress } from '@/helpers/utils';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport {\n  BoostRewardGuard,\n  BoostSubgraph,\n  BoostClaimSubgraph\n} from '@/helpers/boost/types';\n\nconst props = defineProps<{\n  proposal: Proposal;\n  boosts: BoostSubgraph[];\n  eligibleBoosts: BoostSubgraph[];\n  rewards: BoostRewardGuard[];\n  claims: BoostClaimSubgraph[];\n  loadingRewards: boolean;\n}>();\n\nconst emit = defineEmits(['reload']);\n\nconst claimSuccessOpen = ref(false);\nconst claimTx = ref();\nconst claimModalOpen = ref(false);\nconst loadingClaimAll = ref(false);\n\nconst auth = getInstance();\nconst { web3Account, web3 } = useWeb3();\nconst { formatDuration } = useIntl();\nconst { changeNetwork } = useChangeNetwork();\nconst { loadVouchers } = useBoost();\n\nconst unclaimedBoostsWithReward = computed(() => {\n  if (!props.rewards.length) return [];\n  return props.eligibleBoosts\n    .filter(boost => {\n      const hasClaimed = props.claims.some(\n        claim => claim.boost.id === boost.id && claim.chainId === boost.chainId\n      );\n      return !hasClaimed;\n    })\n    .filter(boost => {\n      const reward = props.rewards.find(reward => reward.boost_id === boost.id);\n      return Number(reward?.reward) > 0;\n    });\n});\n\nasync function handleClaimAll() {\n  if (props.rewards[0].chain_id !== web3.value.network.chainId.toString()) {\n    await changeNetwork(props.rewards[0].chain_id);\n    handleClaimAll();\n  }\n\n  try {\n    loadingClaimAll.value = true;\n    const vouchers = await loadVouchers(\n      unclaimedBoostsWithReward.value,\n      props.proposal.id\n    );\n    if (!vouchers?.length) throw new Error('No vouchers found');\n\n    const boosts = vouchers.map(voucher => ({\n      boostId: voucher.boost_id,\n      recipient: toChecksumAddress(web3Account.value),\n      amount: voucher.reward\n    }));\n    const signatures = vouchers.map(voucher => voucher.signature);\n    const chainId = vouchers[0].chain_id;\n    claimTx.value = await claimAllTokens(\n      auth.web3,\n      chainId,\n      boosts,\n      signatures\n    );\n\n    await claimTx.value.wait();\n    claimModalOpen.value = false;\n    claimSuccessOpen.value = true;\n    emit('reload');\n  } catch (e: any) {\n    console.error('Claim error:', e);\n  } finally {\n    claimTx.value = undefined;\n    loadingClaimAll.value = false;\n  }\n}\n\nconst timeLeftToClaim = computed(() => {\n  if (!unclaimedBoostsWithReward.value.length) return 0;\n  return (\n    Number(unclaimedBoostsWithReward.value[0].end) -\n    Math.floor(Date.now() / 1000)\n  );\n});\n</script>\n\n<template>\n  <div>\n    <TuneBlock\n      v-if=\"unclaimedBoostsWithReward.length\"\n      slim\n      class=\"!bg-boost bg-[url('@/assets/images/stars-big-horizontal.svg')] py-[32px] !border-0 mb-[20px] lg:mb-4\"\n    >\n      <div>\n        <div\n          class=\"bg-white w-[64px] h-[64px] rounded-[20px] flex justify-center items-center shadow-xl mx-auto relative\"\n        >\n          <i-s-boost-icon class=\"text-black text-[20px]\" />\n          <div\n            class=\"absolute bg-white border -top-[10px] -right-3 border-[#000000]/10 rounded-full flex items-center pr-2 pl-[6px] text-[#444]\"\n          >\n            <i-ho-gift class=\"text-[14px] mr-[2px]\" />\n            <span class=\"text-sm\">\n              {{ unclaimedBoostsWithReward.length }}\n            </span>\n          </div>\n        </div>\n        <div class=\"text-white text-md text-center leading-5 mt-3\">\n          <div class=\"font-semibold mb-1\">Claim rewards</div>\n          You can now claim your rewards!\n        </div>\n      </div>\n\n      <div class=\"flex justify-center mt-3\">\n        <TuneButton\n          variant=\"white\"\n          class=\"text-white\"\n          :class=\"{\n            'cursor-not-allowed': loadingRewards || timeLeftToClaim <= 0\n          }\"\n          @click=\"\n            !loadingRewards && timeLeftToClaim > 0 && (claimModalOpen = true)\n          \"\n        >\n          <TuneLoadingSpinner v-if=\"loadingRewards\" class=\"text-white\" />\n          <div v-else class=\"flex items-center\">\n            <i-ho-gift class=\"text-sm mr-2\" />\n            Claim\n            <span class=\"ml-[4px]\">\n              {{ unclaimedBoostsWithReward.length }} reward<span\n                v-if=\"unclaimedBoostsWithReward.length > 1\"\n                >s</span\n              >\n            </span>\n          </div>\n        </TuneButton>\n      </div>\n      <div class=\"flex text-white justify-center mt-2\">\n        <span v-if=\"timeLeftToClaim > 0\" class=\"flex items-center\">\n          <i-ho-clock class=\"mr-1 text-sm\" />\n          {{ formatDuration(timeLeftToClaim) }}\n          left\n        </span>\n        <span v-else class=\"flex items-center\">\n          <i-ho-exclamation-circle class=\"mr-1 text-sm\" />\n          Claiming period ended\n        </span>\n      </div>\n    </TuneBlock>\n    <SpaceProposalBoostClaimModalSuccess\n      :open=\"claimSuccessOpen === true\"\n      :proposal=\"proposal\"\n      :more-to-claim=\"unclaimedBoostsWithReward.length > 0\"\n      @close=\"claimSuccessOpen = false\"\n      @open-claim-modal=\"claimModalOpen = true\"\n    />\n    <SpaceProposalBoostClaimModal\n      :open=\"claimModalOpen\"\n      :proposal=\"proposal\"\n      :boosts=\"eligibleBoosts\"\n      :claimable-boosts=\"unclaimedBoostsWithReward\"\n      :claims=\"claims\"\n      :rewards=\"rewards\"\n      :loading-claim-all=\"loadingClaimAll\"\n      @close=\"claimModalOpen = false\"\n      @claim-all=\"handleClaimAll\"\n      @reload=\"$emit('reload')\"\n      @open-success=\"claimSuccessOpen = true\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalBoostClaimModal.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  BoostClaimSubgraph,\n  BoostRewardGuard,\n  BoostSubgraph\n} from '@/helpers/boost/types';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { Proposal } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  open: boolean;\n  proposal: Proposal;\n  boosts: BoostSubgraph[];\n  claimableBoosts: BoostSubgraph[];\n  claims: BoostClaimSubgraph[];\n  rewards: BoostRewardGuard[];\n  loadingClaimAll: boolean;\n}>();\n\nconst emit = defineEmits(['close', 'claimAll', 'reload', 'openSuccess']);\n\nconst allOnSameNetwork = computed(() => {\n  const chainIds = new Set(props.claimableBoosts.map(boost => boost.chainId));\n  return chainIds.size === 1;\n});\n\nconst boostsSorted = computed(() => {\n  return clone(props.boosts)\n    .sort((a, b) => {\n      const rewardA = props.rewards.find(reward => reward.boost_id === a.id);\n      const rewardB = props.rewards.find(reward => reward.boost_id === b.id);\n      return (Number(rewardB?.reward) || 0) - (Number(rewardA?.reward) || 0);\n    })\n    .sort((a, b) => {\n      const claimedA = props.claims.some(\n        claim => claim.boost.id === a.id && claim.chainId === a.chainId\n      )\n        ? 1\n        : 0;\n      const claimedB = props.claims.some(\n        claim => claim.boost.id === b.id && claim.chainId === b.chainId\n      )\n        ? 1\n        : 0;\n      return claimedA - claimedB;\n    })\n    .filter(boost =>\n      props.rewards.some(\n        reward =>\n          reward.boost_id === boost.id && reward.chain_id === boost.chainId\n      )\n    );\n});\n\nfunction handleOpenSuccess() {\n  emit('openSuccess');\n  emit('close');\n}\n\nwatch(\n  () => props.claimableBoosts,\n  () => {\n    if (props.open && props.claimableBoosts.length === 0) handleOpenSuccess();\n  }\n);\n</script>\n\n<template>\n  <TuneModal :open=\"open\" @close=\"$emit('close')\">\n    <div class=\"px-[64px] py-[32px] text-center\">\n      <TuneModalIndicator variant=\"gift\" />\n      <TuneModalTitle as=\"h4\" class=\"mt-3 leading-none\">\n        Claim your rewards\n      </TuneModalTitle>\n      <TuneModalDescription class=\"text-md leading-none mt-1\">\n        You can now claim your rewards!\n      </TuneModalDescription>\n    </div>\n    <div\n      class=\"px-3 space-y-2 max-h-[calc(100vh-255px)] md:max-h-[300px] overflow-y-auto\"\n    >\n      <div v-for=\"boost in boostsSorted\" :key=\"boost.id\">\n        <SpaceProposalBoostClaimModalItem\n          :proposal=\"proposal\"\n          :boost=\"boost\"\n          :rewards=\"rewards\"\n          :claims=\"claims\"\n          @reload=\"$emit('reload')\"\n        />\n      </div>\n    </div>\n    <div class=\"m-3\">\n      <TuneButton\n        v-if=\"allOnSameNetwork && claimableBoosts.length > 1\"\n        class=\"w-full\"\n        :loading=\"loadingClaimAll\"\n        @click=\"$emit('claimAll')\"\n      >\n        Claim all\n      </TuneButton>\n    </div>\n  </TuneModal>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalBoostClaimModalItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { formatUnits } from '@ethersproject/units';\nimport { Proposal } from '@/helpers/interfaces';\nimport { explorerUrl } from '@/helpers/utils';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { getUrl } from '@snapshot-labs/snapshot.js/src/utils';\nimport {\n  BoostClaimSubgraph,\n  BoostRewardGuard,\n  BoostSubgraph\n} from '@/helpers/boost/types';\n\nconst props = defineProps<{\n  proposal: Proposal;\n  boost: BoostSubgraph;\n  rewards: BoostRewardGuard[];\n  claims: BoostClaimSubgraph[];\n}>();\n\nconst emit = defineEmits(['reload']);\n\nconst { handleClaim, loadingClaim } = useBoost();\nconst { formatNumber, getNumberFormatter } = useIntl();\n\nconst reward = computed(() => {\n  const reward = props.rewards.find(\n    reward => reward.boost_id === props.boost.id\n  ) as BoostRewardGuard;\n  if (!reward) return '0';\n  const amountDecimal = formatUnits(\n    reward.reward.toString(),\n    props.boost.token.decimals\n  );\n\n  return amountDecimal;\n});\n\nconst claim = computed(() => {\n  return props.claims.find(\n    claim =>\n      claim.boost.id === props.boost.id && claim.chainId === props.boost.chainId\n  );\n});\n\nconst hasClaimed = computed(() => {\n  return claim.value !== undefined;\n});\n\nconst boostNetworkInfo = computed(() => {\n  return networks?.[props.boost.chainId];\n});\n\nasync function handleClaimAndReload() {\n  await handleClaim(props.boost, props.proposal.id);\n  emit('reload');\n}\n</script>\n\n<template>\n  <div\n    class=\"flex items-center justify-between border rounded-xl p-[12px]\"\n    :class=\"{\n      'border-green/30 bg-green/5': hasClaimed,\n      'border-boost/30 bg-boost/5': !hasClaimed && Number(reward) > 0\n    }\"\n  >\n    <div class=\"text-skin-heading flex items-center\">\n      <div\n        class=\"border rounded-full p-[3px] mr-2 self-start\"\n        :class=\"{\n          'border-green/40 bg-green/10': hasClaimed,\n          'border-boost/40 bg-boost/10': !hasClaimed && Number(reward) > 0\n        }\"\n      >\n        <i-ho-cash v-if=\"hasClaimed\" class=\"text-green text-xs\" />\n        <i-ho-gift v-else class=\"text-boost text-xs\" />\n      </div>\n\n      <div>\n        <span class=\"mr-1\">\n          {{ hasClaimed ? 'Claimed' : 'Reward' }}\n        </span>\n        <TuneTag class=\"text-skin-heading text-base\">\n          {{\n            formatNumber(\n              Number(reward),\n              getNumberFormatter({ maximumFractionDigits: 8 }).value\n            )\n          }}\n          {{ props.boost.token.symbol }}\n        </TuneTag>\n        <div class=\"mt-1 flex items-center flex-wrap\">\n          <span\n            v-tippy=\"{\n              content:\n                'Boost ID is a unique identifier for this boost on the given network.',\n              delay: 100\n            }\"\n          >\n            Boost #{{ boost.id }}\n          </span>\n          <BaseInterpunct />\n          <div class=\"flex items-center gap-2\">\n            <BaseAvatar\n              v-if=\"boostNetworkInfo?.logo\"\n              :src=\"getUrl(boostNetworkInfo.logo)\"\n              size=\"18\"\n            />\n\n            <span>\n              {{ boostNetworkInfo?.name }}\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"self-start mr-0\">\n      <TuneButton\n        v-if=\"!hasClaimed && Number(reward) > 0\"\n        :loading=\"loadingClaim\"\n        class=\"h-[32px] px-[12px] bg-skin-bg\"\n        @click=\"handleClaimAndReload\"\n      >\n        Claim\n      </TuneButton>\n      <BaseLink\n        v-else-if=\"claim?.transactionHash\"\n        :link=\"explorerUrl(boost.chainId, claim.transactionHash, 'tx')\"\n        >View\n      </BaseLink>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalBoostClaimModalSuccess.vue",
    "content": "<script setup lang=\"ts\">\nimport { Proposal } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  open: boolean;\n  moreToClaim: boolean;\n  proposal: Proposal;\n}>();\n\nconst emit = defineEmits(['close', 'openClaimModal']);\n\nconst { shareClaim } = useSharing();\n\nfunction handleClose() {\n  if (props.moreToClaim) {\n    emit('openClaimModal');\n  }\n  emit('close');\n}\n</script>\n\n<template>\n  <TuneModal :open=\"open\" @close=\"emit('close')\">\n    <div\n      class=\"h-full bg-[url('@/assets/images/confetti.svg')] pt-[40px] bg-no-repeat\"\n    >\n      <div class=\"text-center\">\n        <div\n          class=\"w-[64px] h-[64px] mb-[24px] mx-auto shadow-xl bg-boost rounded-[20px] flex justify-center items-center\"\n        >\n          <i-ho-gift class=\"text-white\" />\n        </div>\n        <TuneModalTitle class=\"m-0 leading-6\">\n          Congratulations!\n        </TuneModalTitle>\n        <TuneModalDescription class=\"text-md leading-5 mt-1 mx-[48px]\">\n          Your rewards have been claimed.\n        </TuneModalDescription>\n      </div>\n      <div\n        class=\"border rounded-xl mx-3 p-[12px] border-boost/20 bg-boost/5 flex justify-between items-center mt-[40px]\"\n      >\n        <div>\n          <div class=\"font-semibold text-skin-heading leading-5 text-md\">\n            Tell the world!\n          </div>\n          <div class=\"leading-5 text-md\">Share your victory with friends.</div>\n        </div>\n        <div class=\"flex gap-x-2\">\n          <TuneButton\n            class=\"w-[46px] h-[46px] p-0 flex justify-center items-center\"\n            @click=\"shareClaim('x', { proposal: proposal })\"\n          >\n            <i-s-x />\n          </TuneButton>\n          <TuneButton\n            class=\"w-[46px] h-[46px] p-0 flex justify-center items-center\"\n            @click=\"shareClaim('hey', { proposal: proposal })\"\n          >\n            <i-s-hey class=\"text-[#FB3A5D] text-sm\" />\n          </TuneButton>\n        </div>\n      </div>\n      <div class=\"p-3 pt-[12px]\">\n        <TuneButton class=\"w-full\" @click=\"handleClose\">\n          {{ moreToClaim ? 'Claim more' : 'Close' }}\n        </TuneButton>\n      </div>\n    </div>\n  </TuneModal>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalBoostItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { Proposal } from '@/helpers/interfaces';\nimport { formatUnits } from '@ethersproject/units';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { withdrawAndBurn } from '@/helpers/boost';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { toChecksumAddress, explorerUrl } from '@/helpers/utils';\nimport { getUrl } from '@snapshot-labs/snapshot.js/src/utils';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { getWinners } from '@/helpers/boost/api';\nimport { useIntervalFn } from '@vueuse/core';\nimport {\n  BoostClaimSubgraph,\n  BoostRewardGuard,\n  BoostSubgraph\n} from '@/helpers/boost/types';\n\nconst props = defineProps<{\n  proposal: Proposal;\n  boost: BoostSubgraph;\n  claims?: BoostClaimSubgraph[];\n  isEligible?: boolean;\n  reward?: BoostRewardGuard;\n}>();\n\nconst emit = defineEmits(['reload']);\n\nconst auth = getInstance();\nconst { web3Account } = useWeb3();\nconst { formatNumber, getNumberFormatter, formatDuration } = useIntl();\n\nconst openWinnersModal = ref(false);\nconst lotteryWinners = ref<string[]>([]);\nconst lotteryPrize = ref('0');\nconst lotteryEpochNotFinalized = ref(false);\nconst loadingWithdraw = ref(false);\nconst loadingWinners = ref(false);\nconst minutesUntilEpochEnd = ref(0);\nconst winnersError = ref(false);\n\nconst boostBalanceFormatted = computed(() => {\n  const formattedUnits = formatUnits(\n    props.boost.poolSize,\n    props.boost.token.decimals\n  );\n  return formatNumber(\n    Number(formattedUnits),\n    getNumberFormatter({ maximumFractionDigits: 8 }).value\n  );\n});\n\nconst isFinal = computed(() => {\n  return props.proposal.scores_state === 'final';\n});\n\nconst hasClaimed = computed(() => {\n  if (!props.claims?.length) return false;\n\n  return props.claims.some(\n    claim =>\n      claim.boost.id === props.boost.id && claim.chainId === props.boost.chainId\n  );\n});\n\nconst claimedAmount = computed(() => {\n  if (!props.claims?.length) return '0';\n  const claim = props.claims.find(claim => claim.boost.id === props.boost.id);\n\n  if (!claim) return '0';\n\n  const formattedUnits = formatUnits(claim.amount, props.boost.token.decimals);\n\n  return formatNumber(\n    Number(formattedUnits),\n    getNumberFormatter({ maximumFractionDigits: 8 }).value\n  );\n});\n\nconst claimedTransactionHash = computed(() => {\n  if (!props.claims?.length) return undefined;\n  const claim = props.claims.find(\n    claim =>\n      claim.boost.id === props.boost.id && claim.chainId === props.boost.chainId\n  );\n\n  if (!claim) return undefined;\n\n  return claim.transactionHash;\n});\n\nconst rewardFormatted = computed(() => {\n  if (!props.reward) return '0';\n  const formattedUnits = formatUnits(\n    props.reward.reward,\n    props.boost.token.decimals\n  );\n  return formatNumber(\n    Number(formattedUnits),\n    getNumberFormatter({ maximumFractionDigits: 8 }).value\n  );\n});\n\nconst withdrawalAmount = computed(() => {\n  const formattedUnits = formatUnits(\n    props.boost.currentBalance,\n    props.boost.token.decimals\n  );\n  return formatNumber(\n    Number(formattedUnits),\n    getNumberFormatter({ maximumFractionDigits: 8 }).value\n  );\n});\n\nconst claimPeriodEnded = computed(() => {\n  return Number(props.boost.end) < Date.now() / 1000;\n});\n\nconst isOwner = computed(() => {\n  return (\n    toChecksumAddress(props.boost.owner) ===\n    toChecksumAddress(web3Account.value)\n  );\n});\n\nconst isLottery = computed(() => {\n  return props.boost.strategy.distribution.type === 'lottery';\n});\n\nconst amountPerWinner = computed(() => {\n  if (!isLottery.value) return;\n  const amount = BigNumber.from(props.boost.poolSize).div(\n    BigNumber.from(props.boost.strategy.distribution.numWinners)\n  );\n\n  return formatNumber(\n    Number(formatUnits(amount, props.boost.token.decimals)),\n    getNumberFormatter({ maximumFractionDigits: 8 }).value\n  );\n});\n\nconst lotteryNoRewardFinal = computed(() => {\n  return isLottery.value && !props.reward && isFinal.value;\n});\n\nconst lotteryLimit = computed(() => {\n  if (!isLottery || !props.boost.strategy.distribution.limit) return 0;\n  return Number(props.boost.strategy.distribution.limit) / 100;\n});\n\nconst weightedLimit = computed(() => {\n  if (isLottery.value || !props.boost.strategy.distribution.limit) return 0;\n  return formatUnits(\n    props.boost.strategy.distribution.limit,\n    props.boost.token.decimals\n  );\n});\n\nconst boostNetworkInfo = computed(() => {\n  return networks?.[props.boost.chainId];\n});\n\nasync function withdraw(boost: BoostSubgraph) {\n  try {\n    loadingWithdraw.value = true;\n    const tx = await withdrawAndBurn(\n      auth.web3,\n      boost.chainId,\n      boost.id,\n      web3Account.value\n    );\n    await tx.wait();\n    emit('reload');\n  } catch (e) {\n    console.error('Error withdrawing boost', e);\n  } finally {\n    loadingWithdraw.value = false;\n  }\n}\n\nasync function loadWinners() {\n  if (!isFinal.value || !isLottery.value) return;\n  loadingWinners.value = true;\n  winnersError.value = false;\n  lotteryEpochNotFinalized.value = false;\n\n  try {\n    const response = await getWinners(\n      props.boost.strategy.proposal,\n      props.boost.id,\n      props.boost.chainId\n    );\n\n    lotteryWinners.value = response.winners;\n    lotteryPrize.value = response.prize;\n  } catch (e: any) {\n    console.error('Error loading winners', e);\n    if (\n      e.message === 'epoch is not finalized' ||\n      e.message === 'failed to parse epoch'\n    ) {\n      lotteryEpochNotFinalized.value = true;\n    } else {\n      winnersError.value = true;\n    }\n  } finally {\n    loadingWinners.value = false;\n  }\n}\n\nwatch(\n  isFinal,\n  async () => {\n    loadWinners();\n  },\n  { immediate: true }\n);\n\nconst { pause } = useIntervalFn(() => {\n  if (!isFinal.value) return;\n  const proposalEnd = Number(props.proposal.end);\n  const twentyMinutes = 20 * 60;\n  minutesUntilEpochEnd.value = Math.floor(\n    (proposalEnd + twentyMinutes - Date.now() / 1000) / 60\n  );\n\n  if (minutesUntilEpochEnd.value <= 2) pause();\n}, 1000);\n</script>\n\n<template>\n  <div>\n    <div\n      class=\"border border-[--border-color-soft] rounded-xl p-[12px]\"\n      :class=\"[\n        {\n          'border-boost/30 bg-boost/5': !hasClaimed && isEligible\n        },\n        { 'border-green/30 bg-green/5': hasClaimed }\n      ]\"\n    >\n      <div class=\"flex justify-between relative\">\n        <div class=\"w-full\">\n          <div class=\"text-skin-heading flex flex-wrap -mt-1 pr-5\">\n            <div class=\"whitespace-nowrap mt-1 mr-1 flex items-center\">\n              <template v-if=\"boost.strategy.eligibility.type === 'prediction'\">\n                Anyone who votes for the winning choice\n              </template>\n              <template v-else-if=\"boost.strategy.eligibility.choice !== null\">\n                Who votes\n                <div>\n                  <TuneTag\n                    :label=\"\n                      proposal.choices[\n                        Number(boost.strategy.eligibility.choice) - 1\n                      ]\n                    \"\n                    class=\"text-skin-heading ml-1\"\n                  />\n                </div>\n              </template>\n              <template v-else> Anyone who votes </template>\n            </div>\n            <div\n              v-if=\"boost.strategy.distribution.type === 'weighted'\"\n              class=\"whitespace-nowrap mt-1 mr-1 flex items-center\"\n            >\n              shares a pool of\n              <div>\n                <TuneTag\n                  :label=\"`${boostBalanceFormatted} ${boost.token.symbol}`\"\n                  class=\"text-skin-heading ml-1\"\n                />\n              </div>\n            </div>\n            <div v-else-if=\"isLottery\" class=\"mt-1 mr-1 flex items-center\">\n              can win\n              <div>\n                <TuneTag\n                  v-tippy=\"{\n                    content: `The pool of ${boostBalanceFormatted} ${boost.token.symbol} will be equally distributed among ${boost.strategy.distribution.numWinners} winners. Chances of winning are proportional to the amount of voting-power. If the maximum amount of winners is not reached, the reward will be increased equally.`\n                  }\"\n                  :label=\"`${amountPerWinner} ${props.boost.token.symbol}`\"\n                  class=\"text-skin-heading ml-1 cursor-help\"\n                />\n              </div>\n            </div>\n            <div class=\"whitespace-nowrap mt-1 mr-1 flex items-center\">\n              <span v-if=\"isLottery\" class=\"mr-1\"> chances </span>\n              based on\n              <div>\n                <TuneTag label=\"Voting power\" class=\"text-skin-heading ml-1\" />\n              </div>\n            </div>\n            <div\n              v-if=\"boost.strategy.distribution.limit\"\n              class=\"flex items-center gap-1 mt-1 whitespace-nowrap\"\n            >\n              with a max\n              <div v-if=\"isLottery\">\n                chance of\n                <TuneTag\n                  v-tippy=\"{\n                    content:\n                      'If there are not enough voters, this limit will not be enforced and the chance to win will be increased proportionally to voting power.',\n                    delay: 100\n                  }\"\n                  :label=\"`${lotteryLimit}%`\"\n                  class=\"text-skin-heading cursor-help\"\n                />\n              </div>\n              <div v-else>\n                reward of\n                <TuneTag\n                  v-tippy=\"{\n                    content:\n                      'The maximum amount of tokens that can be distributed to each voter. If the pool is larger than the total reward for eligible voters, the reward will increase proportionally.'\n                  }\"\n                  :label=\"`${weightedLimit} ${boost.token.symbol}`\"\n                  class=\"text-skin-heading cursor-help\"\n                />\n              </div>\n            </div>\n          </div>\n          <div class=\"mt-1 flex items-center\">\n            <span\n              v-tippy=\"{\n                content:\n                  'Boost ID is a unique identifier for this boost on the given network.',\n                delay: 100\n              }\"\n            >\n              Boost #{{ boost.id }}\n            </span>\n            <BaseInterpunct />\n            <div class=\"flex items-center gap-2\">\n              <BaseAvatar\n                v-if=\"boostNetworkInfo?.logo\"\n                :src=\"getUrl(boostNetworkInfo.logo)\"\n                size=\"18\"\n              />\n\n              <span>\n                {{ boostNetworkInfo?.name }}\n              </span>\n            </div>\n          </div>\n          <div\n            v-if=\"claimedTransactionHash || isEligible\"\n            class=\"border w-full bg-[--border-color-faint] px-[12px] py-2 rounded-xl mt-[12px]\"\n            :class=\"[\n              {\n                'border-boost/30 bg-boost/5 text-boost':\n                  isEligible && !hasClaimed && !lotteryNoRewardFinal,\n                'border-green/30 bg-green/5 text-green': hasClaimed\n              }\n            ]\"\n          >\n            <BaseLink\n              v-if=\"claimedTransactionHash\"\n              :link=\"explorerUrl(boost.chainId, claimedTransactionHash, 'tx')\"\n              class=\"flex items-center gap-1 text-green hover:text-skin-link justify-center\"\n            >\n              <i-ho-cash class=\"text-xs\" />\n              Claimed {{ claimedAmount }} {{ boost.token.symbol }}\n            </BaseLink>\n            <div\n              v-else-if=\"loadingWinners\"\n              class=\"text-skin-heading flex justify-center h-[24px]\"\n            >\n              <TuneLoadingSpinner />\n            </div>\n            <div\n              v-else-if=\"isFinal && lotteryEpochNotFinalized\"\n              class=\"text-center\"\n            >\n              Finalizing winners. Please check back\n              <span v-if=\"minutesUntilEpochEnd > 2\">\n                in {{ minutesUntilEpochEnd }} minutes!\n              </span>\n              <span v-else> shortly!</span>\n            </div>\n            <div v-else-if=\"winnersError\" class=\"text-center\">\n              Something went wrong. Please try again later.\n            </div>\n            <div\n              v-else-if=\"!reward && isFinal && isLottery\"\n              class=\"flex flex-wrap items-center justify-center gap-x-1\"\n            >\n              <i-ho-emoji-sad class=\"text-xs\" />\n              Oops, you didn't win this time!\n              <button\n                type=\"button\"\n                class=\"text-skin-link\"\n                @click=\"openWinnersModal = true\"\n              >\n                View winners\n              </button>\n            </div>\n            <div\n              v-else-if=\"isEligible\"\n              class=\"flex flex-wrap items-center justify-center gap-x-1\"\n            >\n              <i-ho-fire class=\"text-xs\" />\n              <template v-if=\"reward && isFinal\">\n                Reward\n                {{ `${rewardFormatted} ${boost.token.symbol}` }}\n                <button\n                  v-if=\"isLottery\"\n                  type=\"button\"\n                  class=\"ml-1 text-skin-link\"\n                  @click=\"openWinnersModal = true\"\n                >\n                  View winners\n                </button>\n              </template>\n              <template v-else> You are eligible </template>\n            </div>\n          </div>\n          <BaseMessage\n            v-if=\"\n              isLottery &&\n              Number(rewardFormatted) > 0 &&\n              Number(amountPerWinner) < Number(rewardFormatted)\n            \"\n            level=\"info\"\n            class=\"border bg-[--border-color-subtle] p-3 rounded-xl text-skin-text mt-2\"\n          >\n            The reward increased due to not enough eligible voters to hit the\n            {{ boost.strategy.distribution.numWinners }} winner cap for this\n            boost.\n          </BaseMessage>\n        </div>\n        <SpaceProposalBoostItemMenu\n          :boost=\"boost\"\n          :claimed-transaction-hash=\"claimedTransactionHash\"\n          :show-winners=\"\n            !lotteryEpochNotFinalized && isLottery && !winnersError && isFinal\n          \"\n          @open-winners-modal=\"openWinnersModal = true\"\n        />\n      </div>\n      <div\n        v-if=\"isOwner\"\n        class=\"border-t border-[--border-color-soft] bg-[--border-color-faint] p-[12px] rounded-b-xl -mx-[12px] -mb-[12px] mt-[12px]\"\n        :class=\"[\n          {\n            'border-boost/20 bg-boost/5': !hasClaimed && isEligible\n          },\n          { 'border-green/20 bg-green/5': hasClaimed }\n        ]\"\n      >\n        <div v-if=\"isOwner && !claimPeriodEnded\" class=\"text-skin-heading\">\n          About your boost\n        </div>\n        <div\n          v-if=\"claimPeriodEnded\"\n          class=\"sm:flex justify-between items-center\"\n        >\n          <div class=\"leading-none\">\n            <div class=\"font-semibold text-skin-heading flex items-center\">\n              Withdraw\n              <i-ho-information-circle\n                v-tippy=\"{\n                  content:\n                    'You can withdraw your remaining unclaimed tokens and burn the boost NFT or you give voters more time to claim their rewards and withdraw your remaining tokens later.'\n                }\"\n                class=\"text-xs ml-1 text-skin-text\"\n              />\n            </div>\n            <div>\n              You have {{ withdrawalAmount }} {{ boost.token.symbol }} to\n              withdraw\n            </div>\n          </div>\n\n          <TuneButton\n            v-if=\"Number(withdrawalAmount) > 0\"\n            class=\"h-5 px-[12px] text-skin-link bg-skin-bg w-full sm:w-auto mt-2 sm:mt-0\"\n            :loading=\"loadingWithdraw\"\n            @click=\"withdraw(boost)\"\n          >\n            Withdraw {{ withdrawalAmount }} {{ boost.token.symbol }}\n          </TuneButton>\n        </div>\n        <div v-else class=\"md:flex items-center\">\n          <div>\n            Amount remaining:\n            <span class=\"text-skin-heading\">\n              {{ withdrawalAmount }} {{ boost.token.symbol }}\n            </span>\n          </div>\n          <BaseInterpunct class=\"hidden md:block\" />\n          <div class=\"mt-[2px] md:mt-0\">\n            Withdrawable in:\n            <span class=\"text-skin-heading\">\n              {{\n                formatDuration(\n                  Number(boost.end) - Math.floor(Date.now() / 1000)\n                )\n              }}\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <SpaceProposalBoostWinnersModal\n      :open=\"openWinnersModal\"\n      :boost=\"boost\"\n      :winners=\"lotteryWinners\"\n      :prize=\"lotteryPrize\"\n      @close=\"openWinnersModal = false\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalBoostItemMenu.vue",
    "content": "<script setup lang=\"ts\">\nimport { BoostSubgraph } from '@/helpers/boost/types';\nimport { explorerUrl, openProfile } from '@/helpers/utils';\n\nconst props = defineProps<{\n  boost: BoostSubgraph;\n  claimedTransactionHash?: string;\n  showWinners?: boolean;\n}>();\n\nconst emit = defineEmits(['openWinnersModal']);\n\nconst { domain } = useApp();\nconst router = useRouter();\n\nfunction handleAction(action: string) {\n  if (action === 'viewEtherscan') {\n    window.open(\n      explorerUrl(props.boost.chainId, props.boost.transaction, 'tx'),\n      '_blank'\n    );\n  } else if (action === 'seeCreatorProfile') {\n    openProfile(props.boost.owner, domain, router);\n  } else if (action === 'viewClaimEtherscan') {\n    window.open(\n      explorerUrl(props.boost.chainId, props.claimedTransactionHash!, 'tx'),\n      '_blank'\n    );\n  } else if (action === 'viewWinners') {\n    emit('openWinnersModal');\n  }\n}\n\nconst items = computed(() => {\n  const itemList: any = [\n    { text: 'View boost', action: 'viewEtherscan', extras: { external: true } }\n  ];\n\n  if (props.claimedTransactionHash) {\n    itemList.push({\n      text: 'View claim ',\n      action: 'viewClaimEtherscan',\n      extras: { external: true }\n    });\n  }\n\n  if (props.showWinners) {\n    itemList.push({ text: 'View winners', action: 'viewWinners' });\n  }\n\n  itemList.push({ text: 'Creator profile', action: 'seeCreatorProfile' });\n\n  return itemList;\n});\n</script>\n\n<template>\n  <BaseMenu :items=\"items\" @select=\"handleAction\">\n    <template #button>\n      <BaseButtonIcon class=\"absolute right-[-4px] top-[-5px]\">\n        <i-ho-dots-horizontal class=\"text-[18px]\" />\n      </BaseButtonIcon>\n    </template>\n    <template #item=\"{ item }\">\n      <div class=\"flex items-center gap-2\">\n        <span>{{ item.text }}</span>\n        <i-ho-external-link v-if=\"item.extras?.external\" class=\"text-xs\" />\n      </div>\n    </template>\n  </BaseMenu>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalBoostModalCreate.vue",
    "content": "<script setup lang=\"ts\">\nimport { useStorage } from '@vueuse/core';\n\ndefineProps<{\n  open: boolean;\n  size?: { width: string; height: string };\n}>();\n\ndefineEmits(['close', 'start']);\n\nconst dontShowAgain = useStorage(\n  'snapshot.boosts-modal-dont-show-again',\n  false\n);\n\nconst content = [\n  {\n    id: 1,\n    title: 'How does it work?',\n    description:\n      'When you create a boost, you specify the amount of tokens you want to deposit and the rules for distribution. After the boost was created you receive an NFT from the Boost contract, this represents the ownership of the boost and can be used to withdraw unclaimed rewards.'\n  },\n  {\n    id: 2,\n    title: 'Distribution',\n    description:\n      'Rewards are distributed either proportionally to voting power or through a lottery system. The lottery system is weighted by voting power, meaning the more voting power, the higher your chances of winning.'\n  },\n  {\n    id: 3,\n    title: 'Disclaimer',\n    description:\n      'Boosts are a new and experimental feature and currently not audited. Use at your own risk.'\n  }\n];\n</script>\n\n<template>\n  <TuneModal :open=\"open\" size=\"big\" @close=\"$emit('close')\">\n    <TuneModalTitle as=\"h1\" class=\"md:hidden text-lg px-[20px] py-3 leading-6\"\n      >Welcome to Boost!</TuneModalTitle\n    >\n    <div class=\"md:flex\">\n      <div\n        class=\"h-[160px] md:h-[506px] md:w-[240px] shrink-0 bg-boost relative\"\n      >\n        <div\n          class=\"md:bg-[url('@/assets/images/stars-big.svg')] bg-right bg-[url('@/assets/images/stars-big-horizontal.svg')] md:bg-cover absolute top-0 left-0 right-0 bottom-0\"\n        />\n        <div class=\"flex justify-center items-center h-full\">\n          <div\n            class=\"w-[80px] h-[80px] bg-skin-bg z-50 rounded-3xl flex items-center justify-center shadow-xl\"\n          >\n            <i-s-boost-icon class=\"text-[30px] text-skin-link\" />\n          </div>\n        </div>\n      </div>\n\n      <div class=\"flex flex-col justify-between\">\n        <div\n          class=\"p-[20px] md:pb-0 md:p-[32px] overflow-y-auto max-h-[calc(100vh-330px)] md:max-h-[410px]\"\n        >\n          <TuneModalTitle as=\"h1\" class=\"hidden md:block leading-7 mb-4\"\n            >Welcome to Boost!</TuneModalTitle\n          >\n          <TuneModalDescription class=\"text-lg leading-6 mb-5\">\n            Boosts are a powerful way to incentivize voting on proposals by\n            rewarding active participation or specific actions.\n          </TuneModalDescription>\n          <div class=\"space-y-5\">\n            <div v-for=\"(c, i) in content\" :key=\"i\">\n              <h4 class=\"leading-5 mb-2\">{{ c.title }}</h4>\n              <p class=\"text-md leading-5\">{{ c.description }}</p>\n              <p v-if=\"c.id === 1\" class=\"text-md leading-5 mt-3\">\n                <BaseLink link=\"https://docs.snapshot.org/user-guides/boost\"\n                  >Learn more</BaseLink\n                >\n              </p>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"flex justify-between items-center border-t md:border-0 px-[20px] py-3 md:p-[32px] md:!pt-0\"\n        >\n          <TuneCheckbox\n            id=\"dont-show-again\"\n            v-model=\"dontShowAgain\"\n            class=\"text-sm\"\n            hint=\"Don't show this again\"\n          />\n          <TuneButton @click=\"$emit('start')\"> Get started </TuneButton>\n        </div>\n      </div>\n    </div>\n  </TuneModal>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalBoostWinnersModal.vue",
    "content": "<script setup lang=\"ts\">\nimport { BoostSubgraph } from '@/helpers/boost/types';\nimport { formatUnits } from '@ethersproject/units';\n\nconst props = defineProps<{\n  open: boolean;\n  boost: BoostSubgraph;\n  winners: string[];\n  prize: string;\n}>();\n\ndefineEmits(['close']);\n\nconst { web3Account } = useWeb3();\nconst { formatNumber, getNumberFormatter } = useIntl();\nconst { loadProfiles, profiles } = useProfiles();\n\nconst searchInput = ref('');\n\nconst formattedPrize = computed(() => {\n  if (!props.prize) return '0';\n  const formattedPrize = formatUnits(props.prize, props.boost.token.decimals);\n  return formatNumber(\n    +formattedPrize,\n    getNumberFormatter({ maximumFractionDigits: 8 }).value\n  );\n});\n\nconst sortedWinners = computed(() => {\n  if (!props.winners) return [];\n  return props.winners\n    .filter(winner =>\n      winner.toLowerCase().includes(searchInput.value.toLowerCase())\n    )\n    .sort((a, b) => {\n      if (a.toLowerCase() === web3Account.value.toLowerCase()) return -1;\n      if (b.toLowerCase() === web3Account.value.toLowerCase()) return 1;\n      return 0;\n    });\n});\n\nwatch(\n  () => props.winners,\n  () => {\n    if (!props.winners) return;\n    loadProfiles(props.winners);\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <TuneModal :open=\"open\" @close=\"$emit('close')\">\n    <TuneModalTitle as=\"h4\" class=\"flex items-center gap-1 m-3 mb-2\">\n      Winners\n      <BaseCounter v-if=\"winners\" :counter=\"winners.length\" />\n    </TuneModalTitle>\n    <BaseSearch\n      v-model=\"searchInput\"\n      placeholder=\"Search by address\"\n      modal\n      class=\"!pl-3 pr-[12px]\"\n    />\n    <div\n      class=\"p-3 space-y-2 max-h-[calc(100vh-130px)] md:max-h-[488px] overflow-y-auto\"\n    >\n      <div\n        v-if=\"winners.length === 0\"\n        class=\"pt-3 flex flex-col items-center text-center\"\n      >\n        <i-ho-emoji-sad class=\"mb-1\" />\n        No winners due to no eligible votes.\n      </div>\n      <BaseNoResults v-else-if=\"sortedWinners.length === 0\" class=\"!py-0\" />\n      <template v-else>\n        <div v-for=\"winner in sortedWinners\" :key=\"winner\">\n          <div class=\"flex justify-between\">\n            <BaseUser :address=\"winner\" :profile=\"profiles[winner]\" />\n            {{ formattedPrize }}\n            {{ props.boost.token.symbol }}\n          </div>\n        </div>\n      </template>\n    </div>\n    <BaseMessage\n      level=\"info\"\n      class=\"p-3 border bg-[--border-color-subtle] rounded-xl mx-3 mb-3\"\n    >\n      Wondering how the winners are chosen? Check out\n      <BaseLink link=\"https://docs.snapshot.org/user-guides/boost\"\n        >the docs</BaseLink\n      >\n    </BaseMessage>\n    <div class=\"px-3 pb-3\">\n      <TuneButton class=\"w-full\" @click=\"$emit('close')\"> Close </TuneButton>\n    </div>\n  </TuneModal>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalContent.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace, Proposal } from '@/helpers/interfaces';\ndefineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n}>();\n\nconst showFullMarkdownBody = ref(false);\n\n// Scroll to top of the page after clicking \"Show less\" button\nwatch(showFullMarkdownBody, () => {\n  if (!showFullMarkdownBody.value) window.scrollTo(0, 0);\n});\n\n// Ref to the proposal body element\nconst markdownBody = ref<HTMLElement | null>(null);\n\n// Detect if the proposal body is too long and should be shortened\nconst truncateMarkdownBody = computed(() => {\n  const markdownBodyHeight = markdownBody.value?.clientHeight\n    ? markdownBody.value.clientHeight\n    : 0;\n  return markdownBodyHeight > 580;\n});\n</script>\n\n<template>\n  <div v-if=\"proposal.body.length\" class=\"relative\">\n    <div\n      v-if=\"!showFullMarkdownBody && truncateMarkdownBody\"\n      class=\"absolute bottom-0 h-[80px] w-full bg-gradient-to-t from-skin-bg\"\n    />\n    <div\n      v-if=\"truncateMarkdownBody\"\n      class=\"absolute flex w-full justify-center\"\n      :class=\"{\n        '-bottom-[64px]': showFullMarkdownBody,\n        '-bottom-[14px]': !showFullMarkdownBody\n      }\"\n    >\n      <TuneButton\n        class=\"z-10 !bg-skin-bg flex items-center gap-2 !pr-[18px]\"\n        @click=\"showFullMarkdownBody = !showFullMarkdownBody\"\n      >\n        {{ showFullMarkdownBody ? 'View less' : 'View more' }}\n        <i-ho-arrow-sm-up v-if=\"showFullMarkdownBody\" />\n        <i-ho-arrow-sm-down v-else />\n      </TuneButton>\n    </div>\n    <div\n      class=\"overflow-hidden\"\n      :class=\"{\n        'h-[600px]': !showFullMarkdownBody && truncateMarkdownBody,\n        'mb-[92px]': showFullMarkdownBody,\n        'mb-[56px]': !showFullMarkdownBody\n      }\"\n    >\n      <div ref=\"markdownBody\">\n        <BaseMarkdown\n          :body=\"proposal.body\"\n          data-testid=\"proposal-page-content\"\n        />\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalHeader.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace, Proposal } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n  isAdmin: boolean;\n  isModerator: boolean;\n}>();\n\nconst router = useRouter();\n\nconst { t } = useI18n();\nconst { send, isSending } = useClient();\nconst { removeSpaceProposal } = useProposals();\nconst { notify } = useFlashNotification();\nconst { web3Account } = useWeb3();\n\nconst isCreator = computed(() => props.proposal?.author === web3Account.value);\n\nconst threeDotItems = computed(() => {\n  const items: { text: string; action: string }[] = [];\n  if (isCreator.value && props.proposal.state === 'pending')\n    items.push({ text: t('edit'), action: 'edit' });\n  items.push({ text: t('duplicate'), action: 'duplicate' });\n\n  if ((props.isAdmin || props.isModerator) && !props.proposal.flagged) {\n    items.push({ text: t('flag'), action: 'flag' });\n  } else {\n    items.push({ text: t('report'), action: 'report' });\n  }\n  if (props.isAdmin || props.isModerator || isCreator.value)\n    items.push({ text: t('delete'), action: 'delete' });\n  return items;\n});\n\nasync function deleteProposal() {\n  const result = await send(props.space, 'delete-proposal', {\n    proposal: props.proposal\n  });\n  console.log('Result', result);\n  if (result.id) {\n    removeSpaceProposal(props.proposal.id);\n    notify(['green', t('notify.proposalDeleted')]);\n    router.push({ name: 'spaceProposals' });\n  }\n}\n\nconst {\n  shareProposalX,\n  shareProposalHey,\n  shareToClipboard,\n  shareProposal,\n  sharingIsSupported,\n  sharingItems\n} = useSharing();\n\nconst { resetForm } = useFormSpaceProposal();\n\nasync function handleSelect(e) {\n  if (!props.proposal) return;\n  if (e === 'delete') deleteProposal();\n  if (e === 'report') window.open('https://tally.so/r/mDBEGb', '_blank');\n  if (e === 'flag') {\n    await send(props.space, 'flag-proposal', {\n      proposal: props.proposal\n    });\n  }\n  if (e === 'duplicate' || e === 'edit') {\n    resetForm();\n    router.push({\n      name: 'spaceCreate',\n      params: {\n        key: props.proposal.space.id,\n        sourceProposal: props.proposal.id\n      },\n      query: { editing: e === 'edit' ? 'true' : undefined }\n    });\n  }\n}\n\nfunction handleSelectShare(e: string) {\n  if (e === 'shareProposalHey')\n    return shareProposalHey(props.space, props.proposal);\n\n  if (sharingIsSupported.value)\n    return shareProposal(props.space, props.proposal);\n\n  if (e === 'shareProposalX')\n    return shareProposalX(props.space, props.proposal);\n\n  if (e === 'shareToClipboard')\n    return shareToClipboard(props.space, props.proposal);\n}\n\nconst { profiles, loadProfiles } = useProfiles();\n\nwatch(\n  () => props.proposal,\n  () => {\n    if (!props.proposal) return;\n    loadProfiles([props.proposal.author]);\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <h1\n    class=\"break-words text-xl leading-8 sm:leading-[44px] sm:text-2xl\"\n    data-testid=\"proposal-page-title\"\n    v-text=\"proposal.title\"\n  />\n\n  <div class=\"mb-4 flex\">\n    <div class=\"flex items-center space-x-1\">\n      <LinkSpace :space-id=\"space.id\" class=\"group text-skin-text\">\n        <div class=\"flex items-center\">\n          <AvatarSpace :space=\"space\" size=\"20\" />\n          <span class=\"ml-1 group-hover:text-skin-link\" v-text=\"space.name\" />\n        </div>\n      </LinkSpace>\n      <span v-text=\"$t('proposalBy')\" />\n      <BaseUser\n        :address=\"proposal.author\"\n        :profile=\"profiles[proposal.author]\"\n        :space=\"space\"\n        :proposal=\"proposal\"\n        hide-avatar\n      />\n    </div>\n    <div class=\"flex grow items-center space-x-3\">\n      <BaseMenu\n        class=\"!ml-auto pl-3\"\n        :items=\"sharingItems\"\n        @select=\"handleSelectShare\"\n      >\n        <template #button>\n          <ButtonShare />\n        </template>\n        <template #item=\"{ item }\">\n          <div class=\"flex items-center gap-2\">\n            <i-s-x v-if=\"item.extras.icon === 'x'\" />\n            <i-s-hey v-if=\"item.extras.icon === 'hey'\" class=\"mr-1 text-sm\" />\n            <i-ho-link v-if=\"item.extras.icon === 'link'\" />\n            {{ item.text }}\n          </div>\n        </template>\n      </BaseMenu>\n      <BaseMenu :items=\"threeDotItems\" @select=\"handleSelect\">\n        <template #button>\n          <div>\n            <BaseButtonIcon :loading=\"isSending\" class=\"!p-0\">\n              <i-ho-dots-horizontal />\n            </BaseButtonIcon>\n          </div>\n        </template>\n        <template #item=\"{ item }\">\n          <div class=\"flex items-center gap-2\">\n            <i-ho-pencil v-if=\"item.action === 'edit'\" />\n            <i-ho-document-duplicate v-if=\"item.action === 'duplicate'\" />\n            <i-ho-flag\n              v-if=\"item.action === 'report' || item.action === 'flag'\"\n            />\n            <i-ho-trash v-if=\"item.action === 'delete'\" />\n            {{ item.text }}\n          </div>\n        </template>\n      </BaseMenu>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalInformation.vue",
    "content": "<script setup lang=\"ts\">\nimport { explorerUrl, getIpfsUrl } from '@/helpers/utils';\nimport { ExtendedSpace, Proposal, SpaceStrategy } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n  strategies: SpaceStrategy[];\n}>();\n\nconst isModalStrategiesOpen = ref(false);\n\nconst { formatRelativeTime, formatNumber } = useIntl();\n\nconst symbols = computed((): string[] =>\n  props.strategies.map(strategy => (strategy.params.symbol as string) || '')\n);\n</script>\n\n<template>\n  <TuneBlock>\n    <template #header>\n      <TuneBlockHeader :title=\"$t('information')\" />\n    </template>\n    <div class=\"space-y-1\">\n      <div>\n        <b>{{ $t('strategies') }}</b>\n        <span\n          class=\"float-right flex cursor-pointer text-skin-link\"\n          @click=\"isModalStrategiesOpen = true\"\n        >\n          <span\n            v-for=\"(symbol, symbolIndex) of symbols.slice(0, 5)\"\n            :key=\"symbol\"\n            class=\"flex\"\n          >\n            <AvatarSpace :space=\"space\" :symbol-index=\"symbolIndex\" />\n            <span v-show=\"symbolIndex !== symbols.length - 1\" class=\"ml-1\" />\n          </span>\n        </span>\n      </div>\n\n      <div>\n        <b>IPFS</b>\n        <BaseLink :link=\"getIpfsUrl(proposal.ipfs)\" class=\"float-right\">\n          #{{ proposal.ipfs.slice(0, 7) }}\n        </BaseLink>\n      </div>\n      <div>\n        <b>{{ $t('proposal.votingSystem') }}</b>\n        <span class=\"float-right text-skin-link\">\n          {{ $t(`voting.${proposal.type}.label`) }}\n        </span>\n      </div>\n      <div v-if=\"proposal.privacy\">\n        <b>{{ $t('proposal.privacy') }}</b>\n        <BaseLink\n          v-tippy=\"{ content: $t(`privacy.${proposal.privacy}.tooltip`) }\"\n          :link=\"$t(`privacy.${proposal.privacy}.url`)\"\n          class=\"float-right cursor-pointer text-skin-link\"\n        >\n          {{ $t(`privacy.${proposal.privacy}.label`) }}\n        </BaseLink>\n      </div>\n      <div>\n        <b>{{ $t('proposal.startDate') }}</b>\n        <span\n          v-tippy=\"{\n            content: formatRelativeTime(proposal.start)\n          }\"\n          class=\"float-right text-skin-link\"\n          v-text=\"$d(proposal.start * 1e3, 'short', 'en-US')\"\n        />\n      </div>\n      <div>\n        <b>{{ $t('proposal.endDate') }}</b>\n        <span\n          v-tippy=\"{\n            content: formatRelativeTime(proposal.end)\n          }\"\n          class=\"float-right text-skin-link\"\n          v-text=\"$d(proposal.end * 1e3, 'short', 'en-US')\"\n        />\n      </div>\n      <div>\n        <b>{{ $t('snapshot') }}</b>\n        <BaseLink\n          :link=\"explorerUrl(proposal.network, proposal.snapshot, 'block')\"\n          class=\"float-right\"\n        >\n          {{ formatNumber(Number(proposal.snapshot)) }}\n        </BaseLink>\n      </div>\n    </div>\n  </TuneBlock>\n  <teleport to=\"#modal\">\n    <ModalStrategies\n      :open=\"isModalStrategiesOpen\"\n      :proposal=\"proposal\"\n      :strategies=\"strategies\"\n      @close=\"isModalStrategiesOpen = false\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalPage.vue",
    "content": "<script setup lang=\"ts\">\nimport voting from '@snapshot-labs/snapshot.js/src/voting';\nimport { ExtendedSpace, Proposal, Results } from '@/helpers/interfaces';\nimport { BOOST_ENABLED_VOTING_TYPES } from '@/helpers/constants';\n\nconst props = defineProps<{ space: ExtendedSpace; proposal: Proposal }>();\nconst emit = defineEmits(['reload-proposal']);\n\nuseMeta({\n  title: {\n    key: 'metaInfo.space.proposal.title',\n    params: {\n      space: props.space.name,\n      proposal: props.proposal.title\n    }\n  },\n  description: {\n    key: 'metaInfo.space.proposal.description',\n    params: {\n      body: props.proposal.body.slice(0, 160)\n    }\n  }\n});\n\nconst route = useRoute();\nconst { web3, web3Account } = useWeb3();\nconst { modalEmailOpen } = useModal();\nconst { isMessageVisible, setMessageVisibility } = useFlaggedMessageStatus(\n  route.params.id as string\n);\n\nconst proposalId: string = route.params.id as string;\n\nconst modalOpen = ref(false);\nconst selectedChoices = ref<any>(null);\nconst loadedResults = ref(false);\nconst results = ref<Results | null>(null);\nconst waitingForSigners = ref(false);\n\nconst isAdmin = computed(() => {\n  const admins = (props.space.admins || []).map(admin => admin.toLowerCase());\n  return admins.includes(web3Account.value?.toLowerCase());\n});\n\nconst isModerator = computed(() => {\n  const moderators = (props.space.moderators || []).map(moderator =>\n    moderator.toLowerCase()\n  );\n  return moderators.includes(web3Account.value?.toLowerCase());\n});\n\nconst strategies = computed(\n  // Needed for older proposal that are missing strategies\n  () => props.proposal?.strategies ?? props.space.strategies\n);\n\nconst boostEnabled = computed(() => {\n  return (\n    BOOST_ENABLED_VOTING_TYPES.includes(props.proposal.type) &&\n    props.space.boost.enabled\n  );\n});\n\nconst { modalAccountOpen, isModalPostVoteOpen } = useModal();\nconst { modalTermsOpen, termsAccepted, acceptTerms } = useTerms(props.space.id);\n\nfunction clickVote() {\n  !web3.value.account\n    ? (modalAccountOpen.value = true)\n    : !termsAccepted.value && props.space.terms\n      ? (modalTermsOpen.value = true)\n      : (modalOpen.value = true);\n}\n\nfunction reloadProposal() {\n  emit('reload-proposal');\n}\n\nfunction openPostVoteModal(isWaitingForSigners: boolean) {\n  waitingForSigners.value = isWaitingForSigners;\n  isModalPostVoteOpen.value = true;\n}\n\nasync function loadResults() {\n  if (props.proposal.scores.length === 0) {\n    const votingClass = new voting[props.proposal.type](\n      props.proposal,\n      [],\n      strategies.value\n    );\n    results.value = {\n      scores: votingClass.getScores(),\n      scoresByStrategy: votingClass.getScoresByStrategy(),\n      scoresTotal: votingClass.getScoresTotal()\n    };\n  } else {\n    results.value = {\n      scores: props.proposal.scores,\n      scoresByStrategy: props.proposal.scores_by_strategy,\n      scoresTotal: props.proposal.scores_total\n    };\n  }\n  loadedResults.value = true;\n}\n\nfunction handleChoiceQuery() {\n  const choice = route.query.choice as string;\n  if (web3Account.value && choice && props.proposal.state === 'active') {\n    selectedChoices.value = parseInt(choice);\n    clickVote();\n  }\n}\n\nwatch(\n  web3Account,\n  () => {\n    handleChoiceQuery();\n  },\n  { immediate: true }\n);\n\nwatch(\n  () => props.proposal,\n  () => loadResults(),\n  { immediate: true }\n);\n\nonMounted(() => setMessageVisibility(props.proposal.flagged));\n</script>\n\n<template>\n  <SpaceBreadcrumbs :space=\"space\" :proposal=\"proposal\" />\n  <TheLayout v-bind=\"$attrs\" class=\"mt-[20px]\">\n    <template #content-left>\n      <MessageWarningFlagged\n        v-if=\"isMessageVisible\"\n        type=\"proposal\"\n        responsive\n        @force-show=\"setMessageVisibility(false)\"\n      />\n\n      <template v-else>\n        <div class=\"px-[20px] md:px-0\">\n          <LabelProposalState :state=\"proposal.state\" class=\"mb-[12px]\" />\n\n          <SpaceProposalHeader\n            :space=\"space\"\n            :proposal=\"proposal\"\n            :is-admin=\"isAdmin\"\n            :is-moderator=\"isModerator\"\n          />\n          <SpaceProposalContent :space=\"space\" :proposal=\"proposal\" />\n        </div>\n        <div class=\"space-y-[20px] md:space-y-4 px-[20px] md:px-0\">\n          <div v-if=\"proposal?.discussion\">\n            <BlockLink\n              :link=\"proposal.discussion\"\n              data-testid=\"proposal-page-discussion-link\"\n            >\n              <template #title>\n                <h3 v-text=\"$t('discussion')\" />\n              </template>\n            </BlockLink>\n          </div>\n\n          <SpaceProposalVote\n            v-model=\"selectedChoices\"\n            :proposal=\"proposal\"\n            @open=\"modalOpen = true\"\n            @click-vote=\"clickVote\"\n          />\n\n          <SpaceProposalBoost\n            v-if=\"boostEnabled\"\n            :proposal=\"proposal\"\n            :space=\"space\"\n          />\n\n          <SpaceProposalVotes :space=\"space\" :proposal=\"proposal\" />\n\n          <SpaceProposalPlugins\n            v-if=\"Object.keys(space.plugins).length && loadedResults && results\"\n            :id=\"proposalId\"\n            :space=\"space\"\n            :proposal=\"proposal\"\n            :results=\"results\"\n            :loaded-results=\"loadedResults\"\n            :strategies=\"strategies\"\n          />\n        </div>\n      </template>\n    </template>\n    <template #sidebar-right>\n      <div\n        v-if=\"!isMessageVisible\"\n        class=\"mt-[20px] lg:space-y-3 space-y-[20px] lg:mt-0 px-[20px] md:px-0\"\n      >\n        <SpaceProposalInformation\n          :space=\"space\"\n          :proposal=\"proposal\"\n          :strategies=\"strategies\"\n        />\n        <SpaceProposalResults\n          :loaded=\"loadedResults\"\n          :space=\"space\"\n          :proposal=\"proposal\"\n          :results=\"results\"\n          :strategies=\"strategies\"\n          :is-admin=\"isAdmin\"\n          @reload=\"reloadProposal\"\n        />\n        <SpaceProposalPluginsSidebar\n          v-if=\"Object.keys(space.plugins).length && loadedResults && results\"\n          :id=\"proposalId\"\n          :space=\"space\"\n          :proposal=\"proposal\"\n          :results=\"results\"\n          :loaded-results=\"loadedResults\"\n          :strategies=\"strategies\"\n        />\n      </div>\n    </template>\n  </TheLayout>\n  <teleport to=\"#modal\">\n    <ModalVote\n      :open=\"modalOpen\"\n      :space=\"space\"\n      :proposal=\"proposal\"\n      :selected-choices=\"selectedChoices\"\n      :strategies=\"strategies\"\n      @close=\"modalOpen = false\"\n      @reload=\"reloadProposal\"\n      @open-post-vote-modal=\"openPostVoteModal\"\n    />\n    <ModalTerms\n      :open=\"modalTermsOpen\"\n      :space=\"space\"\n      :action=\"$t('modalTerms.actionVote')\"\n      @close=\"modalTermsOpen = false\"\n      @accept=\"acceptTerms(), (modalOpen = true)\"\n    />\n    <ModalPostVote\n      :open=\"isModalPostVoteOpen\"\n      :space=\"space\"\n      :proposal=\"proposal\"\n      :selected-choices=\"selectedChoices\"\n      :waiting-for-signers=\"waitingForSigners\"\n      @close=\"isModalPostVoteOpen = false\"\n      @subscribe-email=\"modalEmailOpen = true\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalPlugins.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace, Proposal, Results } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  id: string;\n  proposal: Proposal;\n  space: ExtendedSpace;\n  results: Results;\n  loadedResults: boolean;\n  strategies: Record<string, any>[];\n}>();\n\nconst { getPluginComponents } = usePlugins();\nconst components = getPluginComponents(\n  'Proposal',\n  Object.keys(props.space.plugins)\n);\n\nconst showPlugin = computed(() => {\n  if (props.space.plugins.hasOwnProperty('oSnap'))\n    return props.proposal.plugins.hasOwnProperty('oSnap');\n  else return true;\n});\n</script>\n\n<template>\n  <div v-if=\"showPlugin\">\n    <component\n      :is=\"component\"\n      v-for=\"(component, key) in components\"\n      :key=\"key\"\n      v-bind=\"props\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalPluginsSidebar.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  ExtendedSpace,\n  Proposal,\n  Results,\n  SpaceStrategy\n} from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  id: string;\n  proposal: Proposal;\n  space: ExtendedSpace;\n  results: Results;\n  loadedResults: boolean;\n  strategies: SpaceStrategy[];\n}>();\n\nconst { getPluginComponents } = usePlugins();\nconst components = getPluginComponents(\n  'ProposalSidebar',\n  Object.keys(props.space.plugins)\n);\n</script>\n\n<template>\n  <component\n    :is=\"component\"\n    v-for=\"(component, key) in components\"\n    :key=\"key\"\n    v-bind=\"props\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalResults.vue",
    "content": "<script setup lang=\"ts\">\nimport { SNAPSHOT_HELP_LINK } from '@/helpers/constants';\nimport {\n  ExtendedSpace,\n  Proposal,\n  Results,\n  SpaceStrategy\n} from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n  results: Results | null;\n  strategies: SpaceStrategy[];\n  loaded: boolean;\n  isAdmin: boolean;\n}>();\n\nconst emit = defineEmits(['reload']);\n\nconst refreshScores = async () => {\n  try {\n    const response = await fetch(\n      `${import.meta.env.VITE_HUB_URL}/api/scores/${props.proposal.id}`\n    );\n\n    const result = await response.json();\n\n    if (result.result === true) {\n      emit('reload');\n    }\n  } catch (e) {}\n};\n\nconst isInvalidScore = computed(\n  () =>\n    props.proposal?.scores_state === 'invalid' &&\n    props.proposal.state === 'closed'\n);\n\nconst isPendingScore = computed(\n  () =>\n    props.proposal?.scores_state === 'pending' &&\n    props.proposal.state === 'closed'\n);\n\nonMounted(() => {\n  if (isPendingScore.value) {\n    refreshScores();\n  }\n});\n</script>\n\n<template>\n  <TuneBlock :loading=\"!loaded\">\n    <template #header>\n      <TuneBlockHeader\n        :title=\"\n          proposal.state === 'closed' ? $t('results') : $t('currentResults')\n        \"\n      />\n    </template>\n    <template v-if=\"isPendingScore || isInvalidScore\">\n      <div v-if=\"isPendingScore\" class=\"leading-5\">\n        <p class=\"flex gap-2 text-skin-link mb-3\">\n          <LoadingSpinner />\n          Finalizing results…\n        </p>\n        {{ $t('resultsCalculating') }}\n      </div>\n      <BaseMessage v-else-if=\"isInvalidScore\" level=\"warning\">\n        <div>{{ $t('resultsError') }}</div>\n      </BaseMessage>\n      <BaseLink\n        v-if=\"isAdmin\"\n        :link=\"SNAPSHOT_HELP_LINK\"\n        class=\"mt-3 block\"\n        hide-external-icon\n      >\n        <TuneButton tabindex=\"-1\" class=\"w-full\">\n          {{ $t('getHelp') }}\n        </TuneButton>\n      </BaseLink>\n    </template>\n    <template v-else>\n      <SpaceProposalResultsList\n        v-if=\"results\"\n        :space=\"space\"\n        :proposal=\"proposal\"\n        :results=\"results\"\n        :strategies=\"strategies\"\n      />\n      <SpaceProposalResultsShutter\n        v-if=\"proposal.privacy === 'shutter'\"\n        class=\"pt-2\"\n      />\n    </template>\n  </TuneBlock>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalResultsList.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  ExtendedSpace,\n  Proposal,\n  Results,\n  SpaceStrategy\n} from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n  results: Results;\n  strategies: SpaceStrategy[];\n}>();\n\nconst choices = computed<{ i: number; choice: string }[]>(() =>\n  props.proposal.choices\n    .map((choice, i) => ({ i, choice }))\n    .sort((a, b) => props.results.scores[b.i] - props.results.scores[a.i])\n);\n\nconst showQuorum = computed(\n  () => props.proposal?.quorum || props.space?.plugins?.quorum\n);\n</script>\n\n<template>\n  <div class=\"space-y-3\">\n    <SpaceProposalResultsListItem\n      v-for=\"choice in choices\"\n      :key=\"choice.i\"\n      :choice=\"choice\"\n      :space=\"space\"\n      :proposal=\"proposal\"\n      :results=\"results\"\n      :strategies=\"strategies\"\n    />\n    <SpaceProposalResultsQuorum\n      v-if=\"showQuorum\"\n      :space=\"space\"\n      :proposal=\"proposal\"\n      :results=\"results\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalResultsListItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\nimport {\n  ExtendedSpace,\n  Proposal,\n  Results,\n  SpaceStrategy\n} from '@/helpers/interfaces';\n\nconst { formatCompactNumber, formatPercentNumber, formatNumber } = useIntl();\n\nconst props = defineProps<{\n  choice: { i: number; choice: string };\n  space: ExtendedSpace;\n  proposal: Proposal;\n  results: Results;\n  strategies: SpaceStrategy[];\n}>();\n\nconst titles = computed(() =>\n  props.strategies.map(strategy => strategy.params.symbol || '')\n);\n\nconst getPercentage = (n, max) => (max ? ((100 / max) * n) / 1e2 : 0);\n\nconst hideAbstain = props.space?.voting?.hideAbstain ?? false;\n\nconst choicePercentage = computed(() => {\n  if (props.proposal.type === 'basic' && hideAbstain) {\n    if (props.choice.i === 0)\n      return getPercentage(\n        props.results.scores[0],\n        props.results.scores[0] + props.results.scores[1]\n      );\n\n    if (props.choice.i === 1)\n      return getPercentage(\n        props.results.scores[1],\n        props.results.scores[0] + props.results.scores[1]\n      );\n  }\n\n  return getPercentage(\n    props.results.scores[props.choice.i],\n    props.results.scoresTotal\n  );\n});\n\nconst choiceString = ref<HTMLElement | null>(null);\n\nconst isTruncated = computed(() => {\n  if (!choiceString.value) return false;\n  return choiceString.value.scrollWidth > choiceString.value.clientWidth;\n});\n\nconst isVisible = computed(() => {\n  if (props.proposal.type === 'basic' && hideAbstain) {\n    if (props.choice.i === 2) return false;\n  }\n  return true;\n});\n</script>\n\n<template>\n  <div v-if=\"isVisible\">\n    <div class=\"mb-1 flex justify-between text-skin-link\">\n      <div class=\"flex overflow-hidden\">\n        <span\n          ref=\"choiceString\"\n          v-tippy=\"{\n            content: isTruncated ? choice.choice : null\n          }\"\n          class=\"mr-1 truncate\"\n          v-text=\"choice.choice\"\n        />\n      </div>\n      <div class=\"flex justify-end\">\n        <i-ho-lock-closed\n          v-if=\"\n            proposal.privacy === 'shutter' && proposal.scores_state !== 'final'\n          \"\n          v-tippy=\"{ content: $t('privacy.shutter.tooltip') }\"\n          class=\"mx-auto cursor-help\"\n        />\n        <div v-else class=\"space-x-2\">\n          <span\n            v-tippy=\"{\n              content: results.scoresByStrategy[choice.i]\n                .map(\n                  (score, index) => `${formatNumber(score)} ${titles[index]}`\n                )\n                .join(' + ')\n            }\"\n            class=\"whitespace-nowrap\"\n          >\n            {{ formatCompactNumber(results.scores[choice.i]) }}\n            {{ shorten(proposal.symbol || space.symbol, 'symbol') }}\n          </span>\n          <span v-text=\"formatPercentNumber(choicePercentage)\" />\n        </div>\n      </div>\n    </div>\n\n    <SpaceProposalResultsProgressBar\n      :value=\"results.scoresByStrategy[choice.i]\"\n      :max=\"\n        proposal.type === 'basic' && hideAbstain\n          ? results.scores[0] + results.scores[1]\n          : results.scoresTotal\n      \"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalResultsProgressBar.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{ value: number | number[]; max: number }>();\n\nconst bars = computed(() =>\n  Array.isArray(props.value) ? props.value : [props.value]\n);\n</script>\n\n<template>\n  <div class=\"relative flex h-2 overflow-hidden rounded-full\">\n    <div class=\"z-5 absolute h-full w-full bg-[color:var(--border-color)]\" />\n    <div\n      v-for=\"(bar, i) in bars.filter(b => b !== 0)\"\n      :key=\"i\"\n      :style=\"`width: ${((100 / max) * bar).toFixed(3)}%;`\"\n      class=\"z-10 h-full bg-skin-primary\"\n      :class=\"{\n        'opacity-80': i === 1,\n        'opacity-60': i === 2,\n        'opacity-40': i === 3,\n        'opacity-20': i >= 4\n      }\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalResultsQuorum.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace, Proposal, Results } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n  results: Results;\n}>();\n\nconst { totalQuorumScore, quorum, quorumType, loadingQuorum } =\n  useQuorum(props);\n\nconst { formatCompactNumber, formatPercentNumber } = useIntl();\n</script>\n\n<template>\n  <div class=\"pt-2 text-skin-link\">\n    <div class=\"flex justify-between\">\n      <div class=\"flex items-center gap-1\">\n        {{ quorumType === 'rejection' ? 'Quorum of rejection' : 'Quorum' }}\n      </div>\n      <LoadingSpinner v-if=\"loadingQuorum\" class=\"mr-1\" />\n      <div v-else class=\"flex gap-2\">\n        <i-ho-check\n          v-if=\"\n            quorum && quorumType === 'default' && totalQuorumScore >= quorum\n          \"\n          class=\"text-green\"\n        />\n        <i-ho-x\n          v-if=\"\n            quorum && quorumType === 'rejection' && totalQuorumScore >= quorum\n          \"\n          class=\"text-red\"\n        />\n\n        <span\n          v-tippy=\"{ content: formatPercentNumber(totalQuorumScore / quorum) }\"\n        >\n          {{ formatCompactNumber(totalQuorumScore) }}\n          /\n          {{ formatCompactNumber(quorum) }}\n        </span>\n      </div>\n    </div>\n    <BaseProgressBar :value=\"(totalQuorumScore / quorum) * 100\" />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalResultsShutter.vue",
    "content": "<template>\n  <div>\n    <div class=\"flex items-center mt-3\">\n      <BaseLink\n        v-tippy=\"{\n          content: $t('privacy.shutter.tooltip')\n        }\"\n        :link=\"$t('privacy.shutter.url')\"\n        hide-external-icon\n        class=\"inline-block cursor-pointer\"\n      >\n        <i-s-shutter class=\"w-[80px]\" />\n      </BaseLink>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalVote.vue",
    "content": "<script setup lang=\"ts\">\nimport { SNAPSHOT_HELP_LINK } from '@/helpers/constants';\nimport { Proposal, Choice } from '@/helpers/interfaces';\nimport voting from '@snapshot-labs/snapshot.js/src/voting';\n\nconst props = defineProps<{\n  proposal: Proposal;\n  modelValue: Choice | null;\n}>();\n\nconst emit = defineEmits(['update:modelValue', 'clickVote']);\n\nconst { web3, web3Account } = useWeb3();\nconst { userVote, loadUserVote, loadingUserVote } = useProposalVotes(\n  props.proposal\n);\n\nconst isEditing = ref(false);\n\nconst selectedChoices = computed(() => {\n  if (Array.isArray(props.modelValue)) return props.modelValue.length;\n  if (typeof props.modelValue === 'object' && props.modelValue !== null)\n    return Object.keys(props.modelValue).length;\n  return props.modelValue ?? 0;\n});\n\nconst validatedUserChoice = computed(() => {\n  if (selectedChoices.value) {\n    return voting[props.proposal.type].isValidChoice(\n      props.modelValue,\n      props.proposal.choices\n    )\n      ? props.modelValue\n      : null;\n  }\n  if (!userVote.value?.choice) return null;\n  if (\n    voting[props.proposal.type].isValidChoice(\n      userVote.value.choice,\n      props.proposal.choices\n    )\n  ) {\n    return userVote.value.choice as any;\n  }\n  return null;\n});\n\nconst buttonTooltip = computed(() => {\n  if (\n    props.proposal.type === 'ranked-choice' &&\n    selectedChoices.value < props.proposal.choices.length\n  )\n    return 'Please rank all choices';\n\n  if (\n    props.proposal.type !== 'approval' &&\n    props.proposal.type !== 'ranked-choice' &&\n    selectedChoices.value < 1\n  )\n    return 'Please select at least one choice';\n\n  return '';\n});\n\nconst votedAndShutter = computed(\n  () => props.proposal.privacy === 'shutter' && userVote.value\n);\n\nfunction emitChoice(c) {\n  emit('update:modelValue', c);\n}\n\nwatch(\n  web3Account,\n  () => {\n    isEditing.value = false;\n    loadUserVote(web3Account.value);\n  },\n  { immediate: true }\n);\n\nwatch(\n  () => props.proposal,\n  () => {\n    isEditing.value = false;\n    loadUserVote(web3Account.value);\n  }\n);\n</script>\n\n<template>\n  <TuneBlock\n    v-if=\"!loadingUserVote && (userVote || proposal.state === 'active')\"\n  >\n    <template #header>\n      <TuneBlockHeader\n        :title=\"\n          isEditing\n            ? 'Change your vote'\n            : userVote\n              ? 'Your vote'\n              : 'Cast your vote'\n        \"\n      >\n        <BaseButtonIcon\n          v-if=\"!isEditing && userVote && proposal.state === 'active'\"\n          v-tippy=\"{\n            content: 'Change your vote',\n            delay: 100\n          }\"\n          class=\"!p-0 !pr-1\"\n          @click=\"isEditing = true\"\n        >\n          <i-ho-pencil class=\"text-sm\" />\n        </BaseButtonIcon>\n      </TuneBlockHeader>\n    </template>\n    <div\n      v-if=\"votedAndShutter && !isEditing && proposal.scores_state !== 'final'\"\n      class=\"border px-3 py-[12px] rounded-xl bg-[--border-color-subtle]\"\n    >\n      <i-ho-lock-closed class=\"inline-block text-sm\" />\n      Your vote is encrypted with Shutter privacy until the proposal ends and\n      the final score is calculated. You can still change your vote until then.\n    </div>\n    <BaseMessage\n      v-else-if=\"userVote && !validatedUserChoice && !isEditing\"\n      level=\"info\"\n      class=\"border px-3 py-[12px] rounded-xl bg-[--border-color-subtle]\"\n    >\n      Oops, we were unable to validate your vote. Please try voting again or\n      consider contacting our support team on\n      <BaseLink :link=\"SNAPSHOT_HELP_LINK\">Help Center</BaseLink>\n    </BaseMessage>\n    <div v-else>\n      <SpaceProposalVoteSingleChoice\n        v-if=\"proposal.type === 'single-choice' || proposal.type === 'basic'\"\n        :proposal=\"proposal\"\n        :user-choice=\"validatedUserChoice\"\n        :is-editing=\"isEditing || !userVote\"\n        @select-choice=\"emitChoice\"\n      />\n      <SpaceProposalVoteApproval\n        v-if=\"proposal.type === 'approval'\"\n        :proposal=\"proposal\"\n        :user-choice=\"validatedUserChoice\"\n        :is-editing=\"isEditing || !userVote\"\n        @select-choice=\"emitChoice\"\n      />\n      <SpaceProposalVoteQuadratic\n        v-if=\"proposal.type === 'quadratic' || proposal.type === 'weighted'\"\n        :proposal=\"proposal\"\n        :user-choice=\"validatedUserChoice\"\n        :is-editing=\"isEditing || !userVote\"\n        @select-choice=\"emitChoice\"\n      />\n      <SpaceProposalVoteRankedChoice\n        v-if=\"proposal.type === 'ranked-choice'\"\n        :proposal=\"proposal\"\n        :user-choice=\"validatedUserChoice\"\n        :is-editing=\"isEditing || !userVote\"\n        @select-choice=\"emitChoice\"\n      />\n    </div>\n    <div\n      v-if=\"!userVote || isEditing\"\n      v-tippy=\"{\n        content: buttonTooltip\n      }\"\n      class=\"pt-3\"\n    >\n      <TuneButton\n        :disabled=\"\n          web3.authLoading ||\n          (selectedChoices < 1 && proposal.type !== 'approval') ||\n          (selectedChoices < proposal.choices.length &&\n            proposal.type === 'ranked-choice')\n        \"\n        class=\"block w-full\"\n        primary\n        data-testid=\"proposal-vote-button\"\n        @click=\"$emit('clickVote')\"\n      >\n        {{ $t('proposal.vote') }}\n      </TuneButton>\n    </div>\n  </TuneBlock>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalVoteApproval.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\nimport { Proposal } from '@/helpers/interfaces';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst props = defineProps<{\n  proposal: Proposal;\n  userChoice: number[] | null;\n  isEditing: boolean;\n}>();\n\nconst emit = defineEmits(['selectChoice']);\n\nconst selectedChoices = ref<number[]>([]);\n\nfunction selectChoice(i: number) {\n  if (selectedChoices.value.includes(i))\n    selectedChoices.value.splice(selectedChoices.value.indexOf(i), 1);\n  else selectedChoices.value.push(i);\n\n  emit('selectChoice', selectedChoices.value);\n}\n\nwatch(\n  () => props.userChoice,\n  () => {\n    if (selectedChoices.value.length === 0)\n      selectedChoices.value = clone(props.userChoice) || [];\n    emit('selectChoice', selectedChoices.value || []);\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <div>\n    <div v-if=\"userChoice?.length === 0 && !isEditing\">\n      <BaseMessage level=\"info\">\n        You voted without selecting a choice. Your vote will be counted as a\n        <strong>blank vote</strong>.\n      </BaseMessage>\n    </div>\n    <div data-testid=\"approval-choice-list\">\n      <template v-for=\"(choice, i) in proposal.choices\" :key=\"i\">\n        <TuneButton\n          v-if=\"isEditing ? true : userChoice?.includes(i + 1) || !userChoice\"\n          class=\"relative mb-2 last:mb-0 block w-full text-left\"\n          :class=\"{\n            '!border-skin-link': selectedChoices.includes(i + 1),\n            'border-skin-border hover:border-skin-link':\n              !selectedChoices.includes(i + 1),\n            '!cursor-default': !isEditing\n          }\"\n          :data-testid=\"`approval-choice-button-${i}`\"\n          @click=\"isEditing && selectChoice(i + 1)\"\n        >\n          <i-ho-check\n            v-if=\"selectedChoices.includes(i + 1)\"\n            class=\"absolute right-[20px]\"\n          />\n          {{ shorten(choice, 32) }}\n        </TuneButton>\n      </template>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalVoteQuadratic.vue",
    "content": "<script setup lang=\"ts\">\nimport { calcPercentageOfSum } from '@snapshot-labs/snapshot.js/src/voting/quadratic';\nimport { useMediaQuery } from '@vueuse/core';\nimport { Proposal } from '@/helpers/interfaces';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst props = defineProps<{\n  proposal: Proposal;\n  userChoice: Record<string, any> | null;\n  isEditing: boolean;\n}>();\n\nconst emit = defineEmits(['selectChoice']);\n\nconst selectedChoices = ref({});\n\nconst isSmallScreen = useMediaQuery('(max-width: 543px)');\n\nfunction percentage(i) {\n  return (\n    Math.round(\n      calcPercentageOfSum(\n        selectedChoices.value[i + 1],\n        Object.values(selectedChoices.value)\n      ) * 1000\n    ) / 10\n  );\n}\n\nfunction addVote(i) {\n  selectedChoices.value[i] = selectedChoices.value[i]\n    ? (selectedChoices.value[i] += 1)\n    : 1;\n}\n\nfunction removeVote(i) {\n  if (selectedChoices.value[i])\n    selectedChoices.value[i] =\n      selectedChoices.value[i] < 1 ? 0 : (selectedChoices.value[i] -= 1);\n}\n\n// Delete choice if empty string or 0\nwatch(\n  () => selectedChoices.value,\n  currentValue => {\n    Object.entries(currentValue).forEach(choice => {\n      if (choice[1] === '' || (typeof choice[1] === 'number' && choice[1] <= 0))\n        delete selectedChoices.value[choice[0]];\n    });\n    emit('selectChoice', selectedChoices.value);\n  },\n  { immediate: true, deep: true }\n);\n\nwatch(\n  () => props.userChoice,\n  () => {\n    if (Object.keys(selectedChoices.value).length === 0)\n      selectedChoices.value = clone(props.userChoice) || {};\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <div data-testid=\"quadratic-choice-list\">\n    <template v-for=\"(choice, i) in proposal.choices\" :key=\"i\">\n      <TuneButton\n        v-if=\"\n          isEditing ? true : (userChoice && userChoice[i + 1]) || !userChoice\n        \"\n        class=\"mb-2 last:mb-0 flex w-full items-center justify-between overflow-hidden\"\n        :class=\"[\n          selectedChoices[i + 1] > 0 && '!border-skin-link',\n          {\n            '!cursor-default': !isEditing\n          }\n        ]\"\n        :data-testid=\"`quadratic-choice-button-${i}`\"\n      >\n        <div\n          v-tippy=\"{\n            content: choice.length > 20 && isSmallScreen ? choice : null\n          }\"\n          class=\"truncate pr-3 text-left\"\n        >\n          {{ choice }}\n        </div>\n        <div class=\"flex items-center justify-end\">\n          <template v-if=\"isEditing || !userChoice\">\n            <button\n              :disabled=\"!selectedChoices[i + 1]\"\n              class=\"btn-choice\"\n              :data-testid=\"`quadratic-remove-button-${i}`\"\n              @click=\"removeVote(i + 1)\"\n            >\n              -\n            </button>\n            <input\n              v-if=\"!isSmallScreen\"\n              v-model.number=\"selectedChoices[i + 1]\"\n              class=\"input text-center\"\n              :class=\"{ 'btn-choice': isSmallScreen }\"\n              style=\"width: 40px; height: 44px\"\n              placeholder=\"0\"\n              type=\"number\"\n              :data-testid=\"`quadratic-input-${i}`\"\n            />\n            <div v-if=\"isSmallScreen\" style=\"min-width: 56px\">\n              {{ percentage(i) }}%\n            </div>\n            <button\n              class=\"btn-choice\"\n              :data-testid=\"`quadratic-add-button-${i}`\"\n              @click=\"addVote(i + 1)\"\n            >\n              +\n            </button>\n          </template>\n          <div\n            v-if=\"!isSmallScreen || !isEditing\"\n            style=\"min-width: 52px; margin-right: -5px\"\n            class=\"text-right\"\n          >\n            {{ percentage(i) }}%\n          </div>\n        </div>\n      </TuneButton>\n    </template>\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n.btn-choice {\n  background-color: transparent;\n  color: var(--link-color);\n  width: 40px;\n  height: 44px;\n  border-left: 1px solid var(--border-color);\n  border-right: 1px solid var(--border-color);\n  border-bottom: none;\n  border-top: none;\n  &:disabled {\n    color: gray;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/SpaceProposalVoteRankedChoice.vue",
    "content": "<script setup lang=\"ts\">\nimport draggable from 'vuedraggable';\nimport { Proposal } from '@/helpers/interfaces';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst props = defineProps<{\n  proposal: Proposal;\n  userChoice: number[] | null;\n  isEditing: boolean;\n}>();\n\nconst emit = defineEmits(['selectChoice']);\n\nconst selectedChoices = ref<number[]>([]);\n\nfunction selectChoice(i) {\n  selectedChoices.value.push(i);\n  emit('selectChoice', selectedChoices.value);\n}\n\nfunction updateChoices() {\n  emit('selectChoice', selectedChoices.value);\n}\n\nwatch(\n  () => props.userChoice,\n  () => {\n    if (selectedChoices.value.length === 0)\n      selectedChoices.value = clone(props.userChoice) || [];\n    emit('selectChoice', selectedChoices.value);\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <div class=\"space-y-3\">\n    <div v-if=\"selectedChoices.length\">\n      <draggable\n        v-model=\"selectedChoices\"\n        :component-data=\"{ name: 'list', type: 'transition-group' }\"\n        item-key=\"id\"\n        data-testid=\"ranked-choice-selected-list\"\n        v-bind=\"{ animation: 200 }\"\n        :disabled=\"userChoice?.length && !isEditing\"\n        @change=\"updateChoices\"\n      >\n        <template #item=\"{ element, index }\">\n          <TuneButton\n            class=\"!mb-2 last:!mb-0 flex w-full items-center justify-between !border-skin-link !px-3\"\n            :class=\"[!isEditing ? '!cursor-default' : 'cursor-grabbing']\"\n          >\n            <div class=\"pl-1 flex truncate\">\n              <div>#{{ index + 1 }}</div>\n\n              <div class=\"truncate pl-1\">\n                {{ proposal.choices[element - 1] }}\n              </div>\n            </div>\n            <div class=\"pl-6\">\n              <i-ho-menu-alt-4 v-if=\"isEditing\" class=\"text-sm\" />\n            </div>\n          </TuneButton>\n        </template>\n      </draggable>\n    </div>\n\n    <div\n      v-if=\"selectedChoices.length !== proposal.choices.length\"\n      class=\"space-y-2\"\n    >\n      <div\n        v-for=\"(choice, i) in proposal.choices\"\n        :key=\"i\"\n        data-testid=\"ranked-choice-select-list\"\n      >\n        <TuneButton\n          v-if=\"!selectedChoices.includes(i + 1)\"\n          class=\"block w-full\"\n          :class=\"selectedChoices.includes(i + 1) && 'border-skin-link'\"\n          @click=\"selectChoice(i + 1)\"\n        >\n          <span class=\"truncate\">{{ choice }}</span>\n        </TuneButton>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalVoteSingleChoice.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\nimport { Proposal } from '@/helpers/interfaces';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst props = defineProps<{\n  proposal: Proposal;\n  userChoice: number | null;\n  isEditing: boolean;\n}>();\n\nconst emit = defineEmits(['selectChoice']);\n\nconst selectedChoice = ref<number | null>(null);\n\nfunction selectChoice(i: number) {\n  if (props.userChoice && !props.isEditing) return;\n  selectedChoice.value = i;\n  emit('selectChoice', i);\n}\n\nwatch(\n  () => props.userChoice,\n  () => {\n    if (!selectedChoice.value)\n      selectedChoice.value = clone(props.userChoice) || null;\n    emit('selectChoice', selectedChoice.value);\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <div>\n    <template v-for=\"(choice, i) in proposal.choices\" :key=\"i\">\n      <TuneButton\n        v-if=\"isEditing ? true : userChoice === i + 1 || !userChoice\"\n        class=\"relative mb-2 last:mb-0 block w-full text-left\"\n        :class=\"[\n          selectedChoice === i + 1 && '!border-skin-link',\n          {\n            '!cursor-default': !isEditing\n          }\n        ]\"\n        :data-testid=\"`sc-choice-button-${i}`\"\n        @click=\"selectChoice(i + 1)\"\n      >\n        <i-ho-check\n          v-if=\"selectedChoice === i + 1\"\n          class=\"absolute right-[20px]\"\n        />\n        {{ shorten(choice, 32) }}\n      </TuneButton>\n    </template>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalVotes.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace, Proposal } from '@/helpers/interfaces';\nimport { useBreakpoints } from '@vueuse/core';\nimport { SNAPSHOT_BREAKPOINTS } from '@/helpers/constants';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n}>();\n\nconst VOTES_LIMIT = 6;\n\nconst isSmallScreen = useBreakpoints(SNAPSHOT_BREAKPOINTS).smaller('sm');\n\nconst { profiles, votes, loadingVotes, loadVotes } = useProposalVotes(\n  props.proposal,\n  VOTES_LIMIT\n);\n\nconst modalVotesOpen = ref(false);\n\nconst voteCount = computed(() => props.proposal.votes);\n\nconst showModalDownloadMessage = ref(false);\nconst { downloadVotes, isDownloadingVotes, errorCode } = useReportDownload();\n\nasync function downloadReport(proposalId: string) {\n  const response = await downloadVotes(proposalId);\n\n  if (!response) {\n    showModalDownloadMessage.value = true;\n  }\n}\n\nwatch(\n  () => props.proposal,\n  async () => await loadVotes(),\n  { immediate: true }\n);\n</script>\n\n<template>\n  <TuneBlock v-if=\"proposal.votes > 0\" :loading=\"loadingVotes\">\n    <template #header>\n      <TuneBlockHeader :title=\"$t('votes')\" :counter=\"voteCount\">\n        <BaseButtonIcon\n          v-if=\"props.proposal.state === 'closed'\"\n          v-tippy=\"{\n            content: $t('proposal.downloadCsvVotes.title'),\n            delay: 100\n          }\"\n          :loading=\"isDownloadingVotes\"\n          class=\"!p-0 !pr-1\"\n          @click=\"downloadReport(proposal.id)\"\n        >\n          <i-ho-download />\n        </BaseButtonIcon>\n      </TuneBlockHeader>\n    </template>\n    <SpaceProposalVotesItem\n      v-for=\"(vote, i) in votes\"\n      :key=\"i\"\n      :vote=\"vote\"\n      :profiles=\"profiles\"\n      :space=\"space\"\n      :proposal=\"proposal\"\n      :is-small=\"isSmallScreen\"\n      :data-testid=\"`proposal-votes-list-item-${i}`\"\n      class=\"last:pb-0\"\n    />\n    <div v-if=\"proposal.votes > VOTES_LIMIT\" class=\"pt-3\">\n      <TuneButton\n        class=\"w-full\"\n        @click=\"modalVotesOpen = true\"\n        @keypress=\"modalVotesOpen = true\"\n      >\n        View all\n      </TuneButton>\n    </div>\n    <teleport to=\"#modal\">\n      <SpaceProposalVotesModalDownload\n        :open=\"showModalDownloadMessage\"\n        :error-code=\"errorCode\"\n        @close=\"showModalDownloadMessage = false\"\n      />\n      <SpaceProposalVotesModal\n        :space=\"space\"\n        :proposal=\"proposal\"\n        :open=\"modalVotesOpen\"\n        @close=\"modalVotesOpen = false\"\n      />\n    </teleport>\n  </TuneBlock>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalVotesFilters.vue",
    "content": "<script setup lang=\"ts\">\nimport { Proposal, VoteFilters } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  proposal: Proposal;\n  modelValue: VoteFilters;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nfunction updateFilters(\n  key: keyof VoteFilters,\n  val: VoteFilters[keyof VoteFilters]\n) {\n  emit('update:modelValue', { ...props.modelValue, [key]: val });\n}\n</script>\n\n<template>\n  <BasePopover :focus=\"false\">\n    <template #button>\n      <BaseButtonIcon>\n        <i-ho-adjustments class=\"text-skin-link\" />\n      </BaseButtonIcon>\n    </template>\n    <template #content>\n      <div>\n        <h3 class=\"-mb-2 mt-3 text-center text-skin-heading\">\n          {{ $t('proposal.votesModal.filtersPopover.title') }}\n        </h3>\n        <div class=\"m-4 space-y-3\">\n          <div class=\"space-y-2\">\n            <span class=\"text-skin-heading\">\n              {{ $t('proposal.votesModal.filtersPopover.votingPower') }}\n            </span>\n\n            <TuneRadio\n              value=\"asc\"\n              hint=\"Asc\"\n              :model-value=\"modelValue.orderDirection\"\n              @update:model-value=\"updateFilters('orderDirection', $event)\"\n            />\n            <TuneRadio\n              value=\"desc\"\n              hint=\"Desc\"\n              :model-value=\"modelValue.orderDirection\"\n              @update:model-value=\"updateFilters('orderDirection', $event)\"\n            />\n          </div>\n          <div class=\"space-y-2\">\n            <span class=\"text-skin-heading\">\n              {{ $t('proposal.votesModal.filtersPopover.more') }}\n            </span>\n            <TuneCheckbox\n              :model-value=\"Boolean(modelValue.onlyWithReason)\"\n              :hint=\"\n                $t('proposal.votesModal.filtersPopover.onlyVotesWithReason')\n              \"\n              name=\"searchOnlyWithReason\"\n              @update:model-value=\"updateFilters('onlyWithReason', $event)\"\n            />\n          </div>\n        </div>\n      </div>\n    </template>\n  </BasePopover>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalVotesItem.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ExtendedSpace, Proposal, Vote, Profile } from '@/helpers/interfaces';\nimport { shorten, getIpfsUrl } from '@/helpers/utils';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n  vote: Vote;\n  profiles: Record<string, Profile>;\n  isSmall: boolean;\n}>();\n\ndefineEmits(['openReceiptModal']);\n\nconst relayerIpfsHash = ref('');\n\nconst titles = computed(() =>\n  props.proposal.strategies.map(strategy => strategy.params.symbol || '')\n);\n\nconst { formatCompactNumber } = useIntl();\n\nconst balanceFormatted = computed(() => {\n  const balance = formatCompactNumber(props.vote.balance);\n  return balance.length >= 8 ? shorten(balance) : balance;\n});\n</script>\n\n<template>\n  <div class=\"py-[12px]\" :class=\"{ 'py-[8px]': isSmall }\">\n    <div\n      class=\"flex items-center gap-4\"\n      :class=\"{ 'justify-between': isSmall }\"\n    >\n      <BaseUser\n        :key=\"vote.voter\"\n        :profile=\"profiles[vote.voter]\"\n        :address=\"vote.voter\"\n        :space=\"{ network: props.space.network }\"\n        :proposal=\"proposal\"\n        :width-class=\"\n          isSmall\n            ? 'w-[136px] min-w-[136px] text-left'\n            : 'w-[200px] min-w-[200px] text-left'\n        \"\n      />\n\n      <SpaceProposalVotesListItemChoice\n        v-if=\"!isSmall\"\n        :proposal=\"proposal\"\n        :vote=\"vote\"\n      />\n      <div\n        class=\"flex w-[130px] min-w-[130px] items-center justify-end whitespace-nowrap text-right text-skin-link\"\n      >\n        <span\n          v-tippy=\"{\n            content: vote.scores\n              ?.map(\n                (score, index) =>\n                  `${formatCompactNumber(score)} ${titles[index]}`\n              )\n              .join(' + ')\n          }\"\n          class=\"truncate\"\n        >\n          {{\n            `${balanceFormatted} ${shorten(\n              proposal.symbol || space.symbol,\n              'symbol'\n            )}`\n          }}\n        </span>\n        <BasePopover>\n          <template #button>\n            <BaseButtonIcon class=\"!p-0 ml-1\">\n              <BaseIcon name=\"signature\" />\n            </BaseButtonIcon>\n          </template>\n          <template #content>\n            <div class=\"m-4 space-y-4\">\n              <h3 class=\"text-center\">{{ $t('receipt') }}</h3>\n              <BaseBlock slim class=\"p-4 text-skin-link\">\n                <div class=\"flex\">\n                  <span\n                    class=\"mr-1 flex-auto text-skin-text\"\n                    v-text=\"$t('author')\"\n                  />\n                  <BaseLink\n                    :link=\"getIpfsUrl(vote.ipfs)\"\n                    class=\"text-skin-link\"\n                  >\n                    #{{ vote.ipfs.slice(0, 7) }}\n                  </BaseLink>\n                </div>\n                <div v-if=\"relayerIpfsHash\" class=\"flex\">\n                  <span\n                    class=\"mr-1 flex-auto text-skin-text\"\n                    v-text=\"$t('relayer')\"\n                  />\n                  <BaseLink\n                    :link=\"getIpfsUrl(relayerIpfsHash)\"\n                    class=\"text-skin-link\"\n                  >\n                    #{{ relayerIpfsHash.slice(0, 7) }}\n                  </BaseLink>\n                </div>\n              </BaseBlock>\n              <BaseLink\n                :link=\"`https://signator.io/ipfs/${vote.ipfs}`\"\n                class=\"mb-2 block\"\n                hide-external-icon\n              >\n                <TuneButton class=\"w-full\" tabindex=\"-1\">\n                  {{ $t('verifyOnSignatorio') }}\n                  <i-ho-external-link\n                    class=\"mb-[2px] ml-1 inline-block text-xs\"\n                  />\n                </TuneButton>\n              </BaseLink>\n            </div>\n          </template>\n        </BasePopover>\n      </div>\n    </div>\n\n    <SpaceProposalVotesListItemChoice\n      v-if=\"isSmall\"\n      :proposal=\"proposal\"\n      :vote=\"vote\"\n      class=\"mt-1\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalVotesListItemChoice.vue",
    "content": "<script setup lang=\"ts\">\nimport { Proposal, Vote } from '@/helpers/interfaces';\nimport { getChoiceString } from '@/helpers/utils';\nimport voting from '@snapshot-labs/snapshot.js/src/voting';\nimport { useTippy } from 'vue-tippy';\nimport TextAutolinker from './TextAutolinker.vue';\n\nconst format = getChoiceString;\n\nconst props = defineProps<{\n  proposal: Proposal;\n  vote: Vote;\n}>();\n\nconst refReasonTooltip = ref();\n\nuseTippy(refReasonTooltip, {\n  content: h(TextAutolinker, { text: `Reason: ${props.vote.reason}` }),\n  interactive: true,\n  theme: 'urlified',\n  trigger: 'mouseenter focus click',\n  delay: 100,\n  appendTo: () => document.body\n});\n</script>\n\n<template>\n  <div class=\"flex-auto truncate text-skin-link\">\n    <div\n      v-if=\"proposal.privacy === 'shutter' && proposal.scores_state !== 'final'\"\n      v-tippy=\"{ content: $t('privacy.shutter.tooltip') }\"\n      class=\"cursor-help flex items-center gap-1 w-fit max-w-full\"\n    >\n      <i-ho-lock-closed class=\"text-sm\" />\n      Encrypted choice\n    </div>\n\n    <div\n      v-else-if=\"\n        !voting[proposal.type].isValidChoice(vote.choice, proposal.choices)\n      \"\n      v-tippy=\"{ content: $t('proposal.invalidChoice') }\"\n      class=\"cursor-help flex items-center gap-1 w-fit max-w-full\"\n    >\n      <i-ho-exclamation class=\"text-sm\" />\n      Invalid choice\n    </div>\n\n    <div v-else class=\"flex items-center gap-1\">\n      <div\n        v-tippy=\"{\n          content: format(proposal, vote.choice)\n        }\"\n        class=\"truncate text-skin-link w-fit max-w-full\"\n      >\n        {{ format(proposal, vote.choice) }}\n      </div>\n      <div\n        v-if=\"vote.reason !== '' && proposal.privacy !== 'shutter'\"\n        ref=\"refReasonTooltip\"\n      >\n        <BaseButtonIcon class=\"cursor-default !p-0\">\n          <i-ho-annotation class=\"text-[16px]\" />\n        </BaseButtonIcon>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalVotesModal.vue",
    "content": "<script setup lang=\"ts\">\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { useIntersectionObserver, watchDebounced } from '@vueuse/core';\nimport { ExtendedSpace, Proposal, VoteFilters } from '@/helpers/interfaces';\n\nconst VOTES_FILTERS_DEFAULT: VoteFilters = {\n  orderDirection: 'desc',\n  onlyWithReason: false\n};\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n  open: boolean;\n}>();\n\ndefineEmits(['close']);\n\nconst {\n  votes,\n  loadingVotes,\n  loadingMoreVotes,\n  profiles,\n  loadVotes,\n  loadMoreVotes,\n  loadSingleVote\n} = useProposalVotes(props.proposal, 20);\n\nconst votesEndEl = ref<HTMLElement | null>(null);\nconst filterOptions = ref<VoteFilters>(clone(VOTES_FILTERS_DEFAULT));\nconst searchInput = ref('');\n\nconst filters = computed(() => {\n  return {\n    orderDirection: filterOptions.value.orderDirection,\n    onlyWithReason: filterOptions.value.onlyWithReason\n  };\n});\n\nconst showNoResults = computed(() => {\n  return (\n    !loadingVotes.value &&\n    votes.value.length === 0 &&\n    (searchInput.value || filters.value.onlyWithReason)\n  );\n});\n\nuseIntersectionObserver(\n  votesEndEl,\n  ([{ isIntersecting }]) => {\n    const hasMoreVotes = props.proposal.votes > votes.value.length;\n    if (\n      props.open &&\n      isIntersecting &&\n      searchInput.value === '' &&\n      hasMoreVotes\n    ) {\n      loadMoreVotes(filters.value);\n    }\n  },\n  {\n    threshold: 1\n  }\n);\n\nwatch(\n  () => props.open,\n  () => {\n    filterOptions.value = clone(VOTES_FILTERS_DEFAULT);\n    searchInput.value = '';\n  }\n);\n\nwatchDebounced(\n  searchInput,\n  async value => {\n    if (value) {\n      loadSingleVote(searchInput.value);\n    }\n    if (votes.value.length < 2 && value === '') {\n      loadVotes(filters.value);\n    }\n  },\n  { debounce: 300, deep: true }\n);\n\nwatch(filters, value => {\n  loadVotes(value);\n});\n</script>\n\n<template>\n  <TuneModal :open=\"open\" @close=\"$emit('close')\">\n    <div class=\"px-3 pb-3\">\n      <TuneModalTitle as=\"h4\" class=\"mt-3 flex items-center gap-1\">\n        {{ $t('proposal.votesModal.title') }}\n        <BaseCounter :counter=\"proposal.votes\" />\n      </TuneModalTitle>\n    </div>\n    <BaseSearch\n      v-model=\"searchInput\"\n      :placeholder=\"$t('searchPlaceholderVotes')\"\n      modal\n      focus-on-mount\n      class=\"max-h-[56px] w-full !px-3 pb-3\"\n    >\n      <template #after>\n        <SpaceProposalVotesFilters\n          v-if=\"!searchInput\"\n          v-model=\"filterOptions\"\n          :proposal=\"proposal\"\n        />\n      </template>\n    </BaseSearch>\n\n    <div class=\"max-h-[calc(100vh-130px)] md:max-h-[400px] overflow-y-auto\">\n      <div v-if=\"loadingVotes\" class=\"block p-3\">\n        <LoadingList />\n      </div>\n\n      <BaseNoResults v-else-if=\"showNoResults\" />\n\n      <div v-else-if=\"votes.length\">\n        <div\n          class=\"flex h-full min-h-full flex-col overflow-auto px-[16px] py-2\"\n        >\n          <SpaceProposalVotesItem\n            v-for=\"(vote, i) in votes\"\n            :key=\"i\"\n            :vote=\"vote\"\n            :profiles=\"profiles\"\n            :space=\"space\"\n            :proposal=\"proposal\"\n            is-small\n            :data-testid=\"`proposal-votes-list-item-${i}`\"\n          />\n          <div ref=\"votesEndEl\" />\n          <div v-if=\"loadingMoreVotes\" class=\"block min-h-[34px] text-center\">\n            <LoadingSpinner />\n          </div>\n        </div>\n      </div>\n    </div>\n  </TuneModal>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalVotesModalDownload.vue",
    "content": "<script setup lang=\"ts\">\nimport camelCase from 'lodash/camelCase';\n\nconst props = defineProps<{\n  open: boolean;\n  errorCode: null | Error;\n}>();\n\ndefineEmits(['close']);\n\nconst errorMessageKeyPrefix = computed(() => {\n  const knownErrors = ['PENDING_GENERATION', 'UNSUPPORTED_ENV'];\n\n  return `proposal.downloadCsvVotes.postDownloadModal.message.${camelCase(\n    knownErrors.includes(props.errorCode?.message as string)\n      ? props.errorCode?.message\n      : 'UNKNOWN_ERROR'\n  )}`;\n});\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <div class=\"flex flex-row items-center justify-center\">\n        <h3>\n          {{ $t('proposal.downloadCsvVotes.postDownloadModal.title') }}\n        </h3>\n      </div>\n    </template>\n\n    <div class=\"m-4 text-center\">\n      <i-ho-clock\n        v-if=\"errorCode?.message === 'PENDING_GENERATION'\"\n        class=\"mx-auto my-4 text-center text-[3em]\"\n      />\n      <i-ho-exclamation\n        v-else\n        class=\"mx-auto my-4 text-center text-[3em] text-red\"\n      />\n      <h3>\n        {{ $t(`${errorMessageKeyPrefix}.title`) }}\n      </h3>\n      <p class=\"mt-3 italic\">\n        {{ $t(`${errorMessageKeyPrefix}.description`) }}\n      </p>\n    </div>\n\n    <template #footer>\n      <TuneButton class=\"w-full\" primary @click=\"$emit('close')\">\n        {{ $t('close') }}\n      </TuneButton>\n    </template>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalsNotice.vue",
    "content": "<script setup lang=\"ts\">\nimport { SNAPSHOT_HELP_LINK } from '@/helpers/constants';\nimport { useStorage } from '@vueuse/core';\n\ndefineProps<{\n  spaceId: string;\n}>();\n\nconst { web3Account } = useWeb3();\n\n// Reactive local storage with help from vueuse package\nconst createdSpaces = useStorage(\n  `snapshot.createdSpaces.${web3Account.value.slice(0, 8).toLowerCase()}`,\n  {}\n);\n</script>\n\n<template>\n  <BaseBlock\n    v-if=\"createdSpaces?.[spaceId]?.showMessage\"\n    class=\"absolute left-0 z-10 !bg-skin-bg\"\n  >\n    <div>\n      <div>\n        <div>\n          <h3 class=\"mt-0\">{{ $t('newSpaceNotice.header') }}</h3>\n          <div class=\"text-skin-text\">\n            <BaseIcon name=\"info\" size=\"24\" class=\"float-left mr-1\" />\n            <i18n-t keypath=\"newSpaceNotice.mainText\" tag=\"p\" scope=\"global\">\n              <template #settings>\n                <BaseLink\n                  :link=\"{ name: 'spaceSettings', params: { key: spaceId } }\"\n                >\n                  settings</BaseLink\n                >\n              </template>\n            </i18n-t>\n          </div>\n\n          <i18n-t\n            keypath=\"newSpaceNotice.learnMore\"\n            tag=\"p\"\n            class=\"mt-2 text-skin-text\"\n            scope=\"global\"\n          >\n            <template #documentation>\n              <BaseLink\n                link=\"https://docs.snapshot.org/strategies/what-is-a-strategy\"\n              >\n                documentation</BaseLink\n              >\n            </template>\n            <template #help>\n              <BaseLink :link=\"SNAPSHOT_HELP_LINK\">Help Center</BaseLink>\n            </template>\n          </i18n-t>\n        </div>\n        <TuneButton\n          class=\"mt-3\"\n          @click=\"createdSpaces[spaceId].showMessage = false\"\n        >\n          {{ $t('newSpaceNotice.gotIt') }}\n        </TuneButton>\n      </div>\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalsSearch.vue",
    "content": "<script setup lang=\"ts\">\nconst router = useRouter();\nconst route = useRoute();\n\nconst titleSearch = computed(() => (route.query.q as string) || '');\n\nfunction handleUpdateSearch(e: string) {\n  router.push({\n    query: { ...route.query, q: e || undefined }\n  });\n}\n</script>\n\n<template>\n  <div\n    class=\"w-full rounded-full border border-skin-border pl-3 pr-0 focus-within:!border-skin-text md:max-w-[340px]\"\n  >\n    <div class=\"flex\">\n      <BaseSearch\n        :model-value=\"titleSearch\"\n        :placeholder=\"$t('searchPlaceholder')\"\n        class=\"flex-auto pr-2\"\n        @update:model-value=\"handleUpdateSearch\"\n      />\n      <div class=\"flex h-[44px] items-center border-l\">\n        <SpaceProposalsSearchFilter />\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceProposalsSearchFilter.vue",
    "content": "<script setup lang=\"ts\">\nconst route = useRoute();\nconst router = useRouter();\nconst { t } = useI18n();\n\nconst stateFilter = computed(() => (route.query.state as string) || 'all');\nconst showOnlyCore = computed(() => (route.query.onlyCore as string) || '0');\nconst showFlagged = computed(() => (route.query.showFlagged as string) || '0');\n\nconst stateFilters = computed(() => [\n  {\n    text: t('proposals.states.all'),\n    action: 'all',\n    extras: { selected: stateFilter.value === 'all' }\n  },\n  {\n    text: t('proposals.states.active'),\n    action: 'active',\n    extras: { selected: stateFilter.value === 'active' }\n  },\n  {\n    text: t('proposals.states.pending'),\n    action: 'pending',\n    extras: { selected: stateFilter.value === 'pending' }\n  },\n  {\n    text: t('proposals.states.closed'),\n    action: 'closed',\n    extras: { selected: stateFilter.value === 'closed' }\n  }\n]);\n\nfunction updateFilters(e: string) {\n  router.push({\n    query: { ...route.query, state: e || undefined }\n  });\n}\n\nfunction updateCore(e: boolean) {\n  router.push({\n    query: { ...route.query, onlyCore: e ? '1' : undefined }\n  });\n}\n\nfunction updateFlagged(e: boolean) {\n  router.push({\n    query: { ...route.query, showFlagged: e ? '1' : undefined }\n  });\n}\n</script>\n\n<template>\n  <BasePopover :focus=\"false\" class=\"h-full\">\n    <template #button>\n      <BaseButtonIcon class=\"flex h-full w-[54px] justify-center outline-none\">\n        <i-ho-adjustments class=\"mr-1 text-base text-skin-link\" />\n      </BaseButtonIcon>\n    </template>\n    <template #content>\n      <div>\n        <h3 class=\"-mb-2 mt-3 text-center text-skin-heading\">Filters</h3>\n        <div class=\"m-4 space-y-3\">\n          <div class=\"space-y-2\">\n            <span class=\"text-skin-heading\"> Proposal status </span>\n            <TuneRadio\n              v-for=\"(state, index) in stateFilters\"\n              :id=\"JSON.stringify(index)\"\n              :key=\"state.action\"\n              :value=\"state.action\"\n              :hint=\"state.text\"\n              :model-value=\"stateFilter\"\n              @update:model-value=\"updateFilters($event as string)\"\n            />\n          </div>\n          <div class=\"space-y-2\">\n            <span class=\"text-skin-heading\"> More </span>\n            <TuneCheckbox\n              id=\"onlyCore\"\n              :model-value=\"showOnlyCore === '1'\"\n              hint=\"Only core member proposals\"\n              name=\"onlyCore\"\n              @update:model-value=\"updateCore($event as boolean)\"\n            />\n            <TuneCheckbox\n              id=\"showFlagged\"\n              :model-value=\"showFlagged === '1'\"\n              hint=\"Show flagged proposals\"\n              name=\"showFlagged\"\n              @update:model-value=\"updateFlagged($event as boolean)\"\n            />\n          </div>\n        </div>\n      </div>\n    </template>\n  </BasePopover>\n</template>\n"
  },
  {
    "path": "src/components/SpaceSettingsMessageHibernated.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\ndefineProps<{\n  space: ExtendedSpace;\n  isSending: boolean;\n  isValid: boolean;\n}>();\n\nconst emit = defineEmits(['showErrors', 'reactivateSpace']);\n\nonMounted(() => {\n  emit('showErrors');\n});\n</script>\n\n<template>\n  <BaseMessageBlock level=\"warning-red\" is-responsive>\n    <div v-if=\"isValid\">\n      {{ $t('settings.reactivatingHibernatedSpace.information') }}\n    </div>\n\n    <div v-else>\n      {{ $t('settings.reactivatingHibernatedSpace.disabledInformation') }}\n    </div>\n\n    <TuneButton\n      primary\n      :loading=\"isSending\"\n      :disabled=\"!isValid\"\n      class=\"mt-3 whitespace-nowrap\"\n      @click=\"emit('reactivateSpace')\"\n    >\n      {{ $t('reactivateSpace') }}\n    </TuneButton>\n  </BaseMessageBlock>\n</template>\n"
  },
  {
    "path": "src/components/SpaceSidebar.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport SpaceSidebarSubspaces from './SpaceSidebarSubspaces.vue';\n\ndefineProps<{ space: ExtendedSpace }>();\n</script>\n\n<template>\n  <div class=\"-mt-[4px] mb-[20px] md:mt-0 lg:fixed lg:mb-0 lg:w-[240px]\">\n    <BaseBlock slim class=\"overflow-hidden !border-t-0 md:!border-t\">\n      <div class=\"relative lg:max-h-[calc(100vh-120px)] lg:overflow-y-auto\">\n        <SpaceSidebarHeader :space=\"space\" />\n        <SpaceSidebarNavigation :space=\"space\" class=\"lg:mt-0\" />\n        <SpaceSidebarSubspaces :space=\"space\" class=\"hidden lg:flex\" />\n        <SpaceSidebarFooter :space=\"space\" class=\"hidden lg:flex\" />\n        <div\n          class=\"absolute -top-1 right-[16px] md:right-[12px] md:top-[10px] lg:right-[10px]\"\n        >\n          <SpaceSidebarMenuThreeDot class=\"hidden lg:block\" />\n        </div>\n      </div>\n    </BaseBlock>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceSidebarFooter.vue",
    "content": "<script setup lang=\"ts\">\nimport { sanitizeUrl } from '@braintree/sanitize-url';\n\nconst props = defineProps<{ space?: Record<string, any> }>();\n\ntype SocialItem = { icon: string; link: string };\n\nconst socials = computed<SocialItem[]>(() => {\n  const socialsArray: SocialItem[] = [];\n\n  if (props.space?.twitter) {\n    socialsArray.push({\n      icon: 'x',\n      link: `https://x.com/${props.space?.twitter}`\n    });\n  }\n\n  if (props.space?.github) {\n    socialsArray.push({\n      icon: 'github',\n      link: `https://github.com/${props.space?.github}`\n    });\n  }\n\n  if (props.space?.website) {\n    socialsArray.push({\n      icon: 'earth',\n      link: sanitizeUrl(props.space?.website)\n    });\n  }\n\n  if (props.space?.coingecko) {\n    socialsArray.push({\n      icon: 'coingecko',\n      link: `https://www.coingecko.com/coins/${props.space?.coingecko}`\n    });\n  }\n\n  return socialsArray;\n});\n</script>\n\n<template>\n  <div v-if=\"socials.length\" class=\"mb-3 mt-4 flex items-center space-x-2 px-3\">\n    <BaseLink\n      v-for=\"social in socials\"\n      :key=\"social.icon\"\n      :link=\"social.link\"\n      class=\"text-sm text-skin-text hover:text-skin-link\"\n      hide-external-icon\n    >\n      <i-s-x v-if=\"social.icon === 'x'\" class=\"text-[18px]\" />\n      <i-s-github v-if=\"social.icon === 'github'\" />\n      <i-ho-globe-alt v-if=\"social.icon === 'earth'\" class=\"text-[19px]\" />\n      <i-s-coingecko v-if=\"social.icon === 'coingecko'\" />\n    </BaseLink>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceSidebarHeader.vue",
    "content": "<script setup lang=\"ts\">\nimport { Space, ExtendedSpace } from '@/helpers/interfaces';\nconst props = defineProps<{\n  space: Space | ExtendedSpace;\n}>();\n\nconst { formatCompactNumber } = useIntl();\n\nconst {\n  loading,\n  toggleSubscription,\n  isSubscribed,\n  loadSubscriptions,\n  subscriptions\n} = useSpaceSubscription(props.space.id);\n\nconst { isFollowing } = useFollowSpace(props.space.id);\n\nconst notificationIcon = ref('notifications-off');\n\nwatchEffect(() => {\n  if (subscriptions.value === undefined) {\n    loadSubscriptions();\n  }\n  if (isSubscribed.value) {\n    notificationIcon.value = 'notifications-on';\n  } else notificationIcon.value = 'notifications-off';\n});\n</script>\n\n<template>\n  <div\n    class=\"relative block px-[20px] text-center md:flex md:px-3 md:pt-3 lg:block lg:pb-[24px]\"\n  >\n    <div>\n      <AvatarSpace :space=\"space\" symbol-index=\"space\" size=\"48\" class=\"mr-3\" />\n      <div class=\"mt-2 truncate text-left\">\n        <h3 class=\"my-0 flex items-center text-2xl leading-[44px] lg:text-lg\">\n          <div\n            v-tippy=\"{\n              content: space.name.length > 16 ? space.name : null\n            }\"\n            class=\"mr-1 truncate\"\n          >\n            {{ space.name }}\n          </div>\n          <IconVerifiedSpace v-if=\"space.verified\" :turbo=\"space.turbo\" />\n        </h3>\n        <div class=\"text-md text-skin-text lg:text-base\">\n          {{\n            $tc('members', space.followersCount, {\n              count: formatCompactNumber(space.followersCount)\n            })\n          }}\n        </div>\n      </div>\n    </div>\n\n    <div\n      class=\"mt-3 flex w-full items-end justify-end gap-[12px] md:mt-0 lg:mt-[12px]\"\n    >\n      <ButtonFollow\n        :space=\"space\"\n        :primary=\"!isFollowing\"\n        block\n        class=\"w-full md:max-w-[180px] lg:max-w-none\"\n      />\n      <BaseButtonRound\n        v-if=\"isFollowing\"\n        class=\"inline shrink-0\"\n        @click=\"toggleSubscription()\"\n      >\n        <LoadingSpinner v-if=\"loading\" />\n        <BaseIcon\n          v-else\n          size=\"20\"\n          class=\"text-skin-link\"\n          :name=\"notificationIcon\"\n        />\n      </BaseButtonRound>\n\n      <BaseButtonRound class=\"inline shrink-0 lg:hidden\">\n        <SpaceSidebarMenuThreeDot class=\"\" />\n      </BaseButtonRound>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceSidebarMenuThreeDot.vue",
    "content": "<script setup lang=\"ts\">\nconst { t } = useI18n();\n\nconst threeDotItems = computed(() => {\n  const items = [{ text: t('report'), action: 'report' }];\n  return items;\n});\n\nfunction handleSelect(e) {\n  if (e === 'report') window.open('https://tally.so/r/mKzXo7', '_blank');\n}\n</script>\n\n<template>\n  <div>\n    <BaseMenu :items=\"threeDotItems\" @select=\"handleSelect\">\n      <template #button>\n        <div>\n          <BaseButtonIcon :loading=\"false\">\n            <i-ho-dots-horizontal class=\"text-[17px]\" />\n          </BaseButtonIcon>\n        </div>\n      </template>\n      <template #item=\"{ item }\">\n        <div class=\"flex items-center gap-2\">\n          <i-ho-flag v-if=\"item.action === 'report'\" />\n          {{ item.text }}\n        </div>\n      </template>\n    </BaseMenu>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceSidebarNavigation.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport legacySpaces from '@/../snapshot-spaces/spaces/legacy.json';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n}>();\n\nconst hasDelegationStrategy = computed(() => {\n  return props.space.strategies?.some(\n    strategy =>\n      strategy.name.includes('delegation') ||\n      JSON.stringify(strategy.params).includes('\"delegation\"')\n  );\n});\n\nconst hasDelegatesSettings = computed(() => {\n  // delegation dashboard enabled on v2 for these spaces\n  const ignoreSpaces = ['ens.eth', 'gitcoindao.eth', 'uniswapgovernance.eth'];\n  if (ignoreSpaces.includes(props.space.id)) return false;\n  return props.space.delegationPortal?.delegationType;\n});\n\nconst isLegacySpace = computed(() => {\n  return Object.keys(legacySpaces).includes(props.space.id);\n});\n</script>\n\n<template>\n  <div class=\"no-scrollbar mt-4 flex overflow-y-auto lg:my-3 lg:block\">\n    <router-link v-slot=\"{ isExactActive }\" :to=\"{ name: 'spaceProposals' }\">\n      <BaseSidebarNavigationItem :is-active=\"isExactActive\">\n        {{ $t('proposals.header') }}\n      </BaseSidebarNavigationItem>\n    </router-link>\n    <router-link\n      v-if=\"hasDelegationStrategy && !hasDelegatesSettings\"\n      v-slot=\"{ isExactActive }\"\n      :to=\"{ name: 'delegate', params: { key: space.id } }\"\n    >\n      <BaseSidebarNavigationItem :is-active=\"isExactActive\">\n        {{ $t('delegate.header') }}\n      </BaseSidebarNavigationItem>\n    </router-link>\n    <router-link\n      v-if=\"hasDelegatesSettings\"\n      v-slot=\"{ isActive }\"\n      :to=\"{ name: 'spaceDelegates' }\"\n    >\n      <BaseSidebarNavigationItem :is-active=\"isActive\">\n        {{ $t('delegates.header') }}\n      </BaseSidebarNavigationItem>\n    </router-link>\n    <router-link\n      v-if=\"space.treasuries.length\"\n      v-slot=\"{ isActive }\"\n      :to=\"{ name: 'spaceTreasury' }\"\n    >\n      <BaseSidebarNavigationItem :is-active=\"isActive\">\n        {{ $t('treasury.title') }}\n      </BaseSidebarNavigationItem>\n    </router-link>\n    <router-link v-slot=\"{ isExactActive }\" :to=\"{ name: 'spaceAbout' }\">\n      <BaseSidebarNavigationItem :is-active=\"isExactActive\">\n        {{ $t('about') }}\n      </BaseSidebarNavigationItem>\n    </router-link>\n    <router-link\n      v-if=\"!isLegacySpace\"\n      v-slot=\"{ isExactActive }\"\n      :to=\"{ name: 'spaceSettings' }\"\n    >\n      <BaseSidebarNavigationItem :is-active=\"isExactActive\">\n        {{ $t('settings.header') }}\n      </BaseSidebarNavigationItem>\n    </router-link>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceSidebarSubspaces.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n}>();\n\nconst mainSpace = computed(() => {\n  return props.space?.parent?.children?.some(\n    child => child.id === props.space?.id\n  )\n    ? props.space?.parent\n    : null;\n});\n\nconst subSpaces = computed(() => {\n  return props.space?.children || [];\n});\n</script>\n\n<template>\n  <div v-if=\"mainSpace || subSpaces.length\" class=\"my-3\">\n    <div v-if=\"mainSpace\">\n      <h5 class=\"px-3 font-normal text-skin-text\">{{ $t('mainspace') }}</h5>\n      <LinkSpace :space-id=\"mainSpace.id\">\n        <BaseSidebarNavigationItem class=\"flex items-center\">\n          <AvatarSpace :space=\"mainSpace\" size=\"22\" />\n          <span class=\"mx-2 truncate text-skin-link\">\n            {{ mainSpace.name }}\n          </span>\n        </BaseSidebarNavigationItem>\n      </LinkSpace>\n    </div>\n    <div v-if=\"subSpaces?.length\">\n      <h5 class=\"px-3 font-normal text-skin-text\">{{ $t('subspaces') }}</h5>\n      <LinkSpace\n        v-for=\"subSpace in subSpaces\"\n        :key=\"subSpace.id\"\n        :space-id=\"subSpace.id\"\n      >\n        <BaseSidebarNavigationItem class=\"flex items-center\">\n          <AvatarSpace :space=\"subSpace\" size=\"22\" />\n          <span class=\"mx-2 truncate text-skin-link\">\n            {{ subSpace.name }}\n          </span>\n        </BaseSidebarNavigationItem>\n      </LinkSpace>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/SpaceSplitDelegationRow.vue",
    "content": "<script setup lang=\"ts\">\nimport { validateForm } from '@/helpers/validation';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\n\ntype DelegateRowForm = {\n  to: string;\n  weight: number;\n};\n\nconst definition = {\n  type: 'object',\n  properties: {\n    to: {\n      type: 'string',\n      format: 'address',\n      title: 'Delegate to',\n      description: 'The address of who you want to delegate to',\n      examples: ['Address']\n    },\n    weight: {\n      type: 'number',\n      minimum: 0,\n      maximum: 100\n    }\n  },\n  required: ['to', 'weight'],\n  additionalProperties: false,\n  errorMessage: {\n    properties: {\n      to: 'Must be a valid checksum address'\n    }\n  }\n};\n\nconst props = defineProps<{\n  address: string;\n  weight: number;\n}>();\n\nconst emit = defineEmits(['deleteDelegate', 'update:modelValue']);\n\nconst form = ref<DelegateRowForm>({\n  to: props.address,\n  weight: props.weight ?? 0\n});\n\nconst validationErrors = computed(() => {\n  return validateForm(definition, clone(form.value));\n});\n\nconst roundedWeight = computed(() => {\n  return Math.round(form.value.weight * 10) / 10;\n});\n\nconst updateFormValue = <T extends keyof DelegateRowForm>(\n  value: DelegateRowForm[T],\n  field: T\n) => {\n  if (field === 'weight') {\n    form.value[field] = (Math.round(parseFloat(value as string) * 10) /\n      10) as DelegateRowForm[T];\n  } else {\n    form.value[field] = value;\n  }\n  emit('update:modelValue', clone(form.value));\n};\n\nwatch(\n  () => props.address,\n  newAddress => {\n    form.value.to = newAddress;\n  },\n  { immediate: true }\n);\n\nwatch(\n  () => props.weight,\n  newWeight => {\n    form.value.weight = newWeight;\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <div class=\"items-end flex space-x-1\">\n    <div class=\"min-w-[66.7%]\">\n      <TuneInput\n        :model-value=\"form.to\"\n        :placeholder=\"definition.properties.to.examples[0]\"\n        :class=\"{ 'tune-error-border': validationErrors?.to && form.to }\"\n        @update:model-value=\"event => updateFormValue(event, 'to')\"\n      />\n    </div>\n    <div class=\"relative\">\n      <TuneInput\n        :model-value=\"roundedWeight\"\n        type=\"number\"\n        :class=\"[\n          'text-right pr-5',\n          { 'tune-error-border': validationErrors?.weight }\n        ]\"\n        @update:model-value=\"\n          event => updateFormValue(parseFloat(event), 'weight')\n        \"\n      >\n        <template #after><span class=\"-mr-2\">%</span></template>\n      </TuneInput>\n    </div>\n    <BaseButtonIcon\n      class=\"h-[42px] min-w-[42px] rounded-full border border-skin-border flex items-center justify-center tune-button\"\n      @click=\"() => $emit('deleteDelegate')\"\n    >\n      <i-ho-x class=\"text-[17px]\" />\n    </BaseButtonIcon>\n  </div>\n  <TuneErrorInput\n    v-if=\"validationErrors && form.to\"\n    :error=\"validationErrors?.to\"\n  />\n  <TuneErrorInput v-if=\"validationErrors\" :error=\"validationErrors?.weight\" />\n</template>\n"
  },
  {
    "path": "src/components/StrategiesBlockWarning.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  context?: 'setup' | 'settings';\n  error?: string | Record<string, any>;\n}>();\n\nconst strategyTestnetErrors = computed(() => {\n  if (typeof props.error === 'object') {\n    const entries = Object.entries(props.error).filter(e => e[1].network);\n    return entries.filter(e => e[1].network === 'Testnet not allowed.');\n  }\n});\n</script>\n\n<template>\n  <div v-if=\"error\" class=\"mt-3\">\n    <BaseMessageBlock\n      v-if=\"error === 'ticketWithAnyOrBasicError'\"\n      level=\"warning-red\"\n    >\n      <i18n-t\n        :keypath=\"\n          context === 'setup'\n            ? 'ticketWithAnyOrBasicErrorSetup'\n            : 'ticketWithAnyOrBasicError'\n        \"\n        tag=\"span\"\n        scope=\"global\"\n      >\n        <template #article>\n          <BaseLink\n            link=\"https://snapshot.mirror.xyz/-uSylOUP82hGAyWUlVn4lCg9ESzKX9QCvsUgvv-ng84\"\n          >\n            {{ $t('learnMore') }}\n          </BaseLink>\n        </template>\n      </i18n-t>\n    </BaseMessageBlock>\n    <MessageWarningTestnet\n      v-else-if=\"strategyTestnetErrors\"\n      context=\"Strategy\"\n      :error=\"error\"\n    />\n\n    <BaseMessageBlock v-else level=\"warning-red\">\n      {{ error }}\n    </BaseMessageBlock>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/StrategiesListItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { isAddress } from '@ethersproject/address';\nimport { shorten, explorerUrl } from '@/helpers/utils';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\n\nimport { Proposal, SpaceStrategy } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  strategy: SpaceStrategy;\n  proposal?: Proposal;\n  showDelete?: boolean;\n  showEdit?: boolean;\n}>();\n\ndefineEmits(['delete', 'edit']);\n\nconst { domain } = useApp();\nconst router = useRouter();\n\nfunction openStrategy() {\n  if (domain) {\n    return window.open(\n      `https://snapshot.org/#/strategy/${props.strategy.name}`,\n      '_blank'\n    );\n  }\n  const strategyRoute = router.resolve({\n    name: 'strategy',\n    params: { name: props.strategy.name }\n  });\n  window.open(strategyRoute.href, '_blank');\n}\n</script>\n\n<template>\n  <TuneBlock class=\"group text-skin-link\">\n    <div class=\"items-center justify-between sm:flex -mt-1\">\n      <h3 class=\"my-0 leading-5\" v-text=\"strategy.name\" />\n      <div class=\"flex\">\n        <div\n          class=\"-mx-[8px] my-2 flex shrink flex-row-reverse items-center gap-3 sm:my-0 sm:flex-row\"\n        >\n          <BaseButtonIcon v-if=\"showDelete\" @click=\"$emit('delete')\">\n            <i-ho-trash />\n          </BaseButtonIcon>\n          <BaseButtonIcon v-if=\"showEdit\" @click=\"$emit('edit')\">\n            <i-ho-pencil />\n          </BaseButtonIcon>\n          <ButtonPlayground\n            :name=\"strategy.name\"\n            :network=\"strategy.network || proposal?.network\"\n            :params=\"strategy.params\"\n            :snapshot=\"proposal?.snapshot\"\n          />\n          <BaseButtonIcon @click=\"openStrategy()\">\n            <i-ho-information-circle />\n          </BaseButtonIcon>\n        </div>\n      </div>\n    </div>\n\n    <div>\n      <div v-if=\"strategy.network\" class=\"flex justify-between\">\n        <span class=\"mr-1 flex-auto text-skin-text\"> network </span>\n        <span\n          v-text=\"networks[strategy.network || proposal?.network || 'x']?.name\"\n        />\n      </div>\n      <div v-for=\"(param, key) in strategy.params\" :key=\"key\" class=\"flex\">\n        <span class=\"mr-1 flex-auto text-skin-text\" v-text=\"key\" />\n        <BaseLink\n          v-if=\"\n            key === 'address' || (typeof param === 'string' && isAddress(param))\n          \"\n          :link=\"\n            explorerUrl(strategy.network || proposal?.network, param as string)\n          \"\n          class=\"block\"\n        >\n          <span v-text=\"shorten(param as string)\" />\n        </BaseLink>\n        <BaseLink\n          v-else-if=\"typeof param === 'string' && param.startsWith('http')\"\n          :link=\"param\"\n          class=\"ml-2 block truncate\"\n        >\n          <span v-text=\"param\" />\n        </BaseLink>\n        <span\n          v-else\n          class=\"ml-2 truncate\"\n          v-text=\"\n            ['string', 'number', 'boolean'].includes(typeof param)\n              ? param\n              : typeof param\n          \"\n        />\n      </div>\n    </div>\n  </TuneBlock>\n</template>\n"
  },
  {
    "path": "src/components/TextAutolinker.vue",
    "content": "<script setup lang=\"ts\">\nimport Autolinker from 'autolinker';\nimport { isSnapshotUrl } from '@/helpers/utils';\n\ninterface Props {\n  text: string;\n  truncate?: number;\n}\nconst props = withDefaults(defineProps<Props>(), {\n  truncate: 0\n});\n\nconst textAutolinker = ref();\nconst showModal = ref(false);\nconst clickedUrl = ref('');\n\nconst textWithLinks = computed(() =>\n  Autolinker.link(props.text, {\n    truncate: props.truncate,\n    sanitizeHtml: true\n  })\n);\n\nfunction handleLinkClick(e, url) {\n  e.preventDefault();\n  clickedUrl.value = url;\n\n  if (isSnapshotUrl(url)) {\n    return handleConfirm();\n  }\n\n  showModal.value = true;\n}\n\nfunction handleConfirm() {\n  window.open(clickedUrl.value, '_blank', 'noopener,noreferrer');\n}\n\nonMounted(() => {\n  textAutolinker.value.querySelectorAll('a[href]').forEach(function (link) {\n    link.addEventListener('click', function (e) {\n      handleLinkClick(e, link.getAttribute('href'));\n    });\n  });\n});\n</script>\n\n<template>\n  <!-- eslint-disable-next-line vue/no-v-html -->\n  <span ref=\"textAutolinker\" v-html=\"textWithLinks\" />\n  <Teleport to=\"#modal\">\n    <ModalLinkPreview\n      :open=\"showModal\"\n      :clicked-url=\"clickedUrl\"\n      @close=\"showModal = false\"\n      @confirm=\"handleConfirm\"\n    />\n  </Teleport>\n</template>\n"
  },
  {
    "path": "src/components/TextareaArray.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  modelValue: string[];\n  title?: string;\n  placeholder?: string;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nfunction handleInput(input) {\n  const inputString = input\n    .replace(/\\n/g, ' ')\n    .replace(/,/g, ' ')\n    .replace(/;/g, ' ')\n    .split(' ')\n    .map(item => item.trim())\n    .filter(item => !!item)\n    .filter((item, index, array) => array.indexOf(item) === index);\n  emit('update:modelValue', inputString);\n}\n</script>\n\n<template>\n  <LabelInput>\n    {{ title || '' }}\n  </LabelInput>\n  <TextareaAutosize\n    v-bind=\"$attrs\"\n    :model-value=\"modelValue?.join('\\n')\"\n    :placeholder=\"placeholder || ''\"\n    class=\"input w-full text-left\"\n    @update:model-value=\"handleInput($event)\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/TextareaAutosize.vue",
    "content": "<script setup lang=\"ts\">\nconst props = withDefaults(\n  defineProps<{\n    modelValue?: string | number;\n    autosize?: boolean;\n    minHeight?: number;\n    maxHeight?: number;\n    maxLength?: number;\n    placeholder?: string;\n    title?: string;\n    information?: string;\n    definition?: any;\n    isDisabled?: boolean;\n  }>(),\n  {\n    modelValue: '',\n    autosize: true,\n    placeholder: '',\n    title: '',\n    information: '',\n    definition: null,\n    minHeight: 0,\n    maxHeight: 0,\n    maxLength: undefined,\n    isDisabled: false\n  }\n);\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst { modelValue, minHeight, maxHeight } = toRefs(props);\n\n// data property for v-model binding with real textarea tag\nconst val = ref(props.modelValue);\n// works when content height becomes more then value of the maxHeight property\nconst maxHeightScroll = ref(false);\nconst height = ref('auto');\nconst textarea = ref<HTMLTextAreaElement | null>(null);\n\nconst computedStyles = computed(() => {\n  if (minHeight.value) return `min-height: ${minHeight.value}px;`;\n  if (!props.autosize) return '';\n  return `resize: none; height: ${height.value}; overflow: ${\n    maxHeightScroll.value ? 'auto' : 'hidden'\n  }`;\n});\n\nfunction resize() {\n  height.value = 'auto';\n  nextTick(() => {\n    if (!textarea.value) return;\n    let contentHeight = textarea.value.scrollHeight + 1;\n    if (props.minHeight) {\n      contentHeight =\n        contentHeight < props.minHeight ? props.minHeight : contentHeight;\n    }\n    if (props.maxHeight) {\n      if (contentHeight > props.maxHeight) {\n        contentHeight = props.maxHeight;\n        maxHeightScroll.value = true;\n      } else {\n        maxHeightScroll.value = false;\n      }\n    }\n    const heightVal = `${contentHeight}px`;\n    height.value = heightVal;\n  });\n  return;\n}\n\nwatch(modelValue, v => {\n  val.value = v;\n});\n\nwatch(val, v => {\n  nextTick(resize);\n  emit('update:modelValue', v);\n  if (v) resize();\n});\n\nwatch([minHeight, maxHeight], () => {\n  nextTick(resize);\n});\n\nonMounted(() => resize());\n</script>\n\n<template>\n  <LabelInput v-if=\"title || definition?.title\" :information=\"information\">\n    {{ title ?? definition.title }}\n  </LabelInput>\n  <textarea\n    v-bind=\"$attrs\"\n    ref=\"textarea\"\n    v-model=\"val\"\n    class=\"!mt-1 h-auto w-full rounded-3xl border border-skin-border px-4 py-3 focus-within:!border-skin-text\"\n    :class=\"{\n      'cursor-not-allowed': isDisabled\n    }\"\n    :style=\"computedStyles\"\n    :maxlength=\"maxLength\"\n    :placeholder=\"placeholder\"\n    :disabled=\"isDisabled\"\n    @focus=\"resize\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/TextareaJson.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  modelValue: Record<string, unknown>;\n  isValid: boolean;\n}>();\n\nconst emit = defineEmits(['update:modelValue', 'update:isValid']);\n\nconst input = ref('');\n\nfunction handleInput() {\n  try {\n    emit('update:modelValue', JSON.parse(input.value));\n    emit('update:isValid', true);\n  } catch (e) {\n    emit('update:isValid', false);\n  }\n}\n\nwatch(input, () => handleInput());\n\nif (props.modelValue) input.value = JSON.stringify(props.modelValue, null, 2);\n</script>\n\n<template>\n  <TextareaAutosize\n    v-model=\"input\"\n    class=\"no-scrollbar w-full !overflow-x-scroll whitespace-pre font-mono text-sm\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/TheActionbar.vue",
    "content": "<script setup lang=\"ts\">\nimport { useBreakpoints } from '@vueuse/core';\nimport { SNAPSHOT_BREAKPOINTS } from '@/helpers/constants';\n\nconst props = withDefaults(\n  defineProps<{\n    breakPoint?: 'sm' | 'md';\n  }>(),\n  {\n    breakPoint: 'sm'\n  }\n);\n\nconst disabled = useBreakpoints(SNAPSHOT_BREAKPOINTS).greater(props.breakPoint);\n</script>\n\n<template>\n  <Teleport v-if=\"!disabled\" to=\"#action-bar\">\n    <div\n      class=\"fixed bottom-0 z-40 w-full border-t border-skin-border bg-skin-bg sm:w-[calc(100%-60px)]\"\n    >\n      <slot />\n    </div>\n  </Teleport>\n\n  <slot v-else />\n</template>\n"
  },
  {
    "path": "src/components/TheFlashNotification.vue",
    "content": "<script setup>\nconst { items } = useFlashNotification();\n</script>\n\n<template>\n  <div\n    class=\"pointer-events-none fixed bottom-0 left-0 right-0 z-[60] mb-4 flex w-full flex-col items-center space-y-2\"\n  >\n    <TransitionGroup name=\"fade\">\n      <div\n        v-for=\"item in items\"\n        :key=\"item.id\"\n        class=\"pointer-events-auto w-full px-4 sm:max-w-[460px]\"\n      >\n        <div\n          class=\"flex w-full items-center justify-between rounded-full bg-red px-[18px] py-[12px] text-white\"\n          :class=\"`!bg-${item.type}`\"\n        >\n          <div class=\"flex items-center gap-2\">\n            <i-ho-exclamation-circle\n              v-if=\"item.type === 'red'\"\n              class=\"shrink-0 text-base\"\n            />\n            <i-ho-check\n              v-if=\"item.type === 'green'\"\n              class=\"shrink-0 text-base\"\n            />\n            <span class=\"text-left\">{{ item.message }}</span>\n          </div>\n\n          <i-ho-x\n            class=\"shrink-0 cursor-pointer text-base\"\n            @click=\"item.remove()\"\n          />\n        </div>\n      </div>\n    </TransitionGroup>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/TheFooter.vue",
    "content": "<script setup lang=\"ts\">\nimport { SNAPSHOT_HELP_LINK } from '@/helpers/constants';\n\nconst yearNow = new Date().getFullYear();\n\nconst snapshotTextLinks = [\n  {\n    text: 'about',\n    link: 'https://docs.snapshot.org/introduction'\n  },\n  {\n    text: 'blog',\n    link: 'https://snapshot.mirror.xyz/'\n  },\n  {\n    text: 'network-support',\n    link: 'https://snapshot.box/#/network'\n  },\n  {\n    text: 'terms',\n    link: {\n      name: 'terms-and-conditions'\n    }\n  }\n];\n\nconst resourcesTextLinks = [\n  {\n    text: 'faqs',\n    link: 'https://docs.snapshot.org/faq'\n  },\n  {\n    text: 'github',\n    link: 'https://github.com/snapshot-labs'\n  },\n  {\n    text: 'docs',\n    link: 'https://docs.snapshot.org/'\n  },\n  {\n    text: 'featureRequests',\n    link: 'https://snapshot.canny.io/feature-requests'\n  },\n  {\n    text: 'support',\n    link: SNAPSHOT_HELP_LINK\n  }\n];\n</script>\n\n<template>\n  <div class=\"-mt-3 border-t\" />\n  <BaseContainer class=\"space-y-5 py-[40px] md:space-y-0\">\n    <div class=\"space-y-5 md:flex md:space-y-0\">\n      <div>\n        <div class=\"mx-auto md:mx-0\">\n          <FooterTitle class=\"mb-2 text-center md:text-left\">\n            {{ $t('newsletter.title') }}\n          </FooterTitle>\n\n          <InputNewsletter tag=\"6449077\" class=\"relative mx-auto w-[300px]\" />\n        </div>\n\n        <div class=\"hidden md:block lg:hidden\">\n          <FooterSocials />\n        </div>\n      </div>\n      <div\n        class=\"flex justify-center space-x-[70px] text-center md:w-full md:text-left lg:ml-[60px] lg:justify-start\"\n      >\n        <FooterLinks>\n          <FooterTitle> Snapshot </FooterTitle>\n          <FooterLinksItem\n            v-for=\"item in snapshotTextLinks\"\n            :key=\"item.text\"\n            :link=\"item.link\"\n          >\n            {{ $t(`footerView.${item.text}`) }}\n            <BasePill\n              v-if=\"item.text === 'jobs'\"\n              class=\"ml-2 inline-block !rounded px-1 py-0 text-xs\"\n            >\n              {{ $t('footerView.hiring') }}\n            </BasePill>\n          </FooterLinksItem>\n        </FooterLinks>\n\n        <FooterLinks>\n          <FooterTitle> {{ $t('footerView.resources') }} </FooterTitle>\n\n          <FooterLinksItem\n            v-for=\"item in resourcesTextLinks\"\n            :key=\"item.text\"\n            :link=\"item.link\"\n          >\n            {{ $t(`footerView.${item.text}`) }}\n          </FooterLinksItem>\n        </FooterLinks>\n      </div>\n      <div class=\"pb-1 md:hidden lg:mt-0 lg:block\">\n        <FooterTitle class=\"hidden whitespace-nowrap lg:block\">\n          {{ $t('joinCommunity') }}\n        </FooterTitle>\n        <FooterSocials />\n      </div>\n    </div>\n\n    <div class=\"whitespace-nowrap text-center opacity-40 md:text-left\">\n      © {{ yearNow }} Snapshot Labs.\n    </div>\n  </BaseContainer>\n</template>\n"
  },
  {
    "path": "src/components/TheLayout.vue",
    "content": "<script lang=\"ts\" setup>\nwithDefaults(\n  defineProps<{\n    slim?: boolean;\n    reverse?: boolean;\n  }>(),\n  {\n    slim: true,\n    reverse: false\n  }\n);\n</script>\n\n<template>\n  <BaseContainer :slim=\"slim\">\n    <slot />\n    <div\n      v-if=\"$slots['sidebar-left']\"\n      id=\"sidebar-left\"\n      class=\"float-left w-full lg:w-1/4\"\n    >\n      <slot name=\"sidebar-left\" />\n    </div>\n    <div\n      v-if=\"$slots['content-right']\"\n      id=\"content-right\"\n      class=\"relative float-right w-full pl-0 lg:w-3/4 lg:pl-5\"\n    >\n      <slot name=\"content-right\" />\n    </div>\n    <div\n      class=\"lg:flex\"\n      :class=\"{ 'flex flex-col-reverse lg:flex-row': reverse }\"\n    >\n      <div\n        v-if=\"$slots['content-left']\"\n        id=\"content-left\"\n        class=\"relative w-full lg:w-8/12 lg:pr-5\"\n      >\n        <slot name=\"content-left\" />\n      </div>\n      <div\n        v-if=\"$slots['sidebar-right']\"\n        id=\"sidebar-right\"\n        class=\"w-full lg:w-4/12 lg:min-w-[321px]\"\n      >\n        <slot name=\"sidebar-right\" />\n      </div>\n    </div>\n  </BaseContainer>\n</template>\n"
  },
  {
    "path": "src/components/TheModalNotification.vue",
    "content": "<script setup lang=\"ts\">\nimport { SNAPSHOT_HELP_LINK } from '@/helpers/constants';\nimport defaults from '@/locales/default.json';\n\nconst { items } = useModalNotification();\n</script>\n\n<template>\n  <teleport to=\"#modal\">\n    <template v-for=\"item in items\" :key=\"item.id\">\n      <ModalMessage\n        v-if=\"defaults.modalNotifications?.[item.description]\"\n        :open=\"items.length > 0\"\n        :title=\"$t(`modalNotifications.${item.description}.title`)\"\n        :level=\"item.type\"\n        @close=\"item.remove()\"\n      >\n        <template #message>\n          <i18n-t\n            :keypath=\"`modalNotifications.${item.description}.message`\"\n            tag=\"span\"\n            scope=\"global\"\n          >\n            <template #help>\n              <BaseLink :link=\"SNAPSHOT_HELP_LINK\">Discord</BaseLink>\n            </template>\n          </i18n-t>\n        </template>\n      </ModalMessage>\n    </template>\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/components/TheNavbar.vue",
    "content": "<script setup lang=\"ts\">\nconst { pendingTransactions, pendingTransactionsWithHash } = useTxStatus();\nconst { env, showSidebar, domain } = useApp();\nconst { web3Account } = useWeb3();\n\nconst showPendingTransactionsModal = ref(false);\n\nwatch(\n  () => pendingTransactionsWithHash.value.length === 0,\n  () => {\n    showPendingTransactionsModal.value = false;\n  }\n);\n</script>\n\n<template>\n  <div>\n    <div class=\"px-3 sm:px-4\">\n      <div class=\"flex items-center py-[12px]\">\n        <div class=\"flex flex-auto items-center\">\n          <BaseButtonRound\n            v-if=\"!domain\"\n            class=\"sm:hidden\"\n            @click=\"showSidebar = !showSidebar\"\n          >\n            <i-ho-menu class=\"text-skin-link\" />\n          </BaseButtonRound>\n          <router-link\n            :to=\"{ path: '/' }\"\n            class=\"hidden items-center sm:block\"\n            style=\"font-size: 24px\"\n          >\n            snapshot\n          </router-link>\n          <span\n            v-if=\"env === 'demo'\"\n            class=\"ml-1 hidden uppercase text-[12px] sm:block mb-1\"\n            v-text=\"'testnet'\"\n          />\n          <Banner class=\"ml-3\" />\n        </div>\n        <div :key=\"web3Account\" class=\"flex space-x-2\">\n          <NavbarAccount />\n\n          <NavbarNotifications v-if=\"web3Account && !domain\" />\n\n          <NavbarExtras />\n        </div>\n      </div>\n    </div>\n  </div>\n  <div\n    v-if=\"pendingTransactions.length > 0\"\n    class=\"flex items-center justify-center gap-x-2 bg-skin-border py-2 text-center text-skin-link\"\n    :class=\"{\n      'cursor-pointer': pendingTransactions.length > 0\n    }\"\n    @click=\"\n      pendingTransactionsWithHash.length\n        ? (showPendingTransactionsModal = true)\n        : null\n    \"\n  >\n    <TuneLoadingSpinner class=\"text-skin-link\" />\n    <span>\n      {{ $t('setup.pendingTransactions') }}:\n      {{ pendingTransactions.length }}\n    </span>\n  </div>\n  <Teleport to=\"#modal\">\n    <ModalPendingTransactions\n      :open=\"showPendingTransactionsModal\"\n      @close=\"showPendingTransactionsModal = false\"\n    />\n  </Teleport>\n</template>\n"
  },
  {
    "path": "src/components/TheSearchBar.vue",
    "content": "<script setup lang=\"ts\">\nimport { watchDebounced } from '@vueuse/core';\n\nconst route = useRoute();\nconst router = useRouter();\nconst { t } = useI18n();\n\nconst emit = defineEmits(['update:inputSearch']);\n\nconst input = ref((route.query.q as string) || '');\n\nconst routeQuery = computed(() => route.query.q || undefined);\nconst searchOptions = computed(() => [\n  {\n    text: t('spaces'),\n    action: 'spaces',\n    extras: { selected: route.query.filter === 'spaces' }\n  },\n  {\n    text: t('networks'),\n    action: 'networks',\n    extras: { selected: route.query.filter === 'networks' }\n  },\n  {\n    text: t('strategiesPage'),\n    action: 'strategies',\n    extras: { selected: route.query.filter === 'strategies' }\n  },\n  {\n    text: t('plugins'),\n    action: 'plugins',\n    extras: { selected: route.query.filter === 'plugins' }\n  }\n]);\n\nconst searchSelectedOption = computed(\n  () =>\n    searchOptions.value.find(option => option.action === route.query.filter)\n      ?.text ?? t('spaces')\n);\n\nfunction selectFilter(e) {\n  router.push({\n    query: { q: routeQuery.value, filter: e }\n  });\n}\n\nfunction handleUpdateSearch(e: string) {\n  input.value = e;\n  emit('update:inputSearch', e);\n}\n\nwatchDebounced(\n  input,\n  () => {\n    router.push({\n      query: { ...route.query, q: input.value || undefined }\n    });\n  },\n  { debounce: 300 }\n);\n</script>\n\n<template>\n  <div class=\"flex rounded-full border pl-3 pr-0 focus-within:border-skin-text\">\n    <BaseSearch\n      :model-value=\"input\"\n      :placeholder=\"$t('searchPlaceholder')\"\n      class=\"flex-auto pr-2\"\n      @update:model-value=\"handleUpdateSearch\"\n    />\n    <div class=\"flex items-center border-l text-skin-link\" style=\"height: 44px\">\n      <BaseMenu :items=\"searchOptions\" @select=\"selectFilter\">\n        <template #button>\n          <button class=\"flex h-full flex-grow items-center\">\n            <span class=\"ml-3\" v-text=\"searchSelectedOption\" />\n            <i-ho-chevron-down class=\"ml-1 mr-[12px] text-xs text-skin-link\" />\n          </button>\n        </template>\n      </BaseMenu>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/TheSidebar.vue",
    "content": "<script setup lang=\"ts\">\nimport draggable from 'vuedraggable';\nimport { lsSet, lsGet } from '@/helpers/utils';\n\nconst router = useRouter();\nconst { web3Account } = useWeb3();\nconst { loadFollows, followingSpaces, loadingFollows } = useFollowSpace();\nconst { spaceHasUnseenProposals } = useUnseenProposals();\nconst { showSidebar } = useApp();\nconst { loadSpaces, spaces, isLoadingSpaces } = useSpaces();\n\nconst orderedSpaces = ref<string[]>([]);\n\nconst spacesMap = computed(() => {\n  return (\n    spaces.value?.reduce((acc, space) => ({ ...acc, [space.id]: space }), {}) ??\n    {}\n  );\n});\n\nfunction updateSpaceOrder() {\n  if (web3Account.value)\n    lsSet(\n      `savedSpaceOrder.${web3Account.value.slice(0, 8).toLowerCase()}`,\n      orderedSpaces.value\n    );\n}\n\nwatch(followingSpaces, () => {\n  orderedSpaces.value = followingSpaces.value;\n  const savedSpaceOrder = lsGet(\n    `savedSpaceOrder.${web3Account.value.slice(0, 8).toLowerCase()}`,\n    []\n  );\n  // Order side bar and add new spaces to the end of the sidebar\n  orderedSpaces.value.sort((a, b) => {\n    if (!savedSpaceOrder.includes(a)) return -1;\n    if (!savedSpaceOrder.includes(b)) return 1;\n    return savedSpaceOrder.indexOf(a) - savedSpaceOrder.indexOf(b);\n  });\n\n  updateSpaceOrder();\n});\n\nwatch(followingSpaces, () => {\n  loadSpaces(followingSpaces.value);\n});\n\nwatch(\n  web3Account,\n  () => {\n    loadFollows();\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <div\n    class=\"no-scrollbar flex h-full flex-col items-end overflow-auto overscroll-contain pb-[12px]\"\n    @click=\"showSidebar = false\"\n  >\n    <div class=\"w-full\" @click=\"router.push({ name: 'home' })\">\n      <div class=\"flex h-[70px] items-center justify-center\">\n        <BaseIcon size=\"35\" name=\"snapshot\" class=\"text-snapshot\" />\n      </div>\n    </div>\n    <div class=\"mt-[6px] px-[10px]\">\n      <BaseButtonRound\n        v-tippy=\"{\n          content: 'Timeline',\n          placement: 'right',\n          delay: [750, 0],\n          touch: ['hold', 500]\n        }\"\n        size=\"40px\"\n        @click=\"router.push({ name: 'timeline' })\"\n      >\n        <BaseIcon size=\"20\" name=\"feed\" />\n      </BaseButtonRound>\n    </div>\n    <SidebarSpacesSkeleton\n      v-if=\"spaces.length === 0 && (isLoadingSpaces || loadingFollows)\"\n    />\n    <draggable\n      v-else-if=\"spaces.length > 0 && web3Account\"\n      v-model=\"orderedSpaces\"\n      :component-data=\"{ type: 'transition-group' }\"\n      v-bind=\"{ animation: 200 }\"\n      item-key=\"id\"\n      class=\"mt-[12px] space-y-[12px]\"\n      :delay=\"200\"\n      :delay-on-touch-only=\"true\"\n      @update=\"updateSpaceOrder\"\n    >\n      <template #item=\"{ element }\">\n        <div\n          v-if=\"spacesMap[element]\"\n          class=\"group relative flex items-center px-[10px]\"\n        >\n          <SidebarUnreadIndicator\n            :space=\"element\"\n            :has-unseen=\"spaceHasUnseenProposals(element)\"\n          />\n          <router-link\n            v-tippy=\"{\n              content: spacesMap[element].name,\n              placement: 'right',\n              delay: [750, 0],\n              touch: ['hold', 500]\n            }\"\n            :to=\"{\n              name: 'spaceProposals',\n              params: { key: spacesMap[element].id }\n            }\"\n          >\n            <AvatarSpace\n              :key=\"element\"\n              :space=\"spacesMap[element]\"\n              symbol-index=\"space\"\n              size=\"40\"\n              class=\"pointer-events-none\"\n            />\n            <BaseCounter\n              v-if=\"spacesMap[element].activeProposals\"\n              :counter=\"spacesMap[element].activeProposals\"\n              class=\"absolute -top-[1px] right-[9px] !h-[16px] !min-w-[16px] !bg-green !leading-[16px]\"\n            />\n          </router-link>\n        </div>\n      </template>\n    </draggable>\n\n    <div class=\"flex w-[60px] items-center justify-center py-[15px]\">\n      <div class=\"h-[1px] w-[20px] bg-skin-border\" />\n    </div>\n\n    <div class=\"flex flex-col items-center space-y-2 px-[10px]\">\n      <BaseButtonRound\n        v-tippy=\"{\n          content: 'Create space',\n          placement: 'right',\n          delay: [750, 0],\n          touch: ['hold', 500]\n        }\"\n        size=\"40px\"\n        @click=\"\n          router.push({\n            name: 'setup',\n            query: {\n              step: '0'\n            }\n          })\n        \"\n      >\n        <i-ho-plus-sm />\n      </BaseButtonRound>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/TreasuryAssetsList.vue",
    "content": "<script setup lang=\"ts\">\nimport { TreasuryWallet } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  wallet: TreasuryWallet;\n}>();\n\nconst { loadFilteredTokenBalances, treasuryAssets, loadingBalances } =\n  useTreasury();\n\nconst walletAssets = computed(\n  () => treasuryAssets.value?.[props.wallet.address] ?? []\n);\n\nconst router = useRouter();\n\nonMounted(() =>\n  loadFilteredTokenBalances(props.wallet.address, props.wallet.network)\n);\n</script>\n\n<template>\n  <div class=\"mb-3 px-4 md:px-0\">\n    <ButtonBack @click=\"router.push({ name: 'spaceTreasury' })\" />\n\n    <h3>{{ wallet.name }}</h3>\n  </div>\n  <BaseBlock\n    :title=\"$t('treasury.assets.title')\"\n    :counter=\"walletAssets?.length\"\n    :label=\"$t('treasury.24hChange')\"\n    :loading=\"loadingBalances\"\n    slim\n  >\n    <ul v-if=\"walletAssets.length\">\n      <TreasuryAssetsListItem\n        v-for=\"asset in walletAssets\"\n        :key=\"asset.contract_address\"\n        :asset=\"asset\"\n      />\n    </ul>\n    <div v-else>\n      <p class=\"p-4 text-center\">\n        {{ $t('treasury.assets.empty') }}\n      </p>\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/TreasuryAssetsListItem.spec.js",
    "content": "import { shallowMount } from '@vue/test-utils';\nimport { describe, afterEach, it, expect } from 'vitest';\nimport TreasuryAssetsListItem from '@/components/TreasuryAssetsListItem.vue';\nimport { formatUnits } from '@ethersproject/units';\n\nconst { formatCompactNumber, formatNumber } = useIntl();\n\ndescribe('TreasuryAssetsListItem', () => {\n  let wrapper;\n\n  const findAssetName = () => wrapper.find('[data-testid=\"asset-name\"]');\n  const findAssetBalance = () => wrapper.find('[data-testid=\"asset-balance\"]');\n  const findAssetSymbol = () => wrapper.find('[data-testid=\"asset-symbol\"]');\n  const findAssetQuote = () => wrapper.find('[data-testid=\"asset-quote\"]');\n\n  const asset = {\n    contract_name: 'Wrapped Ether',\n    contract_ticker_symbol: 'WETH',\n    contract_address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',\n    contract_decimals: 18,\n    logo_url: 'https://logos.covalenthq.com/',\n    balance: '12000045317566025999',\n    balance_24h: '12000045317566025999',\n    quote: 2700,\n    quote_24h: 2800\n  };\n\n  function createComponent(params = {}) {\n    wrapper = shallowMount(TreasuryAssetsListItem, {\n      ...params,\n      props: { asset }\n    });\n  }\n\n  afterEach(() => {\n    wrapper.unmount();\n  });\n\n  it('should render the correct name', () => {\n    createComponent();\n\n    expect(findAssetName().text()).toContain(asset.contract_name);\n  });\n\n  it('should render the correct asset balance', () => {\n    createComponent();\n\n    expect(findAssetBalance().text()).toContain(\n      formatCompactNumber(\n        Number(formatUnits(asset.balance, asset.contract_decimals))\n      )\n    );\n  });\n\n  it('should render the correct asset symbol', () => {\n    createComponent();\n\n    expect(findAssetSymbol().text()).toContain(asset.contract_ticker_symbol);\n  });\n\n  it('should render the correct quote', () => {\n    createComponent();\n\n    expect(findAssetQuote().text()).toContain(`$${formatNumber(asset.quote)}`);\n  });\n});\n"
  },
  {
    "path": "src/components/TreasuryAssetsListItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { formatUnits } from '@ethersproject/units';\nimport { TreasuryAsset } from '@/helpers/interfaces';\n\ndefineProps<{\n  asset: TreasuryAsset;\n}>();\n\nconst { formatCompactNumber, formatNumber } = useIntl();\n</script>\n\n<template>\n  <li\n    class=\"flex items-center gap-2 border-b border-skin-border px-4 py-[12px] last:border-b-0\"\n  >\n    <AvatarToken :address=\"asset.contract_address\" class=\"mr-1\" size=\"38\" />\n\n    <div class=\"flex w-full justify-between\">\n      <div class=\"leading-6\">\n        <div\n          data-testid=\"asset-name\"\n          class=\"text-md font-semibold text-skin-heading\"\n        >\n          {{ asset.contract_name }}\n        </div>\n        <div>\n          <span data-testid=\"asset-balance\" class=\"mr-1\">\n            {{\n              formatCompactNumber(\n                Number(formatUnits(asset.balance, asset.contract_decimals))\n              )\n            }}\n          </span>\n          <span data-testid=\"asset-symbol\">\n            {{ asset.contract_ticker_symbol }}\n          </span>\n        </div>\n      </div>\n      <div class=\"text-right\">\n        <div data-testid=\"asset-quote\" class=\"text-md text-skin-heading\">\n          ${{ formatNumber(asset.quote) }}\n        </div>\n        <IndicatorAssetsChange\n          :quote=\"{ quote: asset.quote, quote_24h: asset.quote_24h }\"\n        />\n      </div>\n    </div>\n  </li>\n</template>\n"
  },
  {
    "path": "src/components/TreasuryWalletsList.spec.js",
    "content": "import { shallowMount } from '@vue/test-utils';\nimport { describe, afterEach, it, expect } from 'vitest';\nimport TreasuryWalletsList from '@/components/TreasuryWalletsList.vue';\nimport i18n from '@/helpers/i18n';\n\ndescribe('TreasuryWalletsList', () => {\n  let wrapper;\n\n  const findComponentWalletsBlock = () =>\n    wrapper.findComponent('[data-testid=\"treasury-wallets-block\"]');\n  const findComponentWalletsMessageBlock = () =>\n    wrapper.findComponent('[data-testid=\"treasury-wallets-message-block\"]');\n\n  function createComponent(params = {}) {\n    wrapper = shallowMount(TreasuryWalletsList, {\n      ...params,\n      global: {\n        plugins: [i18n]\n      }\n    });\n  }\n\n  afterEach(() => {\n    wrapper.unmount();\n  });\n\n  it('should render wallets block when wallets is not empty', () => {\n    createComponent({\n      props: {\n        wallets: [\n          {\n            address: '0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f',\n            name: 'Test wallet',\n            network: '1'\n          }\n        ]\n      }\n    });\n    expect(findComponentWalletsBlock().exists()).toBe(true);\n  });\n\n  it('should not render wallets block when wallets is empty', () => {\n    createComponent({\n      props: {\n        wallets: []\n      }\n    });\n    expect(findComponentWalletsBlock().exists()).toBe(false);\n  });\n\n  it('should not render message block when wallets is not empty', () => {\n    createComponent({\n      props: {\n        wallets: [\n          {\n            address: '0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f',\n            name: 'Test wallet',\n            network: '1'\n          }\n        ]\n      }\n    });\n    expect(findComponentWalletsMessageBlock().exists()).toBe(false);\n  });\n\n  it('should render message block when wallets is empty', () => {\n    createComponent({\n      props: {\n        wallets: []\n      }\n    });\n    expect(findComponentWalletsMessageBlock().exists()).toBe(true);\n  });\n});\n"
  },
  {
    "path": "src/components/TreasuryWalletsList.vue",
    "content": "<script setup lang=\"ts\">\nimport { TreasuryWallet } from '@/helpers/interfaces';\nimport { lookupAddress } from '@/helpers/utils';\n\nconst props = defineProps<{\n  wallets: TreasuryWallet[];\n  admins: string[];\n}>();\n\nconst { web3Account } = useWeb3();\n\nconst ensAddresses = ref<{ [k: string]: string } | null>(null);\n\nonMounted(async () => {\n  ensAddresses.value = await lookupAddress(props.wallets.map(w => w.address));\n});\n</script>\n\n<template>\n  <h1 class=\"hidden lg:mb-3 lg:block\">\n    {{ $t('treasury.title') }}\n  </h1>\n\n  <BaseBlock\n    v-if=\"wallets.length\"\n    :title=\"$t('treasury.wallets.title')\"\n    :counter=\"wallets.length\"\n    :label=\"$t('treasury.24hChange')\"\n    data-testid=\"treasury-wallets-block\"\n    slim\n  >\n    <ul>\n      <TreasuryWalletsListItem\n        v-for=\"wallet in wallets\"\n        :key=\"wallet.address\"\n        :wallet=\"wallet\"\n        :ens-address=\"ensAddresses?.[wallet.address]\"\n      />\n    </ul>\n  </BaseBlock>\n  <BaseBlock\n    v-else\n    class=\"text-center\"\n    data-testid=\"treasury-wallets-message-block\"\n  >\n    <div>\n      <div>\n        {{ $t('treasury.wallets.empty') }}\n      </div>\n      <TuneButton\n        v-if=\"admins?.includes(web3Account)\"\n        class=\"mt-3\"\n        @click=\"$router.push({ name: 'spaceSettings' })\"\n      >\n        {{ $t('treasury.wallets.addTreasury') }}\n      </TuneButton>\n    </div>\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/components/TreasuryWalletsListItem.spec.js",
    "content": "import { mount, RouterLinkStub } from '@vue/test-utils';\nimport { describe, afterEach, it, expect } from 'vitest';\nimport TreasuryWalletsListItem from '@/components/TreasuryWalletsListItem.vue';\nimport AvatarUser from './AvatarUser.vue';\nimport BaseLink from './BaseLink.vue';\nimport { shorten, explorerUrl } from '@/helpers/utils';\n\ndescribe('TreasuryWalletsListItem', () => {\n  let wrapper;\n\n  const findWalletName = () => wrapper.find('[data-testid=\"wallet-name\"]');\n  const findWalletEnsAddress = () =>\n    wrapper.find('[data-testid=\"wallet-ens-address\"]');\n  const findComponentBaseAvatar = () => wrapper.findComponent(AvatarUser);\n  const findComponentBaseLinkOne = () => wrapper.findAllComponents(BaseLink)[0];\n  const findComponentBaseLinkTwo = () => wrapper.findAllComponents(BaseLink)[1];\n\n  const wallet = {\n    name: 'Test Wallet',\n    address: '0x0000000000000000000000000000000000000000',\n    network: 1\n  };\n\n  function createComponent(params = {}) {\n    wrapper = mount(TreasuryWalletsListItem, {\n      ...params,\n      global: {\n        stubs: { AvatarUser, BaseLink, RouterLink: RouterLinkStub }\n      },\n      props: {\n        wallet,\n        ensAddress: 'test.eth'\n      }\n    });\n  }\n\n  afterEach(() => {\n    wrapper.unmount();\n  });\n\n  it('should render base avatar using wallet address', () => {\n    createComponent();\n\n    expect(findComponentBaseAvatar().props('address')).toBe(wallet.address);\n  });\n\n  it('should render wallet name', () => {\n    createComponent();\n\n    expect(findWalletName().text()).toBe(wallet.name);\n  });\n\n  it('should render shortened wallet address', () => {\n    createComponent();\n\n    expect(findComponentBaseLinkOne().text()).toContain(\n      shorten(wallet.address)\n    );\n  });\n\n  it('should render external link pointing to explorer', () => {\n    createComponent();\n\n    expect(findComponentBaseLinkTwo().props('link')).toBe(\n      explorerUrl(wallet.network, wallet.address)\n    );\n  });\n\n  it('should render wallet ens address', () => {\n    createComponent();\n\n    expect(findWalletEnsAddress().text()).toBe('test.eth');\n  });\n\n  it('should not render ens address when not set', async () => {\n    createComponent();\n\n    await wrapper.setProps({ ensAddress: undefined });\n    expect(findWalletEnsAddress().exists()).toBe(false);\n  });\n\n  it('should render link to the wallet route', () => {\n    createComponent();\n\n    expect(findComponentBaseLinkOne().props('link')).toMatchObject({\n      name: 'spaceTreasury',\n      params: { wallet: wallet.address }\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/TreasuryWalletsListItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { TreasuryWallet } from '@/helpers/interfaces';\nimport { shorten, explorerUrl } from '@/helpers/utils';\n\nconst { formatNumber } = useIntl();\n\nconst props = defineProps<{\n  wallet: TreasuryWallet;\n  ensAddress?: string;\n}>();\n\nconst { loadFilteredTokenBalances, treasuryAssets, loadingBalances } =\n  useTreasury();\n\nconst walletQuote = computed(() => {\n  const assets = treasuryAssets.value[props.wallet.address];\n  if (!assets?.length)\n    return {\n      quote: 0,\n      quote_24h: 0\n    };\n  const sumOfAssetsQuote = assets.reduce((sum, asset) => {\n    return sum + asset.quote;\n  }, 0);\n  const sumOfAssetsQuote24h = assets.reduce((sum, asset) => {\n    return sum + asset.quote_24h;\n  }, 0);\n  return {\n    quote: sumOfAssetsQuote,\n    quote_24h: sumOfAssetsQuote24h\n  };\n});\n\nonMounted(() =>\n  loadFilteredTokenBalances(props.wallet.address, props.wallet.network)\n);\n</script>\n\n<template>\n  <li class=\"border-b border-skin-border last:border-b-0\">\n    <BaseLink\n      :link=\"{\n        name: 'spaceTreasury',\n        params: { wallet: wallet.address }\n      }\"\n      class=\"flex justify-between px-4 py-[12px]\"\n    >\n      <div class=\"flex items-center gap-2\">\n        <AvatarUser size=\"35\" :address=\"wallet.address\" />\n        <div>\n          <div\n            data-testid=\"wallet-name\"\n            class=\"text-md font-semibold text-skin-heading\"\n          >\n            {{ wallet.name }}\n          </div>\n          <div class=\"flex items-center space-x-[6px] text-sm text-skin-text\">\n            <span\n              v-if=\"ensAddress\"\n              data-testid=\"wallet-ens-address\"\n              class=\"flex items-center\"\n            >\n              {{ ensAddress }}\n              <div class=\"ml-1 h-1 w-1 rounded-full bg-skin-text\" />\n            </span>\n            <BaseLink\n              :link=\"explorerUrl(wallet.network, wallet.address)\"\n              class=\"!text-skin-text hover:!text-skin-link\"\n              @click.stop\n            >\n              {{ shorten(wallet.address) }}\n            </BaseLink>\n          </div>\n        </div>\n      </div>\n      <div\n        v-if=\"loadingBalances\"\n        class=\"flex flex-col items-end space-y-[12px]\"\n      >\n        <div class=\"lazy-loading h-3 w-[100px] rounded-md\" />\n        <div class=\"lazy-loading h-3 w-[120px] rounded-md\" />\n      </div>\n      <div v-else-if=\"walletQuote\" class=\"text-right\">\n        <span class=\"text-md\"> ${{ formatNumber(walletQuote.quote) }} </span>\n        <IndicatorAssetsChange :quote=\"walletQuote\" />\n      </div>\n    </BaseLink>\n  </li>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneBlock.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  slim?: boolean;\n  loading?: boolean;\n}>();\n</script>\n\n<template>\n  <div class=\"border-skin-border bg-skin-block-bg rounded-2xl border\">\n    <slot name=\"header\" />\n    <div class=\"p-3\" :class=\"{ '!p-0': slim }\">\n      <div v-if=\"loading\">\n        <LoadingList />\n      </div>\n      <slot v-else />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneBlockFooter.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div\n    class=\"bg-[--border-color-faint] border-t border-[--border-color-soft] -mx-3 -mb-3 p-3 rounded-b-xl mt-3\"\n  >\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneBlockHeader.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  title?: string;\n  subTitle?: string;\n  counter?: number;\n  information?: string;\n}>();\n</script>\n\n<template>\n  <div class=\"flex justify-between p-3 pb-0\">\n    <h4>\n      <div class=\"leading-5 flex items-center\">\n        <span class=\"text-skin-heading\">\n          {{ title }}\n        </span>\n        <BaseCounter :counter=\"counter\" class=\"mx-1\" />\n      </div>\n      <div class=\"text-skin-text font-normal leading-5 mt-1\">\n        {{ subTitle }}\n      </div>\n    </h4>\n    <div class=\"flex items-center relative\">\n      <div class=\"absolute right-0 top-0 flex\">\n        <slot v-if=\"$slots.default\" />\n        <IconInformationTooltip\n          v-else\n          :information=\"information\"\n          class=\"ml-1 !text-base text-skin-text\"\n        />\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneButton.story.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <Story> <TuneButton> Confirm </TuneButton> </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneButton.vue",
    "content": "<script setup lang=\"ts\">\nwithDefaults(\n  defineProps<{\n    type?: 'button' | 'submit' | 'reset';\n    variant?: 'danger' | 'white';\n    primary?: boolean;\n    loading?: boolean;\n    disabled?: boolean;\n    useWhiteText?: boolean;\n  }>(),\n  {\n    type: 'button',\n    variant: undefined,\n    primary: false,\n    loading: false,\n    disabled: false,\n    useWhiteText: false\n  }\n);\n\nconst skin = ref<string | null>(null);\n\nconst { domain } = useApp();\nconst { getSkin } = useSkin();\n\nonMounted(async () => await getSkin(domain));\n</script>\n\n<template>\n  <button\n    :type=\"type || 'button'\"\n    :class=\"[\n      'tune-button',\n      {\n        primary: primary,\n        'white-border': variant === 'white',\n        danger: variant === 'danger',\n        disabled: disabled || loading,\n        '!text-skin-bg': !skin && primary && !useWhiteText\n      }\n    ]\"\n    :disabled=\"disabled || loading\"\n  >\n    <TuneLoadingSpinner v-if=\"loading\" class=\"mx-auto\" />\n\n    <slot v-else />\n  </button>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneButtonSelect.story.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <Story> <TuneButtonSelect model-value=\"Confirm\" /> </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneButtonSelect.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  modelValue?: string;\n  label?: string;\n  hint?: string;\n  disabled?: boolean;\n  tooltip?: string | null;\n  definition?: any;\n}>();\n\nconst emit = defineEmits(['select']);\n</script>\n\n<template>\n  <div class=\"w-full\">\n    <TuneLabelInput\n      v-if=\"label || definition?.title\"\n      :hint=\"hint || definition?.description\"\n    >\n      {{ label || definition.title }}\n    </TuneLabelInput>\n    <TuneButton\n      v-tippy=\"{ content: tooltip }\"\n      :class=\"[$attrs.class, { disabled: disabled }]\"\n      class=\"tune-button-select\"\n      :disabled=\"disabled\"\n      @click=\"disabled ? null : emit('select')\"\n    >\n      <template v-if=\"$slots.default\">\n        <slot />\n      </template>\n      <span v-else>\n        {{ modelValue }}\n      </span>\n      <i-ho-chevron-down\n        class=\"absolute inset-y-[12px] right-[14px] text-sm text-skin-link\"\n      />\n    </TuneButton>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneCheckbox.story.vue",
    "content": "<script setup lang=\"ts\">\nconst input = ref(false);\n</script>\n\n<template>\n  <Story>\n    <TuneCheckbox\n      id=\"1\"\n      v-model=\"input\"\n      label=\"Switch me\"\n      hint=\"I'm a checkbox\"\n      class=\"m-1\"\n    />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneCheckbox.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  modelValue: boolean;\n  id: string;\n  label?: string;\n  hint?: string;\n  definition?: any;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n</script>\n\n<template>\n  <div>\n    <label :for=\"id\" class=\"hover:cursor-pointer\">\n      <div class=\"flex gap-[8px]\">\n        <input\n          :id=\"id\"\n          :checked=\"modelValue\"\n          :name=\"label || definition?.title\"\n          type=\"checkbox\"\n          class=\"tune-input-checkbox hover:cursor-pointer my-[2px]\"\n          @input=\"\n            emit(\n              'update:modelValue',\n              ($event.target as HTMLInputElement).checked\n            )\n          \"\n        />\n        <TuneLabelInput\n          v-if=\"hint || definition?.description || $slots.hint\"\n          class=\"!mb-0\"\n        >\n          <slot v-if=\"$slots.hint\" name=\"hint\" />\n          <template v-else>\n            {{ hint || definition.description }}\n          </template>\n        </TuneLabelInput>\n      </div>\n    </label>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneCombobox.story.vue",
    "content": "<script setup lang=\"ts\">\nconst items = [\n  { name: 'One', id: '1' },\n  { name: 'Two', id: '2' },\n  { name: 'Three', id: '3' },\n  { name: 'Four', id: '4' },\n  { name: 'Five', id: '5' },\n  { name: 'Six', id: '6' }\n];\n\nconst input = ref('1');\n</script>\n\n<template>\n  <Story>\n    <TuneCombobox v-model=\"input\" :items=\"items\" />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneCombobox.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  Combobox,\n  ComboboxInput,\n  ComboboxOptions,\n  ComboboxOption,\n  ComboboxLabel,\n  ComboboxButton\n} from '@headlessui/vue';\nimport MiniSearch from 'minisearch';\n\ntype ComboboxItem = {\n  id: string;\n  name: string;\n  extras?: Record<string, any>;\n};\n\nconst props = defineProps<{\n  modelValue: string;\n  items: ComboboxItem[];\n  label?: string;\n  hint?: string;\n  placeholder?: string;\n  disabled?: boolean;\n  definition?: any;\n  error?: string;\n  showErrors?: boolean;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nlet miniSearch = new MiniSearch({\n  fields: ['id', 'name'],\n  storeFields: ['id'],\n  searchOptions: {\n    boost: { name: 2 },\n    fuzzy: 0.6\n  }\n});\n\nminiSearch.addAll(props.items);\n\nconst searchInput = ref('');\nconst selectedItem = computed({\n  get: () => props.items.find(item => item.id === props.modelValue) || null,\n  set: newVal =>\n    newVal\n      ? emit('update:modelValue', newVal.id)\n      : emit('update:modelValue', '')\n});\n\nconst filteredItems = computed(() => {\n  if (searchInput.value === '') {\n    return props.items.filter(item => !item.extras?.hidden);\n  }\n\n  const searchLower = searchInput.value.toLowerCase();\n  const searchResults = new Set(miniSearch.search(searchLower).map(i => i.id));\n\n  return props.items.filter(item => {\n    const isIncluded = item.name.toLowerCase().includes(searchLower);\n    const isSearchResult = searchResults.has(item.id);\n    const isNotHidden = !item.extras?.hidden;\n\n    return (isIncluded || isSearchResult) && isNotHidden;\n  });\n});\n</script>\n<template>\n  <div class=\"w-full\">\n    <Combobox\n      v-model=\"selectedItem\"\n      :disabled=\"disabled\"\n      as=\"div\"\n      class=\"w-full\"\n      nullable\n    >\n      <ComboboxLabel v-if=\"label || definition?.title\" class=\"block\">\n        <TuneLabelInput :hint=\"hint || definition?.description\">\n          {{ label || definition.title }}\n        </TuneLabelInput>\n      </ComboboxLabel>\n      <div class=\"relative\">\n        <ComboboxButton class=\"w-full\">\n          <ComboboxInput\n            class=\"tune-input w-full py-2 !pr-[30px] pl-3 focus:outline-none\"\n            spellcheck=\"false\"\n            :display-value=\"(item: any) => item?.name\"\n            :class=\"{ 'cursor-not-allowed': disabled }\"\n            :placeholder=\"\n              placeholder || definition?.examples[0] || 'Select option'\n            \"\n            :disabled=\"disabled\"\n            @change=\"searchInput = $event.target.value\"\n          />\n        </ComboboxButton>\n        <ComboboxButton\n          class=\"absolute inset-y-0 right-1 flex items-center px-2 focus:outline-none\"\n          :class=\"{ 'cursor-not-allowed': disabled }\"\n        >\n          <i-ho-chevron-down class=\"text-sm text-skin-link\" />\n        </ComboboxButton>\n        <ComboboxOptions\n          v-if=\"filteredItems.length > 0\"\n          class=\"tune-listbox-options absolute z-40 mt-1 w-full overflow-hidden focus:outline-none\"\n        >\n          <div class=\"max-h-[180px] overflow-y-auto\">\n            <ComboboxOption\n              v-for=\"item in filteredItems\"\n              v-slot=\"{ active, selected, disabled: itemDisabled }\"\n              :key=\"item.id\"\n              as=\"template\"\n              :value=\"item\"\n            >\n              <li\n                :class=\"[\n                  { active: active },\n                  'tune-listbox-item relative cursor-default select-none truncate py-2 pl-3 pr-[50px]'\n                ]\"\n              >\n                <span\n                  :class=\"[\n                    selected ? 'selected' : 'font-normal',\n                    { disabled: itemDisabled },\n                    'tune-listbox-item block truncate'\n                  ]\"\n                >\n                  <slot v-if=\"$slots.item\" name=\"item\" :item=\"item\" />\n                  <span v-else>\n                    {{ item.name }}\n                  </span>\n                </span>\n\n                <span\n                  v-if=\"selected\"\n                  :class=\"['absolute inset-y-0 right-0 flex items-center pr-3']\"\n                >\n                  <i-ho-check class=\"text-sm\" />\n                </span>\n              </li>\n            </ComboboxOption>\n          </div>\n        </ComboboxOptions>\n      </div>\n    </Combobox>\n    <TuneErrorInput v-if=\"error && showErrors\" :error=\"error\" />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneComboboxMultiple.story.vue",
    "content": "<script setup lang=\"ts\">\nconst items = [\n  { name: 'One', id: '1' },\n  { name: 'Two', id: '2' },\n  { name: 'ThreeThreeThreeThreeThreeThree', id: '3' },\n  { name: 'FourFourFourFourFourFourFourFour', id: '4' },\n  { name: 'FiveFiveFiveFiveFiveFiveFiveFive', id: '5' },\n  { name: 'Six', id: '6' }\n];\n\nconst input = ref(['1']);\n</script>\n\n<template>\n  <Story>\n    <TuneComboboxMultiple v-model=\"input\" :items=\"items\" />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneComboboxMultiple.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  Combobox,\n  ComboboxInput,\n  ComboboxOptions,\n  ComboboxOption,\n  ComboboxLabel,\n  ComboboxButton\n} from '@headlessui/vue';\nimport MiniSearch from 'minisearch';\n\ntype ComboboxItem = {\n  id: string;\n  name: string;\n  extras?: Record<string, any>;\n};\n\nconst props = defineProps<{\n  modelValue: string[];\n  items: ComboboxItem[];\n  label?: string;\n  hint?: string;\n  disabled?: boolean;\n  definition?: any;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nlet miniSearch = new MiniSearch({\n  fields: ['id', 'name'],\n  storeFields: ['id'],\n  searchOptions: {\n    boost: { name: 2 },\n    fuzzy: 0.4\n  }\n});\n\nminiSearch.addAll(props.items);\n\nconst searchInput = ref('');\nconst selectedItems = computed({\n  get: () => props.items.filter(item => props.modelValue.includes(item.id)),\n  set: newVal =>\n    emit(\n      'update:modelValue',\n      newVal.map((item: any) => item.id)\n    )\n});\n\nconst filteredItems = computed(() => {\n  if (searchInput.value === '') {\n    return props.items;\n  }\n\n  const miniSearchIds = miniSearch.search(searchInput.value).map(i => i.id);\n\n  const includesIds = props.items\n    ?.filter(i =>\n      i.name.toLowerCase().includes(searchInput.value.toLowerCase())\n    )\n    .map(i => i.id);\n\n  const filterIds = [...miniSearchIds, ...includesIds];\n\n  const filteredItems = props.items.filter(item => {\n    return filterIds.includes(item.id);\n  });\n\n  return filteredItems;\n});\n</script>\n<template>\n  <Combobox\n    v-model=\"selectedItems\"\n    multiple\n    :disabled=\"disabled\"\n    as=\"div\"\n    class=\"w-full\"\n  >\n    <ComboboxLabel v-if=\"label || definition?.title\" class=\"block\">\n      <TuneLabelInput :hint=\"hint || definition?.description\">\n        {{ label || definition.title }}\n      </TuneLabelInput>\n    </ComboboxLabel>\n    <div class=\"relative\">\n      <ComboboxButton class=\"tune-listbox-button w-full\">\n        <div class=\"no-scrollbar flex items-center overflow-x-auto\">\n          <div v-if=\"selectedItems.length\" class=\"whitespace-nowrap py-2 pl-2\">\n            <span\n              v-for=\"item in selectedItems\"\n              :key=\"item.id\"\n              class=\"mr-1 inline-block\"\n            >\n              <TuneTag :label=\"item.name\" />\n            </span>\n          </div>\n\n          <ComboboxInput\n            class=\"mr-1 w-full min-w-[200px] py-2 !pr-[30px] pl-2 focus:outline-none\"\n            spellcheck=\"false\"\n            :class=\"{ 'cursor-not-allowed': disabled }\"\n            :disabled=\"disabled\"\n            @blur=\"searchInput = ''\"\n            @change=\"searchInput = $event.target.value\"\n          />\n        </div>\n      </ComboboxButton>\n      <ComboboxButton\n        class=\"absolute inset-y-0 right-1 flex items-center px-2 focus:outline-none\"\n        :class=\"{ 'cursor-not-allowed': disabled }\"\n      >\n        <i-ho-chevron-down class=\"text-sm text-skin-link\" />\n      </ComboboxButton>\n      <ComboboxOptions\n        v-if=\"filteredItems.length > 0\"\n        class=\"tune-listbox-options absolute z-40 mt-1 w-full overflow-hidden focus:outline-none\"\n      >\n        <div class=\"max-h-[180px] overflow-y-auto\">\n          <ComboboxOption\n            v-for=\"item in filteredItems\"\n            v-slot=\"{ active, selected, disabled: itemDisabled }\"\n            :key=\"item.id\"\n            as=\"template\"\n            :value=\"item\"\n          >\n            <li\n              :class=\"[\n                { active: active },\n                'tune-listbox-item relative cursor-default select-none truncate py-2 pl-3 pr-[50px]'\n              ]\"\n            >\n              <span\n                :class=\"[\n                  selected ? 'selected' : 'font-normal',\n                  { disabled: itemDisabled },\n                  'tune-listbox-item block truncate'\n                ]\"\n              >\n                <slot v-if=\"$slots.item\" name=\"item\" :item=\"item\" />\n                <span v-else>\n                  {{ item.name }}\n                </span>\n              </span>\n\n              <span\n                v-if=\"selected\"\n                :class=\"['absolute inset-y-0 right-0 flex items-center pr-3']\"\n              >\n                <i-ho-check class=\"text-sm\" />\n              </span>\n            </li>\n          </ComboboxOption>\n        </div>\n      </ComboboxOptions>\n    </div>\n  </Combobox>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneErrorInput.story.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <Story>\n    <TuneErrorInput error=\"I'm an error\" />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneErrorInput.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  error?: string;\n}>();\n</script>\n\n<template>\n  <div :class=\"['tune-error-text mt-[2px]']\">\n    {{ error }}\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneForm.story.vue",
    "content": "<script setup lang=\"ts\">\nconst schema = {\n  type: 'object',\n  title: 'Space',\n  additionalProperties: true,\n  required: ['name', 'about', 'categories'],\n  properties: {\n    name: {\n      type: 'string',\n      title: 'Name',\n      minLength: 1,\n      examples: ['Space name']\n    },\n    about: {\n      type: 'string',\n      format: 'long',\n      title: 'About',\n      description: 'A short description of your space.',\n      examples: ['Space description'],\n      minLength: 1\n    },\n    categories: {\n      type: 'array',\n      title: 'Categories',\n      examples: ['Select categories'],\n      minLength: 1,\n      items: {\n        type: 'string',\n        anyOf: [\n          {\n            const: 'social',\n            title: 'Social'\n          },\n          {\n            const: 'service',\n            title: 'Service'\n          }\n        ]\n      }\n    },\n    twitter: {\n      type: 'string',\n      title: 'Twitter',\n      examples: ['Twitter handle']\n    },\n    private: {\n      type: 'boolean',\n      title: 'private',\n      description: 'Set space to private.'\n    },\n    votingType: {\n      type: 'string',\n      title: 'Voting type',\n      description: 'Enforce voting type for your space.',\n      anyOf: [\n        {\n          const: 'single-choice',\n          title: 'Single choice'\n        },\n        {\n          const: 'approval',\n          title: 'Approval'\n        }\n      ]\n    }\n  }\n};\n\nconst input = ref({\n  name: '',\n  about: '',\n  categories: [],\n  twitter: '',\n  private: false,\n  votingType: ''\n});\nconst formRef = ref();\n\nfunction forceShowError() {\n  formRef?.value?.forceShowError();\n}\n</script>\n\n<template>\n  <Story>\n    <TuneForm\n      ref=\"formRef\"\n      v-model=\"input\"\n      :definition=\"schema\"\n      :error=\"{\n        name: 'Invalid field',\n        about: 'Invalid field',\n        categories: 'Invalid field'\n      }\"\n    />\n    <TuneButton primary class=\"mt-3\" @click=\"forceShowError\">\n      Show errors\n    </TuneButton>\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneForm.vue",
    "content": "<script setup lang=\"ts\">\nimport TuneForm from './TuneForm.vue';\nimport FormString from './_Form/FormString.vue';\nimport FormNumber from './_Form/FormNumber.vue';\nimport FormBoolean from './_Form/FormBoolean.vue';\nimport FormArray from './_Form/FormArray.vue';\n\nconst props = defineProps<{\n  modelValue: Record<string, any>;\n  definition: Record<string, any>;\n  error: Record<string, any>;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst input = computed({\n  get: () => props.modelValue || props.definition?.default || {},\n  set: value => emit('update:modelValue', value)\n});\n\nconst getComponent = (type: string) => {\n  switch (type) {\n    case 'object':\n      return TuneForm;\n    case 'string':\n      return FormString;\n    case 'number':\n      return FormNumber;\n    case 'boolean':\n      return FormBoolean;\n    case 'array':\n      return FormArray;\n    default:\n      return null;\n  }\n};\n\nconst componentRefs = ref();\n\nfunction forceShowError() {\n  componentRefs?.value?.forEach((ref: any) => {\n    if (ref?.forceShowError) ref?.forceShowError();\n  });\n}\n\ndefineExpose({\n  forceShowError\n});\n</script>\n\n<template>\n  <div class=\"space-y-2\">\n    <component\n      :is=\"getComponent(property.type)\"\n      v-for=\"(property, key) in definition.properties as Record<string, any>\"\n      ref=\"componentRefs\"\n      :key=\"key\"\n      v-model=\"input[key]\"\n      :definition=\"property\"\n      :error=\"error[key] || ''\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneIconHint.story.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <Story>\n    <div class=\"flex\">\n      <TuneIconHint\n        hint=\"Lorem ipsum dolor sit amet consectetur adipisicing elit.\"\n      />\n    </div>\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneIconHint.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  hint?: string;\n}>();\n</script>\n\n<template>\n  <span v-if=\"!!hint\" v-tippy=\"{ content: hint }\" class=\"tune-icon-hint\">\n    <i-ho-question-mark-circle />\n  </span>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneInput.story.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <Story>\n    <TuneInput label=\"My input\" placeholder=\"Enter input\" />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneInput.vue",
    "content": "<script lang=\"ts\">\nexport default {\n  inheritAttrs: false\n};\n</script>\n\n<script setup lang=\"ts\">\nconst props = withDefaults(\n  defineProps<{\n    label?: string;\n    hint?: string;\n    loading?: boolean;\n    error?: string;\n    block?: boolean;\n    type?: 'text' | 'number';\n    modelValue?: string | number;\n    autofocus?: boolean;\n    placeholder?: string;\n    maxLength?: number;\n    readonly?: boolean;\n    disabled?: boolean;\n    definition?: any;\n    alwaysShowError?: boolean;\n  }>(),\n  {\n    label: '',\n    hint: '',\n    loading: false,\n    error: '',\n    block: true,\n    type: 'text',\n    modelValue: '',\n    autofocus: false,\n    placeholder: '',\n    maxLength: undefined,\n    readonly: false,\n    disabled: false,\n    definition: {},\n    alwaysShowError: false\n  }\n);\n\ndefineEmits(['update:modelValue']);\n\nconst inputRef = ref();\n\nconst forceError = ref(false);\nconst showError = ref(false);\n\nconst showErrorMessage = computed(() => {\n  return forceError.value || props.alwaysShowError || showError.value;\n});\n\nfunction forceShowError() {\n  forceError.value = true;\n}\n\ndefineExpose({\n  forceShowError\n});\n\nonMounted(() => {\n  if (props.autofocus) {\n    inputRef?.value?.focus();\n  }\n});\n</script>\n\n<template>\n  <div :class=\"{ 'w-full': block }\">\n    <TuneLabelInput\n      v-if=\"label || definition?.title\"\n      :hint=\"hint || definition?.description\"\n    >\n      {{ label || definition.title }}\n    </TuneLabelInput>\n    <div class=\"flex\">\n      <div :class=\"['group relative z-10 flex', { 'w-full': block }]\">\n        <div\n          v-if=\"$slots.before\"\n          class=\"pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3\"\n        >\n          <slot name=\"before\" />\n        </div>\n        <input\n          v-bind=\"$attrs\"\n          ref=\"inputRef\"\n          :type=\"type\"\n          :value=\"modelValue\"\n          :class=\"[\n            'tune-input px-3 py-2',\n            { 'tune-error-border': error && showErrorMessage },\n            { 'cursor-not-allowed': disabled },\n            { 'w-full': block }\n          ]\"\n          :placeholder=\"placeholder || definition?.examples?.[0] || ''\"\n          :readonly=\"readonly\"\n          :disabled=\"disabled\"\n          :maxlength=\"maxLength || definition?.maxLength\"\n          @blur=\"error ? (showError = true) : null\"\n          @focus=\"error ? null : (showError = false)\"\n          @input=\"\n            $emit(\n              'update:modelValue',\n              ($event.target as HTMLInputElement).value\n            )\n          \"\n        />\n        <div\n          v-if=\"loading\"\n          class=\"tune-input-loading absolute inset-y-0 right-0 top-[1px] mr-1 flex h-[40px] items-center overflow-hidden pl-2 pr-2\"\n        >\n          <TuneLoadingSpinner v-if=\"loading\" />\n        </div>\n        <div\n          v-else-if=\"$slots.after\"\n          class=\"absolute inset-y-0 right-0 flex items-center pr-4\"\n        >\n          <slot name=\"after\" />\n        </div>\n      </div>\n    </div>\n    <TuneErrorInput v-if=\"error && showErrorMessage\" :error=\"error\" />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneInputDuration.story.vue",
    "content": "<script setup lang=\"ts\">\nconst input = ref(14400);\n</script>\n\n<template>\n  <Story>\n    <div class=\"space-y-2\">\n      <TuneInputDuration v-model=\"input\" label=\"Duration\" />\n      <TuneInputDuration\n        v-model=\"input\"\n        label=\"Duration without minutes\"\n        hide-minutes\n      />\n      <TuneInputDuration\n        v-model=\"input\"\n        label=\"Duration without days\"\n        hide-day\n      />\n    </div>\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneInputDuration.vue",
    "content": "<script setup lang=\"ts\">\ninterface DurationInputProps {\n  modelValue: number;\n  label?: string;\n  hint?: string;\n  error?: string;\n  definition?: any;\n  hideDay?: boolean;\n  hideMinutes?: boolean;\n  disabled?: boolean;\n  block?: boolean;\n}\n\nconst props = defineProps<DurationInputProps>();\nconst emit = defineEmits(['update:modelValue']);\n\nconst duration = computed<any>({\n  get() {\n    if (props.hideDay) {\n      return {\n        hours: Math.floor(props.modelValue / 3600),\n        minutes: Math.floor((props.modelValue % 3600) / 60)\n      };\n    }\n    if (props.hideMinutes) {\n      return {\n        days: Math.floor(props.modelValue / 86400),\n        hours: Math.floor((props.modelValue % 86400) / 3600)\n      };\n    }\n    return {\n      days: Math.floor(props.modelValue / 86400),\n      hours: Math.floor((props.modelValue % 86400) / 3600),\n      minutes: Math.floor((props.modelValue % 3600) / 60)\n    };\n  },\n  set(newDuration) {\n    const minutes = newDuration.minutes || 0;\n    const hours = newDuration.hours || 0;\n    const days = newDuration.days || 0;\n    const totalSeconds = days * 86400 + hours * 3600 + minutes * 60;\n    emit('update:modelValue', totalSeconds);\n  }\n});\n\nconst inputItems = computed(() => {\n  return [\n    {\n      key: 'days',\n      label: 'Days',\n      hidden: props.hideDay || false,\n      max: 365\n    },\n    {\n      key: 'hours',\n      label: 'Hours',\n      hidden: false,\n      max: 24\n    },\n    {\n      key: 'minutes',\n      label: 'Minutes',\n      hidden: props.hideMinutes || false,\n      max: 60\n    }\n  ];\n});\n\nconst updateDuration = (key: string, value: number) => {\n  if (isNaN(value) || value === undefined) return;\n  duration.value = { ...duration.value, [key]: value };\n};\n\nconst inputRef = ref<any[]>([]);\n\nfunction addRef(ref: any) {\n  inputRef.value.push(ref);\n}\n\nconst showErrorMessage = ref(false);\n\nfunction forceShowError() {\n  showErrorMessage.value = true;\n}\n\ndefineExpose({\n  forceShowError\n});\n</script>\n\n<template>\n  <div>\n    <TuneLabelInput :hint=\"hint || definition?.description\">\n      {{ label || definition?.title }}\n    </TuneLabelInput>\n    <div\n      :class=\"[\n        'tune-input-duration inline-flex overflow-hidden',\n        { 'w-full': block },\n        { 'tune-error-border': error && showErrorMessage },\n        { disabled: disabled }\n      ]\"\n      @click=\"inputRef?.[0].focus()\"\n    >\n      <template v-for=\"item in inputItems\" :key=\"item.label\">\n        <div v-if=\"!item.hidden\" class=\"flex items-center first:-ml-3\">\n          <input\n            :ref=\"addRef\"\n            :value=\"duration[item.key]\"\n            type=\"number\"\n            min=\"0\"\n            :max=\"item.max\"\n            placeholder=\"0\"\n            :disabled=\"disabled\"\n            :class=\"[\n              'bg-transparent py-2 pl-4 pr-1 outline-none',\n              { 'cursor-not-allowed bg-transparent': disabled }\n            ]\"\n            @click.stop\n            @input=\"\n              updateDuration(\n                item.key,\n                ($event.target as HTMLInputElement)?.valueAsNumber\n              )\n            \"\n            @blur=\"error ? (showErrorMessage = true) : null\"\n            @focus=\"error ? null : (showErrorMessage = false)\"\n          />\n          <span class=\"tune-input-duration-label\">\n            {{ item.label }}\n          </span>\n        </div>\n      </template>\n    </div>\n    <TuneErrorInput v-if=\"error && showErrorMessage\" :error=\"error\" />\n  </div>\n</template>\n\n<style scoped>\ninput::-webkit-outer-spin-button,\ninput::-webkit-inner-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\n/* Firefox */\ninput[type='number'] {\n  -moz-appearance: textfield;\n}\n</style>\n"
  },
  {
    "path": "src/components/Tune/TuneInputSocial.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  modelValue?: string;\n  error?: string;\n  placeholder?: string;\n  maxLength?: number;\n  label?: string;\n  disabled?: boolean;\n  icon?: 'x' | 'github' | 'earth' | 'coingecko';\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst inputRef = ref();\n\nfunction forceShowError() {\n  inputRef?.value?.forceShowError();\n}\n\ndefineExpose({\n  forceShowError\n});\n</script>\n\n<template>\n  <TuneInput\n    ref=\"inputRef\"\n    :label=\"label\"\n    :model-value=\"modelValue\"\n    :error=\"error\"\n    :placeholder=\"placeholder\"\n    :max-length=\"maxLength\"\n    :disabled=\"disabled\"\n    class=\"!pl-[40px]\"\n    @update:model-value=\"(value: string) => emit('update:modelValue', value)\"\n  >\n    <template #before>\n      <i-s-x v-if=\"icon === 'x'\" class=\"text-[16px]\" />\n      <i-s-github v-if=\"icon === 'github'\" class=\"text-[16px]\" />\n      <i-ho-globe-alt v-if=\"icon === 'earth'\" class=\"text-[16px]\" />\n      <i-s-coingecko v-if=\"icon === 'coingecko'\" class=\"text-[16px]\" />\n    </template>\n  </TuneInput>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneInputUrl.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  modelValue?: string;\n  placeholder?: string;\n  label?: string;\n  error?: string;\n  hint?: string;\n  disabled?: boolean;\n  maxLength?: number;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst inputRef = ref();\n\nfunction forceShowError() {\n  inputRef?.value?.forceShowError();\n}\n\ndefineExpose({\n  forceShowError\n});\n</script>\n\n<template>\n  <TuneInput\n    ref=\"inputRef\"\n    :label=\"label\"\n    :model-value=\"modelValue\"\n    :error=\"error\"\n    :hint=\"hint\"\n    :disabled=\"disabled\"\n    :placeholder=\"placeholder\"\n    :max-length=\"maxLength\"\n    class=\"!pl-[40px]\"\n    @update:model-value=\"input => emit('update:modelValue', input)\"\n  >\n    <template #before>\n      <i-ho-globe-alt class=\"text-sm\" />\n    </template>\n  </TuneInput>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneLabelInput.story.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <Story>\n    <TuneLabelInput\n      hint=\"I'm a hint\"\n      sublabel=\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod.\"\n    >\n      Label\n    </TuneLabelInput>\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneLabelInput.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  hint?: string;\n  sublabel?: string;\n}>();\n</script>\n\n<template>\n  <div class=\"tune-label-container flex flex-col\">\n    <div class=\"tune-label flex items-center gap-1\">\n      <slot />\n      <TuneIconHint :hint=\"hint\" />\n    </div>\n    <div v-if=\"sublabel\" class=\"tune-sublabel\">\n      {{ sublabel }}\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneListbox.story.vue",
    "content": "<script setup lang=\"ts\">\nconst items = [\n  { name: 'One', value: 1 },\n  { name: 'Two', value: 2 },\n  { name: 'Three', value: 3 }\n];\n\nconst input = ref(1);\n</script>\n\n<template>\n  <Story>\n    <TuneListbox v-model=\"input\" :items=\"items\" />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneListbox.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  Listbox,\n  ListboxButton,\n  ListboxOptions,\n  ListboxOption,\n  ListboxLabel\n} from '@headlessui/vue';\nimport isEqual from 'lodash/isEqual';\n\ntype ListboxItem = {\n  value: any;\n  name?: string;\n  extras?: Record<string, any>;\n};\n\nconst props = defineProps<{\n  items: ListboxItem[];\n  modelValue: any;\n  label?: string;\n  disabled?: boolean;\n  definition?: any;\n  hint?: string;\n  error?: string;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst selectedItem = computed({\n  get: () =>\n    props.items.find(item => isEqual(item.value, props.modelValue)) ?? {\n      value: ''\n    },\n  set: newVal => emit('update:modelValue', newVal.value)\n});\n\nconst showErrorMessage = ref(false);\n\nfunction forceShowError() {\n  showErrorMessage.value = true;\n}\n\ndefineExpose({\n  forceShowError\n});\n</script>\n\n<template>\n  <div>\n    <Listbox v-model=\"selectedItem\" as=\"div\" :disabled=\"disabled\">\n      <ListboxLabel>\n        <TuneLabelInput :hint=\"hint || definition?.description\">\n          {{ label || definition?.title }}\n        </TuneLabelInput>\n      </ListboxLabel>\n      <div class=\"relative\">\n        <ListboxButton\n          class=\"tune-listbox-button relative h-[42px] w-full truncate pl-3 pr-[40px] text-left\"\n          :class=\"[\n            { 'disabled cursor-not-allowed': disabled },\n            {\n              error: showErrorMessage && error\n            }\n          ]\"\n        >\n          <slot\n            v-if=\"$slots.selected\"\n            name=\"selected\"\n            :selected-item=\"selectedItem\"\n          />\n\n          <span v-else-if=\"selectedItem?.value\">\n            {{ selectedItem?.name || selectedItem?.value }}\n          </span>\n          <span v-else class=\"tune-listbox-placeholder\"> Select </span>\n          <span\n            class=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-[12px]\"\n          >\n            <i-ho-chevron-down class=\"text-sm text-skin-link\" />\n          </span>\n        </ListboxButton>\n        <transition\n          enter-active-class=\"transition duration-100 ease-out\"\n          enter-from-class=\"transform -translate-y-2 scale-95 opacity-0\"\n          enter-to-class=\"transform scale-100 opacity-100\"\n          leave-active-class=\"transition duration-75 ease-out\"\n          leave-from-class=\"transform scale-100 opacity-100\"\n          leave-to-class=\"transform scale-95 opacity-0\"\n        >\n          <ListboxOptions\n            class=\"tune-listbox-options absolute z-40 mt-1 w-full overflow-hidden focus:outline-none\"\n          >\n            <div class=\"max-h-[180px] overflow-y-auto\">\n              <ListboxOption\n                v-for=\"item in items\"\n                :key=\"item.value\"\n                v-slot=\"{ active, selected, disabled: itemDisabled }\"\n                as=\"template\"\n                :value=\"item\"\n                :disabled=\"item.extras?.disabled\"\n              >\n                <li\n                  :class=\"[\n                    {\n                      active: active && !itemDisabled && !item.extras?.disabled\n                    },\n                    { disabled: itemDisabled || item.extras?.disabled },\n                    'tune-listbox-item relative cursor-default select-none py-2 pl-3 pr-[50px]'\n                  ]\"\n                >\n                  <span\n                    :class=\"[\n                      selected ? 'selected' : 'font-normal',\n                      'tune-listbox-item block truncate'\n                    ]\"\n                  >\n                    <slot v-if=\"$slots.item\" name=\"item\" :item=\"item\" />\n                    <span v-else>\n                      {{ item?.name || item.value }}\n                    </span>\n                  </span>\n\n                  <span\n                    v-if=\"selected\"\n                    :class=\"[\n                      'absolute inset-y-0 right-0 flex items-center pr-3'\n                    ]\"\n                  >\n                    <i-ho-check class=\"text-sm\" />\n                  </span>\n                </li>\n              </ListboxOption>\n            </div>\n          </ListboxOptions>\n        </transition>\n      </div>\n    </Listbox>\n    <TuneErrorInput v-if=\"error && showErrorMessage\" :error=\"error\" />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneListboxMultiple.story.vue",
    "content": "<script setup lang=\"ts\">\nconst items = [\n  { name: 'One', value: 1 },\n  { name: 'Two', value: 2 },\n  { name: 'Three', value: 3 }\n];\n\nconst input = ref(['1']);\n</script>\n\n<template>\n  <Story>\n    <TuneListboxMultiple v-model=\"input\" :items=\"items\" />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneListboxMultiple.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  Listbox,\n  ListboxButton,\n  ListboxOptions,\n  ListboxOption,\n  ListboxLabel\n} from '@headlessui/vue';\n\ntype ListboxItem = {\n  value: any;\n  name?: string;\n  extras?: Record<string, any>;\n};\n\nconst props = defineProps<{\n  modelValue?: string[];\n  items: ListboxItem[];\n  definition?: any;\n  label?: string;\n  placeholder?: string;\n  limit?: number;\n  disabled?: boolean;\n  hint?: string;\n  error?: string;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst selectedItems = computed({\n  get: () =>\n    props.items.filter(item => props.modelValue?.includes(item.value)) || [],\n  set: newVal =>\n    emit(\n      'update:modelValue',\n      newVal.map(item => item.value)\n    )\n});\n\nfunction isItemDisabled(item: string) {\n  if (!props.limit) return false;\n  if (selectedItems.value.length < props.limit) return false;\n  return !selectedItems.value.some(selectedItem => selectedItem.value === item);\n}\n\nconst showErrorMessage = ref(false);\n\nfunction forceShowError() {\n  showErrorMessage.value = true;\n}\n\ndefineExpose({\n  forceShowError\n});\n</script>\n\n<template>\n  <div>\n    <Listbox v-model=\"selectedItems\" as=\"div\" :disabled=\"disabled\" multiple>\n      <ListboxLabel>\n        <TuneLabelInput :hint=\"hint || definition?.description\">\n          {{ label || definition?.title }}\n        </TuneLabelInput>\n      </ListboxLabel>\n      <div class=\"relative\">\n        <ListboxButton\n          v-tippy=\"{\n            content: selectedItems\n              .map(item => item?.name || item.value)\n              .join(', ')\n          }\"\n          :class=\"[\n            'tune-listbox-button relative h-[42px] w-full truncate pl-3 pr-[40px] text-left',\n            { 'disabled cursor-not-allowed': disabled },\n            {\n              error: showErrorMessage && error\n            }\n          ]\"\n        >\n          <span\n            v-if=\"selectedItems.length < 1\"\n            class=\"tune-listbox-placeholder\"\n          >\n            {{ placeholder || definition?.examples?.[0] }}\n          </span>\n\n          <slot\n            v-else-if=\"$slots.selected\"\n            name=\"selected\"\n            :selected-items=\"selectedItems\"\n          />\n\n          <span v-else>\n            {{ selectedItems.map(item => item?.name || item.value).join(', ') }}\n          </span>\n          <span\n            class=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-[12px]\"\n          >\n            <i-ho-chevron-down class=\"text-sm text-skin-link\" />\n          </span>\n        </ListboxButton>\n        <transition\n          enter-active-class=\"transition duration-100 ease-out\"\n          enter-from-class=\"transform -translate-y-2 scale-95 opacity-0\"\n          enter-to-class=\"transform scale-100 opacity-100\"\n          leave-active-class=\"transition duration-75 ease-out\"\n          leave-from-class=\"transform scale-100 opacity-100\"\n          leave-to-class=\"transform scale-95 opacity-0\"\n        >\n          <ListboxOptions\n            class=\"tune-listbox-options absolute z-40 mt-1 w-full overflow-hidden focus:outline-none\"\n          >\n            <div class=\"max-h-[180px] overflow-y-auto\">\n              <ListboxOption\n                v-for=\"(item, i) in items\"\n                :key=\"i\"\n                v-slot=\"{ active, selected, disabled: itemDisabled }\"\n                as=\"template\"\n                :value=\"item\"\n                :disabled=\"isItemDisabled(item.value)\"\n              >\n                <li\n                  :class=\"[\n                    { active: active },\n                    'tune-listbox-item relative cursor-default select-none py-2 pl-3 pr-[50px]'\n                  ]\"\n                >\n                  <span\n                    :class=\"[\n                      selected ? 'selected' : 'font-normal',\n                      { disabled: itemDisabled },\n                      'tune-listbox-item block truncate'\n                    ]\"\n                  >\n                    <slot v-if=\"$slots.item\" name=\"item\" :item=\"item\" />\n                    <span v-else>\n                      {{ item?.name || item.value }}\n                    </span>\n                  </span>\n\n                  <span\n                    v-if=\"selected\"\n                    :class=\"[\n                      'absolute inset-y-0 right-0 flex items-center pr-3'\n                    ]\"\n                  >\n                    <i-ho-check class=\"text-sm\" />\n                  </span>\n                </li>\n              </ListboxOption>\n            </div>\n          </ListboxOptions>\n        </transition>\n      </div>\n    </Listbox>\n    <TuneErrorInput v-if=\"error && showErrorMessage\" :error=\"error\" />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneLoadingSpinner.story.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <Story>\n    <TuneLoadingSpinner />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneLoadingSpinner.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <i-s-spinner class=\"h-[22px] animate-spin\" />\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneMenu.story.vue",
    "content": "<script setup lang=\"ts\">\nconst items = [\n  {\n    text: 'View profile',\n    action: 'viewProfile',\n    extras: { icon: 'profile' }\n  },\n  {\n    text: 'Delegate',\n    action: 'delegate',\n    extras: { icon: 'user-add' }\n  },\n  {\n    text: 'Switch wallet',\n    action: 'switchWallet',\n    extras: { icon: 'switch' }\n  },\n  { text: 'Log out', action: 'logout', extras: { icon: 'logout' } }\n];\n</script>\n\n<template>\n  <Story>\n    <TuneMenu :items=\"items\" selected=\"Button\" class=\"m-5\" />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneMenu.vue",
    "content": "<script setup lang=\"ts\">\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';\nimport { Float } from '@headlessui-float/vue';\nimport type { Placement } from '@floating-ui/dom';\n\ntype Item = {\n  text: string;\n  action: string;\n  extras?: any;\n};\n\nwithDefaults(\n  defineProps<{\n    items: Item[];\n    selected?: string;\n    placement?: Placement;\n  }>(),\n  {\n    selected: '',\n    placement: 'bottom-start'\n  }\n);\n\nconst emit = defineEmits(['select']);\n</script>\n\n<template>\n  <Menu as=\"div\" class=\"inline-block h-full text-left\">\n    <Float\n      enter=\"transition ease-out duration-100\"\n      enter-from=\"transform opacity-0 scale-95\"\n      enter-to=\"transform opacity-100 scale-100\"\n      leave=\"transition ease-in duration-75\"\n      leave-from=\"transform opacity-100 scale-100\"\n      leave-to=\"transform opacity-0 scale-95\"\n      :placement=\"placement\"\n      :offset=\"8\"\n      :shift=\"16\"\n      :flip=\"16\"\n      :z-index=\"50\"\n    >\n      <MenuButton class=\"h-full\">\n        <slot v-if=\"$slots.button\" name=\"button\" />\n\n        <TuneButton v-else class=\"flex items-center\">\n          {{ selected }}\n          <i-ho-chevron-down\n            class=\"-mr-1 ml-1 text-sm text-skin-link\"\n            aria-hidden=\"true\"\n          />\n        </TuneButton>\n      </MenuButton>\n\n      <MenuItems class=\"tune-menu-list overflow-hidden outline-none\">\n        <div class=\"no-scrollbar max-h-[300px] overflow-auto\">\n          <MenuItem v-for=\"item in items\" :key=\"item.text\" v-slot=\"{ active }\">\n            <div\n              :class=\"[\n                { active: active },\n                'tune-menu-list-item cursor-pointer whitespace-nowrap px-3 py-2'\n              ]\"\n              @click=\"emit('select', item.action)\"\n            >\n              <slot :key=\"item\" name=\"item\" :item=\"item\">\n                {{ item.text }}\n              </slot>\n            </div>\n          </MenuItem>\n        </div>\n      </MenuItems>\n    </Float>\n  </Menu>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneModal.story.vue",
    "content": "<script setup lang=\"ts\">\nconst isOpen = ref(false);\n</script>\n\n<template>\n  <Story>\n    <TuneButton @click=\"isOpen = true\">Open modal</TuneButton>\n    <TuneModal :open=\"isOpen\" @close=\"isOpen = false\">\n      Lorem ipsum dolor sit amet consectetur adipisicing elit. Distinctio illo\n      expedita libero, eaque iste aliquam praesentium necessitatibus ipsum\n      impedit hic temporibus officia omnis deleniti, at sint, doloremque atque\n      sit id?\n    </TuneModal>\n  </Story>\n</template>\n\n<docs lang=\"md\">\n# TuneModal\n\nThe TuneModal component provides a modal dialog box that appears on top of the current page, dimming the background content and prompting the user for an action. This component uses the @headlessui/vue package for creating accessible dialogs and transitions.\n\n### Props\n\n- **open**: a boolean value that determines whether the modal should be shown or hidden. When true, the modal is displayed; otherwise, it is hidden. This property is required.\n- **title**: a string value that specifies the title of the modal dialog box.\n\n### Slots\n\nThe TuneModal component provides two slots for inserting content:\n\n- **Default slot**: Use this slot to add the main content of the modal dialog box. You can include any valid HTML markup, such as text, images, forms, or other components.\n\n- **Footer slot**: Use this slot to add any additional buttons, links, or other UI elements to the bottom of the modal dialog box.\n\n### Emits\n\n- **close**: an event emitted when the user closes the modal dialog box. This event does not contain any data.\n</docs>\n"
  },
  {
    "path": "src/components/Tune/TuneModal.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  Dialog,\n  DialogPanel,\n  TransitionChild,\n  TransitionRoot\n} from '@headlessui/vue';\nimport { useBreakpoints } from '@vueuse/core';\nimport { SNAPSHOT_BREAKPOINTS } from '@/helpers/constants';\n\ndefineEmits(['close']);\n\nconst props = defineProps<{\n  open: boolean;\n  hideClose?: boolean;\n  size?: 'big' | 'medium';\n}>();\n\nconst sizeClass = computed(() => {\n  if (!props.size) return 'md:w-[440px] w-full';\n  if (props.size === 'big') return 'md:w-[860px] w-full';\n  if (props.size === 'medium') return 'md:w-[578px] w-full';\n});\n\nconst closePositionClass = computed(() => {\n  if (!props.size) return 'top-[12px] right-[10px]';\n  if (props.size === 'big')\n    return 'md:right-[28px] md:top-[16px] right-[10px] top-[10px]';\n  if (props.size === 'medium') return 'top-[12px] right-[10px]';\n});\n\nconst isDesktop = useBreakpoints(SNAPSHOT_BREAKPOINTS).greater('md');\n\nconst panelTransitionClasses = computed(() => {\n  return isDesktop.value\n    ? {\n        enter: 'duration-300 ease-out',\n        enterFrom: 'opacity-0 scale-95',\n        enterTo: 'opacity-100 scale-100',\n        leave: 'duration-200 ease-in',\n        leaveFrom: 'opacity-100 scale-100',\n        leaveTo: 'opacity-0 scale-95'\n      }\n    : {\n        enter: 'duration-300 ease-out',\n        enterFrom: 'opacity-0 translate-y-full',\n        enterTo: 'opacity-100 translate-y-0',\n        leave: 'duration-200 ease-in',\n        leaveFrom: 'opacity-100 translate-y-0',\n        leaveTo: 'opacity-0 translate-y-full'\n      };\n});\n\nwatch(\n  () => props.open,\n  value => {\n    document.body.classList[value ? 'add' : 'remove']('overflow-hidden');\n  }\n);\n\nonBeforeUnmount(() => {\n  document.body.classList.remove('overflow-hidden');\n});\n</script>\n\n<template>\n  <TransitionRoot :show=\"open\" as=\"template\">\n    <Dialog as=\"div\" class=\"relative z-50\" @close=\"$emit('close')\">\n      <TransitionChild\n        as=\"template\"\n        enter=\"duration-300 ease-out\"\n        enter-from=\"opacity-0\"\n        enter-to=\"opacity-100\"\n        leave=\"duration-200 ease-in\"\n        leave-from=\"opacity-100\"\n        leave-to=\"opacity-0\"\n      >\n        <div class=\"bg-black/40 fixed inset-0\" />\n      </TransitionChild>\n\n      <div class=\"fixed inset-0 overflow-y-auto\">\n        <div\n          class=\"flex h-full items-end md:items-center justify-center md:p-4\"\n        >\n          <TransitionChild\n            as=\"template\"\n            :enter=\"panelTransitionClasses.enter\"\n            :enter-from=\"panelTransitionClasses.enterFrom\"\n            :enter-to=\"panelTransitionClasses.enterTo\"\n            :leave=\"panelTransitionClasses.leave\"\n            :leave-from=\"panelTransitionClasses.leaveFrom\"\n            :leave-to=\"panelTransitionClasses.leaveTo\"\n          >\n            <DialogPanel\n              class=\"rounded-t-[20px] md:rounded-[20px] bg-skin-bg transform overflow-hidden align-middle transition-all\"\n              :class=\"sizeClass\"\n            >\n              <div\n                v-if=\"!hideClose\"\n                class=\"absolute\"\n                :class=\"closePositionClass\"\n              >\n                <BaseButtonIcon @click=\"$emit('close')\">\n                  <span class=\"sr-only\">Close</span>\n                  <i-ho-x class=\"text-base\" aria-hidden=\"true\" />\n                </BaseButtonIcon>\n              </div>\n\n              <slot />\n            </DialogPanel>\n          </TransitionChild>\n        </div>\n      </div>\n    </Dialog>\n  </TransitionRoot>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneModalDescription.vue",
    "content": "<script setup lang=\"ts\">\nimport { DialogDescription } from '@headlessui/vue';\n</script>\n\n<template>\n  <DialogDescription as=\"p\">\n    <slot />\n  </DialogDescription>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneModalIndicator.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  variant: 'success' | 'loading' | 'error' | 'gift';\n}>();\n\nconst badgeClass = computed(() => {\n  switch (props.variant) {\n    case 'success':\n      return 'bg-green/10 border-green/40';\n    case 'loading':\n      return 'bg-[--border-color-subtle] border-[--border-color-soft]';\n    case 'error':\n      return 'bg-red/10 border-red/40';\n    case 'gift':\n      return 'bg-boost/10 border-boost/40';\n  }\n});\n</script>\n\n<template>\n  <div\n    class=\"w-[64px] h-[64px] mx-auto shadow-xl border rounded-[20px] flex justify-center items-center\"\n    :class=\"badgeClass\"\n  >\n    <i-ho-check-circle\n      v-if=\"variant === 'success'\"\n      class=\"text-green text-sm\"\n    />\n    <i-ho-exclamation-circle v-else-if=\"variant === 'error'\" class=\"text-red\" />\n    <i-ho-gift v-else-if=\"variant === 'gift'\" class=\"text-boost\" />\n    <TuneLoadingSpinner v-else />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneModalTitle.vue",
    "content": "<script setup lang=\"ts\">\nimport { DialogTitle } from '@headlessui/vue';\n\nwithDefaults(\n  defineProps<{\n    as?: string;\n  }>(),\n  {\n    as: 'h3'\n  }\n);\n</script>\n\n<template>\n  <DialogTitle :as=\"as\">\n    <slot />\n  </DialogTitle>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TunePopover.story.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <Story>\n    <TunePopover label=\"Button\">\n      <template #content>\n        <div>\n          <div class=\"p-3\">\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet\n            aperiam vitae eveniet vel ex praesentium nam quis deserunt porro\n            harum. Magni maiores ipsa ea? Rem, quidem. Quas sunt ducimus rerum.\n          </div>\n        </div>\n      </template>\n    </TunePopover>\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TunePopover.vue",
    "content": "<script setup lang=\"ts\">\nimport { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue';\nimport { Float } from '@headlessui-float/vue';\nimport type { Placement } from '@floating-ui/dom';\n\nwithDefaults(\n  defineProps<{\n    label?: string;\n    placement?: Placement;\n  }>(),\n  {\n    label: '',\n    placement: 'bottom-end'\n  }\n);\n</script>\n\n<template>\n  <Popover>\n    <Float\n      enter=\"transition ease-out duration-100\"\n      enter-from=\"transform opacity-0 scale-95\"\n      enter-to=\"transform opacity-100 scale-100\"\n      leave=\"transition ease-in duration-75\"\n      leave-from=\"transform opacity-100 scale-100\"\n      leave-to=\"transform opacity-0 scale-95\"\n      :placement=\"placement\"\n      :offset=\"4\"\n      :shift=\"16\"\n      :flip=\"16\"\n      :z-index=\"50\"\n      portal\n    >\n      <PopoverButton class=\"outline-none\">\n        <slot v-if=\"$slots.button\" name=\"button\" />\n        <TuneButton v-else class=\"flex items-center gap-1\">\n          <span>{{ label }}</span>\n          <i-ho-chevron-down\n            class=\"text-sm text-skin-link\"\n            aria-hidden=\"true\"\n          />\n        </TuneButton>\n      </PopoverButton>\n\n      <PopoverPanel\n        v-slot=\"{ close }\"\n        class=\"w-screen max-w-xs outline-none sm:max-w-sm\"\n      >\n        <div class=\"tune-popover overflow-hidden\">\n          <div\n            class=\"no-scrollbar max-h-[85vh] overflow-y-auto overscroll-contain\"\n          >\n            <slot name=\"content\" :close=\"close\" />\n          </div>\n        </div>\n      </PopoverPanel>\n    </Float>\n  </Popover>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneRadio.story.vue",
    "content": "<script setup lang=\"ts\">\nconst input = ref('1');\n</script>\n\n<template>\n  <Story>\n    <TuneRadio\n      v-model=\"input\"\n      value=\"1\"\n      label=\"Switch me\"\n      hint=\"I'm radio 1\"\n      class=\"m-1\"\n    />\n    <TuneRadio\n      v-model=\"input\"\n      value=\"2\"\n      label=\"Switch me\"\n      hint=\"I'm radio 2\"\n      class=\"m-1\"\n    />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneRadio.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  modelValue: string;\n  value: string;\n  id?: string;\n  label?: string;\n  hint?: string;\n  definition?: any;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst onChange = (event: Event) => {\n  if (event.target && (event.target as HTMLInputElement).checked) {\n    emit('update:modelValue', props.value);\n  }\n};\n</script>\n\n<template>\n  <label\n    :for=\"id || value\"\n    class=\"flex items-center align-center gap-[10px] hover:cursor-pointer\"\n  >\n    <input\n      :id=\"id || value\"\n      type=\"radio\"\n      :name=\"label || definition?.title\"\n      :checked=\"modelValue === value\"\n      :value=\"value\"\n      class=\"tune-input-radio hover:cursor-pointer\"\n      @input=\"onChange\"\n    />\n    <TuneLabelInput v-if=\"hint || definition?.description\" class=\"!mb-0\">\n      {{ hint || definition.description }}\n    </TuneLabelInput>\n  </label>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneSelect.vue",
    "content": "<script setup lang=\"ts\">\ntype Item = {\n  label: string;\n  value: string | number;\n  extras: {\n    disabled: boolean;\n  };\n};\n\ndefineProps<{\n  items: Item[];\n  modelValue: string | number;\n  disabled?: boolean;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nfunction handleChange(event: any) {\n  emit('update:modelValue', event.target.value);\n}\n</script>\n\n<template>\n  <select\n    :disabled=\"disabled\"\n    :value=\"modelValue\"\n    :class=\"['tune-select', { disabled: disabled }]\"\n    @change=\"handleChange($event)\"\n  >\n    <option\n      v-for=\"(item, index) in items\"\n      :key=\"index\"\n      :value=\"item.value\"\n      :disabled=\"item.extras?.disabled\"\n    >\n      {{ item.label }}\n    </option>\n  </select>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneSwitch.story.vue",
    "content": "<script setup lang=\"ts\">\nconst input = ref(false);\n</script>\n\n<template>\n  <Story>\n    <TuneSwitch\n      v-model=\"input\"\n      label=\"Switch me\"\n      hint=\"I'm a switch\"\n      sublabel=\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod.\"\n    />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneSwitch.vue",
    "content": "<script setup lang=\"ts\">\nimport { Switch } from '@headlessui/vue';\n\ntype Definition = {\n  title: string;\n  description: string;\n  sublabel: string;\n};\n\ndefineProps<{\n  modelValue: boolean;\n  label?: string;\n  sublabel?: string;\n  hint?: string;\n  definition?: Partial<Definition>;\n  disabled?: boolean;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n</script>\n\n<template>\n  <div\n    class=\"flex space-x-2 pr-2 pt-1\"\n    :class=\"sublabel || definition?.sublabel ? 'items-start' : 'items-center'\"\n  >\n    <Switch\n      :model-value=\"modelValue\"\n      :class=\"[\n        'tune-switch relative inline-flex h-[22px] w-[38px] flex-shrink-0 cursor-pointer rounded-full border-2 transition-colors duration-200 ease-in-out',\n        modelValue ? 'switched-on-bg' : 'switched-off-bg',\n        { '!cursor-not-allowed ': disabled }\n      ]\"\n      :disabled=\"disabled\"\n      @update:model-value=\"value => emit('update:modelValue', value)\"\n    >\n      <span v-if=\"label || definition?.title\" class=\"sr-only\">\n        {{ label || definition?.title }}\n      </span>\n      <span\n        :class=\"[\n          modelValue ? 'translate-x-[16px]' : 'translate-x-0',\n          'shadow tune-switch-button pointer-events-none inline-block h-[18px] w-[18px] transform rounded-full transition duration-200 ease-in-out'\n        ]\"\n      >\n        <span\n          :class=\"[\n            modelValue\n              ? 'opacity-0 duration-100 ease-out'\n              : 'opacity-100 duration-200 ease-in',\n            'tune-switch switched-off-text absolute inset-0 flex h-full w-full items-center justify-center transition-opacity'\n          ]\"\n          aria-hidden=\"true\"\n        >\n          <svg class=\"h-[10px] w-[10px]\" fill=\"none\" viewBox=\"0 0 12 12\">\n            <path\n              d=\"M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2\"\n              stroke=\"currentColor\"\n              stroke-width=\"2\"\n              stroke-linecap=\"round\"\n              stroke-linejoin=\"round\"\n            />\n          </svg>\n        </span>\n        <span\n          :class=\"[\n            modelValue\n              ? 'opacity-100 duration-200 ease-in'\n              : 'opacity-0 duration-100 ease-out',\n            'tune-switch switched-on-text absolute inset-0 flex h-full w-full items-center justify-center transition-opacity'\n          ]\"\n          aria-hidden=\"true\"\n        >\n          <svg\n            class=\"h-[10px] w-[10px]\"\n            fill=\"currentColor\"\n            viewBox=\"0 0 12 12\"\n          >\n            <path\n              d=\"M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z\"\n            />\n          </svg>\n        </span>\n      </span>\n    </Switch>\n    <TuneLabelInput\n      v-if=\"label || definition?.title\"\n      :hint=\"hint || definition?.description\"\n      :sublabel=\"sublabel || definition?.sublabel\"\n    >\n      <div class=\"text-skin-heading\">\n        {{ label || definition?.title }}\n      </div>\n    </TuneLabelInput>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneTag.story.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <Story>\n    <TuneTag label=\"Social\" />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneTag.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  label?: string | number;\n}>();\n</script>\n\n<template>\n  <span class=\"tune-tag\">\n    <slot v-if=\"$slots.default\" />\n    {{ label }}\n  </span>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneTextarea.story.vue",
    "content": "<script setup lang=\"ts\">\nconst input = ref('');\n</script>\n\n<template>\n  <Story>\n    <TuneTextarea\n      v-model=\"input\"\n      label=\"About you\"\n      placeholder=\"Tell us about yourself\"\n    />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneTextarea.vue",
    "content": "<script lang=\"ts\">\nexport default {\n  inheritAttrs: false\n};\n</script>\n\n<script setup lang=\"ts\">\nconst props = withDefaults(\n  defineProps<{\n    modelValue: string;\n    definition?: any;\n    label?: string;\n    hint?: string;\n    placeholder?: string;\n    error?: string;\n    autosize?: boolean;\n    disabled?: boolean;\n    maxLength?: number;\n  }>(),\n  {\n    label: '',\n    hint: '',\n    placeholder: '',\n    error: '',\n    autosize: true,\n    disabled: false,\n    maxLength: undefined,\n    definition: undefined\n  }\n);\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst input = computed({\n  get: () => props.modelValue || props.definition?.default || '',\n  set: value => emit('update:modelValue', value)\n});\n\nconst textareaRef = ref();\nconst showErrorMessage = ref(false);\nconst inputHeight = ref('auto');\n\nconst autoResizeStyles = computed(() => {\n  if (!props.autosize) return '';\n  return `resize: none; height: ${inputHeight.value};`;\n});\n\nfunction forceShowError() {\n  showErrorMessage.value = true;\n}\n\nfunction adjustHeight() {\n  inputHeight.value = 'auto';\n  nextTick(() => {\n    if (!textareaRef.value) return;\n    let contentHeight = textareaRef.value.scrollHeight + 1;\n    inputHeight.value = `${contentHeight}px`;\n  });\n}\n\ndefineExpose({\n  forceShowError\n});\n\nwatch(input, value => {\n  nextTick(adjustHeight);\n  if (value) adjustHeight();\n});\n\nonMounted(() => adjustHeight());\n</script>\n\n<template>\n  <div>\n    <TuneLabelInput :hint=\"hint || definition?.description\">\n      {{ label || definition?.title }}\n    </TuneLabelInput>\n    <textarea\n      v-bind=\"$attrs\"\n      ref=\"textareaRef\"\n      v-model=\"input\"\n      :class=\"[\n        'tune-textarea w-full',\n        {\n          'tune-error-border': !!error && showErrorMessage\n        },\n        { disabled: disabled }\n      ]\"\n      :style=\"autoResizeStyles\"\n      :placeholder=\"placeholder || definition?.examples?.[0]\"\n      :disabled=\"disabled\"\n      :maxlength=\"maxLength || definition?.maxLength\"\n      @blur=\"error ? (showErrorMessage = true) : null\"\n      @focus=\"error ? null : (showErrorMessage = false)\"\n    />\n    <TuneErrorInput\n      v-if=\"error && showErrorMessage\"\n      class=\"!-mt-1\"\n      :error=\"error\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneTextareaArray.story.vue",
    "content": "<script setup lang=\"ts\">\nconst input = ref([]);\n</script>\n\n<template>\n  <Story>\n    <TuneTextareaArray\n      v-model=\"input\"\n      label=\"Addresses\"\n      :placeholder=\"`0xBdCA4F610e7101Cc172E2135ba025737B99AbD30\\n0xbc61f6973cE564eFFB16Cd79B5BC3916eaD592E2`\"\n    />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneTextareaArray.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  modelValue: string[];\n  definition?: any;\n  label?: string;\n  placeholder?: string;\n  error?: string;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nfunction handleInput(input: string) {\n  const inputString = input\n    .replace(/\\n/g, ' ')\n    .replace(/,/g, ' ')\n    .replace(/;/g, ' ')\n    .split(' ')\n    .map(item => item.trim())\n    .filter(item => !!item)\n    .filter((item, index, array) => array.indexOf(item) === index);\n  emit('update:modelValue', inputString);\n}\n\nconst textareaRef = ref();\n\nfunction forceShowError() {\n  textareaRef?.value?.forceShowError();\n}\n\ndefineExpose({\n  forceShowError\n});\n</script>\n\n<template>\n  <TuneTextarea\n    ref=\"textareaRef\"\n    :model-value=\"modelValue?.join('\\n')\"\n    :definition=\"definition\"\n    :label=\"label\"\n    :placeholder=\"placeholder || definition?.examples?.join('\\n') || ''\"\n    :error=\"error\"\n    @update:model-value=\"handleInput($event)\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneTextareaJson.story.vue",
    "content": "<script setup lang=\"ts\">\nconst input = ref({ test: 1 });\n</script>\n\n<template>\n  <Story>\n    <TuneTextareaJson\n      v-model=\"input\"\n      label=\"About you\"\n      placeholder=\"Tell us about yourself\"\n    />\n  </Story>\n</template>\n"
  },
  {
    "path": "src/components/Tune/TuneTextareaJson.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  modelValue: Record<string, unknown> | any[];\n  definition?: any;\n  label?: string;\n  placeholder?: string;\n  error?: string;\n}>();\n\nconst emit = defineEmits(['update:modelValue', 'update:isValid']);\n\nconst input = ref('');\nconst errorJson = ref('');\n\nfunction handleInput() {\n  try {\n    emit('update:modelValue', JSON.parse(input.value));\n    emit('update:isValid', true);\n    errorJson.value = '';\n  } catch (e: any) {\n    emit('update:isValid', false);\n    errorJson.value = e?.message;\n  }\n}\n\nwatch(input, () => handleInput());\n\nif (props.modelValue) input.value = JSON.stringify(props.modelValue, null, 2);\n\nconst textareaRef = ref();\n\nfunction forceShowError() {\n  textareaRef?.value?.forceShowError();\n}\n\ndefineExpose({\n  forceShowError\n});\n</script>\n\n<template>\n  <TuneTextarea\n    ref=\"textareaRef\"\n    v-model=\"input\"\n    :definition=\"definition\"\n    :label=\"label\"\n    :placeholder=\"placeholder\"\n    :error=\"errorJson || error\"\n    class=\"tune-textarea-json font-mono\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/Tune/_Form/FormArray.vue",
    "content": "<script setup lang=\"ts\">\nimport cloneDeep from 'lodash/cloneDeep';\n\nimport TuneForm from '../TuneForm.vue';\nimport FormString from './FormString.vue';\nimport FormNumber from './FormNumber.vue';\nimport FormBoolean from './FormBoolean.vue';\n\nconst props = defineProps<{\n  modelValue: any[];\n  definition: any;\n  error: any;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst input = computed({\n  get: () => props.modelValue,\n  set: value => emit('update:modelValue', value)\n});\n\nconst getComponent = (type: string) => {\n  switch (type) {\n    case 'object':\n      return TuneForm;\n    case 'string':\n      return FormString;\n    case 'number':\n      return FormNumber;\n    case 'boolean':\n      return FormBoolean;\n    default:\n      return null;\n  }\n};\n\nfunction addItem() {\n  const array = cloneDeep(input.value);\n  array.push(cloneDeep(props.definition?.items?.default) || '');\n  input.value = array;\n}\n\nconst componentRefs = ref();\n\nfunction forceShowError() {\n  if (componentRefs?.value?.forceShowError)\n    componentRefs?.value?.forceShowError();\n  else\n    componentRefs?.value?.forEach((ref: any) => {\n      if (ref?.forceShowError) ref?.forceShowError();\n    });\n}\n\ndefineExpose({\n  forceShowError\n});\n\nonMounted(() => {\n  if (props.definition?.title === 'Strategies') return;\n  if (!props.modelValue)\n    input.value = cloneDeep([props.definition?.items?.default] || []);\n});\n</script>\n\n<template>\n  <div v-if=\"definition?.title === 'Strategies'\" />\n\n  <TuneListboxMultiple\n    v-else-if=\"definition?.items?.anyOf && definition?.items?.type === 'string'\"\n    ref=\"componentRefs\"\n    v-model=\"input\"\n    :items=\"\n      definition.items.anyOf.map((item: any) => ({\n        value: item.const,\n        name: item.title\n      }))\n    \"\n    :definition=\"definition\"\n    :error=\"error\"\n  />\n\n  <TuneTextareaArray\n    v-else-if=\"definition?.items?.type === 'string'\"\n    v-model=\"input\"\n    :definition=\"definition\"\n    :error=\"error\"\n  />\n\n  <div v-else-if=\"definition?.items\" class=\"space-y-2\">\n    <TuneLabelInput v-if=\"definition?.title\" :hint=\"definition?.description\">\n      {{ definition?.title }}\n    </TuneLabelInput>\n    <div\n      v-for=\"(_, i) in input\"\n      :key=\"i\"\n      :class=\"{\n        'tune-form-array-objects': definition?.items?.type === 'object'\n      }\"\n    >\n      <div class=\"mb-2\">\n        <TuneTag :label=\"i + 1\" />\n      </div>\n      <component\n        :is=\"getComponent(definition.items.type)\"\n        ref=\"componentRefs\"\n        v-model=\"input[i]\"\n        :definition=\"definition.items\"\n        :error=\"error\"\n      />\n    </div>\n    <TuneButton class=\"w-full\" @click=\"addItem\"> Add </TuneButton>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Tune/_Form/FormBoolean.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  modelValue: boolean;\n  definition?: any;\n}>();\n\nconst emit = defineEmits(['update:modelValue']);\n\nonMounted(() => {\n  emit(\n    'update:modelValue',\n    props.modelValue || props.definition?.default || false\n  );\n});\n</script>\n\n<template>\n  <TuneSwitch\n    v-bind=\"props\"\n    @update:model-value=\"emit('update:modelValue', $event)\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/Tune/_Form/FormNumber.vue",
    "content": "<script setup lang=\"ts\">\nconst props = withDefaults(\n  defineProps<{\n    modelValue: number;\n    definition: any;\n    error?: string;\n    type?: 'number';\n  }>(),\n  {\n    error: '',\n    type: 'number'\n  }\n);\n\nconst emit = defineEmits(['update:modelValue']);\n\nconst numberInputRef = ref();\n\nfunction forceShowError() {\n  numberInputRef?.value?.forceShowError();\n}\n\ndefineExpose({\n  forceShowError\n});\n</script>\n\n<template>\n  <TuneInput\n    ref=\"numberInputRef\"\n    v-bind=\"props\"\n    @update:model-value=\"emit('update:modelValue', Number($event))\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/Tune/_Form/FormString.vue",
    "content": "<script setup lang=\"ts\">\nconst props = withDefaults(\n  defineProps<{\n    modelValue: string;\n    definition: any;\n    error?: string;\n  }>(),\n  {\n    error: ''\n  }\n);\nconst emit = defineEmits(['update:modelValue']);\n\nconst textInputRef = ref();\n\nfunction forceShowError() {\n  textInputRef?.value?.forceShowError();\n}\n\ndefineExpose({\n  forceShowError\n});\n\nconst itemsListbox = computed(() => {\n  if (props.definition?.enum) {\n    return props.definition.enum.map((item: any) => ({\n      value: item\n    }));\n  } else if (props.definition?.anyOf) {\n    return props.definition.anyOf.map((item: any) => ({\n      value: item.const,\n      name: item.title\n    }));\n  }\n  return [];\n});\n</script>\n\n<template>\n  <TuneListbox\n    v-if=\"definition?.enum || definition?.anyOf\"\n    ref=\"textInputRef\"\n    :items=\"itemsListbox\"\n    :model-value=\"modelValue\"\n    :definition=\"definition\"\n    :error=\"error\"\n    @update:model-value=\"emit('update:modelValue', $event)\"\n  />\n  <TuneTextarea\n    v-else-if=\"definition?.format === 'long'\"\n    ref=\"textInputRef\"\n    v-bind=\"props\"\n    @update:model-value=\"emit('update:modelValue', $event)\"\n  />\n  <TuneInput\n    v-else\n    ref=\"textInputRef\"\n    v-bind=\"props\"\n    @update:model-value=\"emit('update:modelValue', $event)\"\n  />\n</template>\n"
  },
  {
    "path": "src/components/Ui/UiCollapsible.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  open: boolean;\n  title: string;\n  number: number;\n  hideRemove: boolean;\n  borderless?: boolean;\n}>();\n\ndefineEmits(['remove', 'toggle']);\n</script>\n\n<template>\n  <div class=\"collapsible-container w-full\" :class=\"{ borderless }\">\n    <div class=\"collapsible-header flex items-center px-2\">\n      <div v-if=\"number !== undefined\" class=\"header-number mr-4\">\n        {{ number }}\n      </div>\n      <span\n        class=\"flex flex-auto flex-nowrap justify-center overflow-hidden text-center\"\n        style=\"min-height: 24px\"\n        @click=\"$emit('toggle')\"\n      >\n        {{ title }}\n      </span>\n      <span\n        v-if=\"!hideRemove\"\n        class=\"-mr-2 ml-1 cursor-pointer px-3\"\n        @click=\"$emit('remove')\"\n      >\n        <BaseIcon name=\"close\" size=\"12\" />\n      </span>\n    </div>\n\n    <div :class=\"{ hide: !open }\" class=\"p-2\">\n      <slot />\n    </div>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.collapsible-container {\n  border: 1px solid var(--border-color);\n  color: var(--link-color);\n  border-radius: 23px;\n  outline: none;\n}\n.collapsible-container.borderless {\n  border-radius: 0;\n  border: none;\n}\n.collapsible-header {\n  cursor: pointer;\n  height: 46px;\n  font-size: 18px;\n}\n.hide {\n  display: none;\n}\n.header-number {\n  border: 1px solid var(--border-color);\n  padding: 2px;\n  width: 32px;\n  height: 32px;\n  border-radius: 16px;\n}\n</style>\n"
  },
  {
    "path": "src/components/Ui/UiCollapsibleContent.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  open: boolean;\n  title: string;\n  showArrow: boolean;\n}>();\n\ndefineEmits(['toggle']);\n</script>\n\n<template>\n  <div class=\"collapsible-container w-full text-left\">\n    <div class=\"collapsible-header flex items-stretch px-2\">\n      <div\n        class=\"ml-2 flex flex-auto flex-nowrap items-center\"\n        @click=\"$emit('toggle')\"\n      >\n        <span class=\"overflow-hidden\" style=\"line-height: 1\">\n          {{ title }}\n        </span>\n      </div>\n      <button\n        v-if=\"showArrow\"\n        class=\"mr-2 flex cursor-pointer items-center\"\n        @click=\"$emit('toggle')\"\n      >\n        <i-ho-chevron-up :class=\"{ rotate: !open }\" class=\"text-xs\" />\n      </button>\n      <slot name=\"icons\"></slot>\n    </div>\n    <div :class=\"{ hide: !open }\">\n      <slot />\n    </div>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.collapsible-container {\n  border: 1px solid var(--border-color);\n  color: var(--link-color);\n  outline: none;\n}\n\n.collapsible-header {\n  cursor: pointer;\n  line-height: 46px;\n  height: 46px;\n  font-size: 18px;\n}\n\n.hide {\n  display: none;\n}\n\n.arrow {\n  width: 18px;\n}\n\n.rotate {\n  transform: rotate(180deg);\n}\n</style>\n"
  },
  {
    "path": "src/components/Ui/UiCollapsibleText.vue",
    "content": "<script setup lang=\"ts\">\nconst { copyToClipboard } = useCopy();\n\ndefineProps<{\n  open: boolean;\n  title: string;\n  text: string;\n  hideRemove?: boolean;\n  showArrow: boolean;\n  pre?: boolean;\n}>();\n\ndefineEmits(['toggle']);\n</script>\n\n<template>\n  <UiCollapsibleContent\n    :open=\"open\"\n    :title=\"title\"\n    :show-arrow=\"showArrow\"\n    @toggle=\"$emit('toggle')\"\n  >\n    <template #icons>\n      <button\n        v-if=\"!hideRemove\"\n        class=\"mr-2 flex cursor-pointer items-center\"\n        @click=\"copyToClipboard(text)\"\n      >\n        <BaseIcon style=\"color: #b2b5b2\" name=\"copy\" size=\"20\" />\n      </button>\n    </template>\n    <div\n      :class=\"{ pre }\"\n      class=\"border border-gray-400 bg-gray-200 text-black\"\n      style=\"\n        border-radius: 8px;\n        margin: 0 12px 12px;\n        overflow-wrap: break-word;\n        line-height: 18px;\n        padding: 12px;\n      \"\n    >\n      {{ text }}\n    </div>\n  </UiCollapsibleContent>\n</template>\n\n<style scoped lang=\"scss\">\n.pre {\n  white-space: pre;\n  max-height: 300px;\n  overflow-y: auto;\n}\n</style>\n"
  },
  {
    "path": "src/components/Ui/UiInput.vue",
    "content": "<script setup lang=\"ts\">\nconst props = defineProps<{\n  modelValue?: string | number;\n  placeholder?: string;\n  error?: string | boolean;\n  number?: boolean;\n  disabled?: boolean;\n  maxlength?: string | number;\n  additionalInputClass?: string;\n  focusOnMount?: boolean;\n  readonly?: boolean;\n  quickFix?(): void;\n}>();\n\nconst emit = defineEmits(['update:modelValue', 'blur']);\n\nfunction handleInput(e: Event) {\n  const input = (e.target as HTMLInputElement).value;\n  if (props.number) {\n    return emit('update:modelValue', !input ? undefined : parseFloat(input));\n  }\n  emit('update:modelValue', input);\n}\n\nconst inputRef = ref<null | HTMLInputElement>(null);\n\nonMounted(() => {\n  if (props.focusOnMount) {\n    inputRef?.value?.focus();\n  }\n});\n</script>\n\n<template>\n  <div class=\"w-full rounded-3xl\">\n    <div\n      class=\"relative z-10 flex w-full rounded-3xl border border-skin-border bg-skin-bg px-3 text-left leading-[42px] outline-none transition-colors focus-within:border-skin-text\"\n      :class=\"{ '!border-red': !!error }\"\n    >\n      <div class=\"mr-2 whitespace-nowrap text-skin-text\">\n        <slot name=\"label\" />\n      </div>\n      <button\n        v-if=\"$slots.selected\"\n        class=\"flex-auto overflow-x-auto whitespace-nowrap text-left text-skin-link outline-none\"\n        :class=\"{ 'cursor-not-allowed text-skin-border': disabled }\"\n      >\n        <slot name=\"selected\" />\n      </button>\n      <input\n        v-else\n        ref=\"inputRef\"\n        :value=\"modelValue\"\n        :placeholder=\"placeholder\"\n        :type=\"number ? 'number' : 'text'\"\n        :disabled=\"disabled\"\n        class=\"input w-full flex-auto\"\n        :class=\"[additionalInputClass, { 'cursor-not-allowed': disabled }]\"\n        :readonly=\"readonly\"\n        :maxlength=\"maxlength\"\n        @input=\"handleInput\"\n        @blur=\"emit('blur')\"\n      />\n      <slot name=\"info\" />\n    </div>\n    <div\n      :class=\"[\n        's-error relative z-0',\n        !!error ? '-mt-[20px] opacity-100' : '-mt-[48px] opacity-0'\n      ]\"\n    >\n      <BaseIcon name=\"warning\" class=\"text-red-500 mr-2\" />\n      <!-- The fact that error can be bool or string makes this necessary -->\n      {{ error || '' }}\n      <!-- Allow parent to format value with action -->\n      <button v-if=\"quickFix\" class=\"ml-auto\" @click=\"quickFix\">\n        Quick Fix\n        <i-ho-sparkles class=\"inline\" />\n      </button>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/components/Ui/UiSelect.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{ modelValue?: string | number; disabled?: boolean }>();\n\nconst emit = defineEmits(['update:modelValue', 'change']);\n\nfunction handleChange(event) {\n  emit('update:modelValue', event.target.value);\n  emit('change', event.target.value);\n}\n</script>\n\n<template>\n  <TuneButton class=\"mb-2 flex w-full items-center overflow-hidden !px-3\">\n    <div class=\"no-shrink mr-2 text-skin-text\">\n      <slot name=\"label\" />\n    </div>\n    <div v-if=\"$slots.image\" class=\"no-shrink mr-2 text-skin-text\">\n      <slot name=\"image\" />\n    </div>\n    <select\n      :disabled=\"disabled\"\n      :value=\"modelValue\"\n      :class=\"{ disabled }\"\n      class=\"input h-full w-full flex-auto !bg-skin-bg\"\n      @change=\"handleChange($event)\"\n    >\n      <slot />\n    </select>\n  </TuneButton>\n</template>\n\n<style scoped lang=\"scss\">\n.no-shrink {\n  flex-shrink: 0;\n}\n.disabled {\n  appearance: none;\n}\n</style>\n"
  },
  {
    "path": "src/composables/useAccount.ts",
    "content": "import snapshot from '@snapshot-labs/snapshot.js';\nimport { ERC20ABI } from '@/helpers/constants';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\n\nasync function getERC20Account(\n  provider: any,\n  account: string,\n  token: string,\n  chainId: string,\n  contract: string\n) {\n  const multi = new snapshot.utils.Multicaller(chainId, provider, ERC20ABI, {});\n  multi.call('balance', token, 'balanceOf', [account]);\n  multi.call('allowance', token, 'allowance', [account, contract]);\n  return await multi.execute();\n}\n\nexport function useAccount() {\n  const { web3Account } = useWeb3();\n\n  const account = ref<{\n    balance?: string;\n    allowance?: string;\n  }>({});\n  const updatingAccount = ref(false);\n\n  async function updateAccount(\n    token: string,\n    chainId: string,\n    contract: string\n  ) {\n    account.value = {};\n    updatingAccount.value = true;\n    const broviderUrl = import.meta.env.VITE_BROVIDER_URL;\n    const provider = getProvider(chainId, { broviderUrl });\n\n    try {\n      account.value = await getERC20Account(\n        provider,\n        web3Account.value,\n        token,\n        chainId,\n        contract\n      );\n    } catch (e) {\n      console.log('Error getting account', e);\n    } finally {\n      updatingAccount.value = false;\n    }\n  }\n\n  return {\n    account,\n    updatingAccount,\n    updateAccount\n  };\n}\n"
  },
  {
    "path": "src/composables/useAliasAction.ts",
    "content": "/**\n * Alias addresses/wallets are used to reduce the need for signing messages manually, e.g. each time a user wants to join a space.\n * An alias is a randomly generated wallet, of which the private key is stored in the browser's local storage.\n * The user only needs to sign a message once, to \"register\" the respective alias address on the hub. All following messages can be signed\n * by the alias wallet, without requiring the user's approval. This leads to much better UX, at the cost of less security.\n * If the private key is removed from local storage, a new one will be created and registered.\n */\n\nimport { lsGet, lsSet } from '@/helpers/utils';\nimport { Wallet } from '@ethersproject/wallet';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { getDefaultProvider, Provider } from '@ethersproject/providers';\nimport { ALIASES_QUERY } from '@/helpers/queries';\nimport client from '@/helpers/clientEIP712';\n\nconst aliases = ref(lsGet('aliases') || {});\nconst isValidAlias = ref(false);\n\nexport function useAliasAction() {\n  const { notify } = useFlashNotification();\n  const { t } = useI18n();\n  const { web3 } = useWeb3();\n  const auth = getInstance();\n  const { apolloQuery } = useApolloQuery();\n\n  const userAlias = computed(() => {\n    return aliases.value?.[web3.value.account];\n  });\n\n  const aliasWallet: any = computed(() => {\n    const provider: Provider = getDefaultProvider();\n    return userAlias.value ? new Wallet(userAlias.value, provider) : null;\n  });\n\n  async function checkAlias() {\n    if (aliasWallet.value?.address && web3.value?.account) {\n      const alias = await apolloQuery(\n        {\n          query: ALIASES_QUERY,\n          variables: {\n            address: web3.value.account,\n            alias: aliasWallet.value.address,\n            created_gt: Math.floor(Date.now() / 1000) - 30 * 60 * 60 * 24\n          }\n        },\n        'aliases'\n      );\n\n      isValidAlias.value =\n        alias[0]?.address === web3.value.account &&\n        alias[0]?.alias === aliasWallet.value.address;\n    }\n  }\n\n  async function setAlias() {\n    const rndWallet = Wallet.createRandom();\n    aliases.value = {\n      ...aliases.value,\n      [web3.value.account]: rndWallet.privateKey\n    };\n    lsSet('aliases', aliases.value);\n\n    if (aliasWallet.value?.address) {\n      await client.alias(auth.web3, web3.value.account, {\n        alias: aliasWallet.value.address\n      });\n    }\n    await checkAlias();\n  }\n\n  const loading = ref(false);\n\n  async function actionWithAlias(action: any) {\n    loading.value = true;\n    try {\n      await checkAlias();\n      if (aliasWallet.value && isValidAlias.value) {\n        return await action();\n      }\n      await setAlias();\n      return await action();\n    } catch (e) {\n      console.error(e);\n      loading.value = false;\n      notify(['red', t('notify.somethingWentWrong')]);\n    } finally {\n      loading.value = false;\n    }\n  }\n\n  return {\n    setAlias,\n    aliasWallet,\n    isValidAlias,\n    checkAlias,\n    actionWithAlias,\n    actionLoading: computed(() => loading.value)\n  };\n}\n"
  },
  {
    "path": "src/composables/useApolloQuery.ts",
    "content": "import cloneDeep from 'lodash/cloneDeep';\nimport { apolloClient } from '@/helpers/apollo';\nimport { ensApolloClient } from '@/helpers/ens';\n\nexport function useApolloQuery() {\n  const loading = ref(false);\n\n  async function apolloQuery(options, path = '') {\n    try {\n      loading.value = true;\n      const response = await apolloClient.query(options);\n      loading.value = false;\n\n      return cloneDeep(!path ? response.data : response.data[path]);\n    } catch (error) {\n      loading.value = false;\n      console.log(error);\n    }\n  }\n\n  async function ensApolloQuery(options) {\n    try {\n      loading.value = true;\n      const response = await ensApolloClient.query(options);\n      loading.value = false;\n\n      return response.data;\n    } catch (error) {\n      loading.value = false;\n      console.log(error);\n    }\n  }\n\n  return {\n    apolloQuery,\n    ensApolloQuery,\n    queryLoading: computed(() => loading.value)\n  };\n}\n"
  },
  {
    "path": "src/composables/useApp.ts",
    "content": "import { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport domains from '@/../snapshot-spaces/spaces/domains.json';\nimport aliases from '@/../snapshot-spaces/spaces/aliases.json';\nimport { getInjected } from '@snapshot-labs/lock/src/utils';\n// import { useStorage } from '@vueuse/core';\n\nconst domainName = window.location.hostname;\nconst env = import.meta.env.VITE_ENV;\nlet domain = domains[domainName];\n\nif (env === 'develop') {\n  domain = import.meta.env.VITE_VIEW_AS_SPACE ?? domain;\n}\n\nconst domainAlias = Object.keys(aliases).find(\n  alias => aliases[alias] === domain\n);\n\nconst isReady = ref(false);\n\n// only affects small screens\nconst showSidebar = ref(false);\n\nexport function useApp() {\n  const { loadLocale } = useI18n();\n  const { getSkin } = useSkin();\n  const { login } = useWeb3();\n\n  // const termsAccepted = useStorage('snapshot.termsAccepted', false);\n\n  function connectWallet() {\n    const auth = getInstance();\n\n    // if (!termsAccepted.value) return;\n\n    if (window?.parent === window)\n      // Auto connect if previous session was connected\n      auth.getConnector().then(connector => {\n        if (connector) return login(connector);\n      });\n\n    // Auto connect with gnosis-connector when gnosis safe is detected\n    login('gnosis');\n\n    const injected = computed(() => getInjected());\n    // edge case if MM and CBW are both installed\n    if (injected.value?.id === 'metamask') return;\n    // Auto connect when coinbase wallet is detected\n    if (injected.value?.id === 'coinbase') return login('injected');\n  }\n\n  async function init() {\n    await loadLocale();\n    await getSkin(domain);\n    isReady.value = true;\n    connectWallet();\n  }\n\n  return {\n    domain,\n    domainAlias,\n    env,\n    isReady,\n    init,\n    showSidebar\n  };\n}\n"
  },
  {
    "path": "src/composables/useBalances.ts",
    "content": "import { formatUnits } from '@ethersproject/units';\nimport { getBalances, GetBalancesResponse } from '@/helpers/alchemy';\nimport {\n  ETH_CONTRACT,\n  COINGECKO_ASSET_PLATFORMS,\n  COINGECKO_BASE_ASSETS,\n  CHAIN_CURRENCIES,\n  ChainCurrency\n} from '@/helpers/constants';\n\nconst COINGECKO_API_URL = 'https://api.coingecko.com/api/v3/simple';\nconst COINGECKO_PARAMS = '&vs_currencies=usd&include_24hr_change=true';\n\nexport function useBalances() {\n  const tokens = ref<GetBalancesResponse>([]);\n  const loading = ref(true);\n  const loaded = ref(false);\n\n  async function callCoinGecko(apiUrl: string): Promise<any> {\n    const response = await fetch(apiUrl);\n    return response.json();\n  }\n\n  async function getCoins(\n    assetPlatform: string,\n    baseToken: string,\n    contractAddresses: string[]\n  ): Promise<Record<string, any>> {\n    const [baseTokenData, tokenData] = await Promise.all([\n      callCoinGecko(\n        `${COINGECKO_API_URL}/price?ids=${baseToken}${COINGECKO_PARAMS}`\n      ),\n      callCoinGecko(\n        `${COINGECKO_API_URL}/token_price/${assetPlatform}?contract_addresses=${contractAddresses.join(\n          ','\n        )}${COINGECKO_PARAMS}`\n      )\n    ]);\n\n    return {\n      [ETH_CONTRACT]: baseTokenData[baseToken],\n      ...tokenData\n    };\n  }\n\n  function filterValidTokens(\n    data: GetBalancesResponse,\n    baseToken: ChainCurrency\n  ): GetBalancesResponse {\n    return data.filter(\n      asset =>\n        formatUnits(asset.tokenBalance, asset.decimals) !== '0.0' ||\n        asset.symbol === baseToken.symbol\n    );\n  }\n\n  function getCoinGeckoConfig(networkId: string): {\n    coingeckoAssetPlatform: string;\n    coingeckoBaseAsset: string;\n  } {\n    return {\n      coingeckoAssetPlatform: COINGECKO_ASSET_PLATFORMS[networkId],\n      coingeckoBaseAsset: COINGECKO_BASE_ASSETS[networkId]\n    };\n  }\n\n  async function fetchCoinPrices(\n    coingeckoAssetPlatform: string,\n    coingeckoBaseAsset: string,\n    tokensWithBalance: GetBalancesResponse\n  ): Promise<Record<string, any>> {\n    if (!coingeckoBaseAsset || !coingeckoAssetPlatform) return {};\n\n    const contractAddresses = tokensWithBalance\n      .filter(asset => asset.contractAddress !== ETH_CONTRACT)\n      .map(token => token.contractAddress);\n\n    return getCoins(\n      coingeckoAssetPlatform,\n      coingeckoBaseAsset,\n      contractAddresses\n    );\n  }\n\n  function mapTokenValues(\n    tokensWithBalance: GetBalancesResponse,\n    coins: Record<string, any>\n  ): GetBalancesResponse {\n    return tokensWithBalance.map(asset => {\n      const coinData = coins[asset.contractAddress];\n      if (!coinData) return asset;\n\n      const price = coinData.usd || 0;\n      const change = coinData.usd_24h_change || 0;\n      const value =\n        parseFloat(formatUnits(asset.tokenBalance, asset.decimals)) * price;\n\n      return {\n        ...asset,\n        price,\n        change,\n        value\n      };\n    });\n  }\n\n  async function loadBalances(\n    address: string,\n    networkId: string\n  ): Promise<void> {\n    try {\n      loading.value = true;\n      tokens.value = [];\n      const baseToken = CHAIN_CURRENCIES[networkId];\n      const data = await getBalances(address, Number(networkId), baseToken);\n\n      const tokensWithBalance = filterValidTokens(data, baseToken);\n      const { coingeckoAssetPlatform, coingeckoBaseAsset } =\n        getCoinGeckoConfig(networkId);\n\n      const coins = await fetchCoinPrices(\n        coingeckoAssetPlatform,\n        coingeckoBaseAsset,\n        tokensWithBalance\n      );\n      tokens.value = mapTokenValues(tokensWithBalance, coins);\n\n      loaded.value = true;\n    } catch (error) {\n      console.error('Error loading balances:', error);\n    } finally {\n      loading.value = false;\n    }\n  }\n\n  const assetsMap = computed(\n    () => new Map(tokens.value.map(asset => [asset.contractAddress, asset]))\n  );\n\n  return { loading, loaded, tokens, assetsMap, loadBalances };\n}\n"
  },
  {
    "path": "src/composables/useBoost.ts",
    "content": "import { claimTokens } from '@/helpers/boost';\nimport { BoostSubgraph } from '@/helpers/boost/types';\nimport { Proposal, ExtendedSpace } from '@/helpers/interfaces';\nimport { TWO_WEEKS } from '@/helpers/constants';\nimport { getVouchers } from '@/helpers/boost/api';\nimport { toChecksumAddress } from '@/helpers/utils';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\n\nexport function useBoost() {\n  const auth = getInstance();\n  const { web3Account, web3 } = useWeb3();\n  const { changeNetwork } = useChangeNetwork();\n  const loadingClaim = ref(false);\n\n  function sanitizeBoosts(\n    boosts: BoostSubgraph[],\n    proposals: Proposal[],\n    space: ExtendedSpace\n  ) {\n    return boosts.filter(boost => {\n      if (\n        !space.boost.bribeEnabled &&\n        boost.strategy.eligibility.type === 'bribe'\n      ) {\n        return false;\n      }\n      if (\n        Number(boost.start) !==\n        proposals.find(p => p.id === boost.strategy.proposal)?.end\n      ) {\n        return false;\n      }\n      if (Number(boost.end) - Number(boost.start) !== TWO_WEEKS) {\n        return false;\n      }\n      return true;\n    });\n  }\n\n  async function loadVouchers(boosts: BoostSubgraph[], proposalId: string) {\n    try {\n      const vouchers = await getVouchers(proposalId, web3Account.value, boosts);\n\n      return vouchers;\n    } catch (e) {\n      console.error('Get vouchers error:', e);\n    }\n  }\n\n  async function handleClaim(boost: BoostSubgraph, proposalId: string) {\n    if (boost.chainId !== web3.value.network.chainId.toString()) {\n      await changeNetwork(boost.chainId);\n      handleClaim(boost, proposalId);\n    }\n\n    try {\n      loadingClaim.value = true;\n      const response = await loadVouchers([boost], proposalId);\n      if (!response) throw new Error('Failed to get vouchers');\n\n      const voucher = response[0];\n      const signature = voucher.signature;\n      const chainId = voucher.chain_id;\n      const tx = await claimTokens(\n        auth.web3,\n        chainId,\n        {\n          boostId: voucher.boost_id,\n          recipient: toChecksumAddress(web3Account.value),\n          amount: voucher.reward\n        },\n        signature\n      );\n      await tx.wait();\n    } catch (e: any) {\n      console.error('Claim error:', e);\n    } finally {\n      loadingClaim.value = false;\n    }\n  }\n\n  return {\n    sanitizeBoosts,\n    loadVouchers,\n    handleClaim,\n    loadingClaim\n  };\n}\n"
  },
  {
    "path": "src/composables/useChangeNetwork.ts",
    "content": "import networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { sleep } from '@snapshot-labs/snapshot.js/src/utils';\n\nexport function useChangeNetwork() {\n  const changingNetwork = ref(false);\n\n  async function changeNetwork(network: string) {\n    changingNetwork.value = true;\n    await window.ethereum?.request({\n      method: 'wallet_switchEthereumChain',\n      params: [\n        {\n          chainId: `0x${Number(networks[network].chainId).toString(16)}`\n        }\n      ]\n    });\n    await sleep(1000);\n    changingNetwork.value = false;\n  }\n\n  return {\n    changingNetwork,\n    changeNetwork\n  };\n}\n"
  },
  {
    "path": "src/composables/useClient.ts",
    "content": "import clientEIP712 from '@/helpers/clientEIP712';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\n\nexport function useClient() {\n  const { t } = useI18n();\n  const { notify } = useFlashNotification();\n  const { notifyModal } = useModalNotification();\n  const { isGnosisSafe } = useGnosis();\n  const { web3 } = useWeb3();\n  const auth = getInstance();\n  const route = useRoute();\n\n  const DEFINED_APP = (route?.query.app as string) || 'snapshot';\n\n  const isSending = ref(false);\n\n  function errorNotification(description: string) {\n    notify([\n      'red',\n      description ? `Oops, ${description}` : t('notify.somethingWentWrong')\n    ]);\n    notifyModal('warning', description);\n  }\n\n  async function send(space: { id: string }, type: string, payload: any) {\n    isSending.value = true;\n    try {\n      return await sendEIP712(space, type, payload);\n    } catch (e: any) {\n      errorNotification(e?.error_description || e?.message || '');\n      return e;\n    } finally {\n      isSending.value = false;\n    }\n  }\n\n  async function sendEIP712(space: { id: string }, type: string, payload: any) {\n    let plugins = {};\n    const client = clientEIP712;\n\n    if (\n      payload.metadata?.plugins &&\n      Object.keys(payload.metadata?.plugins).length !== 0\n    )\n      plugins = payload.metadata.plugins;\n\n    if (type === 'create-proposal') {\n      return client.proposal(auth.web3, web3.value.account, {\n        space: space.id,\n        type: payload.type,\n        title: payload.name,\n        body: payload.body,\n        discussion: payload.discussion,\n        choices: payload.choices,\n        labels: [],\n        start: payload.start,\n        end: payload.end,\n        snapshot: payload.snapshot,\n        privacy: payload.privacy,\n        plugins: JSON.stringify(plugins),\n        app: DEFINED_APP\n      });\n    } else if (type === 'update-proposal') {\n      return client.updateProposal(auth.web3, web3.value.account, {\n        proposal: payload.id,\n        space: space.id,\n        type: payload.type,\n        title: payload.name,\n        body: payload.body,\n        discussion: payload.discussion,\n        choices: payload.choices,\n        labels: payload.labels,\n        privacy: payload.privacy,\n        plugins: JSON.stringify(plugins)\n      });\n    } else if (type === 'vote') {\n      return client.vote(auth.web3, web3.value.account, {\n        space: space.id,\n        proposal: payload.proposal.id,\n        type: payload.proposal.type,\n        choice: payload.choice,\n        privacy: payload.privacy,\n        app: DEFINED_APP,\n        reason: payload.reason\n      });\n    } else if (type === 'delete-proposal') {\n      return client.cancelProposal(auth.web3, web3.value.account, {\n        space: space.id,\n        proposal: payload.proposal.id\n      });\n    } else if (type === 'settings') {\n      return client.space(auth.web3, web3.value.account, {\n        space: space.id,\n        settings: JSON.stringify(payload)\n      });\n    } else if (type === 'delete-space') {\n      return client.deleteSpace(auth.web3, web3.value.account, {\n        space: space.id\n      });\n    } else if (type === 'set-statement') {\n      return client.statement(auth.web3, web3.value.account, {\n        space: space.id,\n        about: payload.about,\n        statement: payload.statement,\n        discourse: payload.discourse,\n        network: payload.network,\n        status: payload.status\n      });\n    } else if (type === 'flag-proposal') {\n      return client.flagProposal(auth.web3, web3.value.account, {\n        space: space.id,\n        proposal: payload.proposal.id\n      });\n    }\n  }\n\n  return { send, isSending, isGnosisSafe };\n}\n"
  },
  {
    "path": "src/composables/useCopy.ts",
    "content": "import { useClipboard } from '@vueuse/core';\n\nexport function useCopy() {\n  const { t } = useI18n();\n  const { copy, copied } = useClipboard();\n  const { notify } = useFlashNotification();\n\n  function copyToClipboard(text) {\n    copy(text);\n    if (copied) notify(t('notify.copied'));\n  }\n\n  return { copyToClipboard };\n}\n"
  },
  {
    "path": "src/composables/useDelegate.ts",
    "content": "import { useEns } from './useEns';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\nimport { contractAddress } from '@/helpers/delegation';\nimport { formatBytes32String } from '@ethersproject/strings';\nimport {\n  sendTransaction,\n  sleep,\n  SNAPSHOT_SUBGRAPH_URL\n} from '@snapshot-labs/snapshot.js/src/utils';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\n\nexport function useDelegate() {\n  const abi = ['function setDelegate(bytes32 id, address delegate)'];\n\n  const auth = getInstance();\n  const { notify } = useFlashNotification();\n  const {\n    createPendingTransaction,\n    updatePendingTransaction,\n    removePendingTransaction\n  } = useTxStatus();\n  const { validEnsTlds } = useEns();\n  const { t } = useI18n();\n  const { web3 } = useWeb3();\n\n  const loading = ref(false);\n\n  const networkKey = computed(() => web3.value.network.key);\n\n  const networkSupportsDelegate = computed(\n    () => SNAPSHOT_SUBGRAPH_URL[networkKey.value] !== undefined\n  );\n\n  async function delegateTo(address, spaceId = '') {\n    loading.value = true;\n    const txPendingId = createPendingTransaction();\n    try {\n      let ethAddress = address;\n      if (validEnsTlds.includes(address.split('.').pop())) {\n        const networkId = import.meta.env.VITE_DEFAULT_NETWORK;\n        const broviderUrl = import.meta.env.VITE_BROVIDER_URL;\n        const provider = getProvider(networkId, { broviderUrl });\n        ethAddress = await provider.resolveName(address);\n      }\n      const tx = await sendTransaction(\n        auth.web3,\n        contractAddress,\n        abi,\n        'setDelegate',\n        [formatBytes32String(spaceId), ethAddress]\n      );\n      notify(t('notify.transactionSent'));\n      updatePendingTransaction(txPendingId, { hash: tx.hash });\n      loading.value = false;\n      const receipt = await tx.wait();\n      console.log('Receipt', receipt);\n      await sleep(3e3);\n      notify(t('notify.delegationSuccess'));\n    } catch (e) {\n      notify(['red', t('notify.somethingWentWrong')]);\n      console.log(e);\n    } finally {\n      loading.value = false;\n      removePendingTransaction(txPendingId);\n    }\n  }\n\n  return {\n    delegateTo,\n    delegationLoading: computed(() => loading.value),\n    networkSupportsDelegate,\n    networkKey\n  };\n}\n"
  },
  {
    "path": "src/composables/useDelegates.ts",
    "content": "import { LEADERBOARD_QUERY } from '@/helpers/queries';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { getAddress } from '@ethersproject/address';\nimport { DelegateWithPercent, ExtendedSpace } from '@/helpers/interfaces';\nimport {\n  DelegationTypes,\n  setupDelegation as getDelegationAdapter\n} from '@/helpers/delegationV2';\n\ntype DelegatesStats = Record<string, { votes: number; proposals: number }>;\n\nconst DELEGATES_LIMIT = 18;\n\nexport function useDelegates(space: ExtendedSpace) {\n  const auth = getInstance();\n  const { resolveName } = useResolveName();\n  const { apolloQuery } = useApolloQuery();\n\n  const { reader, writer } = getDelegationAdapter(space, auth);\n\n  const delegates = ref<DelegateWithPercent[]>([]);\n  const delegate = ref<DelegateWithPercent | null>(null);\n  const isLoadingDelegate = ref(false);\n  const isLoadingDelegates = ref(false);\n  const isLoadingMoreDelegates = ref(false);\n  const hasDelegatesLoadFailed = ref(false);\n  const isLoadingDelegatingTo = ref(false);\n  const isLoadingDelegateBalance = ref(false);\n  const hasMoreDelegates = ref(false);\n  const delegatesStats = ref<DelegatesStats>({});\n\n  const hasDelegationPortal =\n    space.delegationPortal.delegationType === DelegationTypes.COMPOUND ||\n    (space.delegationPortal.delegationType ===\n      DelegationTypes.SPLIT_DELEGATION &&\n      space.strategies.some(\n        ({ name }) => name === DelegationTypes.SPLIT_DELEGATION\n      ));\n\n  async function fetchDelegateBatch(orderBy: string, skip = 0) {\n    return reader.getDelegates(DELEGATES_LIMIT, skip, orderBy);\n  }\n\n  async function loadDelegates(orderBy: string) {\n    if (isLoadingDelegates.value) return;\n    isLoadingDelegates.value = true;\n    hasDelegatesLoadFailed.value = false;\n\n    try {\n      const response = await fetchDelegateBatch(orderBy);\n      delegates.value = response;\n      loadStats(response.map(d => d.id));\n      hasMoreDelegates.value = response.length === DELEGATES_LIMIT;\n    } catch (e) {\n      console.error(e);\n      hasDelegatesLoadFailed.value = true;\n    } finally {\n      isLoadingDelegates.value = false;\n    }\n  }\n\n  async function fetchMoreDelegates(orderBy: string) {\n    if (!delegates.value.length || isLoadingMoreDelegates.value) return;\n    isLoadingMoreDelegates.value = true;\n    hasDelegatesLoadFailed.value = false;\n\n    try {\n      const response = await fetchDelegateBatch(\n        orderBy,\n        delegates.value.length\n      );\n      loadStats(response.map(d => d.id));\n      delegates.value = [...delegates.value, ...response];\n      hasMoreDelegates.value = response.length === DELEGATES_LIMIT;\n    } catch (e) {\n      console.error(e);\n      hasDelegatesLoadFailed.value = true;\n    } finally {\n      isLoadingMoreDelegates.value = false;\n    }\n  }\n\n  async function loadDelegate(addressOrEns: string) {\n    if (isLoadingDelegate.value) return;\n    hasDelegatesLoadFailed.value = false;\n    isLoadingDelegate.value = true;\n    delegate.value = null;\n\n    try {\n      const resolvedAddress = await resolveName(addressOrEns);\n      if (!resolvedAddress) return;\n      const response = await reader.getDelegate(getAddress(resolvedAddress));\n      loadStats([response.id]);\n      delegate.value = response;\n    } catch (e) {\n      console.error(e);\n      hasDelegatesLoadFailed.value = true;\n    } finally {\n      isLoadingDelegate.value = false;\n    }\n  }\n\n  async function loadDelegateBalance(id: string) {\n    try {\n      isLoadingDelegateBalance.value = true;\n      return await reader.getBalance(id.toLowerCase());\n    } catch (e) {\n      console.error(e);\n    } finally {\n      isLoadingDelegateBalance.value = false;\n    }\n  }\n\n  async function setDelegates(\n    addresses: string[],\n    ratio?: number[],\n    expirationTimestamp?: number\n  ) {\n    return writer.sendSetDelegationTx(addresses, ratio, expirationTimestamp);\n  }\n\n  async function clearDelegations() {\n    if (!writer.sendClearDelegationsTx) {\n      throw new Error('Clear delegations not supported');\n    }\n    return writer.sendClearDelegationsTx();\n  }\n\n  async function fetchDelegatingTo(address: string) {\n    if (!address) return;\n    isLoadingDelegatingTo.value = true;\n    try {\n      return await reader.getDelegatingTo(address);\n    } catch (e) {\n      console.error(e);\n    } finally {\n      isLoadingDelegatingTo.value = false;\n    }\n  }\n\n  async function loadStats(addresses: string[]) {\n    const leaderboards = await apolloQuery(\n      {\n        query: LEADERBOARD_QUERY,\n        variables: {\n          space: space.id,\n          user_in: addresses\n        }\n      },\n      'leaderboards'\n    );\n\n    leaderboards.forEach(leaderboard => {\n      delegatesStats.value[leaderboard.user] = {\n        votes: leaderboard.votesCount,\n        proposals: leaderboard.proposalsCount\n      };\n    });\n  }\n\n  return {\n    isLoadingDelegate,\n    isLoadingDelegates,\n    isLoadingMoreDelegates,\n    hasDelegatesLoadFailed,\n    hasDelegationPortal,\n    isLoadingDelegateBalance,\n    isLoadingDelegatingTo,\n    hasMoreDelegates,\n    delegate,\n    delegates,\n    delegatesStats,\n    loadDelegate,\n    loadDelegates,\n    fetchMoreDelegates,\n    setDelegates,\n    clearDelegations,\n    loadDelegateBalance,\n    fetchDelegatingTo\n  };\n}\n"
  },
  {
    "path": "src/composables/useEmailFetchClient.ts",
    "content": "import sign, { DataType } from '@/helpers/sign';\nimport { createFetch } from '@vueuse/core';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\n\nconst SubscribeSchema: DataType = {\n  Subscribe: [\n    { name: 'address', type: 'address' },\n    { name: 'email', type: 'string' }\n  ]\n};\n\nconst UpdateSubscriptionsSchema: DataType = {\n  Subscriptions: [\n    { name: 'address', type: 'address' },\n    { name: 'email', type: 'string' },\n    { name: 'subscriptions', type: 'string[]' }\n  ]\n};\n\nconst useEmailFetch = createFetch({\n  baseUrl: import.meta.env.VITE_ENVELOP_URL,\n  options: {\n    headers: {\n      'Content-Type': 'application/json'\n    }\n  }\n});\n\nexport function useEmailFetchClient() {\n  const { web3Account } = useWeb3();\n\n  function plainSign(message, typesSchema) {\n    const { web3 } = getInstance();\n\n    return sign(web3, web3Account.value, message, typesSchema);\n  }\n\n  const fetchSubscriptionsDetails = body => {\n    return useEmailFetch('/subscriber').post(body).json();\n  };\n\n  const subscribeWithEmail = async unsignedParams => {\n    let signature;\n    try {\n      signature = await plainSign(unsignedParams, SubscribeSchema);\n    } catch (error: any) {\n      return {\n        error: { value: 'sign_error' },\n        data: { value: null }\n      };\n    }\n    const body = {\n      method: 'snapshot.subscribe',\n      params: {\n        ...unsignedParams,\n        signature\n      }\n    };\n\n    return useEmailFetch('/').post(body).json();\n  };\n\n  const updateEmailSubscriptions = async unsignedParams => {\n    let signature;\n    try {\n      signature = await plainSign(unsignedParams, UpdateSubscriptionsSchema);\n    } catch (error: any) {\n      return {\n        error: { value: 'sign_error' },\n        data: { value: null }\n      };\n    }\n    const body = {\n      method: 'snapshot.update',\n      params: {\n        ...unsignedParams,\n        signature\n      }\n    };\n\n    return useEmailFetch('/').post(body).json();\n  };\n\n  return {\n    fetchSubscriptionsDetails,\n    subscribeWithEmail,\n    updateEmailSubscriptions\n  };\n}\n"
  },
  {
    "path": "src/composables/useEmailSubscription.ts",
    "content": "import { createSharedComposable } from '@vueuse/core';\n\nconst subscriptionTypes = ['summary', 'newProposal', 'closedProposal'] as const;\ntype SubscriptionType = (typeof subscriptionTypes)[number];\ntype SubscriptionStatus = 'NOT_SUBSCRIBED' | 'VERIFIED' | 'UNVERIFIED';\n\nfunction useEmailSubscriptionComposable() {\n  const { web3Account } = useWeb3();\n  const {\n    fetchSubscriptionsDetails,\n    subscribeWithEmail,\n    updateEmailSubscriptions\n  } = useEmailFetchClient();\n\n  const userState = ref<SubscriptionStatus>('NOT_SUBSCRIBED');\n  const error = ref('');\n  const initialized = ref(false);\n  const loading = ref(false);\n  const apiSubscriptions = ref<SubscriptionType[]>([]);\n\n  const clientSubscriptions = computed({\n    get() {\n      return subscriptionTypes.reduce(\n        (acc, type) => {\n          acc[type] = apiSubscriptions.value.includes(type);\n          return acc;\n        },\n        {} as Record<SubscriptionType, boolean>\n      );\n    },\n    set(value) {\n      apiSubscriptions.value = Object.entries(value)\n        .map(([key, value]) => (value ? key : undefined))\n        .filter(Boolean)\n        .map(key => key as SubscriptionType);\n    }\n  });\n\n  const loadEmailSubscriptions = async () => {\n    loading.value = true;\n    const { error: err, data } = await fetchSubscriptionsDetails({\n      address: web3Account.value\n    });\n\n    if (err.value) {\n      loading.value = false;\n      return;\n    }\n\n    const { status: usrState, subscriptions } = data.value;\n    userState.value = usrState;\n    apiSubscriptions.value = subscriptions || [];\n    loading.value = false;\n    initialized.value = true;\n  };\n\n  const subscribe = async (email: string) => {\n    loading.value = true;\n    const { data, error: err } = await subscribeWithEmail({\n      address: web3Account.value,\n      email\n    });\n    loading.value = false;\n\n    error.value = err.value;\n\n    if (!data.value || data.value?.result !== 'OK') {\n      error.value = 'unknown';\n    }\n\n    return data.value?.result === 'OK';\n  };\n\n  const updateSubscriptions = async () => {\n    loading.value = true;\n    const { error: err } = await updateEmailSubscriptions({\n      address: web3Account.value,\n      email: '',\n      subscriptions: apiSubscriptions.value\n    });\n    error.value = err.value;\n    loading.value = false;\n    loadEmailSubscriptions();\n  };\n\n  return {\n    userState,\n    error,\n    clientSubscriptions,\n    subscribe,\n    updateSubscriptions,\n    loadEmailSubscriptions,\n    loading,\n    initialized\n  };\n}\n\nexport const useEmailSubscription = createSharedComposable(\n  useEmailSubscriptionComposable\n);\n"
  },
  {
    "path": "src/composables/useEns.ts",
    "content": "import {\n  ENS_DOMAINS_BY_ACCOUNT_QUERY,\n  ENS_DOMAIN_BY_HASH_QUERY\n} from '@/helpers/queries';\n\nconst VALID_ENS_TLDS = ['eth', 'xyz', 'com', 'org', 'io', 'app', 'art', 'id'];\n\nexport function useEns() {\n  const { ensApolloQuery } = useApolloQuery();\n  const ownedEnsDomains = ref<Record<string, any>[]>([]);\n\n  // Fetch owned ENS domains for a given address\n  const loadOwnedEnsDomains = async (address: string) => {\n    if (!address) {\n      ownedEnsDomains.value = [];\n      return;\n    }\n\n    const response = await ensApolloQuery({\n      query: ENS_DOMAINS_BY_ACCOUNT_QUERY,\n      variables: {\n        id: address.toLowerCase()\n      }\n    });\n\n    const domains = response.account?.domains || [];\n    const wrappedDomains = response.account?.wrappedDomains || [];\n    let allDomains = [...domains, ...wrappedDomains];\n    // Filter out expired domains\n    const now = (Date.now() / 1000).toFixed(0);\n    allDomains = allDomains.filter(\n      domain =>\n        !domain.expiryDate ||\n        domain.expiryDate === '0' ||\n        domain.expiryDate > now\n    );\n    ownedEnsDomains.value = await fetchAllDomainData(allDomains);\n  };\n\n  // Fetch data for all domains and handle hash-based TLDs\n  async function fetchAllDomainData(domains: any[]) {\n    const filteredDomains = domains.filter(\n      domain => !domain.name.endsWith('.addr.reverse')\n    );\n\n    const domainPromises = filteredDomains.map(fetchDomainData);\n\n    return (await Promise.all(domainPromises)) || [];\n  }\n\n  // Fetch data for a single domain and update the name if it's a hash-based TLD\n  async function fetchDomainData(domain) {\n    const hash = domain.name.match(/\\[(.*?)\\]/)?.[1];\n\n    if (!hash) return domain;\n\n    const response = await ensApolloQuery({\n      query: ENS_DOMAIN_BY_HASH_QUERY,\n      variables: {\n        id: `0x${hash}`\n      }\n    });\n\n    if (response.registration?.domain?.labelName) {\n      return {\n        ...domain,\n        name: domain.name.replace(\n          `[${hash}]`,\n          response.registration.domain.labelName\n        )\n      };\n    }\n\n    return {\n      ...domain,\n      isInvalid: true\n    };\n  }\n\n  // Check if a domain is valid based on the TLD\n  function isValidEnsDomain(domain: string): boolean {\n    if (!domain?.includes('.')) return false;\n    return VALID_ENS_TLDS.includes(domain.split('.').pop() ?? '');\n  }\n\n  return {\n    loadOwnedEnsDomains,\n    ownedEnsDomains,\n    validEnsTlds: VALID_ENS_TLDS,\n    isValidEnsDomain\n  };\n}\n"
  },
  {
    "path": "src/composables/useExtendedSpaces.ts",
    "content": "import { SPACE_QUERY } from '@/helpers/queries';\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport { mapOldPluginNames } from '@/helpers/utils';\n\nconst extendedSpaces = ref<ExtendedSpace[]>([]);\n\nexport function useExtendedSpaces() {\n  const loading = ref(false);\n\n  const { apolloQuery } = useApolloQuery();\n  async function loadExtendedSpace(spaceId: string) {\n    if (!spaceId || extendedSpaces.value.some(s => s.id === spaceId)) return;\n\n    loading.value = true;\n    try {\n      const response = await apolloQuery(\n        {\n          query: SPACE_QUERY,\n          variables: {\n            id: spaceId\n          }\n        },\n        'space'\n      );\n\n      const mappedSpace = mapOldPluginNames(response);\n      extendedSpaces.value.push(mappedSpace);\n\n      // Remove any duplicates incase two requests were made at the same time\n      extendedSpaces.value = extendedSpaces.value.filter(\n        (space, index, self) => index === self.findIndex(t => t.id === space.id)\n      );\n\n      loading.value = false;\n    } catch (e) {\n      loading.value = false;\n      console.error(e);\n      return e;\n    }\n  }\n\n  async function reloadSpace(spaceId: string) {\n    try {\n      const response = await apolloQuery(\n        {\n          query: SPACE_QUERY,\n          variables: {\n            id: spaceId\n          }\n        },\n        'space'\n      );\n      const mappedSpace = mapOldPluginNames(response);\n\n      extendedSpaces.value = extendedSpaces.value.filter(\n        s => s.id !== mappedSpace.id\n      );\n\n      extendedSpaces.value.push(mappedSpace);\n    } catch (e) {\n      console.error(e);\n      return e;\n    }\n  }\n\n  function deleteSpace(id: string) {\n    extendedSpaces.value = extendedSpaces.value.filter(s => s.id !== id);\n  }\n\n  return {\n    loadExtendedSpace,\n    reloadSpace,\n    deleteSpace,\n    extendedSpaces: computed(() => extendedSpaces.value),\n    spaceLoading: computed(() => loading.value)\n  };\n}\n"
  },
  {
    "path": "src/composables/useFlaggedMessageStatus.ts",
    "content": "import { createGlobalState } from '@vueuse/core';\n\nconst useFlaggedMessageState = createGlobalState(() => {\n  const flaggedMessageStateMap = ref({});\n\n  return {\n    setVisibility: (id, state) => {\n      const isNotVisible = flaggedMessageStateMap.value[id] === false;\n\n      if (isNotVisible) return;\n\n      flaggedMessageStateMap.value = {\n        ...flaggedMessageStateMap.value,\n        [id]: state\n      };\n    },\n    isFlaggedMessageVisible: id => {\n      return flaggedMessageStateMap.value[id] || false;\n    }\n  };\n});\n\nexport function useFlaggedMessageStatus(pageId: Ref<string> | string) {\n  const { setVisibility, isFlaggedMessageVisible } = useFlaggedMessageState();\n  const id = typeof pageId === 'string' ? pageId : pageId.value;\n\n  return {\n    isMessageVisible: computed(() => isFlaggedMessageVisible(id)),\n    setMessageVisibility: (state: boolean) => setVisibility(id, state)\n  };\n}\n"
  },
  {
    "path": "src/composables/useFlashNotification.ts",
    "content": "interface Notification {\n  id: number;\n  message: string;\n  type: string;\n  remove(): any;\n}\n\nconst items = ref<Notification[]>([]);\n\nexport function useFlashNotification() {\n  function notify(payload: any, duration = 4000) {\n    const item: Notification = {\n      id: Math.floor(Date.now() * Math.random()),\n      message: Array.isArray(payload) ? payload[1] : payload,\n      type: Array.isArray(payload) ? payload[0] : 'green',\n      remove() {\n        items.value.splice(\n          items.value.findIndex(i => i.id === this.id),\n          1\n        );\n      }\n    };\n\n    items.value.push(item);\n    setTimeout(() => item.remove(), duration);\n  }\n\n  return { notify, items };\n}\n"
  },
  {
    "path": "src/composables/useFollowSpace.ts",
    "content": "import { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { FOLLOWS_QUERY } from '@/helpers/queries';\nimport client from '@/helpers/clientEIP712';\nimport { useSpaceSubscription } from './useSpaceSubscription';\n\nconst following = ref([]);\nconst loadingFollows = ref(false);\n\nexport function useFollowSpace(spaceId: any = {}) {\n  const { web3, web3Account } = useWeb3();\n  const { modalAccountOpen } = useModal();\n  const { apolloQuery } = useApolloQuery();\n  const { setAlias, aliasWallet, isValidAlias, checkAlias } = useAliasAction();\n  const { toggleSubscription, isSubscribed } = useSpaceSubscription(spaceId);\n  const { notify } = useFlashNotification();\n  const { t } = useI18n();\n\n  const loadingFollow = ref('');\n\n  const followingSpaces = computed(() =>\n    Array.isArray(following.value)\n      ? following.value.map((f: any) => f.space.id)\n      : []\n  );\n\n  const isFollowing = computed(() =>\n    following.value.some(\n      (f: any) => f.space.id === spaceId && f.follower === web3Account.value\n    )\n  );\n\n  async function loadFollows(spaceId?: string) {\n    const { isAuthenticated } = getInstance();\n\n    if (!isAuthenticated.value) return;\n\n    loadingFollows.value = true;\n    try {\n      following.value = await apolloQuery(\n        {\n          query: FOLLOWS_QUERY,\n          variables: {\n            follower_in: web3Account.value,\n            space_in: spaceId ? [spaceId] : undefined\n          }\n        },\n        'follows'\n      );\n      loadingFollows.value = false;\n    } catch (e) {\n      loadingFollows.value = false;\n      console.error(e);\n    }\n  }\n\n  function clickFollow(space) {\n    !web3.value.authLoading\n      ? web3Account.value\n        ? follow(space)\n        : (modalAccountOpen.value = true)\n      : null;\n  }\n\n  async function follow(space) {\n    loadingFollow.value = spaceId;\n\n    try {\n      await checkAlias();\n      if (!aliasWallet.value || !isValidAlias.value) {\n        await setAlias();\n        follow(space);\n      } else {\n        const network = process.env.VITE_ENV === 'production' ? 's' : 's-tn';\n\n        if (isFollowing.value) {\n          // Also unsubscribe to the notifications if the user leaves the space.\n          if (isSubscribed.value) {\n            await toggleSubscription();\n          }\n          await client.unfollow(aliasWallet.value, aliasWallet.value.address, {\n            from: web3Account.value,\n            space,\n            network\n          });\n        } else {\n          await client.follow(aliasWallet.value, aliasWallet.value.address, {\n            from: web3Account.value,\n            space,\n            network\n          });\n        }\n        await loadFollows();\n        loadingFollow.value = '';\n      }\n    } catch (e: any) {\n      loadingFollow.value = '';\n      console.error(e);\n      notify([\n        'red',\n        e?.error_description\n          ? `Oops, ${e.error_description}`\n          : t('notify.somethingWentWrong')\n      ]);\n    }\n  }\n\n  return {\n    clickFollow,\n    loadFollows,\n    loadingFollow: computed(() => loadingFollow.value),\n    loadingFollows: computed(() => loadingFollows.value),\n    isFollowing,\n    followingSpaces\n  };\n}\n"
  },
  {
    "path": "src/composables/useFormSpaceProposal.ts",
    "content": "import { useStorage } from '@vueuse/core';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport schemas from '@snapshot-labs/snapshot.js/src/schemas';\nimport { validateForm } from '@/helpers/validation';\nimport { OsnapPluginData } from '@/plugins/oSnap/types';\n\ninterface ProposalForm {\n  name: string;\n  body: string;\n  discussion: string;\n  choices: { key: number; text: string }[];\n  labels: string[];\n  start: number;\n  end: number;\n  snapshot: number;\n  type: string;\n  metadata: {\n    plugins: {\n      safeSnap?: { valid: boolean };\n      oSnap?: OsnapPluginData;\n    };\n  };\n}\n\nconst EMPTY_PROPOSAL: ProposalForm = {\n  name: '',\n  body: '',\n  discussion: '',\n  choices: [\n    { key: 0, text: '' },\n    { key: 1, text: '' }\n  ],\n  labels: [],\n  start: parseInt((Date.now() / 1e3).toFixed()),\n  end: 0,\n  snapshot: 0,\n  metadata: {\n    plugins: {}\n  },\n  type: 'single-choice'\n};\n\nconst EMPTY_PROPOSAL_DRAFT = {\n  name: '',\n  body: '',\n  choices: [\n    { key: 0, text: '' },\n    { key: 1, text: '' }\n  ],\n  isBodySet: false\n};\n\nconst form = ref<ProposalForm>(clone(EMPTY_PROPOSAL));\nconst userSelectedDateStart = ref(false);\nconst userSelectedDateEnd = ref(false);\nconst sourceProposalLoaded = ref(false);\n\nexport function useFormSpaceProposal({ spaceType = 'default' } = {}) {\n  const route = useRoute();\n\n  const formDraft = useStorage<{\n    name: string;\n    body: string;\n    choices: { key: number; text: string }[];\n    labels: string[];\n    isBodySet: boolean;\n  }>(`snapshot.proposal.${route.params.key}`, clone(EMPTY_PROPOSAL_DRAFT));\n\n  const sourceProposal = computed(() => route.params.sourceProposal as string);\n\n  function resetForm() {\n    formDraft.value = clone(EMPTY_PROPOSAL_DRAFT);\n    form.value = clone(EMPTY_PROPOSAL);\n    sourceProposalLoaded.value = false;\n    userSelectedDateEnd.value = false;\n    userSelectedDateStart.value = false;\n  }\n\n  const validationErrors = computed(() =>\n    validateForm(schemas.proposal, form.value, { spaceType })\n  );\n\n  const isValid = computed(() => {\n    return Object.values(validationErrors.value).length === 0;\n  });\n\n  return {\n    form,\n    formDraft,\n    userSelectedDateStart,\n    userSelectedDateEnd,\n    sourceProposalLoaded,\n    sourceProposal,\n    validationErrors,\n    isValid,\n    resetForm\n  };\n}\n"
  },
  {
    "path": "src/composables/useFormSpaceSettings.ts",
    "content": "import { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport schemas from '@snapshot-labs/snapshot.js/src/schemas';\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport isEqual from 'lodash/isEqual';\nimport isEmpty from 'lodash/isEmpty';\nimport { validateForm } from '@/helpers/validation';\n\nconst DEFAULT_PROPOSAL_VALIDATION = { name: 'any', params: {} };\nconst DEFAULT_VOTE_VALIDATION = { name: 'any', params: {} };\nconst DEFAULT_DELEGATION = {\n  delegationType: 'compound-governor',\n  delegationContract: '',\n  delegationNetwork: '1',\n  delegationApi: ''\n};\nconst EMPTY_SPACE_FORM = {\n  strategies: [],\n  categories: [],\n  labels: [],\n  treasuries: [],\n  admins: [],\n  moderators: [],\n  members: [],\n  plugins: {},\n  delegationPortal: clone(DEFAULT_DELEGATION),\n  filters: {\n    minScore: 0,\n    onlyMembers: false\n  },\n  voting: {\n    delay: 0,\n    hideAbstain: false,\n    period: 0,\n    quorum: 0,\n    type: '',\n    privacy: ''\n  },\n  boost: {\n    enabled: true,\n    bribeEnabled: false\n  },\n  validation: clone(DEFAULT_PROPOSAL_VALIDATION),\n  voteValidation: clone(DEFAULT_VOTE_VALIDATION),\n  name: '',\n  about: '',\n  avatar: '',\n  network: '1',\n  symbol: '',\n  terms: '',\n  website: '',\n  twitter: '',\n  github: '',\n  coingecko: '',\n  parent: null,\n  children: [],\n  private: false,\n  domain: '',\n  skin: '',\n  guidelines: '',\n  template: ''\n};\n\nconst formSetup = ref(clone(EMPTY_SPACE_FORM));\nconst formSettings = ref(clone(EMPTY_SPACE_FORM));\nconst initialFormState = ref(clone(EMPTY_SPACE_FORM));\nconst inputRefs = ref<any[]>([]);\n\nexport function useFormSpaceSettings(\n  context: 'setup' | 'settings',\n  { spaceType = 'default' } = {}\n) {\n  const { isSending } = useClient();\n  const { isUploadingImage } = useImageUpload();\n\n  const form = computed({\n    get: () => (context === 'setup' ? formSetup.value : formSettings.value),\n    set: newVal =>\n      context === 'setup'\n        ? (formSetup.value = newVal)\n        : (formSettings.value = newVal)\n  });\n\n  const hasFormChanged = computed(() => {\n    return !isEqual(formSettings.value, initialFormState.value);\n  });\n\n  const prunedForm = computed(() => {\n    const formData = clone(form.value);\n    Object.entries(formData).forEach(([key, value]) => {\n      if (value === null || value === '') delete formData[key];\n    });\n    if (\n      !formData.delegationPortal.delegationContract &&\n      !formData.delegationPortal.delegationApi\n    ) {\n      delete formData.delegationPortal;\n    }\n    if (formData.voting.quorumType === 'default') {\n      delete formData.voting.quorumType;\n    }\n    return formData;\n  });\n\n  function populateForm(extendedSpace: ExtendedSpace) {\n    const formData = clone(extendedSpace);\n    removeUnnecessaryFields(formData);\n    ensureDefaultValues(formData);\n    ensureMembersLowerCase(formData);\n\n    if (shouldUseAnyValidation(formData)) {\n      formData.validation.name = 'any';\n    }\n\n    if (shouldUseBasicValidation(formData)) {\n      formData.validation.name = 'basic';\n    }\n\n    form.value = clone(formData);\n    initialFormState.value = clone(formData);\n  }\n\n  function ensureMembersLowerCase(formData: any) {\n    formData.admins =\n      formData?.admins.map((admin: string) => admin.toLowerCase()) || [];\n    formData.moderators =\n      formData?.moderators.map((moderator: string) =>\n        moderator.toLowerCase()\n      ) || [];\n    formData.members =\n      formData?.members.map((member: string) => member.toLowerCase()) || [];\n  }\n\n  function removeUnnecessaryFields(formData: any) {\n    delete formData.id;\n    delete formData.followersCount;\n    delete formData.verified;\n    delete formData.flagged;\n    delete formData.hibernated;\n    delete formData.turbo;\n\n    if (formData.filters.invalids) delete formData.filters.invalids;\n  }\n\n  function ensureDefaultValues(formData: any) {\n    formData.strategies = formData.strategies || [];\n    formData.plugins = formData.plugins || {};\n    formData.delegationPortal =\n      formData.delegationPortal || clone(DEFAULT_DELEGATION);\n    formData.validation =\n      formData.validation || clone(DEFAULT_PROPOSAL_VALIDATION);\n    formData.voteValidation =\n      formData.voteValidation || clone(DEFAULT_VOTE_VALIDATION);\n    formData.filters = formData.filters || {};\n    formData.voting = formData.voting || {};\n    formData.voting = {\n      ...formData.voting,\n      delay: formData.voting.delay || undefined,\n      period: formData.voting.period || undefined,\n      type: formData.voting.type || undefined,\n      quorum: formData.voting?.quorum || undefined,\n      quorumType: formData.voting.quorumType || 'default',\n      privacy: formData.voting.privacy || undefined\n    };\n    formData.children = formData.children\n      ? formData.children.map((child: any) => child.id)\n      : [];\n    formData.parent = formData.parent?.id || '';\n    formData.boost = formData.boost || { enabled: true, bribeEnabled: false };\n  }\n\n  function shouldUseAnyValidation(formData: any) {\n    return (\n      formData.validation.name === 'basic' &&\n      !formData.filters.minScore &&\n      !formData.validation.params.minScore &&\n      isEmpty(formData.validation.params)\n    );\n  }\n\n  function shouldUseBasicValidation(formData: any) {\n    return (\n      formData.validation.name === 'nouns' ||\n      formData.validation.name === 'aave'\n    );\n  }\n\n  function validateStrategies(errors: any) {\n    const isTicket = form.value.strategies.some(\n      (strategy: any) => strategy.name === 'ticket'\n    );\n    const isAnyOrBasic = form.value.voteValidation.name === 'any';\n\n    if (isTicket && isAnyOrBasic) {\n      errors.strategies = 'ticketWithAnyOrBasicError';\n    }\n  }\n\n  function validateProposalValidation(errors: any) {\n    const hasProposalValidation =\n      (form.value.validation?.name && form.value.validation.name !== 'any') ||\n      !!form.value.filters?.minScore ||\n      !!form.value.filters?.onlyMembers;\n\n    if (!hasProposalValidation) {\n      errors.validation = 'missingProposalValidationError';\n    }\n  }\n\n  const validationErrors = computed(() => {\n    const errors = validateForm(schemas.space, prunedForm.value, { spaceType });\n\n    validateStrategies(errors);\n    validateProposalValidation(errors);\n\n    return errors;\n  });\n\n  const isValid = computed(() => {\n    return Object.values(validationErrors.value).length === 0;\n  });\n\n  const isReadyToSubmit = computed(\n    () => !isUploadingImage.value && !isSending.value\n  );\n\n  function resetForm() {\n    form.value = clone(initialFormState.value);\n  }\n\n  function forceShowError() {\n    inputRefs?.value?.forEach((ref: any) => {\n      if (ref?.forceShowError) ref?.forceShowError();\n    });\n  }\n\n  function addRef(ref: any) {\n    if (ref) inputRefs.value.push(ref);\n  }\n\n  return {\n    form,\n    prunedForm,\n    validationErrors,\n    isValid,\n    isReadyToSubmit,\n    hasFormChanged,\n    populateForm,\n    resetForm,\n    addRef,\n    forceShowError,\n    DEFAULT_VOTE_VALIDATION\n  };\n}\n"
  },
  {
    "path": "src/composables/useFormValidation.ts",
    "content": "import defaults from '@/locales/default.json';\nimport { validateSchema } from '@snapshot-labs/snapshot.js/src/utils';\nimport { watchDebounced } from '@vueuse/core';\n\nexport function useFormValidation(schema, form) {\n  const { t } = useI18n();\n\n  const validationResult = ref<ReturnType<typeof validateSchema>>(null);\n  const validate = () => {\n    validationResult.value = validateSchema(schema, form.value);\n  };\n\n  watchDebounced(form, validate, {\n    debounce: 200,\n    maxWait: 1000,\n    deep: true,\n    immediate: true\n  });\n\n  const isValid = computed(() => validationResult.value === true);\n\n  function getValidationMessage(key: string): string {\n    const defaultErrors = Object.keys(defaults.errors);\n\n    if (validationResult.value === true || !validationResult.value) return '';\n\n    const errorFound = validationResult.value.find(\n      error =>\n        (defaultErrors.includes(error.keyword) &&\n          error.params.missingProperty === key) ||\n        (defaultErrors.includes(error.keyword) &&\n          error.instancePath.includes(key))\n    );\n\n    // Custom error messages for address fields (needed because minLength validation\n    // on the strategies schema would always show field required)\n    if (\n      errorFound &&\n      errorFound?.instancePath.includes('address') &&\n      errorFound?.keyword.includes('minLength')\n    )\n      return t('errors.invalidAddress');\n\n    if (\n      errorFound?.instancePath.includes('strategies') &&\n      errorFound?.keyword.includes('minItems')\n    )\n      return t('errors.minStrategy');\n\n    if (\n      errorFound?.instancePath.includes('website') ||\n      errorFound?.instancePath.includes('terms') ||\n      errorFound?.instancePath.includes('discussion') ||\n      errorFound?.instancePath.includes('guidelines')\n    )\n      return t('errors.website');\n\n    if (\n      (errorFound?.instancePath.includes('admins') ||\n        errorFound?.instancePath.includes('moderators') ||\n        errorFound?.instancePath.includes('members')) &&\n      errorFound?.keyword.includes('maxItems')\n    )\n      return t('errors.members.maxItems', {\n        limit: errorFound?.params.limit,\n        role:\n          errorFound?.instancePath.replace('/', '') === 'members'\n            ? 'authors'\n            : errorFound?.instancePath.replace('/', '')\n      });\n\n    return errorFound\n      ? t(`errors.${errorFound.keyword}`, [errorFound?.params.limit])\n      : '';\n  }\n\n  return { getValidationMessage, validationResult, isValid };\n}\n"
  },
  {
    "path": "src/composables/useGnosis.ts",
    "content": "import { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport utils from '@snapshot-labs/snapshot.js/src/utils';\nimport { computedAsync, useMemoize } from '@vueuse/core';\nimport { Contract } from '@ethersproject/contracts';\n\nconst defaultNetwork = import.meta.env.VITE_DEFAULT_NETWORK;\n\nconst getSafeVersion = useMemoize(\n  async (networkKey: string, account: string) => {\n    const broviderUrl = import.meta.env.VITE_BROVIDER_URL;\n    const provider = utils.getProvider(networkKey, { broviderUrl });\n    const code = await provider.getCode(account);\n\n    if (code === '0x') return undefined;\n\n    const abi = ['function VERSION() view returns (string)'];\n    const contract = new Contract(account, abi, provider);\n    return contract.VERSION([]);\n  }\n);\n\nexport function useGnosis(space?: ExtendedSpace) {\n  const { web3 } = useWeb3();\n\n  const auth = getInstance();\n  const connectorName = computed(() => auth.provider.value?.connectorName);\n\n  const networkKey = computed(() => web3.value.network.key);\n\n  const spaceNetworkKey = computed(() => space?.network);\n\n  const isSafeContract = computedAsync(async () => {\n    if (!web3.value.account) return false;\n\n    const safeVersion = await getSafeVersion(\n      networkKey.value,\n      web3.value.account\n    );\n\n    return typeof safeVersion === 'string';\n  }, false);\n\n  const isGnosisSafe = computed(\n    () =>\n      web3.value?.walletConnectType === 'Gnosis Safe Multisig' ||\n      web3.value?.walletConnectType === 'WalletConnect Safe App' ||\n      web3.value?.walletConnectType === 'Den' ||\n      connectorName.value === 'gnosis' ||\n      isSafeContract.value\n  );\n\n  const isGnosisAndNotDefaultNetwork = computed(() => {\n    return isGnosisSafe.value && networkKey.value !== defaultNetwork;\n  });\n\n  const isGnosisAndNotSpaceNetwork = computed(() => {\n    return isGnosisSafe.value && networkKey.value !== spaceNetworkKey.value;\n  });\n\n  return {\n    isGnosisSafe,\n    isGnosisAndNotDefaultNetwork,\n    isGnosisAndNotSpaceNetwork\n  };\n}\n"
  },
  {
    "path": "src/composables/useI18n.ts",
    "content": "import { lsGet, lsSet } from '@/helpers/utils';\nimport i18n, {\n  defaultLocale,\n  setI18nLanguage,\n  loadLocaleMessages\n} from '@/helpers/i18n';\n\nconst currentLocale = ref(lsGet('locale', defaultLocale));\n\nexport function useI18n() {\n  const { t, d, tc } = i18n.global;\n\n  async function setLocale(locale) {\n    currentLocale.value = locale;\n    lsSet('locale', locale);\n    await loadLocaleMessages(i18n, locale);\n    setI18nLanguage(i18n, locale);\n  }\n\n  async function loadLocale() {\n    await loadLocaleMessages(i18n, currentLocale.value);\n    setI18nLanguage(i18n, currentLocale.value);\n  }\n\n  return {\n    t,\n    d,\n    tc,\n    setLocale,\n    loadLocale,\n    currentLocale\n  };\n}\n"
  },
  {
    "path": "src/composables/useImageUpload.ts",
    "content": "import { upload as pin } from '@snapshot-labs/pineapple';\nimport { useI18n } from './useI18n';\n\nconst isUploadingImage = ref(false);\n\nexport function useImageUpload() {\n  const imageUploadError = ref('');\n  const imageUrl = ref('');\n  const imageName = ref('');\n\n  const { t } = useI18n();\n  const { notify } = useFlashNotification();\n\n  const reset = () => {\n    isUploadingImage.value = false;\n    imageUploadError.value = '';\n    imageUrl.value = '';\n    imageName.value = '';\n  };\n\n  const upload = async (\n    file,\n    onSuccess: (image: { name: string; url: string }) => void\n  ) => {\n    reset();\n    if (!file) return;\n    isUploadingImage.value = true;\n    const formData = new FormData();\n\n    if (!['image/jpeg', 'image/jpg', 'image/png'].includes(file.type)) {\n      imageUploadError.value = t('errors.unsupportedImageType');\n      isUploadingImage.value = false;\n      return;\n    }\n    if (file.size > 1024 * 1024) {\n      imageUploadError.value = t('errors.fileTooBig');\n      isUploadingImage.value = false;\n      return;\n    }\n    formData.append('file', file);\n    try {\n      const receipt = await pin(formData, import.meta.env.VITE_PINEAPPLE_URL);\n\n      imageUrl.value = `ipfs://${receipt.cid}`;\n      imageName.value = file.name;\n      onSuccess({ name: file.name, url: imageUrl.value });\n    } catch (err: any) {\n      notify(['red', t('notify.somethingWentWrong')]);\n      imageUploadError.value = err.error?.message || err;\n    } finally {\n      isUploadingImage.value = false;\n    }\n  };\n\n  return {\n    isUploadingImage,\n    imageUploadError,\n    image: {\n      url: imageUrl,\n      name: imageName\n    },\n    upload\n  };\n}\n"
  },
  {
    "path": "src/composables/useInfiniteLoader.ts",
    "content": "export function useInfiniteLoader(loadBy = 6) {\n  const loadingMore = ref(false);\n  const stopLoadingMore = ref(false);\n\n  async function loadMore(loadFn) {\n    if (loadingMore.value) return;\n    if (!stopLoadingMore.value) {\n      loadingMore.value = true;\n      await loadFn();\n      loadingMore.value = false;\n    }\n  }\n\n  return { loadBy, loadingMore, stopLoadingMore, loadMore };\n}\n"
  },
  {
    "path": "src/composables/useIntl.ts",
    "content": "/**\n * Wrapper functions for Intl.RelativeTimeFormat/NumberFormat\n * returning computed properties based on current locale from i18n\n */\n\n/**\n * This is needed since Intl still doesn't support durations:\n * https://github.com/tc39/proposal-intl-duration-format (hopefully soon!)\n *\n * The Intl.relativeTimeFormat API (same as basically all libraries like day.js, timeago.js)\n * only supports phrases like \"5 hours ago\" or \"in 35 minutes\". But these time durations can be phrased\n * differently, e.g. we also use \"12 hours left\" instead of \"(ends) in 12 hours\". For that you need\n * a simple duration formatter, that turns 3600 into \"1 hour\" and 180000 into \"2 days\". More granular\n * formats are possible like \"1 hour, 30 minutes\" (which will be covered by Intl.Duration).\n * For now, this function just returns the biggest/closest unit and the resulting number from an integer\n * of seconds. (3678 => { duration: 1, unit: 'hour'}) This is accompanied by manual translations in our message\n * catalogues of strings like \"second\", \"seconds\", \"minute\", \"minutes\", etc.\n */\nconst getDurationAndUnit = (seconds: number) => {\n  let unit = 'second';\n  let duration = seconds;\n  const abs = Math.abs(seconds);\n\n  if (abs >= 60) {\n    unit = 'minute';\n    duration = duration / 60;\n    if (abs >= 60 * 60) {\n      unit = 'hour';\n      duration = duration / 60;\n      if (abs >= 60 * 60 * 24) {\n        unit = 'day';\n        duration = duration / 24;\n        if (abs >= 60 * 60 * 24 * 365) {\n          unit = 'year';\n          duration = duration / 365;\n        } else if (abs >= 60 * 60 * 24 * 30) {\n          unit = 'month';\n          duration = duration / 30;\n        } else if (abs >= 60 * 60 * 24 * 7) {\n          unit = 'week';\n          duration = duration / 7;\n        }\n      }\n    }\n  }\n\n  duration = Math.round(duration);\n\n  return { duration, unit };\n};\n\nexport function useIntl() {\n  const { currentLocale, t } = useI18n();\n\n  /**\n   * functions to create computed formatters based on locale\n   *\n   * If you need a custom format in only one component, you can import these\n   * functions to create a custom formatter locally in that component, to use\n   * it as the formatting functions' 2nd argument.\n   * Otherwise you can add a predefined formatter and a formatting function\n   * below and add them to the return list.\n   */\n\n  const getRelativeTimeFormatter = (options?: object) =>\n    computed(\n      () =>\n        new Intl.RelativeTimeFormat(\n          currentLocale.value,\n          options || { style: 'short', numeric: 'always' }\n        )\n    );\n\n  const getNumberFormatter = (options?: object) =>\n    computed(\n      () =>\n        new Intl.NumberFormat(\n          // currently we are using only english number formatting because other\n          // languages can result in very different string length, which we need to deal with.\n          // (en: 10.2k, de: 10.200)\n          'en', // currentLocale.value,\n          options || { notation: 'standard' }\n        )\n    );\n\n  /**\n   * predefined formatters\n   */\n\n  const defaultRelativeTimeFormatter = getRelativeTimeFormatter();\n  const longRelativeTimeFormatter = getRelativeTimeFormatter({\n    style: 'long',\n    numeric: 'always'\n  });\n  const defaultNumberFormatter = getNumberFormatter(\n    // format with two decimal places\n    { maximumFractionDigits: 2 }\n  );\n  const compactNumberFormatter = getNumberFormatter({\n    notation: 'compact',\n    compactDisplay: 'short'\n  });\n  const percentNumberFormatter = getNumberFormatter({\n    style: 'percent',\n    maximumFractionDigits: 2\n  });\n\n  /**\n   * formatting functions\n   */\n\n  const formatRelativeTime = (\n    timestamp: number,\n    formatter?: Intl.RelativeTimeFormat\n  ) => {\n    const relativeTo = new Date().getTime() / 1e3;\n\n    const { duration, unit } = getDurationAndUnit(timestamp - relativeTo);\n\n    formatter = formatter || defaultRelativeTimeFormatter.value;\n\n    return formatter.format(duration, unit);\n  };\n\n  // doesn't use Intl (yet), needs useI18n's t function, to translate the unit\n  const formatDuration = (seconds: number) => {\n    const { duration, unit } = getDurationAndUnit(seconds);\n\n    return t(`timeUnits.${unit}`, { n: duration });\n  };\n\n  const formatNumber = (number: number, formatter?: Intl.NumberFormat) => {\n    formatter = formatter || defaultNumberFormatter.value;\n\n    return formatter.format(number);\n  };\n\n  const formatCompactNumber = (number: number) =>\n    formatNumber(number, compactNumberFormatter.value);\n\n  const formatPercentNumber = (number: number) =>\n    formatNumber(number, percentNumberFormatter.value);\n\n  const getRelativeProposalPeriod = (state: any, start: any, end: any): any => {\n    if (state === 'closed') {\n      return t('endedAgo', [\n        formatRelativeTime(end, longRelativeTimeFormatter.value)\n      ]);\n    }\n    if (state === 'active') {\n      return t('endIn', [\n        formatRelativeTime(end, longRelativeTimeFormatter.value)\n      ]);\n    }\n    return t('startIn', [\n      formatRelativeTime(start, longRelativeTimeFormatter.value)\n    ]);\n  };\n\n  const getPercentFractionDigits = value => {\n    const absValue = Math.abs(value);\n\n    if (absValue === 0) {\n      return 0;\n    }\n\n    let leadingZeros = 0;\n    let tempValue = absValue;\n    while (tempValue < 1) {\n      tempValue *= 10;\n      leadingZeros++;\n    }\n\n    return Math.max(1, Math.min(leadingZeros, 8));\n  };\n\n  return {\n    getRelativeTimeFormatter,\n    getNumberFormatter,\n    formatRelativeTime,\n    formatDuration,\n    formatNumber,\n    formatCompactNumber,\n    formatPercentNumber,\n    getRelativeProposalPeriod,\n    getPercentFractionDigits,\n    longRelativeTimeFormatter\n  };\n}\n"
  },
  {
    "path": "src/composables/useMeta.ts",
    "content": "import { useHead } from '@vueuse/head';\n\ntype metaInfo = {\n  title: {\n    key: string;\n    params?: Record<string, string>;\n  };\n  description: {\n    key: string;\n    params?: Record<string, string>;\n  };\n};\n\nexport function useMeta(metaInfo: metaInfo) {\n  const { t } = useI18n();\n\n  useHead({\n    title: t(metaInfo.title.key, metaInfo.title.params || {}),\n    meta: [\n      {\n        name: 'description',\n        content: t(metaInfo.description.key, metaInfo.description.params || {})\n      }\n    ]\n  });\n\n  return {};\n}\n"
  },
  {
    "path": "src/composables/useModal.ts",
    "content": "const modalAccountOpen = ref(false);\nconst isModalPostVoteOpen = ref(false);\nconst modalEmailOpen = ref(false);\n\nexport function useModal() {\n  return { modalAccountOpen, isModalPostVoteOpen, modalEmailOpen };\n}\n"
  },
  {
    "path": "src/composables/useModalNotification.ts",
    "content": "interface Notification {\n  id: number;\n  description: string;\n  type: 'info' | 'warning' | 'warning-red';\n  remove(): any;\n}\n\nconst items = ref<Notification[]>([]);\n\nexport function useModalNotification() {\n  function notifyModal(\n    type: 'info' | 'warning' | 'warning-red',\n    description: string\n  ) {\n    const item: Notification = {\n      id: Math.floor(Date.now() * Math.random()),\n      description,\n      type,\n      remove() {\n        items.value.splice(\n          items.value.findIndex(i => i.id === this.id),\n          1\n        );\n      }\n    };\n\n    items.value.push(item);\n  }\n\n  return { notifyModal, items };\n}\n"
  },
  {
    "path": "src/composables/useNetworksFilter.ts",
    "content": "/**\n * Orders networks by spaces count and returns a list of networks\n * filtered by the search string (case insensitive).\n */\n\nimport { NETWORKS_COUNT_QUERY } from '@/helpers/queries';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\n\nconst networksSpacesCount: any = ref(null);\n\nexport function useNetworksFilter() {\n  const loading = ref(false);\n\n  const filterNetworks = (q = '') => {\n    const networksArray = Object.keys(networks)\n      .map(s => ({ ...networks[s] }))\n      .filter(n => !n.disabled);\n\n    const networksArrayBySearchString = networksArray.filter(n =>\n      JSON.stringify(n).toLowerCase().includes(q.toLowerCase())\n    );\n\n    const networksArrayBySpaceCount = networksArrayBySearchString.sort(\n      (a, b) =>\n        (networksSpacesCount.value?.[b.key] ?? 0) -\n        (networksSpacesCount.value?.[a.key] ?? 0)\n    );\n\n    const networksArrayByExactKeyMatch = networksArrayBySpaceCount.sort(\n      (a, b) => (a.key === q ? -1 : b.key === q ? 1 : 0)\n    );\n\n    return networksArrayByExactKeyMatch;\n  };\n\n  const { apolloQuery } = useApolloQuery();\n\n  async function getNetworksSpacesCount() {\n    if (networksSpacesCount.value) return;\n    loading.value = true;\n    const res = await apolloQuery(\n      {\n        query: NETWORKS_COUNT_QUERY\n      },\n      'networks'\n    );\n    networksSpacesCount.value = res.reduce(\n      (obj: any, item: any) => ({ ...obj, [item.id]: item.spacesCount }),\n      {}\n    );\n\n    loading.value = false;\n  }\n  return {\n    filterNetworks,\n    getNetworksSpacesCount,\n    networksSpacesCount,\n    loadingNetworksSpacesCount: loading\n  };\n}\n"
  },
  {
    "path": "src/composables/useNotifications.ts",
    "content": "/**\n * A componsable for handling proposal notifications and possibly more.\n * Seen proposals are stored in the local storage.\n */\n\nimport { NOTIFICATION_PROPOSALS_QUERY } from '@/helpers/queries';\nimport { useStorage } from '@vueuse/core';\nimport uniqBy from 'lodash/uniqBy';\n\ninterface Notification {\n  id: string;\n  event: string;\n  time: number;\n  title: string;\n  space?: { id: string; name: string; avatar: string };\n}\n\nconst notifications = ref<Notification[]>([]);\nconst loading = ref(false);\nconst selectedFilter = ref('all');\n\nconst NotificationEvents = {\n  ProposalStart: 'proposal/start',\n  ProposalEnd: 'proposal/end'\n};\nconst filters = ['all', 'unread'];\n\nexport function useNotifications() {\n  const router = useRouter();\n  const { web3, web3Account } = useWeb3();\n  const { followingSpaces, loadingFollows } = useFollowSpace();\n  const { apolloQuery } = useApolloQuery();\n\n  const notificationsLoading = computed(\n    () => loading.value || web3.value.authLoading || loadingFollows.value\n  );\n\n  async function loadProposals(state, date) {\n    if (followingSpaces.value.length === 0) return [];\n    return (\n      (await apolloQuery(\n        {\n          query: NOTIFICATION_PROPOSALS_QUERY,\n          variables: {\n            first: 100,\n            state,\n            space_in: followingSpaces.value,\n            start_gte: date\n          }\n        },\n        'proposals'\n      )) ?? []\n    );\n  }\n\n  function mapProposalToNotifications(proposals) {\n    if (proposals.length === 0) return;\n    const now = Number(new Date().getTime() / 1000).toFixed(0);\n    proposals.forEach(proposal => {\n      if (notifications.value.some(n => n.id === proposal.id)) return;\n      notifications.value.push({\n        id: proposal.id,\n        event:\n          proposal.end <= now\n            ? NotificationEvents.ProposalEnd\n            : NotificationEvents.ProposalStart,\n        time: proposal.end <= now ? proposal.end : proposal.start,\n        title: proposal.title,\n        space: proposal.space\n      });\n    });\n  }\n\n  async function loadRecentProposals() {\n    const unixTimestampTwoWeeksAgo = Number(\n      (new Date().getTime() / 1000 - 604800 * 2).toFixed(0)\n    );\n    const proposalsObj = await Promise.all([\n      loadProposals('active', unixTimestampTwoWeeksAgo),\n      loadProposals('closed', unixTimestampTwoWeeksAgo)\n    ]);\n\n    mapProposalToNotifications(proposalsObj.flat());\n  }\n\n  async function loadNotifications() {\n    if (notifications.value.length > 0) return;\n    if (!web3Account.value) return;\n    loading.value = true;\n    await loadRecentProposals();\n    loading.value = false;\n  }\n\n  // Reactive local storage with help from vueuse package\n  const readNotificationsStorage = useStorage(\n    `snapshot.unread.${web3Account.value.slice(0, 8).toLowerCase()}`,\n    ['initialValue']\n  );\n\n  function selectNotification(id: string, spaceId: string) {\n    router.push({\n      name: 'spaceProposal',\n      params: { key: spaceId, id: id }\n    });\n    readNotificationsStorage.value.push(id);\n  }\n\n  // Mark all notifications as read and remove duplicates from local storage\n  function markAllAsRead() {\n    readNotificationsStorage.value = readNotificationsStorage.value.concat(\n      notifications.value.map(n => n.id)\n    );\n    readNotificationsStorage.value = uniqBy(readNotificationsStorage.value);\n  }\n\n  const notificationsSortedByTime = computed(() =>\n    [\n      ...notifications.value.map(n => ({\n        text: n.title,\n        action: { spaceId: n.space?.id, id: n.id },\n        seen: readNotificationsStorage.value.includes(n.id),\n        ...n\n      }))\n    ]\n      .sort((a, b) => b.time - a.time)\n      .filter(n => (selectedFilter.value === 'unread' ? !n.seen : true))\n  );\n\n  function reloadNotifications() {\n    notifications.value = [];\n    loadNotifications();\n  }\n\n  watch(followingSpaces, () => {\n    reloadNotifications();\n  });\n\n  // Refresh notifications every 15 minutes and clear on unmount\n  const refreshNotificationInterval = setInterval(\n    reloadNotifications,\n    60000 * 15\n  );\n  onBeforeUnmount(() => {\n    clearInterval(refreshNotificationInterval);\n  });\n\n  return {\n    notifications: computed(() => notifications.value),\n    notificationsSortedByTime,\n    notificationsLoading,\n    NotificationEvents,\n    selectedFilter,\n    filters,\n    loadNotifications,\n    selectNotification,\n    markAllAsRead\n  };\n}\n"
  },
  {
    "path": "src/composables/usePayment.ts",
    "content": "import { sendTransaction } from '@snapshot-labs/snapshot.js/src/utils';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { parseUnits } from '@ethersproject/units';\nimport { BigNumber } from '@ethersproject/bignumber';\n\nconst BASE_PRICE = 1666.66666667;\nconst BASE_UNIT = 1;\nconst BASE_CURRENCY = {\n  symbol: '$'\n};\n\nconst PLANS = {\n  y1: { label: '1 year', unit: 12, discount: 0 }\n  // m6: { label: '6 months', unit: 6, discount: 0 }\n};\nconst CURRENCIES = {\n  ethereum: {\n    name: 'Ethereum',\n    code: 'ETH',\n    decimal: 18,\n    logo: 'ipfs://bafybeieaiuehxdqgmdgxfl7addolzht3x5qnyhk4epn5f7yko7h4egpsvi',\n    address: {\n      1: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',\n      5: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'\n    }\n  },\n  'usd-coin': {\n    name: 'USD Coin',\n    code: 'USDC',\n    decimal: 6,\n    address: {\n      1: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',\n      5: '0x07865c6e87b9f70255377e024ace6630c1eaa37f'\n    },\n    logo: 'ipfs://bafybeiffspz4hsyc5drzf5ioum2x535dxmvfmt3342g4iizbinie7cpehy'\n  },\n  dai: {\n    name: 'DAI',\n    code: 'DAI',\n    decimal: 18,\n    address: {\n      1: '0x6b175474e89094c44da98b954eedeac495271d0f',\n      5: '0xdc31ee1784292379fbb2964b3b9c4124d8f89c60'\n    },\n    logo: 'ipfs://bafkreib3ujd27dovs3aqezrttibnspkprplyb7nf6wzm2vgjohleajkg4q'\n  }\n};\nconst DEFAULT_CURRENCY = 'ethereum';\nconst DEFAULT_PLAN = 'y1';\nconst TRANSFER_ABI = [\n  {\n    name: 'transfer',\n    type: 'function',\n    inputs: [\n      {\n        name: '_to',\n        type: 'address'\n      },\n      {\n        type: 'uint256',\n        name: '_tokens'\n      }\n    ],\n    constant: false,\n    outputs: [],\n    payable: false\n  }\n];\nconst SNAPSHOT_WALLET = '0x01e8CEC73B020AB9f822fD0dee3Aa4da2fe39e38';\nconst fxRates = reactive(\n  Object.fromEntries(Object.keys(CURRENCIES).map(id => [id, 0]))\n);\nconst fxLoadStatus = ref(0);\nconst COINGECKO_API_URL = 'https://api.coingecko.com/api/v3/simple';\nconst COINGECKO_PARAMS = '&vs_currencies=usd&include_24hr_change=true';\n\nexport function usePayment(network: number) {\n  const auth = getInstance();\n  const { web3 } = useWeb3();\n  const { notify } = useFlashNotification();\n  const loading = ref(false);\n  const paymentTx = ref(null);\n  const modalUnsupportedNetworkOpen = ref(false);\n  const walletNetworkKey = computed(() => web3.value.network.key);\n\n  refreshFx();\n\n  async function transfer(amount: number, currencyId: string) {\n    loading.value = true;\n    const currency = CURRENCIES[currencyId];\n\n    try {\n      if (network.toString() !== walletNetworkKey.value) {\n        modalUnsupportedNetworkOpen.value = true;\n        return false;\n      }\n\n      const parsedAmount = parseUnits(\n        amount.toFixed(currency.decimal),\n        currency.decimal\n      );\n      let tx;\n\n      if (currencyId === 'ethereum') {\n        tx = await transferEth(parsedAmount);\n      } else {\n        tx = await transferErc20(parsedAmount, currency.address[network]);\n      }\n      paymentTx.value = { network, ...tx };\n\n      return tx.wait();\n    } catch (e: any) {\n      if (/(insufficient|exceeds).*(balance|fund)/i.test(e.message)) {\n        return notify(['red', 'Insufficient funds']);\n      } else {\n        console.error('Transfer error', e);\n      }\n    } finally {\n      loading.value = false;\n    }\n  }\n\n  async function transferEth(amount: BigNumber) {\n    const signer = auth.web3.getSigner();\n    return signer.sendTransaction({\n      to: SNAPSHOT_WALLET,\n      value: amount\n    });\n  }\n\n  async function transferErc20(amount: BigNumber, address: string) {\n    return sendTransaction(auth.web3, address, TRANSFER_ABI, 'transfer', [\n      SNAPSHOT_WALLET,\n      amount\n    ]);\n  }\n\n  async function refreshFx(): Promise<void> {\n    try {\n      const response = await fetch(\n        `${COINGECKO_API_URL}/price?ids=${Object.keys(CURRENCIES)\n          .map(id => id)\n          .join(',')}${COINGECKO_PARAMS}`\n      );\n      const data = await response.json();\n      fxLoadStatus.value = 1;\n\n      Object.keys(data).map(id => {\n        fxRates[id] = data[id].usd;\n      });\n    } catch (e: any) {\n      fxLoadStatus.value = 2;\n    }\n  }\n\n  return {\n    BASE_PRICE,\n    BASE_UNIT,\n    BASE_CURRENCY,\n    DEFAULT_CURRENCY,\n    DEFAULT_PLAN,\n    CURRENCIES,\n    PLANS,\n    transfer,\n    fxRates,\n    loading,\n    paymentTx,\n    refreshFx,\n    fxLoadStatus,\n    modalUnsupportedNetworkOpen\n  };\n}\n"
  },
  {
    "path": "src/composables/usePlugins.ts",
    "content": "/**\n * Creates a plugin index from each individual plugin.json and provides\n * functions to register and load plugin components and filter plugins by a\n * search string (case insensitive), ordered by space count fetched from\n * backend.\n */\n\nimport { PLUGINS_COUNT_QUERY } from '@/helpers/queries';\nimport { Plugin } from '@/helpers/interfaces';\n\n// aggregate all plugin.json files in src/plugins\nconst pluginIndex = Object.fromEntries(\n  Object.entries(\n    import.meta.globEager('../plugins/*/plugin.json') as {\n      [key: string]: Plugin;\n    }\n  ).map(([path, config]) => {\n    const pluginKey = path\n      .replace('../plugins/', '')\n      .replace('/plugin.json', '');\n    return [pluginKey, { key: pluginKey, ...config }];\n  })\n);\n\n// import all plugin's main components (Create.vue, Proposal.vue, etc.)\n// (plugin root directories should not contain any other components)\nconst allPluginComponents = import.meta.globEager(`../plugins/*/*.vue`);\n\n// Based on list of active plugins in a space (pluginKeys) returns a list of\n// required component objects for a specific location (componentName, e.g.\n// Create) to then mount them with the built-in <component>... component. (e.g.\n// in src/components/Plugin/Create.vue)\nconst getPluginComponents = (componentName: string, pluginKeys) => {\n  pluginKeys = pluginKeys.filter(key => !!pluginIndex[key]); // remove old/non-existent plugins\n\n  return Object.entries(allPluginComponents)\n    .map(([path, componentModule]) => {\n      if (path.endsWith(`${componentName}.vue`)) {\n        const pluginKey = path\n          .replace('../plugins/', '')\n          .replace(`/${componentName}.vue`, '');\n\n        if (pluginKeys.includes(pluginKey)) {\n          // prefix component name for better debugging, e.g. in console warnings\n          componentModule.default.name = `Plugins${pluginKey[0].toUpperCase()}${pluginKey.substring(\n            1\n          )}${componentName}`;\n          return componentModule.default;\n        }\n      }\n      return null;\n    })\n    .filter(c => c);\n};\n\n// space count and filter function\nconst pluginsSpacesCount: any = ref(null);\nconst loadingPluginsSpacesCount = ref(false);\n\nconst { apolloQuery } = useApolloQuery();\nconst getPluginsSpacesCount = async () => {\n  if (pluginsSpacesCount.value) return; // run only once\n\n  loadingPluginsSpacesCount.value = true;\n  const res = await apolloQuery({ query: PLUGINS_COUNT_QUERY }, 'plugins');\n  // turn [{ id: \"myPlugin\", spaceCount: 1 }, ...] to { myPlugin: 1, ... }\n  pluginsSpacesCount.value = res.reduce(\n    (obj: any, item: any) => ({ ...obj, [item.id]: item.spacesCount }),\n    {}\n  );\n  loadingPluginsSpacesCount.value = false;\n};\n\nconst filterPlugins = (q = '') => {\n  return Object.values(pluginIndex)\n    .filter(plugin =>\n      JSON.stringify(plugin).toLowerCase().includes(q.toLowerCase())\n    )\n    .sort(\n      (a, b) =>\n        (pluginsSpacesCount.value?.[b.key] ?? 0) -\n        (pluginsSpacesCount.value?.[a.key] ?? 0)\n    );\n};\n\n/**\n * Composable\n *\n * Does it really make sense to use the composable pattern here? Most of it can\n * be normal imports.\n */\nexport function usePlugins() {\n  return {\n    pluginIndex,\n    getPluginComponents,\n    filterPlugins,\n    getPluginsSpacesCount,\n    pluginsSpacesCount,\n    loadingPluginsSpacesCount\n  };\n}\n"
  },
  {
    "path": "src/composables/useProfiles.ts",
    "content": "import { lookupAddress } from '@/helpers/utils';\nimport { PROFILES_QUERY } from '@/helpers/queries';\nimport { Profile } from '@/helpers/interfaces';\nimport { getAddress } from '@ethersproject/address';\n\n// Holds profile data (ENS name, username, about) for all addresses appearing in the frontend\nconst profiles = ref<{\n  [address: string]: Profile;\n}>({});\n\nconst reloadingProfile = ref(false);\n\nexport function useProfiles() {\n  const loadingProfiles = ref(false);\n\n  const profilesCreated = computed(() => {\n    const profilesWithCreatedAndAvatar = Object.values(profiles.value).filter(\n      profile => profile.avatar && profile.created\n    );\n    const profilesCreatedWithinLastWeek = profilesWithCreatedAndAvatar.filter(\n      profile => profile.created ?? 0 > Date.now() - 1000 * 60 * 60 * 24 * 7\n    );\n    const addressCreatedObject = profilesCreatedWithinLastWeek.reduce(\n      (acc, profile) => ({ ...acc, [profile.id as string]: profile.created }),\n      {}\n    );\n    return addressCreatedObject;\n  });\n\n  /**\n   * Populates global ref with profile data for batches of addresses.\n   */\n  const loadProfiles = async (addresses: string[]) => {\n    const addressesToAdd = addresses.filter(\n      address =>\n        !Object.keys(profiles.value).includes(address) && address !== ''\n    );\n\n    const { apolloQuery } = useApolloQuery();\n    let profilesRes: any = {};\n    if (addressesToAdd.length > 0) {\n      loadingProfiles.value = true;\n      profilesRes = await Promise.all([\n        await lookupAddress(addressesToAdd),\n        await apolloQuery(\n          {\n            query: PROFILES_QUERY,\n            variables: {\n              addresses\n            }\n          },\n          'users'\n        )\n      ]);\n      // add ens from profilesRes to corresponding address in profilesObj\n      Object.keys(profilesRes[0] ?? {}).forEach(address => {\n        profilesRes[0][address] = {\n          ...{ ens: profilesRes[0][address] },\n          ...profilesRes[1]?.find(\n            p => p.id.toLowerCase() === address.toLowerCase()\n          )\n        };\n      });\n    }\n\n    profiles.value = { ...profilesRes[0], ...profiles.value };\n    loadingProfiles.value = false;\n    reloadingProfile.value = false;\n  };\n\n  // Reload a profile in profiles object\n  const reloadProfile = (address: string) => {\n    // find profile in profiles object and delete it (to force reload)\n    const profile = profiles.value[address];\n    if (profile) {\n      delete profiles.value[address];\n    }\n    reloadingProfile.value = true;\n    loadProfiles([address]);\n  };\n\n  function getProfile(address: string) {\n    const normalizedAddress = getAddress(address);\n    return profiles.value[normalizedAddress];\n  }\n\n  return {\n    profiles,\n    loadProfiles,\n    reloadProfile,\n    getProfile,\n    loadingProfiles,\n    reloadingProfile,\n    profilesCreated\n  };\n}\n"
  },
  {
    "path": "src/composables/useProposalVotes.ts",
    "content": "import { Proposal, Vote, VoteFilters } from '@/helpers/interfaces';\nimport { VOTES_QUERY } from '@/helpers/queries';\n\ntype QueryParams = {\n  voter?: string;\n} & Partial<VoteFilters>;\n\nexport function useProposalVotes(proposal: Proposal, loadBy = 6) {\n  const { profiles, loadProfiles } = useProfiles();\n  const { apolloQuery } = useApolloQuery();\n  const { resolveName } = useResolveName();\n\n  const loadingVotes = ref(false);\n  const loadingMoreVotes = ref(false);\n  const loadingUserVote = ref(false);\n  const votes = ref<Vote[]>([]);\n  const userVote = ref<Vote | null>(null);\n\n  async function _fetchVotes(queryParams: QueryParams, skip = 0) {\n    const response = await apolloQuery(\n      {\n        query: VOTES_QUERY,\n        variables: {\n          id: proposal.id,\n          first: loadBy,\n          skip,\n          orderBy: 'vp',\n          orderDirection: queryParams.orderDirection || 'desc',\n          reason_not: queryParams.onlyWithReason ? '' : undefined,\n          voter: queryParams.voter || undefined\n        }\n      },\n      'votes'\n    );\n\n    loadProfiles(response.map(vote => vote.voter));\n    return response;\n  }\n\n  async function _fetchVote(queryParams: QueryParams) {\n    const response = await apolloQuery(\n      {\n        query: VOTES_QUERY,\n        variables: {\n          id: proposal.id,\n          voter: queryParams.voter\n        }\n      },\n      'votes'\n    );\n\n    loadProfiles(response.map(vote => vote.voter));\n    return response;\n  }\n\n  function formatProposalVotes(votes: Vote[]) {\n    if (!votes?.length) return [];\n    return votes.map(vote => {\n      vote.balance = vote.vp;\n      vote.scores = vote.vp_by_strategy;\n      return vote;\n    });\n  }\n\n  async function loadVotes(filter: Partial<VoteFilters> = {}) {\n    if (loadingVotes.value) return;\n\n    loadingVotes.value = true;\n    try {\n      const response = await _fetchVotes(filter);\n      votes.value = formatProposalVotes(response);\n    } catch (e) {\n      console.log(e);\n    } finally {\n      loadingVotes.value = false;\n    }\n  }\n\n  async function loadSingleVote(search: string) {\n    loadingVotes.value = true;\n\n    const response = await resolveName(search);\n    const voter = response || search;\n    try {\n      const response = await _fetchVote({ voter });\n      votes.value = formatProposalVotes(response);\n    } catch (e) {\n      console.log(e);\n    } finally {\n      loadingVotes.value = false;\n    }\n  }\n\n  async function loadMoreVotes(filter: Partial<VoteFilters> = {}) {\n    if (\n      loadingMoreVotes.value ||\n      loadingVotes.value ||\n      loadBy > votes.value.length\n    )\n      return;\n\n    loadingMoreVotes.value = true;\n    try {\n      const response = await _fetchVotes(filter, votes.value.length);\n      votes.value = votes.value.concat(formatProposalVotes(response));\n    } catch (e) {\n      console.log(e);\n    } finally {\n      loadingMoreVotes.value = false;\n    }\n  }\n\n  async function loadUserVote(voter: string) {\n    if (!voter) return;\n    userVote.value = null;\n\n    try {\n      loadingUserVote.value = true;\n      const response = await _fetchVote({ voter });\n      userVote.value = formatProposalVotes(response)[0];\n    } catch (e) {\n      console.log(e);\n    } finally {\n      loadingUserVote.value = false;\n    }\n  }\n\n  return {\n    votes,\n    profiles,\n    loadingVotes,\n    loadingMoreVotes,\n    loadingUserVote: computed(() => loadingUserVote.value),\n    userVote,\n    formatProposalVotes,\n    loadVotes,\n    loadMoreVotes,\n    loadSingleVote,\n    loadUserVote\n  };\n}\n"
  },
  {
    "path": "src/composables/useProposals.ts",
    "content": "import { Proposal } from '@/helpers/interfaces';\nimport { USER_VOTED_PROPOSAL_IDS_QUERY } from '@/helpers/queries';\n\ninterface ProposalsStore {\n  space: {\n    proposals: Proposal[];\n  };\n  timeline: {\n    proposals: Proposal[];\n  };\n}\n\nconst store = reactive<ProposalsStore>({\n  space: {\n    proposals: []\n  },\n  timeline: {\n    proposals: []\n  }\n});\n\nconst userVotedProposalIds = ref<string[]>([]);\n\nexport function useProposals() {\n  function setTimelineProposals(proposals) {\n    store.timeline.proposals = proposals;\n  }\n\n  function addTimelineProposals(proposals) {\n    store.timeline.proposals = store.timeline.proposals.concat(proposals);\n  }\n\n  function setSpaceProposals(proposals) {\n    store.space.proposals = proposals;\n  }\n\n  function addSpaceProposals(proposals) {\n    store.space.proposals = store.space.proposals.concat(proposals);\n  }\n\n  function resetSpaceProposals() {\n    store.space.proposals = [];\n  }\n\n  function removeSpaceProposal(id: string) {\n    store.space.proposals = store.space.proposals.filter(\n      proposal => proposal.id !== id\n    );\n  }\n\n  function addVotedProposalId(id: string) {\n    userVotedProposalIds.value.push(id);\n  }\n\n  const { apolloQuery } = useApolloQuery();\n  async function getUserVotedProposalIds(voter: string, proposals: string[]) {\n    if (!voter || !proposals?.length) return;\n    const votes = await apolloQuery(\n      {\n        query: USER_VOTED_PROPOSAL_IDS_QUERY,\n        variables: {\n          voter,\n          proposals\n        }\n      },\n      'votes'\n    );\n\n    const proposalId = votes?.map(vote => vote.proposal.id) ?? [];\n    userVotedProposalIds.value = [\n      ...new Set(userVotedProposalIds.value.concat(proposalId))\n    ];\n  }\n\n  const proposalIds = computed(() => {\n    const timelineProposals =\n      store.timeline.proposals?.map(proposal => proposal.id) ?? [];\n    const spaceProposals =\n      store.space.proposals?.map(proposal => proposal.id) ?? [];\n    return [...timelineProposals, ...spaceProposals];\n  });\n\n  const { web3Account } = useWeb3();\n  watch(\n    () => [store.space.proposals, store.timeline.proposals],\n    () => {\n      getUserVotedProposalIds(web3Account.value, proposalIds.value);\n    }\n  );\n\n  watch(web3Account, () => {\n    userVotedProposalIds.value = [];\n    getUserVotedProposalIds(web3Account.value, proposalIds.value);\n  });\n\n  return {\n    store,\n    userVotedProposalIds,\n    addVotedProposalId,\n    setSpaceProposals,\n    addSpaceProposals,\n    resetSpaceProposals,\n    removeSpaceProposal,\n    setTimelineProposals,\n    addTimelineProposals\n  };\n}\n"
  },
  {
    "path": "src/composables/useQuorum.ts",
    "content": "import { ExtendedSpace, Proposal, Results } from '@/helpers/interfaces';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { call } from '@snapshot-labs/snapshot.js/src/utils';\nimport { getSnapshots } from '@snapshot-labs/snapshot.js/src/utils/blockfinder';\n\ninterface QuorumProps {\n  space: ExtendedSpace;\n  proposal: Proposal;\n  results: Results;\n}\n\nconst broviderUrl = import.meta.env.VITE_BROVIDER_URL;\n\nexport function useQuorum(props: QuorumProps) {\n  const loading = ref(false);\n  const quorum = ref(0);\n  const quorumType = ref('default');\n\n  const totalQuorumScore = computed(() => {\n    if (props.proposal.quorumType === 'rejection') {\n      return props.results.scores\n        .filter((c, i) => i === 1)\n        .reduce((a, b) => a + b, 0);\n    }\n    const basicCount = props.space.plugins?.quorum?.basicCount;\n    if (basicCount && props.proposal.type === 'basic')\n      return props.results.scores\n        .filter((c, i) => basicCount.includes(i))\n        .reduce((a, b) => a + b, 0);\n    if (props.space.voting.hideAbstain && props.proposal.type === 'basic') {\n      return props.results.scores\n        .filter((c, i) => i !== 2)\n        .reduce((a, b) => a + b, 0);\n    }\n    if (props.results.scoresTotal) return props.results.scoresTotal;\n    return 0;\n  });\n\n  async function getQuorum(web3: any, quorumOptions: any, snapshot: string) {\n    if (props.proposal?.quorum) {\n      return props.proposal?.quorum;\n    }\n\n    if (!quorumOptions) return 0;\n\n    const { strategy } = quorumOptions;\n\n    const quorumModifier = quorumOptions.quorumModifier ?? 1;\n\n    switch (strategy) {\n      case 'static': {\n        return quorumOptions.total;\n      }\n\n      case 'balance': {\n        const { address, methodABI, decimals } = quorumOptions;\n\n        const blockTag = snapshot === 'latest' ? snapshot : parseInt(snapshot);\n\n        const votingPower = await call(\n          web3,\n          [methodABI],\n          [address, methodABI.name],\n          { blockTag }\n        );\n\n        return (\n          BigNumber.from(votingPower)\n            .div(BigNumber.from(10).pow(decimals))\n            .toNumber() * quorumModifier\n        );\n      }\n\n      case 'multichainBalance': {\n        const { network, strategies } = quorumOptions;\n        const blocks = await getSnapshots(\n          network,\n          parseInt(snapshot),\n          web3,\n          strategies.map(s => s.network || network)\n        );\n        const requests: Promise<any>[] = strategies.map(s =>\n          call(\n            getProvider(s.network, { broviderUrl }),\n            [s.methodABI],\n            [s.address, s.methodABI.name],\n            { blockTag: blocks[s.network] }\n          )\n        );\n        const results = await Promise.all(requests);\n        const totalBalance = results.reduce((total, ele, index) => {\n          if (index === 1) {\n            total = total.div(BigNumber.from(10).pow(strategies[0].decimals));\n          }\n          return total.add(\n            ele.div(BigNumber.from(10).pow(strategies[index].decimals))\n          );\n        });\n        return totalBalance.toNumber() * quorumModifier;\n      }\n\n      default:\n        throw new Error(`Unsupported quorum strategy: ${strategy}`);\n    }\n  }\n\n  async function loadQuorum() {\n    loading.value = true;\n    quorum.value = await getQuorum(\n      getProvider(props.space.network, { broviderUrl }),\n      props.space.plugins.quorum,\n      props.proposal.snapshot\n    );\n    quorumType.value = props.proposal.quorumType;\n    loading.value = false;\n  }\n\n  onMounted(() => loadQuorum());\n\n  return {\n    loadingQuorum: loading,\n    totalQuorumScore,\n    quorum,\n    quorumType\n  };\n}\n"
  },
  {
    "path": "src/composables/useReportDownload.ts",
    "content": "import pkg from '@/../package.json';\n\nexport function useReportDownload() {\n  const { env } = useApp();\n  const isDownloadingVotes = ref(false);\n  const errorCode: globalThis.Ref<null | Error> = ref(null);\n\n  async function downloadFile(blob: Blob, fileName: string) {\n    const href = URL.createObjectURL(blob);\n    const a = Object.assign(document.createElement('a'), {\n      href,\n      style: 'display:none',\n      download: fileName\n    });\n    document.body.appendChild(a);\n    a.click();\n    URL.revokeObjectURL(href);\n    a.remove();\n  }\n\n  async function downloadVotes(proposalId: string) {\n    if (env === 'demo') {\n      errorCode.value = new Error('UNSUPPORTED_ENV');\n      return false;\n    }\n\n    isDownloadingVotes.value = true;\n    errorCode.value = null;\n\n    try {\n      const response = await fetch(\n        `${import.meta.env.VITE_SIDEKICK_URL}/api/votes/${proposalId}`,\n        {\n          method: 'POST'\n        }\n      );\n\n      if (response.status !== 200) {\n        throw new Error((await response.json()).error.message);\n      }\n\n      downloadFile(await response.blob(), `${pkg.name}-report-${proposalId}`);\n      return true;\n    } catch (e: any) {\n      errorCode.value = e;\n      return false;\n    } finally {\n      isDownloadingVotes.value = false;\n    }\n  }\n\n  return {\n    downloadVotes,\n    isDownloadingVotes,\n    errorCode\n  };\n}\n"
  },
  {
    "path": "src/composables/useResolveName.ts",
    "content": "import { isAddress } from '@ethersproject/address';\nimport { resolveHandle } from '@/helpers/utils';\n\nexport function useResolveName() {\n  async function resolveName(value: string): Promise<string | null> {\n    let address = value;\n\n    if (isAddress(value)) {\n      return address.toLowerCase();\n    }\n\n    address = await resolveHandle(value);\n\n    return isAddress(address) ? address.toLowerCase() : null;\n  }\n\n  return {\n    resolveName\n  };\n}\n"
  },
  {
    "path": "src/composables/useSafe.ts",
    "content": "interface ExecutionStatus {\n  batchError:\n    | undefined\n    | {\n        num: number;\n        message: string;\n      };\n}\n\nconst state = reactive<ExecutionStatus>({\n  batchError: undefined\n});\n\nexport function useSafe() {\n  function setBatchError(num: number, message: string) {\n    state.batchError = { num, message };\n  }\n\n  function clearBatchError() {\n    state.batchError = undefined;\n  }\n\n  return {\n    setBatchError,\n    clearBatchError,\n    safesnap: computed(() => state)\n  };\n}\n"
  },
  {
    "path": "src/composables/useSharing.ts",
    "content": "import { useShare } from '@vueuse/core';\nimport { ExtendedSpace, Proposal } from '@/helpers/interfaces';\n\nexport function useSharing() {\n  const { t } = useI18n();\n\n  const sharingItems = [\n    {\n      text: 'Share on X',\n      action: 'shareProposalX',\n      extras: { icon: 'x' }\n    },\n    {\n      text: 'Share on Hey',\n      action: 'shareProposalHey',\n      extras: { icon: 'hey' }\n    },\n    {\n      text: t('copyLink'),\n      action: 'shareToClipboard',\n      extras: { icon: 'link' }\n    }\n  ];\n\n  function proposalUrl(key, proposal) {\n    return `https://${window.location.hostname}/#/${key}/proposal/${proposal.id}`;\n  }\n  function encodedProposalUrl(key, proposal) {\n    return encodeURIComponent(proposalUrl(key, proposal));\n  }\n\n  const { share, isSupported } = useShare();\n\n  function shareProposal(space, proposal) {\n    share({\n      title: '',\n      text: `${space.name} - ${proposal.title}`,\n      url: proposalUrl(space.id, proposal)\n    });\n  }\n\n  function shareClaim(shareTo: 'x' | 'hey', payload: { proposal: Proposal }) {\n    const postText = getClaimedText(shareTo, payload);\n\n    if (window && shareTo === 'hey') return shareHey(postText);\n    if (isSupported.value)\n      return share({\n        title: '',\n        text: postText,\n        url: proposalUrl(payload.proposal.space.id, payload.proposal)\n      });\n    if (window && shareTo === 'x') return shareX(postText);\n  }\n\n  function getClaimedText(\n    shareTo: 'x' | 'hey',\n    payload: { proposal: Proposal }\n  ): string {\n    const claimedText = `I just claimed my reward for voting on`;\n\n    const spaceHandle = payload.proposal.space.twitter\n      ? `@${payload.proposal.space.twitter}`\n      : payload.proposal.space.name;\n\n    const hashTag = 'SnapshotClaim';\n\n    if (shareTo === 'hey')\n      return `${encodeURIComponent(claimedText)}%20\"${encodeURIComponent(\n        payload.proposal.title\n      )}\"%20${encodedProposalUrl(\n        payload.proposal.space.id,\n        payload.proposal\n      )}&hashtags=${hashTag}`;\n    if (isSupported.value)\n      return `${claimedText} \"${payload.proposal.title}\" ${spaceHandle} #${hashTag}`;\n    if (shareTo === 'x')\n      return `${encodeURIComponent(claimedText)}%20\"${encodeURIComponent(\n        payload.proposal.title\n      )}\"%20${encodedProposalUrl(\n        payload.proposal.space.id,\n        payload.proposal\n      )}%20${spaceHandle}%20%23${hashTag}`;\n\n    return `${claimedText} \"${payload.proposal.title}\"`;\n  }\n\n  function shareVote(\n    shareTo: 'x' | 'hey',\n    payload: { space: ExtendedSpace; proposal: Proposal; choices: string }\n  ) {\n    const postText = getVotedText(shareTo, payload);\n\n    if (window && shareTo === 'hey') return shareHey(postText);\n    if (isSupported.value)\n      return share({\n        title: '',\n        text: postText,\n        url: proposalUrl(payload.space.id, payload.proposal)\n      });\n    if (window && shareTo === 'x') return shareX(postText);\n  }\n\n  function getVotedText(shareTo: 'x' | 'hey', payload): string {\n    const isSingleChoice =\n      payload.proposal.type === 'single-choice' ||\n      payload.proposal.type === 'basic';\n    const isPrivate = payload.proposal.privacy === 'shutter';\n    const votedText =\n      payload.choices && isSingleChoice && !isPrivate\n        ? `I just voted \"${payload.choices}\" on`\n        : `I just voted on`;\n\n    const spaceHandle = payload.space.twitter\n      ? `@${payload.space.twitter}`\n      : payload.space.name;\n\n    const hashTag = 'SnapshotVote';\n\n    if (shareTo === 'hey')\n      return `${encodeURIComponent(votedText)}%20\"${encodeURIComponent(\n        payload.proposal.title\n      )}\"%20${encodedProposalUrl(\n        payload.space.id,\n        payload.proposal\n      )}&hashtags=${hashTag}`;\n    if (isSupported.value)\n      return `${votedText} \"${payload.proposal.title}\" ${spaceHandle} #${hashTag}`;\n    if (shareTo === 'x')\n      return `${encodeURIComponent(votedText)}%20\"${encodeURIComponent(\n        payload.proposal.title\n      )}\"%20${encodedProposalUrl(\n        payload.space.id,\n        payload.proposal\n      )}%20${spaceHandle}%20%23${hashTag}`;\n\n    return `${votedText} \"${payload.proposal.title}\"`;\n  }\n\n  function shareX(text) {\n    const url = `https://x.com/intent/tweet?text=${text}`;\n    window.open(url, '_blank')?.focus();\n  }\n\n  function shareHey(text) {\n    const url = `https://hey.xyz/?text=${text}`;\n    window.open(url, '_blank')?.focus();\n  }\n\n  function shareProposalX(space, proposal) {\n    const handle = space.twitter ? `@${space.twitter}` : space.name;\n    shareX(\n      `${encodeURIComponent(proposal.title)}%20${encodedProposalUrl(\n        space.id,\n        proposal\n      )}%20${handle}%20%23Snapshot`\n    );\n  }\n\n  function shareProposalHey(space, proposal) {\n    shareHey(\n      `${encodeURIComponent(proposal.title)}%20${encodedProposalUrl(\n        space.id,\n        proposal\n      )}&hashtags=Snapshot`\n    );\n  }\n\n  const { copyToClipboard } = useCopy();\n\n  function shareToClipboard(space, proposal) {\n    copyToClipboard(proposalUrl(space.id, proposal));\n  }\n\n  return {\n    shareProposalX,\n    shareProposalHey,\n    shareToClipboard,\n    proposalUrl,\n    shareProposal,\n    shareVote,\n    shareClaim,\n    sharingIsSupported: isSupported,\n    sharingItems\n  };\n}\n"
  },
  {
    "path": "src/composables/useShortUrls.ts",
    "content": "const shortUrls = ref<string[]>([]);\n\nexport function useShortUrls() {\n  async function fetchShortUrlData(): Promise<string> {\n    try {\n      const response = await fetch(\n        'https://raw.githubusercontent.com/PeterDaveHello/url-shorteners/master/list'\n      );\n\n      if (!response.ok) {\n        throw new Error(`HTTP error! status: ${response.status}`);\n      }\n\n      return await response.text();\n    } catch (error: any) {\n      console.error(`Error fetching short URLs: ${error.message}`);\n      return '';\n    }\n  }\n\n  function parseShortUrlData(data: string): string[] {\n    const lines = data.split('\\n');\n\n    return lines.filter(line => !line.startsWith('#') && line.trim() !== '');\n  }\n\n  async function getShortUrls(): Promise<string[]> {\n    const rawData = await fetchShortUrlData();\n    return parseShortUrlData(rawData);\n  }\n\n  function containsShortUrl(text: string): boolean {\n    if (shortUrls.value.length === 0) return false;\n    return shortUrls.value.some(\n      shortUrl =>\n        text.includes(`http://${shortUrl}/`) ||\n        text.includes(`https://${shortUrl}/`) ||\n        text.includes(`.${shortUrl}/`)\n    );\n  }\n\n  onMounted(async () => {\n    if (shortUrls.value.length === 0) shortUrls.value = await getShortUrls();\n  });\n\n  return { containsShortUrl };\n}\n"
  },
  {
    "path": "src/composables/useSkin.ts",
    "content": "import { SPACE_SKIN_QUERY } from '@/helpers/queries';\nimport { useStorage, usePreferredColorScheme } from '@vueuse/core';\n\nexport const DARK = 'dark';\nexport const LIGHT = 'light';\n\nconst preferredColor = usePreferredColorScheme();\n\nconst userTheme = useStorage('snapshot.userTheme', '');\n\nfunction toggleUserTheme() {\n  if (userTheme.value === LIGHT) {\n    userTheme.value = DARK;\n  } else {\n    userTheme.value = LIGHT;\n  }\n}\n\nconst theme = computed(() =>\n  [DARK, LIGHT].includes(userTheme.value)\n    ? userTheme.value\n    : preferredColor.value\n);\n\nconst skinClass = ref('default');\n\nexport function useSkin() {\n  const { apolloQuery } = useApolloQuery();\n\n  async function getSkin(domain: string): Promise<string | null> {\n    if (domain) {\n      const space = await apolloQuery(\n        {\n          query: SPACE_SKIN_QUERY,\n          variables: {\n            id: domain\n          }\n        },\n        'space'\n      );\n      if (space?.skin) {\n        skinClass.value = space.skin;\n        document.body.classList.add(skinClass.value);\n\n        return space.skin;\n      }\n    }\n\n    return null;\n  }\n\n  const getThemeIcon = () => (theme.value === LIGHT ? 'moon' : 'sun');\n\n  watch(\n    theme,\n    () => {\n      document.documentElement.setAttribute(\n        'data-color-scheme',\n        theme.value === LIGHT ? 'light' : 'dark'\n      );\n    },\n    { immediate: true }\n  );\n\n  return {\n    getThemeIcon,\n    toggleUserTheme,\n    getSkin\n  };\n}\n"
  },
  {
    "path": "src/composables/useSkinsFilter.ts",
    "content": "/**\n * Orders skins by spaces count and returns a list of skins\n * filtered by the search string (case insensitive).\n */\n\nimport { SKINS_COUNT_QUERY } from '@/helpers/queries';\nimport skins from '@/../snapshot-spaces/skins';\n\nconst skinsSpacesCount: any = ref(null);\n\nexport function useSkinsFilter() {\n  const loading = ref(false);\n\n  const filterSkins = (q = '') =>\n    Object.keys(skins)\n      .filter(s => s.toLowerCase().includes(q.toLowerCase()))\n      .sort(\n        (a, b) =>\n          (skinsSpacesCount.value[b] ?? 0) - (skinsSpacesCount.value[a] ?? 0)\n      );\n\n  const { apolloQuery } = useApolloQuery();\n\n  async function getSkinsSpacesCount() {\n    if (skinsSpacesCount.value) return;\n    loading.value = true;\n    const res = await apolloQuery(\n      {\n        query: SKINS_COUNT_QUERY\n      },\n      'skins'\n    );\n    skinsSpacesCount.value = res.reduce(\n      (obj: any, item: any) => ({ ...obj, [item.id]: item.spacesCount }),\n      {}\n    );\n\n    loading.value = false;\n  }\n\n  return {\n    filterSkins,\n    getSkinsSpacesCount,\n    skinsSpacesCount,\n    loadingSkins: loading\n  };\n}\n"
  },
  {
    "path": "src/composables/useSnapshot.ts",
    "content": "import { getBlockNumber } from '@snapshot-labs/snapshot.js/src/utils/web3';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\n\nconst isLoading = ref(false);\nconst error = ref(false);\n\nexport function useSnapshot() {\n  async function getSnapshot(network: string): Promise<number> {\n    try {\n      isLoading.value = true;\n      error.value = false;\n      const broviderUrl = import.meta.env.VITE_BROVIDER_URL;\n      const currentBlock = await getBlockNumber(\n        getProvider(network, { broviderUrl })\n      );\n      console.log('Snapshot block number', currentBlock);\n      return currentBlock - 4;\n    } catch (e) {\n      error.value = true;\n      return 0;\n    } finally {\n      isLoading.value = false;\n    }\n  }\n\n  return {\n    getSnapshot,\n    isSnapshotLoading: isLoading,\n    errorFetchingSnapshot: error\n  };\n}\n"
  },
  {
    "path": "src/composables/useSpaceController.ts",
    "content": "import { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport namehash from '@ensdomains/eth-ens-namehash';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { getAddress } from '@ethersproject/address';\nimport {\n  sendTransaction,\n  getEnsOwner,\n  getSpaceController\n} from '@snapshot-labs/snapshot.js/src/utils';\n\nconst spaceControllerInput = ref('');\nconst modalWrongNetworkOpen = ref(false);\nconst modalConfirmSetTextRecordOpen = ref(false);\nconst settingENSRecord = ref(false);\nconst pendingENSRecord = ref(false);\nconst ensOwner = ref<string | null>(null);\nconst spaceController = ref<string | null>(null);\nconst defaultNetwork = import.meta.env.VITE_DEFAULT_NETWORK;\nconst broviderUrl = import.meta.env.VITE_BROVIDER_URL;\n\nexport function useSpaceController() {\n  const { web3, web3Account } = useWeb3();\n  const auth = getInstance();\n  const { t } = useI18n();\n  const route = useRoute();\n  const { domain } = useApp();\n  const { notify } = useFlashNotification();\n\n  const ensAbi = ['function setText(bytes32 node, string key, string value)'];\n\n  const networkKey = computed(() => web3.value.network.key);\n\n  const ensAddress = computed(\n    () => domain || route.params.ens || route.params.key\n  );\n\n  const isEnsOwner = computed(\n    () => ensOwner.value?.toLowerCase() === web3Account.value?.toLowerCase()\n  );\n\n  const isSpaceController = computed(\n    () =>\n      spaceController.value?.toLowerCase() === web3Account.value?.toLowerCase()\n  );\n\n  async function setRecord() {\n    settingENSRecord.value = true;\n    try {\n      const ensResolvers = networks[networkKey.value].ensResolvers;\n      const ensPublicResolverAddress = ensResolvers[0];\n      if (!ensPublicResolverAddress) {\n        throw new Error('No ENS resolver address for this network');\n      }\n      const ensname = ensAddress.value;\n      const node = namehash.hash(ensname);\n      const tx = await sendTransaction(\n        auth.web3,\n        ensPublicResolverAddress,\n        ensAbi,\n        'setText',\n        [node, 'snapshot', getAddress(spaceControllerInput.value)]\n      );\n      settingENSRecord.value = false;\n      notify(t('notify.transactionSent'));\n\n      return tx;\n    } catch (e) {\n      notify(['red', t('notify.somethingWentWrong')]);\n      console.log(e);\n    } finally {\n      settingENSRecord.value = false;\n    }\n  }\n\n  function confirmSetRecord() {\n    if (networkKey.value !== defaultNetwork) modalWrongNetworkOpen.value = true;\n    else modalConfirmSetTextRecordOpen.value = true;\n  }\n\n  async function loadEnsOwner() {\n    ensOwner.value = await getEnsOwner(ensAddress.value, defaultNetwork, {\n      broviderUrl\n    });\n  }\n\n  async function loadSpaceController() {\n    spaceController.value = await getSpaceController(\n      ensAddress.value,\n      defaultNetwork,\n      { broviderUrl }\n    );\n  }\n\n  return {\n    spaceControllerInput,\n    settingENSRecord,\n    pendingENSRecord,\n    modalWrongNetworkOpen,\n    modalConfirmSetTextRecordOpen,\n    setRecord,\n    confirmSetRecord,\n    ensAddress,\n    loadEnsOwner,\n    ensOwner,\n    isEnsOwner,\n    loadSpaceController,\n    spaceController,\n    isSpaceController\n  };\n}\n"
  },
  {
    "path": "src/composables/useSpaceSubscription.ts",
    "content": "import { SUBSCRIPTIONS_QUERY } from '@/helpers/queries';\n// import { beams } from '@/helpers/beams';\nimport { useFlashNotification } from './useFlashNotification';\nimport client from '@/helpers/clientEIP712';\n\nconst subscriptions = ref<any[] | undefined>(undefined);\n\nexport function useSpaceSubscription(spaceId: any) {\n  const { web3, web3Account } = useWeb3();\n  const { apolloQuery } = useApolloQuery();\n  const { setAlias, aliasWallet, isValidAlias, checkAlias } = useAliasAction();\n  const { notify } = useFlashNotification();\n  const { t } = useI18n();\n  const loading = ref(false);\n  const isSubscribed = computed(() => {\n    return (\n      subscriptions.value?.some((subscription: any) => {\n        return (\n          subscription.space.id === spaceId &&\n          subscription.address === web3Account.value\n        );\n      }) ?? false\n    );\n  });\n\n  async function loadSubscriptions() {\n    if (!web3Account.value) return;\n\n    loading.value = true;\n    try {\n      const spaceSubscriptions = await apolloQuery(\n        {\n          query: SUBSCRIPTIONS_QUERY,\n          variables: {\n            address: web3Account.value\n          }\n        },\n        'subscriptions'\n      );\n      if (spaceSubscriptions) {\n        subscriptions.value = spaceSubscriptions;\n      } else {\n        subscriptions.value = undefined;\n      }\n    } catch (e) {\n      console.error(e);\n      subscriptions.value = undefined;\n    } finally {\n      loading.value = false;\n    }\n  }\n\n  const configurePush = async () => {\n    try {\n      // if (!beams) {\n      //   notify(['red', t('notificationsNotSupported')]);\n      //   return;\n      // }\n\n      // await beams.start();\n      // await beams.addDeviceInterest(web3Account.value);\n      await client.subscribe(aliasWallet.value, aliasWallet.value.address, {\n        from: web3Account.value,\n        space: spaceId\n      });\n    } catch (error: any) {\n      // thrown by beams when the user denies the notification permission or browser doesn't support it\n      if (error.name === 'NotAllowedError')\n        notify(['red', t('notificationsBlocked')]);\n      console.error(error);\n    }\n  };\n\n  async function toggleSubscription() {\n    if (web3.value.authLoading) {\n      return null;\n    }\n\n    loading.value = true;\n    try {\n      await checkAlias();\n      if (!aliasWallet.value || !isValidAlias.value) {\n        await setAlias();\n      }\n\n      if (isSubscribed.value) {\n        await client.unsubscribe(aliasWallet.value, aliasWallet.value.address, {\n          from: web3Account.value,\n          space: spaceId\n        });\n      } else {\n        await configurePush();\n      }\n      await loadSubscriptions();\n    } catch (e) {\n      console.error(e);\n    } finally {\n      loading.value = false;\n    }\n  }\n\n  return {\n    toggleSubscription,\n    loading,\n    isSubscribed,\n    subscriptions,\n    loadSubscriptions\n  };\n}\n"
  },
  {
    "path": "src/composables/useSpaces.ts",
    "content": "import { RankedSpace, Space } from '@/helpers/interfaces';\nimport { SPACES_QUERY, SPACES_RANKING_QUERY } from '@/helpers/queries';\n\ninterface Metrics {\n  total: number;\n  categories: Record<string, number>;\n}\n\nexport function useSpaces() {\n  const { apolloQuery } = useApolloQuery();\n\n  const loadingSpacesHome = ref(false);\n  const loadingMoreSpacesHome = ref(false);\n  const spacesHome = ref<RankedSpace[]>([]);\n  const spacesHomeMetrics = ref<Metrics>({ total: 0, categories: {} });\n  const enableSpaceHomeScroll = ref(false);\n\n  const loadingSpacesRanking = ref(false);\n  const loadingMoreSpacesRanking = ref(false);\n  const spacesRanking = ref<RankedSpace[]>([]);\n  const spacesRankingMetrics = ref<Metrics>({ total: 0, categories: {} });\n\n  const isLoadingSpaces = ref(false);\n  const isLoadingDeletedSpaces = ref(false);\n  const spaces = ref<Space[]>([]);\n\n  function _fetchRankedSpaces(variables: any = {}, skip = 0) {\n    return apolloQuery(\n      {\n        query: SPACES_RANKING_QUERY,\n        variables: {\n          skip,\n          first: 12,\n          private: false,\n          search: variables.search || undefined,\n          network: variables.network || undefined,\n          category: variables.category || undefined\n        }\n      },\n      'ranking'\n    );\n  }\n\n  async function loadSpacesHome(variables?: any) {\n    if (loadingSpacesHome.value) return;\n    loadingSpacesHome.value = true;\n    try {\n      const response = await _fetchRankedSpaces(variables);\n\n      if (!response) return;\n\n      spacesHome.value = response.items || [];\n      spacesHomeMetrics.value = response.metrics as Metrics;\n\n      loadingSpacesHome.value = false;\n    } catch (e) {\n      console.error(e);\n    } finally {\n      loadingSpacesHome.value = false;\n    }\n  }\n\n  async function loadMoreSpacesHome(variables?: any) {\n    if (\n      loadingMoreSpacesHome.value ||\n      spacesHomeMetrics.value.total <= spacesHome.value.length\n    )\n      return;\n    loadingMoreSpacesHome.value = true;\n    try {\n      const response = await _fetchRankedSpaces(\n        variables,\n        spacesHome.value.length\n      );\n\n      if (!response) return;\n\n      spacesHome.value = [...spacesHome.value, ...response.items];\n      spacesHomeMetrics.value = response.metrics as Metrics;\n\n      loadingMoreSpacesHome.value = false;\n    } catch (e) {\n      console.error(e);\n    } finally {\n      loadingMoreSpacesHome.value = false;\n    }\n  }\n\n  async function loadSpacesRanking(variables?: any) {\n    if (loadingSpacesRanking.value) return;\n    loadingSpacesRanking.value = true;\n    try {\n      const response = await _fetchRankedSpaces(variables);\n\n      if (!response) return;\n\n      spacesRanking.value = response.items;\n      spacesRankingMetrics.value = response.metrics as Metrics;\n\n      loadingSpacesRanking.value = false;\n    } catch (e) {\n      console.error(e);\n    } finally {\n      loadingSpacesRanking.value = false;\n    }\n  }\n\n  async function loadMoreSpacesRanking(variables?: any) {\n    if (\n      loadingMoreSpacesRanking.value ||\n      spacesRankingMetrics.value.total <= spacesRanking.value.length\n    )\n      return;\n    loadingMoreSpacesRanking.value = true;\n    try {\n      const response = await _fetchRankedSpaces(\n        variables,\n        spacesRanking.value.length\n      );\n\n      if (!response) return;\n\n      spacesRanking.value = [...spacesRanking.value, ...response.items];\n      spacesRankingMetrics.value = response.metrics as Metrics;\n\n      loadingMoreSpacesRanking.value = false;\n    } catch (e) {\n      console.error(e);\n    } finally {\n      loadingMoreSpacesRanking.value = false;\n    }\n  }\n\n  async function loadSpaces(id_in: string[]) {\n    if (isLoadingSpaces.value || !id_in.length) return;\n\n    isLoadingSpaces.value = true;\n    try {\n      const response = await apolloQuery(\n        {\n          query: SPACES_QUERY,\n          variables: {\n            id_in,\n            skip: 0,\n            first: 1000\n          }\n        },\n        'spaces'\n      );\n\n      if (!response) return;\n\n      spaces.value = response;\n\n      isLoadingSpaces.value = false;\n    } catch (e) {\n      console.error(e);\n    } finally {\n      isLoadingSpaces.value = false;\n    }\n  }\n\n  async function getDeletedSpaces(ids: string[]) {\n    isLoadingDeletedSpaces.value = true;\n    const results = await Promise.allSettled(\n      ids.map(async id => {\n        try {\n          const response = await fetch(\n            `${import.meta.env.VITE_HUB_URL}/api/spaces/${id}`,\n            {\n              headers: { 'Content-Type': 'application/json' }\n            }\n          );\n\n          return (await response.json())?.deleted === true ? id : null;\n        } catch (e) {\n          console.error(e);\n          return null;\n        }\n      })\n    );\n    isLoadingDeletedSpaces.value = false;\n\n    return results.map(r => r.value).filter(a => a);\n  }\n\n  return {\n    loadSpaces,\n    loadSpacesHome,\n    loadMoreSpacesHome,\n    loadSpacesRanking,\n    loadMoreSpacesRanking,\n    getDeletedSpaces,\n    spaces,\n    spacesHome,\n    spacesHomeMetrics,\n    isLoadingSpaces,\n    isLoadingDeletedSpaces,\n    loadingSpacesHome,\n    loadingMoreSpacesHome,\n    enableSpaceHomeScroll,\n    spacesRanking,\n    spacesRankingMetrics,\n    loadingSpacesRanking,\n    loadingMoreSpacesRanking\n  };\n}\n"
  },
  {
    "path": "src/composables/useStatement.ts",
    "content": "import { STATEMENTS_QUERY } from '@/helpers/queries';\nimport { Statement } from '@/helpers/interfaces';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst SET_STATEMENT_ACTION = 'set-statement';\n\nconst statements = ref<Record<string, Statement>>({});\nconst savedSpaceId = ref<string>('');\n\nexport function useStatement() {\n  const { send, isSending } = useClient();\n  const { apolloQuery } = useApolloQuery();\n  const { notify } = useFlashNotification();\n  const { formatNumber, getNumberFormatter, getPercentFractionDigits } =\n    useIntl();\n\n  const loadingStatements = ref(false);\n\n  async function saveStatement(\n    spaceId: string,\n    statement: {\n      about: string;\n      statement: string;\n      discourse: string;\n      network: string;\n      status: string;\n    }\n  ) {\n    const result = await send({ id: spaceId }, SET_STATEMENT_ACTION, statement);\n    if (!result.id) throw new Error('Error saving statement');\n\n    notify(['green', 'Statement saved successfully']);\n    return result;\n  }\n\n  async function loadStatements(spaceId: string, delegateIds: string[]) {\n    loadingStatements.value = true;\n\n    if (savedSpaceId.value !== spaceId) {\n      statements.value = {};\n      savedSpaceId.value = spaceId;\n    }\n\n    try {\n      const filteredDelegateIds = delegateIds.filter(\n        id => !statements.value[id]\n      );\n      if (!filteredDelegateIds.length) return;\n\n      const response: Statement[] = await apolloQuery(\n        {\n          query: STATEMENTS_QUERY,\n          variables: {\n            space: spaceId,\n            delegate_in: filteredDelegateIds\n          }\n        },\n        'statements'\n      );\n\n      if (!response) throw new Error('No statements found');\n\n      const newStatements = response.reduce(\n        (acc, statement) => {\n          acc[statement.delegate.toLowerCase()] = statement;\n          return acc;\n        },\n        {} as Record<string, Statement>\n      );\n      statements.value = { ...statements.value, ...newStatements };\n    } catch (e) {\n      console.error(e);\n    } finally {\n      loadingStatements.value = false;\n    }\n  }\n\n  async function reloadStatement(spaceId: string, id: string) {\n    id = id.toLowerCase();\n    delete statements.value[id];\n    await loadStatements(spaceId, [id]);\n  }\n\n  function getStatement(id: string): {\n    about: string;\n    statement: string;\n    network: string;\n    status: string;\n    discourse: string;\n  } {\n    const defaultStatement = {\n      about: '',\n      statement: '',\n      network: 's',\n      status: 'INACTIVE',\n      discourse: ''\n    };\n    return clone(statements.value?.[id?.toLowerCase()] || defaultStatement);\n  }\n\n  function formatPercentageNumber(value: string | number) {\n    const fractionDigits = getPercentFractionDigits(Number(value));\n    return formatNumber(\n      Number(value),\n      getNumberFormatter({\n        style: 'percent',\n        minimumFractionDigits: fractionDigits,\n        maximumFractionDigits: fractionDigits\n      }).value\n    );\n  }\n\n  return {\n    statements: computed(() => statements.value),\n    loadingStatements: computed(() => loadingStatements.value),\n    savingStatement: computed(() => isSending.value),\n    saveStatement,\n    loadStatements,\n    reloadStatement,\n    getStatement,\n    formatPercentageNumber\n  };\n}\n"
  },
  {
    "path": "src/composables/useStrategies.ts",
    "content": "/**\n * Orders strategies by spaces count and returns a list of strategies\n * filtered by the search string (case insensitive).\n */\n\nimport { STRATEGIES_QUERY, EXTENDED_STRATEGY_QUERY } from '@/helpers/queries';\nimport { Strategy } from '@/helpers/interfaces';\n\nconst strategies = ref<Strategy[]>([]);\nconst extendedStrategies = ref<Strategy[]>([]);\n\nexport function useStrategies() {\n  const isLoadingStrategies = ref(false);\n  const extendedStrategy = ref<Strategy | null>(null);\n  const loadingExtendedStrategy = ref(false);\n\n  const strategyDefinition = computed(() => {\n    if (extendedStrategy.value?.schema?.$ref) {\n      return extendedStrategy.value.schema.definitions.Strategy;\n    }\n    return false;\n  });\n\n  const filterStrategies = (q = '') =>\n    strategies.value.filter(s => s.id.toLowerCase().includes(q.toLowerCase()));\n\n  const { apolloQuery } = useApolloQuery();\n\n  // Get full list of strategies without about, schema and examples\n  async function getStrategies() {\n    if (strategies.value.length > 0) return;\n    isLoadingStrategies.value = true;\n    strategies.value = await apolloQuery(\n      {\n        query: STRATEGIES_QUERY\n      },\n      'strategies'\n    );\n\n    strategies.value = strategies.value.filter(\n      strategy => strategy.id !== 'pagination'\n    );\n\n    isLoadingStrategies.value = false;\n  }\n\n  // Get extended strategy by Id and save it in extendedStrategies\n  // don't load strategy if it's already loaded\n  async function getExtendedStrategy(id: string) {\n    if (extendedStrategies.value.some(st => st?.id === id)) {\n      extendedStrategy.value =\n        extendedStrategies.value.find(st => st?.id === id) ?? null;\n      return;\n    }\n    loadingExtendedStrategy.value = true;\n    const strategyObj = await apolloQuery(\n      {\n        query: EXTENDED_STRATEGY_QUERY,\n        variables: { id }\n      },\n      'strategy'\n    );\n\n    if (strategyObj) {\n      extendedStrategies.value.push(strategyObj);\n      extendedStrategy.value = strategyObj;\n    }\n\n    loadingExtendedStrategy.value = false;\n  }\n\n  return {\n    filterStrategies,\n    getStrategies,\n    getExtendedStrategy,\n    strategies,\n    isLoadingStrategies,\n    extendedStrategy,\n    strategyDefinition,\n    loadingExtendedStrategy\n  };\n}\n"
  },
  {
    "path": "src/composables/useTerms.ts",
    "content": "import { lsSet, lsGet } from '@/helpers/utils';\n\nexport function useTerms(spaceKey) {\n  const modalTermsOpen = ref(false);\n  const acceptedSpaces = ref(JSON.parse(lsGet('acceptedTerms', '[]')));\n  const termsAccepted = ref(acceptedSpaces.value.includes(spaceKey));\n\n  function acceptTerms() {\n    acceptedSpaces.value.push(spaceKey);\n    lsSet('acceptedTerms', JSON.stringify(acceptedSpaces.value));\n    termsAccepted.value = true;\n  }\n\n  return { modalTermsOpen, termsAccepted, acceptTerms };\n}\n"
  },
  {
    "path": "src/composables/useTreasury.ts",
    "content": "import snapshot from '@snapshot-labs/snapshot.js';\nimport { getTokenBalances, ETHER_CONTRACT } from '@/helpers/covalent';\nimport { TreasuryAsset } from '@/helpers/interfaces';\n\nconst TOKEN_LIST_URL = 'https://ipfs.io/ipns/tokens.uniswap.org';\n\nconst tokenListContractAddresses = ref<null | string[]>(null);\nconst treasuryAssets = ref<{ [key: string]: TreasuryAsset[] }>({});\n\nexport function useTreasury() {\n  async function loadTokenList() {\n    if (tokenListContractAddresses.value) return;\n    const response = await snapshot.utils.getJSON(TOKEN_LIST_URL);\n    tokenListContractAddresses.value = [\n      ETHER_CONTRACT,\n      ...response.tokens.map(token => token.address.toLowerCase())\n    ];\n  }\n\n  const loading = ref(false);\n\n  async function loadFilteredTokenBalances(address: string, chainId: string) {\n    loading.value = true;\n    try {\n      await loadTokenList();\n      if (!tokenListContractAddresses.value)\n        throw new Error('Token list not found');\n\n      if (treasuryAssets.value[address]) return;\n      const balances = await getTokenBalances(address, chainId)\n        .then(\n          balances =>\n            balances?.filter(\n              balance =>\n                tokenListContractAddresses.value?.includes(\n                  balance.contract_address\n                )\n            )\n        )\n        .catch(() => []);\n      if (balances) treasuryAssets.value[address] = balances;\n    } catch (error) {\n      console.error(error);\n    } finally {\n      loading.value = false;\n    }\n  }\n\n  function resetTreasuryAssets() {\n    treasuryAssets.value = {};\n  }\n\n  return {\n    loadFilteredTokenBalances,\n    resetTreasuryAssets,\n    treasuryAssets: computed(() => treasuryAssets.value),\n    loadingBalances: computed(() => loading.value)\n  };\n}\n"
  },
  {
    "path": "src/composables/useTxStatus.ts",
    "content": "import { useStorage } from '@vueuse/core';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\nimport { PendingTransaction } from '@/helpers/interfaces';\n\nconst PENDING_TRANSACTIONS_STORAGE_KEY = 'snapshot.pendingTransactions';\nconst pendingTransactions = useStorage(\n  PENDING_TRANSACTIONS_STORAGE_KEY,\n  [] as PendingTransaction[]\n);\n\nexport function useTxStatus() {\n  const { web3 } = useWeb3();\n\n  const pendingTransactionsWithHash = computed(() => {\n    return pendingTransactions.value.filter(tx => tx.hash);\n  });\n\n  const createPendingTransaction = (hash?: string) => {\n    const createdAt = Date.now();\n    const id = createdAt.toString();\n    const tx = {\n      id,\n      network: web3.value.network.key,\n      createdAt,\n      hash: hash ?? null\n    };\n    pendingTransactions.value.push(tx);\n    return id;\n  };\n\n  const updatePendingTransaction = (\n    id: string,\n    payload: Partial<PendingTransaction>\n  ) => {\n    const tx = pendingTransactions.value.find(tx => tx.id === id);\n    if (tx) {\n      Object.assign(tx, payload);\n    }\n  };\n\n  const removePendingTransaction = (id: string) => {\n    const tx = pendingTransactions.value.find(tx => tx.id === id);\n    if (tx) {\n      pendingTransactions.value = pendingTransactions.value.filter(\n        tx => tx.id !== id\n      );\n    }\n  };\n\n  const restorePendingTransactions = () => {\n    pendingTransactions.value.forEach(async tx => {\n      if (tx.hash) {\n        if (Date.now() > tx.createdAt + 1000 * 60)\n          return removePendingTransaction(tx.id);\n        try {\n          const broviderUrl = import.meta.env.VITE_BROVIDER_URL;\n          const provider = getProvider(tx.network, { broviderUrl });\n          await provider.waitForTransaction(tx.hash, 1, 1000 * 60 * 4);\n        } finally {\n          removePendingTransaction(tx.id);\n        }\n      } else {\n        if (Date.now() > tx.createdAt + 1000 * 10)\n          return removePendingTransaction(tx.id);\n      }\n    });\n  };\n\n  return {\n    pendingTransactions,\n    pendingTransactionsWithHash,\n    createPendingTransaction,\n    updatePendingTransaction,\n    removePendingTransaction,\n    restorePendingTransactions\n  };\n}\n"
  },
  {
    "path": "src/composables/useUnseenProposals.ts",
    "content": "import { subgraphRequest } from '@snapshot-labs/snapshot.js/src/utils';\nimport { useStorage } from '@vueuse/core';\n\ntype proposal = { id: string; created: number; space: { id: string } };\n\nconst proposals = ref<proposal[]>([]);\n\nconst lastSeenProposals = useStorage('lastSeenProposals', {});\n\nexport function useUnseenProposals() {\n  const { followingSpaces } = useFollowSpace();\n\n  async function getProposals() {\n    if (followingSpaces.value.length === 0) return;\n    try {\n      const activeProposals = await subgraphRequest(\n        `${import.meta.env.VITE_HUB_URL}/graphql`,\n        {\n          proposals: {\n            __args: {\n              first: 200,\n              where: {\n                space_in: followingSpaces.value\n              }\n            },\n            id: true,\n            created: true,\n            space: {\n              id: true\n            }\n          }\n        }\n      );\n      proposals.value = activeProposals.proposals;\n    } catch (e) {\n      console.log(e);\n    }\n  }\n\n  watch(followingSpaces, getProposals);\n\n  const { web3Account } = useWeb3();\n\n  function emitUpdateLastSeenProposal(spaceId: string) {\n    if (!web3Account.value || !spaceId) return;\n    lastSeenProposals.value[web3Account.value] =\n      lastSeenProposals.value[web3Account.value] || {};\n    lastSeenProposals.value[web3Account.value][spaceId] = new Date().getTime();\n  }\n\n  function spaceHasUnseenProposals(spaceId: string) {\n    return proposals.value.some(p => {\n      return (\n        p.space.id === spaceId &&\n        p.created >\n          (lastSeenProposals.value?.[web3Account.value]?.[spaceId] || 0)\n      );\n    });\n  }\n\n  return {\n    proposals,\n    spaceHasUnseenProposals,\n    emitUpdateLastSeenProposal\n  };\n}\n"
  },
  {
    "path": "src/composables/useUsername.ts",
    "content": "import { shorten } from '@/helpers/utils';\nimport { Profile } from '@/helpers/interfaces';\n\nexport function useUsername() {\n  const { web3Account } = useWeb3();\n\n  function getUsername(address: string, profile?: Profile) {\n    if (\n      web3Account?.value &&\n      address.toLowerCase() === web3Account.value.toLowerCase()\n    ) {\n      return 'You';\n    }\n\n    if (profile?.name) return profile.name;\n    if (profile?.ens) return profile.ens;\n    return shorten(address);\n  }\n\n  return { getUsername };\n}\n"
  },
  {
    "path": "src/composables/useWeb3.ts",
    "content": "import { Web3Provider } from '@ethersproject/providers';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { formatUnits } from '@ethersproject/units';\n\nlet auth;\nconst defaultNetwork: any =\n  import.meta.env.VITE_DEFAULT_NETWORK || Object.keys(networks)[0];\n\nconst state = reactive<{\n  account: string;\n  network: Record<string, any>;\n  authLoading: boolean;\n  walletConnectType: string | null;\n}>({\n  account: '',\n  network: networks[defaultNetwork],\n  authLoading: false,\n  walletConnectType: null\n});\n\nexport function useWeb3() {\n  async function login(connector = 'injected') {\n    auth = getInstance();\n    state.authLoading = true;\n    await auth.login(connector);\n    if (auth.provider.value) {\n      auth.web3 = new Web3Provider(auth.provider.value, 'any');\n      await loadProvider();\n    }\n    state.authLoading = false;\n  }\n\n  function logout() {\n    auth = getInstance();\n    auth.logout();\n    state.account = '';\n  }\n\n  async function loadProvider() {\n    try {\n      if (\n        auth.provider.value.removeAllListeners &&\n        !auth.provider.value.isTorus\n      )\n        auth.provider.value.removeAllListeners();\n      if (auth.provider.value.on) {\n        try {\n          auth.provider.value.on('chainChanged', async chainId => {\n            handleChainChanged(parseInt(formatUnits(chainId, 0)));\n          });\n          auth.provider.value.on('accountsChanged', async accounts => {\n            if (accounts.length !== 0) {\n              await login();\n            }\n          });\n        } catch (e) {\n          console.log(`failed to subscribe to events for provider: ${e}`);\n        }\n      }\n      console.log('Provider', auth.provider.value);\n      let network, accounts;\n      try {\n        const connector = auth.provider.value?.connectorName;\n        if (connector === 'gnosis') {\n          const { chainId: safeChainId, safeAddress } = auth.web3.provider.safe;\n          network = { chainId: safeChainId };\n          accounts = [safeAddress];\n        } else {\n          [network, accounts] = await Promise.all([\n            auth.web3.getNetwork(),\n            auth.web3.listAccounts()\n          ]);\n        }\n      } catch (e) {\n        console.log(e);\n      }\n      console.log('Network', network);\n      console.log('Accounts', accounts);\n      handleChainChanged(network.chainId);\n      const acc = accounts.length > 0 ? accounts[0] : null;\n\n      state.account = acc;\n      state.walletConnectType = auth.provider.value?.wc?.peerMeta?.name || null;\n    } catch (e) {\n      state.account = '';\n      return Promise.reject(e);\n    }\n  }\n\n  function handleChainChanged(chainId) {\n    if (!networks[chainId]) {\n      networks[chainId] = {\n        ...networks[defaultNetwork],\n        chainId,\n        name: 'Unknown',\n        network: 'unknown',\n        unknown: true\n      };\n    }\n    state.network = networks[chainId];\n  }\n\n  return {\n    login,\n    logout,\n    loadProvider,\n    handleChainChanged,\n    web3: computed(() => state),\n    web3Account: computed(() => state.account)\n  };\n}\n"
  },
  {
    "path": "src/env.d.ts",
    "content": "interface ImportMetaEnv {\n  readonly VITE_APP_TITLE: string;\n  readonly VITE_HUB_URL: string;\n  readonly VITE_RELAYER_URL: string;\n  readonly VITE_SCORES_URL: string;\n  readonly VITE_IPFS_GATEWAY: string;\n  readonly VITE_DEFAULT_NETWORK: string;\n  readonly VITE_PUSHER_BEAMS_INSTANCE_ID: string;\n  readonly VITE_BROVIDER_URL: string;\n}\n"
  },
  {
    "path": "src/helpers/alchemy/index.ts",
    "content": "import { ETH_CONTRACT } from '@/helpers/constants';\nimport {\n  GetTokenBalancesResponse,\n  GetTokensMetadataResponse,\n  GetBalancesResponse\n} from './types';\nexport * from './types';\n\nconst apiKey = import.meta.env.VITE_ALCHEMY_API_KEY;\n\nconst NETWORKS = {\n  1: 'eth-mainnet',\n  11155111: 'eth-sepolia',\n  137: 'polygon-mainnet',\n  42161: 'arb-mainnet',\n  8453: 'base-mainnet'\n};\n\nfunction getApiUrl(networkId: number) {\n  const network = NETWORKS[networkId] ?? 'mainnet';\n\n  return `https://${network}.g.alchemy.com/v2/${apiKey}`;\n}\n\nexport async function request(\n  method: string,\n  params: any[],\n  networkId: number\n) {\n  const res = await fetch(getApiUrl(networkId), {\n    method: 'POST',\n    body: JSON.stringify({\n      id: 1,\n      jsonrpc: '2.0',\n      method,\n      params\n    })\n  });\n\n  const { result } = await res.json();\n\n  return result;\n}\n\nexport async function batchRequest(\n  requests: { method: string; params: any[] }[],\n  networkId: number\n) {\n  const res = await fetch(getApiUrl(networkId), {\n    method: 'POST',\n    body: JSON.stringify(\n      requests.map((request, i) => ({\n        id: i,\n        jsonrpc: '2.0',\n        method: request.method,\n        params: request.params\n      }))\n    )\n  });\n\n  const response = await res.json();\n\n  return response.map(entry => entry.result);\n}\n\n/**\n * Gets Ethereum balance as hex encoded string.\n * @param address Ethereum address to fetch ETH balance for\n * @param networkId Network ID\n * @returns Hex encoded ETH balance\n */\nexport async function getBalance(\n  address: string,\n  networkId: number\n): Promise<string> {\n  return request('eth_getBalance', [address], networkId);\n}\n\n/**\n * Gets ERC20 balances of tokens that provided address interacted with.\n * Response might include 0 balances.\n * @param address Ethereum address to fetch token balances for\n * @param networkId Network ID\n * @returns Token balances\n */\nexport async function getTokenBalances(\n  address: string,\n  networkId: number\n): Promise<GetTokenBalancesResponse> {\n  return request('alchemy_getTokenBalances', [address], networkId);\n}\n\n/**\n * Gets ERC20 tokens metadata (name, symbol, decimals, logo).\n * @param addresses Array of ERC20 tokens addresses\n * @param networkId Network ID\n * @returns Array of token metadata\n */\nexport async function getTokensMetadata(\n  addresses: string[],\n  networkId: number\n): Promise<GetTokensMetadataResponse> {\n  return batchRequest(\n    addresses.map(address => ({\n      method: 'alchemy_getTokenMetadata',\n      params: [address]\n    })),\n    networkId\n  );\n}\n\n/**\n * Gets Ethereum and ERC20 balances including metadata for tokens.\n * @param address Ethereum address to fetch balances for\n * @param networkId Network ID\n * @returns Array of balances\n */\nexport async function getBalances(\n  address: string,\n  networkId: number,\n  baseToken: { name: string; symbol: string; logo?: string }\n): Promise<GetBalancesResponse> {\n  if (!address) {\n    return [\n      {\n        name: baseToken.name,\n        symbol: baseToken.symbol,\n        decimals: 18,\n        contractAddress: ETH_CONTRACT,\n        tokenBalance: '0x0',\n        price: 0,\n        value: 0,\n        change: 0\n      }\n    ];\n  }\n\n  const [ethBalance, { tokenBalances }] = await Promise.all([\n    getBalance(address, networkId),\n    getTokenBalances(address, networkId)\n  ]);\n\n  const contractAddresses = tokenBalances.map(\n    balance => balance.contractAddress\n  );\n  const metadata = await getTokensMetadata(contractAddresses, networkId);\n\n  return [\n    {\n      name: baseToken.name,\n      symbol: baseToken.symbol,\n      decimals: 18,\n      contractAddress: ETH_CONTRACT,\n      tokenBalance: ethBalance,\n      price: 0,\n      value: 0,\n      change: 0\n    },\n    ...tokenBalances\n      .map((balance, i) => ({\n        ...balance,\n        ...metadata[i],\n        price: 0,\n        value: 0,\n        change: 0\n      }))\n      .filter(token => !token.symbol?.includes('.'))\n  ];\n}\n"
  },
  {
    "path": "src/helpers/alchemy/types.ts",
    "content": "export type BalanceData = { contractAddress: string; tokenBalance: string };\nexport type Metadata = {\n  decimals: number;\n  name: string;\n  symbol: string;\n};\nexport type Token = BalanceData &\n  Metadata & {\n    price: number;\n    value: number;\n    change: number;\n  };\n\nexport type GetTokenBalancesResponse = {\n  address: string;\n  tokenBalances: BalanceData[];\n};\n\nexport type GetTokensMetadataResponse = Metadata[];\n\nexport type GetBalancesResponse = Token[];\n"
  },
  {
    "path": "src/helpers/apollo.ts",
    "content": "import gql from 'graphql-tag';\nimport {\n  ApolloClient,\n  createHttpLink,\n  InMemoryCache\n} from '@apollo/client/core';\n\n// HTTP connection to the API\nconst httpLink = createHttpLink({\n  // You should use an absolute URL here\n  uri: `${import.meta.env.VITE_HUB_URL}/graphql`\n});\n\n// Create the apollo client\nexport const apolloClient = new ApolloClient({\n  link: httpLink,\n  cache: new InMemoryCache({\n    addTypename: false\n  }),\n  defaultOptions: {\n    query: {\n      fetchPolicy: 'no-cache'\n    }\n  },\n  typeDefs: gql`\n    enum OrderDirection {\n      asc\n      desc\n    }\n  `\n});\n"
  },
  {
    "path": "src/helpers/auth.ts",
    "content": "import injected from '@snapshot-labs/lock/connectors/injected';\nimport walletconnect from '@snapshot-labs/lock/connectors/walletconnect';\nimport portis from '@snapshot-labs/lock/connectors/portis';\nimport connectors from '@/helpers/connectors';\nimport walletlink from '@snapshot-labs/lock/connectors/walletlink';\nimport gnosis from '@snapshot-labs/lock/connectors/gnosis';\nimport stargazer from '@snapshot-labs/lock/connectors/stargazer';\nimport kaikas from '@snapshot-labs/lock/connectors/kaikas';\n\nconst options: any = { connectors: [] };\nconst lockConnectors = {\n  injected,\n  walletconnect,\n  walletlink,\n  portis,\n  stargazer,\n  gnosis,\n  kaikas\n};\n\nObject.entries(connectors).forEach(([connectorName, params]: [string, any]) => {\n  options.connectors.push({\n    key: connectorName,\n    connector: lockConnectors[connectorName],\n    options: params.options\n  });\n});\n\nexport default options;\n"
  },
  {
    "path": "src/helpers/b64.ts",
    "content": "/// URL-safe Base64 encoding and decoding.\n\nconst B64U_LOOKUP = {\n  '/': '_',\n  _: '/',\n  '+': '-',\n  '-': '+',\n  '=': '.',\n  '.': '='\n};\n\nexport const encode = str =>\n  btoa(str).replace(/(\\+|\\/|=)/g, m => B64U_LOOKUP[m]);\n\nexport const decode = str =>\n  atob(str.replace(/(-|_|\\.)/g, m => B64U_LOOKUP[m]));\n\nexport const encodeJson = json => encode(JSON.stringify(json));\nexport const decodeJson = str => JSON.parse(decode(str));\n"
  },
  {
    "path": "src/helpers/beams.ts",
    "content": "import * as PusherPushNotifications from '@pusher/push-notifications-web';\n\nlet beams: any;\n\ntry {\n  beams = new PusherPushNotifications.Client({\n    instanceId: (import.meta.env.VITE_PUSHER_BEAMS_INSTANCE_ID as string) ?? ''\n  });\n} catch (e) {\n  console.log(e);\n}\n\nexport { beams };\n"
  },
  {
    "path": "src/helpers/boost/abi.json",
    "content": "[\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"_protocolOwner\",\n        \"type\": \"address\"\n      },\n      { \"internalType\": \"string\", \"name\": \"name\", \"type\": \"string\" },\n      { \"internalType\": \"string\", \"name\": \"symbol\", \"type\": \"string\" },\n      { \"internalType\": \"string\", \"name\": \"version\", \"type\": \"string\" },\n      { \"internalType\": \"uint256\", \"name\": \"_ethFee\", \"type\": \"uint256\" },\n      { \"internalType\": \"uint256\", \"name\": \"_tokenFee\", \"type\": \"uint256\" }\n    ],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"constructor\"\n  },\n  { \"inputs\": [], \"name\": \"BoostDepositRequired\", \"type\": \"error\" },\n  { \"inputs\": [], \"name\": \"BoostDoesNotExist\", \"type\": \"error\" },\n  { \"inputs\": [], \"name\": \"BoostEndDateBeforeStart\", \"type\": \"error\" },\n  { \"inputs\": [], \"name\": \"BoostEndDateInPast\", \"type\": \"error\" },\n  { \"inputs\": [], \"name\": \"BoostEnded\", \"type\": \"error\" },\n  {\n    \"inputs\": [{ \"internalType\": \"uint256\", \"name\": \"end\", \"type\": \"uint256\" }],\n    \"name\": \"BoostNotEnded\",\n    \"type\": \"error\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"uint256\", \"name\": \"start\", \"type\": \"uint256\" }\n    ],\n    \"name\": \"BoostNotStarted\",\n    \"type\": \"error\"\n  },\n  { \"inputs\": [], \"name\": \"ClaimingPeriodStarted\", \"type\": \"error\" },\n  { \"inputs\": [], \"name\": \"InsufficientBoostBalance\", \"type\": \"error\" },\n  { \"inputs\": [], \"name\": \"InsufficientEthFee\", \"type\": \"error\" },\n  { \"inputs\": [], \"name\": \"InvalidGuard\", \"type\": \"error\" },\n  { \"inputs\": [], \"name\": \"InvalidRecipient\", \"type\": \"error\" },\n  { \"inputs\": [], \"name\": \"InvalidSignature\", \"type\": \"error\" },\n  { \"inputs\": [], \"name\": \"InvalidTokenFee\", \"type\": \"error\" },\n  { \"inputs\": [], \"name\": \"OnlyBoostOwner\", \"type\": \"error\" },\n  { \"inputs\": [], \"name\": \"RecipientAlreadyClaimed\", \"type\": \"error\" },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"owner\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"approved\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"Approval\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"owner\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"operator\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": false,\n        \"internalType\": \"bool\",\n        \"name\": \"approved\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"name\": \"ApprovalForAll\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": false,\n        \"internalType\": \"uint256\",\n        \"name\": \"boostId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"Burn\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"components\": [\n          { \"internalType\": \"uint256\", \"name\": \"boostId\", \"type\": \"uint256\" },\n          { \"internalType\": \"address\", \"name\": \"recipient\", \"type\": \"address\" },\n          { \"internalType\": \"uint256\", \"name\": \"amount\", \"type\": \"uint256\" }\n        ],\n        \"indexed\": false,\n        \"internalType\": \"struct IBoost.ClaimConfig\",\n        \"name\": \"claim\",\n        \"type\": \"tuple\"\n      }\n    ],\n    \"name\": \"Claim\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": false,\n        \"internalType\": \"uint256\",\n        \"name\": \"boostId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"indexed\": false,\n        \"internalType\": \"address\",\n        \"name\": \"sender\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": false,\n        \"internalType\": \"uint256\",\n        \"name\": \"amount\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"Deposit\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": false,\n        \"internalType\": \"uint256\",\n        \"name\": \"ethFee\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"EthFeeSet\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": false,\n        \"internalType\": \"address\",\n        \"name\": \"recipient\",\n        \"type\": \"address\"\n      }\n    ],\n    \"name\": \"EthFeesCollected\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": false,\n        \"internalType\": \"uint256\",\n        \"name\": \"boostId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"indexed\": false,\n        \"internalType\": \"address\",\n        \"name\": \"owner\",\n        \"type\": \"address\"\n      },\n      {\n        \"components\": [\n          {\n            \"internalType\": \"contract IERC20\",\n            \"name\": \"token\",\n            \"type\": \"address\"\n          },\n          { \"internalType\": \"uint256\", \"name\": \"balance\", \"type\": \"uint256\" },\n          { \"internalType\": \"address\", \"name\": \"guard\", \"type\": \"address\" },\n          { \"internalType\": \"uint48\", \"name\": \"start\", \"type\": \"uint48\" },\n          { \"internalType\": \"uint48\", \"name\": \"end\", \"type\": \"uint48\" }\n        ],\n        \"indexed\": false,\n        \"internalType\": \"struct IBoost.BoostConfig\",\n        \"name\": \"boost\",\n        \"type\": \"tuple\"\n      },\n      {\n        \"indexed\": false,\n        \"internalType\": \"string\",\n        \"name\": \"strategyURI\",\n        \"type\": \"string\"\n      }\n    ],\n    \"name\": \"Mint\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"previousOwner\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"newOwner\",\n        \"type\": \"address\"\n      }\n    ],\n    \"name\": \"OwnershipTransferred\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": false,\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenFee\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"TokenFeeSet\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": false,\n        \"internalType\": \"contract IERC20\",\n        \"name\": \"token\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": false,\n        \"internalType\": \"address\",\n        \"name\": \"recipient\",\n        \"type\": \"address\"\n      }\n    ],\n    \"name\": \"TokenFeesCollected\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"from\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"Transfer\",\n    \"type\": \"event\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"address\", \"name\": \"to\", \"type\": \"address\" },\n      { \"internalType\": \"uint256\", \"name\": \"tokenId\", \"type\": \"uint256\" }\n    ],\n    \"name\": \"approve\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"address\", \"name\": \"owner\", \"type\": \"address\" }\n    ],\n    \"name\": \"balanceOf\",\n    \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [{ \"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\" }],\n    \"name\": \"boosts\",\n    \"outputs\": [\n      { \"internalType\": \"contract IERC20\", \"name\": \"token\", \"type\": \"address\" },\n      { \"internalType\": \"uint256\", \"name\": \"balance\", \"type\": \"uint256\" },\n      { \"internalType\": \"address\", \"name\": \"guard\", \"type\": \"address\" },\n      { \"internalType\": \"uint48\", \"name\": \"start\", \"type\": \"uint48\" },\n      { \"internalType\": \"uint48\", \"name\": \"end\", \"type\": \"uint48\" }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"components\": [\n          { \"internalType\": \"uint256\", \"name\": \"boostId\", \"type\": \"uint256\" },\n          { \"internalType\": \"address\", \"name\": \"recipient\", \"type\": \"address\" },\n          { \"internalType\": \"uint256\", \"name\": \"amount\", \"type\": \"uint256\" }\n        ],\n        \"internalType\": \"struct IBoost.ClaimConfig\",\n        \"name\": \"_claimConfig\",\n        \"type\": \"tuple\"\n      },\n      { \"internalType\": \"bytes\", \"name\": \"_signature\", \"type\": \"bytes\" }\n    ],\n    \"name\": \"claim\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"components\": [\n          { \"internalType\": \"uint256\", \"name\": \"boostId\", \"type\": \"uint256\" },\n          { \"internalType\": \"address\", \"name\": \"recipient\", \"type\": \"address\" },\n          { \"internalType\": \"uint256\", \"name\": \"amount\", \"type\": \"uint256\" }\n        ],\n        \"internalType\": \"struct IBoost.ClaimConfig[]\",\n        \"name\": \"_claimConfigs\",\n        \"type\": \"tuple[]\"\n      },\n      { \"internalType\": \"bytes[]\", \"name\": \"_signatures\", \"type\": \"bytes[]\" }\n    ],\n    \"name\": \"claimMultiple\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\" },\n      { \"internalType\": \"address\", \"name\": \"\", \"type\": \"address\" }\n    ],\n    \"name\": \"claimed\",\n    \"outputs\": [{ \"internalType\": \"bool\", \"name\": \"\", \"type\": \"bool\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"address\", \"name\": \"_recipient\", \"type\": \"address\" }\n    ],\n    \"name\": \"collectEthFees\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"contract IERC20\",\n        \"name\": \"_token\",\n        \"type\": \"address\"\n      },\n      { \"internalType\": \"address\", \"name\": \"_recipient\", \"type\": \"address\" }\n    ],\n    \"name\": \"collectTokenFees\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"uint256\", \"name\": \"_boostId\", \"type\": \"uint256\" },\n      { \"internalType\": \"uint256\", \"name\": \"_amount\", \"type\": \"uint256\" }\n    ],\n    \"name\": \"deposit\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"ethFee\",\n    \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"uint256\", \"name\": \"tokenId\", \"type\": \"uint256\" }\n    ],\n    \"name\": \"getApproved\",\n    \"outputs\": [{ \"internalType\": \"address\", \"name\": \"\", \"type\": \"address\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"address\", \"name\": \"owner\", \"type\": \"address\" },\n      { \"internalType\": \"address\", \"name\": \"operator\", \"type\": \"address\" }\n    ],\n    \"name\": \"isApprovedForAll\",\n    \"outputs\": [{ \"internalType\": \"bool\", \"name\": \"\", \"type\": \"bool\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"string\", \"name\": \"_strategyURI\", \"type\": \"string\" },\n      {\n        \"internalType\": \"contract IERC20\",\n        \"name\": \"_token\",\n        \"type\": \"address\"\n      },\n      { \"internalType\": \"uint256\", \"name\": \"_amount\", \"type\": \"uint256\" },\n      { \"internalType\": \"address\", \"name\": \"_owner\", \"type\": \"address\" },\n      { \"internalType\": \"address\", \"name\": \"_guard\", \"type\": \"address\" },\n      { \"internalType\": \"uint48\", \"name\": \"_start\", \"type\": \"uint48\" },\n      { \"internalType\": \"uint48\", \"name\": \"_end\", \"type\": \"uint48\" }\n    ],\n    \"name\": \"mint\",\n    \"outputs\": [],\n    \"stateMutability\": \"payable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"name\",\n    \"outputs\": [{ \"internalType\": \"string\", \"name\": \"\", \"type\": \"string\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"nextBoostId\",\n    \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"owner\",\n    \"outputs\": [{ \"internalType\": \"address\", \"name\": \"\", \"type\": \"address\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"uint256\", \"name\": \"tokenId\", \"type\": \"uint256\" }\n    ],\n    \"name\": \"ownerOf\",\n    \"outputs\": [{ \"internalType\": \"address\", \"name\": \"\", \"type\": \"address\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"renounceOwnership\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"address\", \"name\": \"from\", \"type\": \"address\" },\n      { \"internalType\": \"address\", \"name\": \"to\", \"type\": \"address\" },\n      { \"internalType\": \"uint256\", \"name\": \"tokenId\", \"type\": \"uint256\" }\n    ],\n    \"name\": \"safeTransferFrom\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"address\", \"name\": \"from\", \"type\": \"address\" },\n      { \"internalType\": \"address\", \"name\": \"to\", \"type\": \"address\" },\n      { \"internalType\": \"uint256\", \"name\": \"tokenId\", \"type\": \"uint256\" },\n      { \"internalType\": \"bytes\", \"name\": \"data\", \"type\": \"bytes\" }\n    ],\n    \"name\": \"safeTransferFrom\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"address\", \"name\": \"operator\", \"type\": \"address\" },\n      { \"internalType\": \"bool\", \"name\": \"approved\", \"type\": \"bool\" }\n    ],\n    \"name\": \"setApprovalForAll\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"uint256\", \"name\": \"_ethFee\", \"type\": \"uint256\" }\n    ],\n    \"name\": \"setEthFee\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"uint256\", \"name\": \"_tokenFee\", \"type\": \"uint256\" }\n    ],\n    \"name\": \"setTokenFee\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"bytes4\", \"name\": \"interfaceId\", \"type\": \"bytes4\" }\n    ],\n    \"name\": \"supportsInterface\",\n    \"outputs\": [{ \"internalType\": \"bool\", \"name\": \"\", \"type\": \"bool\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"symbol\",\n    \"outputs\": [{ \"internalType\": \"string\", \"name\": \"\", \"type\": \"string\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"tokenFee\",\n    \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [{ \"internalType\": \"address\", \"name\": \"\", \"type\": \"address\" }],\n    \"name\": \"tokenFeeBalances\",\n    \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"uint256\", \"name\": \"tokenId\", \"type\": \"uint256\" }\n    ],\n    \"name\": \"tokenURI\",\n    \"outputs\": [{ \"internalType\": \"string\", \"name\": \"\", \"type\": \"string\" }],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"address\", \"name\": \"from\", \"type\": \"address\" },\n      { \"internalType\": \"address\", \"name\": \"to\", \"type\": \"address\" },\n      { \"internalType\": \"uint256\", \"name\": \"tokenId\", \"type\": \"uint256\" }\n    ],\n    \"name\": \"transferFrom\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"address\", \"name\": \"newOwner\", \"type\": \"address\" }\n    ],\n    \"name\": \"transferOwnership\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      { \"internalType\": \"uint256\", \"name\": \"_boostId\", \"type\": \"uint256\" },\n      { \"internalType\": \"address\", \"name\": \"_to\", \"type\": \"address\" }\n    ],\n    \"name\": \"withdrawAndBurn\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  }\n]\n"
  },
  {
    "path": "src/helpers/boost/api.ts",
    "content": "import {\n  BoostRewardGuard,\n  BoostVoucherGuard,\n  BoostWinnersGuard,\n  BoostSubgraph\n} from '@/helpers/boost/types';\n\nconst GUARD_URL = 'https://boost.snapshot.org';\n\nexport async function getRewards(\n  proposal_id: string,\n  voter_address: string,\n  boosts: BoostSubgraph[]\n): Promise<BoostRewardGuard[]> {\n  const results = await fetch(`${GUARD_URL}/get-rewards`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify({\n      proposal_id,\n      voter_address,\n      boosts: boosts.map(boost => [boost.id, boost.chainId])\n    })\n  });\n\n  if (results.status !== 200) throw new Error('Error fetching rewards');\n  return results.json();\n}\n\nexport async function getVouchers(\n  proposal_id: string,\n  voter_address: string,\n  boosts: BoostSubgraph[]\n): Promise<BoostVoucherGuard[]> {\n  const results = await fetch(`${GUARD_URL}/create-vouchers`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify({\n      proposal_id,\n      voter_address,\n      boosts: boosts.map(boost => [boost.id, boost.chainId])\n    })\n  });\n\n  if (results.status !== 200) throw new Error('Error fetching vouchers');\n  return results.json();\n}\n\nexport async function getWinners(\n  proposal_id: string,\n  boost_id: string,\n  chain_id: string\n): Promise<BoostWinnersGuard> {\n  const results = await fetch(`${GUARD_URL}/get-lottery-winners`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify({\n      proposal_id,\n      boost_id,\n      chain_id\n    })\n  });\n\n  if (results.status !== 200) {\n    const text = await results.text();\n    throw new Error(text ? text : 'Error fetching winners');\n  }\n  return results.json();\n}\n"
  },
  {
    "path": "src/helpers/boost/index.ts",
    "content": "import { Web3Provider } from '@ethersproject/providers';\nimport { Contract } from '@ethersproject/contracts';\nimport { parseEther } from '@ethersproject/units';\nimport { pin } from '@snapshot-labs/pineapple';\nimport { BoostStrategy } from '@/helpers/boost/types';\nimport ABI from './abi.json';\n\nexport const BOOST_VERSION = '0.0.1';\n\nexport const BOOST_CONTRACTS = {\n  '1': '0x8E8913197114c911F13cfBfCBBD138C1DC74B964',\n  '11155111': '0x8E8913197114c911F13cfBfCBBD138C1DC74B964',\n  '137': '0x8E8913197114c911F13cfBfCBBD138C1DC74B964',\n  '8453': '0x8E8913197114c911F13cfBfCBBD138C1DC74B964'\n};\n\nexport const SUPPORTED_NETWORKS = Object.keys(BOOST_CONTRACTS);\n\nexport const SNAPSHOT_GUARD_ADDRESS =\n  '0x064417ab192edC00E791d5911ecDbb7c9a718383';\n\nexport async function createBoost(\n  web3: Web3Provider,\n  networkId: string,\n  ethFee: string,\n  params: {\n    strategyURI: string;\n    token: string;\n    amount: string;\n    owner: string;\n    guard: string;\n    start: number;\n    end: number;\n  }\n): Promise<any> {\n  const { strategyURI, token, amount, guard, start, end, owner } = params;\n  const signer = web3.getSigner();\n  const contract = new Contract(BOOST_CONTRACTS[networkId], ABI, signer);\n  const options = { value: parseEther(ethFee) };\n  return await contract.mint(\n    strategyURI,\n    token,\n    amount,\n    owner,\n    guard,\n    start,\n    end,\n    options\n  );\n}\n\nexport async function claimTokens(\n  web3: Web3Provider,\n  networkId: string,\n  boost: {\n    boostId: string;\n    recipient: string;\n    amount: string;\n  },\n  signature: string\n): Promise<any> {\n  const { boostId, recipient, amount } = boost;\n  const signer = web3.getSigner();\n  const contract = new Contract(BOOST_CONTRACTS[networkId], ABI, signer);\n  return await contract.claim([boostId, recipient, amount], signature);\n}\n\nexport async function claimAllTokens(\n  web3: Web3Provider,\n  networkId: string,\n  boosts: {\n    boostId: string;\n    recipient: string;\n    amount: string;\n  }[],\n  signatures: string[]\n): Promise<any> {\n  const boostsArray = boosts.map(boost => [\n    boost.boostId,\n    boost.recipient,\n    boost.amount\n  ]);\n  const signer = web3.getSigner();\n  const contract = new Contract(BOOST_CONTRACTS[networkId], ABI, signer);\n  return await contract.claimMultiple(boostsArray, signatures);\n}\n\nexport async function getStrategyURI(strategy: BoostStrategy) {\n  const { cid } = await pin(strategy);\n  return `ipfs://${cid}`;\n}\n\nexport async function withdrawAndBurn(\n  web3: Web3Provider,\n  networkId: string,\n  boostId: string,\n  to: string\n): Promise<any> {\n  const signer = web3.getSigner();\n  const contract = new Contract(BOOST_CONTRACTS[networkId], ABI, signer);\n  return await contract.withdrawAndBurn(boostId, to);\n}\n\nexport async function getFees(web3: Web3Provider, networkId: string) {\n  const contract = new Contract(BOOST_CONTRACTS[networkId], ABI, web3);\n  const ethFee = await contract.ethFee();\n  const tokenFeePercent = await contract.tokenFee();\n  return { ethFee, tokenFeePercent };\n}\n"
  },
  {
    "path": "src/helpers/boost/subgraph.ts",
    "content": "import { subgraphRequest } from '@snapshot-labs/snapshot.js/src/utils';\nimport { SUPPORTED_NETWORKS } from '@/helpers/boost';\nimport { BoostSubgraph } from '@/helpers/boost/types';\n\nconst SUBGRAPH_URLS = {\n  '1': 'https://subgrapher.snapshot.org/subgraph/arbitrum/A6EEuSAB7mFrWvLBnL1HZXwfiGfqFYnFJjc14REtMNkd',\n  '11155111':\n    'https://subgrapher.snapshot.org/subgraph/arbitrum/6T64qrPe7S46zhArSoBF8CAmc5cG3PyKa92Nt4Jhymcy',\n  '137':\n    'https://subgrapher.snapshot.org/subgraph/arbitrum/CkNpf5gY7XPCinJWP1nh8K7u6faXwDjchGGV4P9rgJ7',\n  '8453':\n    'https://subgrapher.snapshot.org/subgraph/arbitrum/52uVpyUHkkMFieRk1khbdshUw26CNHWAEuqLojZzcyjd'\n};\n\nexport async function getClaims(recipient: string) {\n  async function query(chainId: string) {\n    const data = await subgraphRequest(SUBGRAPH_URLS[chainId], {\n      claims: {\n        __args: {\n          where: {\n            recipient\n          }\n        },\n        id: true,\n        amount: true,\n        transactionHash: true,\n        boost: {\n          id: true\n        }\n      }\n    });\n\n    if (data?.claims) {\n      data.claims = data.claims.map(claim => ({\n        ...claim,\n        chainId\n      }));\n    }\n    return data;\n  }\n\n  const requests = SUPPORTED_NETWORKS.map(chainId => query(chainId));\n  const responses = await Promise.all(requests);\n\n  return responses.map(response => response.claims).flat();\n}\n\nexport async function getBoosts(proposalIds: string[]) {\n  async function query(chainId: string) {\n    const data = await subgraphRequest(SUBGRAPH_URLS[chainId], {\n      boosts: {\n        __args: {\n          where: { strategy_: { proposal_in: proposalIds } }\n        },\n        id: true,\n        strategyURI: true,\n        poolSize: true,\n        guard: true,\n        start: true,\n        end: true,\n        owner: true,\n        currentBalance: true,\n        transaction: true,\n        token: {\n          id: true,\n          name: true,\n          symbol: true,\n          decimals: true\n        },\n        strategy: {\n          id: true,\n          name: true,\n          version: true,\n          proposal: true,\n          eligibility: {\n            type: true,\n            choice: true\n          },\n          distribution: {\n            type: true,\n            limit: true,\n            numWinners: true\n          }\n        }\n      }\n    });\n    if (data?.boosts) {\n      data.boosts = data.boosts.map(boost => ({\n        ...boost,\n        chainId\n      }));\n    }\n    return data;\n  }\n  const requests = SUPPORTED_NETWORKS.map(chainId => query(chainId));\n  const responses: { boosts: BoostSubgraph }[] = await Promise.all(requests);\n\n  return responses\n    .map(response => response.boosts)\n    .filter(boost => boost)\n    .flat();\n}\n"
  },
  {
    "path": "src/helpers/boost/tokens.ts",
    "content": "export const EXCLUDED_TOKENS = [\n  {\n    symbol: 'default',\n    '1': '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',\n    '11155111': '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',\n    '137': '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',\n    '8453': '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'\n  },\n  {\n    symbol: 'USDT',\n    '1': '0xdac17f958d2ee523a2206206994597c13d831ec7',\n    '137': '0xc2132d05d31c914a87c6611c10748aeb04b58e8f'\n  },\n  {\n    symbol: 'USDC',\n    '1': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',\n    '137': '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359'\n  },\n  {\n    symbol: 'DAI',\n    '1': '0x6b175474e89094c44da98b954eedeac495271d0f',\n    '137': '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063'\n  },\n  {\n    symbol: 'FDUSD',\n    '1': '0xc5f0f7b66764f6ec8c8dff7ba683102295e16409',\n    '137': ''\n  },\n  {\n    symbol: 'TUSD',\n    '1': '0x0000000000085d4780b73119b644ae5ecd22b376',\n    '137': ''\n  },\n  {\n    symbol: 'FRAX',\n    '1': '0x853d955acef822db058eb8505911ed77f175b99e',\n    '137': '0x45c32fa6df82ead1e2ef74d17b76547eddfaff89'\n  },\n  {\n    symbol: 'GUSD',\n    '1': '0x056fd409e1d7a124bd7017459dfea2f387b6d5cd',\n    '137': ''\n  },\n  {\n    symbol: 'PYUSD',\n    '1': '0x6c3ea9036406852006290770bedfcaba0e23a0e8',\n    '137': ''\n  },\n  {\n    symbol: 'sUSD',\n    '1': '0x57ab1ec28d129707052df4df418d58a2d46d5f51',\n    '137': ''\n  },\n  {\n    symbol: 'USDP',\n    '1': '0x8e870d67f660d95d5be530380d0ec0bd388289e1',\n    '137': ''\n  },\n  {\n    symbol: 'LUSD',\n    '1': '0x5f98805a4e8be255a32880fdec7f6728c6568ba0',\n    '137': '0x23001f892c0c82b79303edc9b9033cd190bb21c7'\n  },\n  {\n    symbol: 'WBTC',\n    '1': '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599',\n    '137': '0x1bfd67037b42cf73acF2047067bd4F2C47D9BfD6'\n  },\n\n  {\n    symbol: 'WETH',\n    '1': '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',\n    '11155111': '0x7b79995e5f793a07bc00c21412e50ecae098e7f9',\n    '137': '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619'\n  },\n  {\n    symbol: 'STETH',\n    '1': '0xae7ab96520de3a18e5e111b5eaab095312d7fe84',\n    '137': ''\n  },\n  {\n    symbol: 'WSTETH',\n    '1': '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0',\n    '137': '0x03b54a6e9a984069379fae1a4fc4dbae93b3bccd'\n  },\n  {\n    symbol: 'CBETH',\n    '1': '0xbe9895146f7af43049ca1c1ae358b0541ea49704',\n    '137': '0x4b4327db1600b8b1440163f667e199cef35385f5'\n  },\n  {\n    symbol: 'ANKRETH',\n    '1': '0xe95a203b1a91a908f9b9ce46459d101078c2c3cb',\n    '137': ''\n  },\n  {\n    symbol: 'OSETH',\n    '1': '0xf1c9acdc66974dfb6decb12aa385b9cd01190e38',\n    '137': ''\n  }\n];\n\nexport function isExcludedToken(chainId: string, contractAddress: string) {\n  return (\n    EXCLUDED_TOKENS.find(token => token?.[chainId] === contractAddress) !==\n    undefined\n  );\n}\n"
  },
  {
    "path": "src/helpers/boost/types.ts",
    "content": "export type BoostClaimSubgraph = {\n  id: string;\n  amount: string;\n  transactionHash: string;\n  chainId: string;\n  boost: {\n    id: string;\n  };\n};\n\nexport type BoostRewardGuard = {\n  boost_id: string;\n  chain_id: string;\n  reward: string;\n};\n\nexport type BoostVoucherGuard = {\n  boost_id: string;\n  chain_id: string;\n  signature: string;\n  reward: string;\n};\n\nexport type BoostWinnersGuard = {\n  winners: string[];\n  prize: string;\n};\n\nexport interface BoostStrategy {\n  name: string;\n  description: string;\n  image: string;\n  external_url: string;\n  params: {\n    version: string;\n    env: string;\n\n    proposal: string;\n    eligibility: {\n      type: 'incentive' | 'bribe';\n      choice?: string;\n    };\n    distribution: {\n      type: 'weighted' | 'lottery';\n      limit?: string;\n      numWinners?: string;\n    };\n  };\n}\n\nexport type BoostSubgraph = {\n  id: string;\n  strategyURI: string;\n  poolSize: string;\n  guard: string;\n  start: string;\n  end: string;\n  owner: string;\n  chainId: string;\n  currentBalance: string;\n  transaction: string;\n  token: {\n    id: string;\n    name: string;\n    symbol: string;\n    decimals: string;\n  };\n  strategy: {\n    id: string;\n    version: string;\n    name: string;\n    proposal: string;\n    eligibility: {\n      type: 'incentive' | 'prediction' | 'bribe';\n      choice: string | null;\n    };\n    distribution: {\n      type: 'weighted' | 'lottery';\n      limit: string | null;\n      numWinners: string | null;\n    };\n  };\n};\n"
  },
  {
    "path": "src/helpers/clientEIP712.ts",
    "content": "import Client from '@snapshot-labs/snapshot.js/src/sign';\n\nconst hubUrl =\n  import.meta.env.VITE_HUB_URL || 'https://testnet.hub.snapshot.org';\nconst relayerURL = import.meta.env.VITE_RELAYER_URL;\nconst client = new Client(hubUrl, { relayerURL });\n\nexport default client;\n"
  },
  {
    "path": "src/helpers/connectors.ts",
    "content": "const connectors = {\n  injected: {\n    id: 'injected',\n    name: 'MetaMask'\n  },\n  walletconnect: {\n    id: 'walletconnect',\n    name: 'WalletConnect',\n    network: '1',\n    options: {\n      projectId: 'e6454bd61aba40b786e866a69bd4c5c6',\n      chains: [],\n      optionalChains: [\n        1, 4, 5, 10, 42, 56, 100, 137, 146, 246, 250, 4002, 1088, 42161, 73799,\n        33139, 11155111\n      ],\n      optionalMethods: [\n        'eth_sendTransaction',\n        'personal_sign',\n        'eth_accounts',\n        'eth_signTypedData_v4'\n      ],\n      rpcMap: {\n        '1': `${import.meta.env.VITE_BROVIDER_URL}/1`,\n        '4': `${import.meta.env.VITE_BROVIDER_URL}/4`,\n        '5': `${import.meta.env.VITE_BROVIDER_URL}/5`,\n        '10': `${import.meta.env.VITE_BROVIDER_URL}/10`,\n        '42': `${import.meta.env.VITE_BROVIDER_URL}/42`,\n        '56': `${import.meta.env.VITE_BROVIDER_URL}/56`,\n        '100': `${import.meta.env.VITE_BROVIDER_URL}/100`,\n        '137': `${import.meta.env.VITE_BROVIDER_URL}/137`,\n        '246': `${import.meta.env.VITE_BROVIDER_URL}/246`,\n        '42161': `${import.meta.env.VITE_BROVIDER_URL}/42161`,\n        '73799': `${import.meta.env.VITE_BROVIDER_URL}/73799`,\n        '11155111': `${import.meta.env.VITE_BROVIDER_URL}/11155111`\n      },\n      showQrModal: true\n    },\n    icon: 'ipfs://QmZRVqHpgRemw13aoovP2EaQdVtjzXRaQGQZsCLXWaNn9x'\n  },\n  walletlink: {\n    id: 'walletlink',\n    name: 'Coinbase Wallet',\n    network: '1',\n    options: {\n      appName: 'Snapshot',\n      appChainIds: [1],\n      appLogoUrl: 'https://snapshot.box/favicon.svg'\n    },\n    icon: 'ipfs://QmbJKEaeMz6qR3DmJSTxtYtrZeQPptVfnnYK72QBsvAw5q',\n    hidden: false\n  },\n  portis: {\n    id: 'portis',\n    name: 'Portis',\n    network: '1',\n    options: {\n      dappId: '3eb93706-c71d-456b-b4eb-322ea27f7d48',\n      network: 'mainnet'\n    },\n    icon: 'ipfs://QmNuLXa47xSrDNKRfpPNhoFTuoztvtWCcwGnPpT5MXJWMb'\n  },\n  stargazer: {\n    id: 'stargazer',\n    name: 'Stargazer',\n    icon: 'ipfs://bafkreiapdizo36f3yeg7g6l46f7ahbbkyo4otufnfyqri6louysr3grpzy'\n  },\n  gnosis: {\n    id: 'gnosis',\n    name: 'Gnosis Safe',\n    icon: 'ipfs://QmfJUHZLtRvadM7fvEJUWWxhS869KXXCMxPCr7TUqkwvUc',\n    hidden: true\n  },\n  kaikas: {\n    id: 'kaikas',\n    name: 'Kaikas',\n    icon: 'ipfs://QmXD4kkxKzXKbbBu3zAzZ279Sm91JhxCDAwSypyzxwe2Hj'\n  }\n};\n\nexport default connectors;\n"
  },
  {
    "path": "src/helpers/constants.ts",
    "content": "export const ETH_CONTRACT = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';\n\nexport const DEFAULT_ETH_ADDRESS = '0x0000000000000000000000000000000000000000';\n\nexport const SNAPSHOT_BREAKPOINTS = {\n  xs: '420px',\n  sm: '544px',\n  md: '768px',\n  lg: '1012px',\n  xl: '1280px',\n  '2xl': '1536px'\n};\n\nexport const KNOWN_HOSTS = [\n  'app.safe.global',\n  'horizen-eon.safe.onchainden.com',\n  'pilot.gnosisguild.org',\n  'metissafe.tech',\n  'multisig.mantle.xyz',\n  'wallet.ambire.com',\n  'multisig.moonbeam.network',\n  'worldassociation.org',\n  'safe.mainnet.frax.com',\n  'safe.fantom.network',\n  'safe.apechain.com'\n];\n\n// All subdomains of these domains are allowed\nexport const KNOWN_DOMAINS = ['blockscout.com'];\n\nexport const SPACE_CATEGORIES = [\n  'protocol',\n  'social',\n  'investment',\n  'grant',\n  'service',\n  'media',\n  'creator',\n  'collector',\n  'ai-agent',\n  'gaming',\n  'wallet',\n  'music',\n  'layer-2',\n  'defai',\n  'defi',\n  'rwa',\n  'depin',\n  'meme'\n];\n\nexport const ERC20ABI = [\n  'constructor(string name, string symbol)',\n  'event Approval(address indexed owner, address indexed spender, uint256 value)',\n  'event Transfer(address indexed from, address indexed to, uint256 value)',\n  'function allowance(address owner, address spender) view returns (uint256)',\n  'function approve(address spender, uint256 amount) returns (bool)',\n  'function balanceOf(address account) view returns (uint256)',\n  'function decimals() view returns (uint8)',\n  'function decreaseAllowance(address spender, uint256 subtractedValue) returns (bool)',\n  'function increaseAllowance(address spender, uint256 addedValue) returns (bool)',\n  'function name() view returns (string)',\n  'function symbol() view returns (string)',\n  'function totalSupply() view returns (uint256)',\n  'function transfer(address recipient, uint256 amount) returns (bool)',\n  'function transferFrom(address sender, address recipient, uint256 amount) returns (bool)'\n];\n\nexport const COINGECKO_ASSET_PLATFORMS = {\n  '137': 'polygon-pos',\n  '42161': 'arbitrum-one'\n};\n\nexport const COINGECKO_BASE_ASSETS = {\n  '137': 'matic-network',\n  '42161': 'ethereum'\n};\n\nexport type ChainCurrency = {\n  name: string;\n  symbol: string;\n  decimals: number;\n  contractAddress: string;\n};\n\nexport const CHAIN_CURRENCIES: Record<string, ChainCurrency> = {\n  '1': {\n    name: 'Ether',\n    symbol: 'ETH',\n    decimals: 18,\n    contractAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'\n  },\n  '8453': {\n    name: 'Ether',\n    symbol: 'ETH',\n    decimals: 18,\n    contractAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'\n  },\n  '11155111': {\n    name: 'Sepolia Ether',\n    symbol: 'SepoliaETH',\n    decimals: 18,\n    contractAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'\n  },\n  '137': {\n    name: 'MATIC',\n    symbol: 'MATIC',\n    decimals: 18,\n    contractAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'\n  },\n  '42161': {\n    name: 'Arbitrum',\n    symbol: 'ARB',\n    decimals: 18,\n    contractAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'\n  }\n};\n\nexport const TWO_WEEKS = 1209600;\nexport const ONE_DAY = 86400;\n\nexport const SNAPSHOT_HELP_LINK = 'https://help.snapshot.org/en';\n\nexport const BOOST_ENABLED_VOTING_TYPES = [\n  'basic',\n  'single-choice',\n  'ranked-choice'\n];\n\nexport const STRATEGIES_LIMITS = {\n  default: 8,\n  verified: 8,\n  turbo: 10\n};\n\nexport const PROPOSAL_BODY_LIMITS = {\n  default: 10000,\n  turbo: 40000\n};\n"
  },
  {
    "path": "src/helpers/covalent.ts",
    "content": "import snapshot from '@snapshot-labs/snapshot.js';\n\nconst API_URL = 'https://api.covalenthq.com/v1';\nconst API_KEY = 'ckey_2d082caf47f04a46947f4f212a8';\nexport const ETHER_CONTRACT = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';\n\nexport async function getTokenBalances(\n  address: string,\n  chainId: string\n): Promise<any[] | null> {\n  const tokenBalanceUrl = `${API_URL}/${chainId}/address/${address}/balances_v2/?quote-currency=USD&format=JSON&nft=false&no-nft-fetch=true&key=${API_KEY}`;\n  const tokenBalances = await snapshot.utils.getJSON(tokenBalanceUrl);\n\n  const validTokenBalances = tokenBalances.data.items.filter(\n    item =>\n      item.contract_name &&\n      item.contract_ticker_symbol &&\n      item.logo_url &&\n      item.quote\n  );\n\n  // If there is an ether item, move it to the top of the list\n  const etherItem = validTokenBalances.find(\n    item => item.contract_address === ETHER_CONTRACT\n  );\n  if (etherItem) {\n    const index = validTokenBalances.findIndex(\n      item => item.contract_address === ETHER_CONTRACT\n    );\n    validTokenBalances.splice(index, 1);\n    validTokenBalances.unshift(etherItem);\n  }\n\n  return validTokenBalances;\n}\n\nexport async function getTokenPrices(\n  contract: string,\n  chainId: string\n): Promise<any> {\n  const tokenPricesUrl = `${API_URL}/pricing/historical_by_addresses_v2/${chainId}/USD/${contract}/?quote-currency=USD&format=JSON&key=${API_KEY}`;\n  return await snapshot.utils.getJSON(tokenPricesUrl);\n}\n"
  },
  {
    "path": "src/helpers/delegation.ts",
    "content": "import {\n  SNAPSHOT_SUBGRAPH_URL,\n  subgraphRequest\n} from '@snapshot-labs/snapshot.js/src/utils';\n\nexport const contractAddress = '0x469788fE6E9E9681C6ebF3bF78e7Fd26Fc015446';\n\nexport async function getDelegates(network: string, address: string) {\n  const params = {\n    delegations: {\n      __args: {\n        where: {\n          delegator: address.toLowerCase()\n        },\n        first: 1000\n      },\n      space: true,\n      delegate: true\n    }\n  };\n  return await subgraphRequest(SNAPSHOT_SUBGRAPH_URL[network], params);\n}\n\nexport async function getDelegators(network: string, address: string) {\n  const params = {\n    delegations: {\n      __args: {\n        where: {\n          delegate: address.toLowerCase()\n        },\n        first: 1000\n      },\n      delegator: true,\n      space: true\n    }\n  };\n  return await subgraphRequest(SNAPSHOT_SUBGRAPH_URL[network], params);\n}\n"
  },
  {
    "path": "src/helpers/delegationV2/compound/index.ts",
    "content": "export { getDelegationReader } from './read';\nexport { getDelegationWriter } from './write';\n"
  },
  {
    "path": "src/helpers/delegationV2/compound/queries.ts",
    "content": "export const getDelegateQuery = (id: string): Record<string, any> => ({\n  delegate: {\n    __args: {\n      id\n    },\n    id: true,\n    delegatedVotes: true,\n    tokenHoldersRepresentedAmount: true\n  },\n  governance: {\n    __args: {\n      id: 'GOVERNANCE'\n    },\n    delegatedVotes: true,\n    totalTokenHolders: true,\n    totalDelegates: true\n  }\n});\n\nexport const getDelegatesQuery = (\n  first: number,\n  skip: number,\n  orderBy: string\n): Record<string, any> => ({\n  delegates: {\n    __args: {\n      first,\n      skip,\n      orderBy: orderBy || 'delegatedVotes',\n      orderDirection: 'desc'\n    },\n    id: true,\n    delegatedVotes: true,\n    tokenHoldersRepresentedAmount: true\n  },\n  governance: {\n    __args: {\n      id: 'GOVERNANCE'\n    },\n    delegatedVotes: true,\n    totalTokenHolders: true,\n    totalDelegates: true\n  }\n});\n\nexport const getBalanceQuery = (id: string): Record<string, any> => ({\n  tokenHolder: {\n    __args: {\n      id\n    },\n    id: true,\n    tokenBalance: true\n  }\n});\n"
  },
  {
    "path": "src/helpers/delegationV2/compound/read.ts",
    "content": "import getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\nimport { subgraphRequest, call } from '@snapshot-labs/snapshot.js/src/utils';\nimport { DelegateWithPercent, ExtendedSpace } from '@/helpers/interfaces';\nimport { DelegatingTo, DelegationReader } from '@/helpers/delegationV2/types';\nimport {\n  getBalanceQuery,\n  getDelegateQuery,\n  getDelegatesQuery\n} from './queries';\n\ntype Governance = {\n  delegatedVotes: string;\n  totalTokenHolders: string;\n  totalDelegates: string;\n};\n\ntype Delegate = {\n  id: string;\n  delegatedVotes: string;\n  tokenHoldersRepresentedAmount: number;\n};\n\nfunction adjustUrl(apiUrl: string) {\n  const hostedPattern =\n    /https:\\/\\/thegraph\\.com\\/hosted-service\\/subgraph\\/([\\w-]+)\\/([\\w-]+)/;\n  const hostedMatch = apiUrl.match(hostedPattern);\n\n  return hostedMatch\n    ? `https://api.thegraph.com/subgraphs/name/${hostedMatch[1]}/${hostedMatch[2]}`\n    : apiUrl;\n}\n\nconst emptyDelegate = (address: string): DelegateWithPercent => ({\n  id: address,\n  delegatedVotes: '0',\n  tokenHoldersRepresentedAmount: 0,\n  delegatorsPercentage: 0,\n  votesPercentage: 0\n});\n\nconst formatDelegatesResponse = (response: any): DelegateWithPercent[] => {\n  const governanceData = response.governance as Governance;\n  const delegatesData = response.delegates as Delegate[];\n\n  return delegatesData.map(delegate => {\n    const delegatorsPercentage =\n      Number(delegate.tokenHoldersRepresentedAmount) /\n      Number(governanceData.totalTokenHolders);\n    const votesPercentage =\n      Number(delegate.delegatedVotes) / Number(governanceData.delegatedVotes);\n\n    delegate.id = delegate.id.toLowerCase();\n\n    return {\n      ...delegate,\n      delegatorsPercentage,\n      votesPercentage\n    };\n  });\n};\n\nconst formatDelegateResponse = (response: any): DelegateWithPercent => {\n  const delegate = response.delegate as Delegate;\n  const governanceData = response.governance as Governance;\n\n  const delegatorsPercentage =\n    Number(delegate.tokenHoldersRepresentedAmount) /\n    Number(governanceData.totalTokenHolders);\n  const votesPercentage =\n    Number(delegate.delegatedVotes) / Number(governanceData.delegatedVotes);\n\n  return {\n    ...{\n      id: delegate.id.toLowerCase(),\n      delegatedVotes: delegate?.delegatedVotes || '0',\n      tokenHoldersRepresentedAmount:\n        delegate?.tokenHoldersRepresentedAmount || 0\n    },\n    delegatorsPercentage: delegatorsPercentage || 0,\n    votesPercentage: votesPercentage || 0\n  };\n};\n\nconst formatBalanceResponse = (response: any): string =>\n  response.tokenHolder?.tokenBalance || '0';\n\nconst getDelegations =\n  (space: ExtendedSpace): DelegationReader['getDelegates'] =>\n  async (first: number, skip: number, orderBy: string) => {\n    const query: any = getDelegatesQuery(first, skip, orderBy);\n\n    const response = await subgraphRequest(\n      adjustUrl(space.delegationPortal.delegationApi),\n      query\n    );\n\n    return formatDelegatesResponse(response);\n  };\n\nconst getDelegate =\n  (space: ExtendedSpace): DelegationReader['getDelegate'] =>\n  async (address: string) => {\n    const query: any = getDelegateQuery(address);\n\n    const response = await subgraphRequest(\n      adjustUrl(space.delegationPortal.delegationApi),\n      query\n    );\n\n    if (!response.delegate) return emptyDelegate(address);\n\n    return formatDelegateResponse(response);\n  };\n\nconst getBalance =\n  (space: ExtendedSpace): DelegationReader['getBalance'] =>\n  async (id: string) => {\n    const query: any = getBalanceQuery(id.toLowerCase());\n\n    const response = await subgraphRequest(\n      adjustUrl(space.delegationPortal.delegationApi),\n      query\n    );\n    return formatBalanceResponse(response);\n  };\n\nconst getDelegatingTo =\n  (space: ExtendedSpace): DelegationReader['getDelegatingTo'] =>\n  async (address: string): Promise<DelegatingTo> => {\n    const broviderUrl = import.meta.env.VITE_BROVIDER_URL;\n    const provider = getProvider(space.network, { broviderUrl });\n    const delegates = await call(\n      provider,\n      ['function delegates(address) view returns (address)'],\n      [space.delegationPortal.delegationContract, 'delegates', [address]]\n    );\n    return { delegates };\n  };\n\nexport const getDelegationReader = (\n  space: ExtendedSpace\n): DelegationReader => ({\n  getDelegates: getDelegations(space),\n  getDelegate: getDelegate(space),\n  getBalance: getBalance(space),\n  getDelegatingTo: getDelegatingTo(space)\n});\n"
  },
  {
    "path": "src/helpers/delegationV2/compound/write.ts",
    "content": "import { sendTransaction } from '@snapshot-labs/snapshot.js/src/utils';\nimport { DelegationWriter } from '@/helpers/delegationV2/types';\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\nconst sendSetDelegationTx =\n  (space: ExtendedSpace, auth: any): DelegationWriter['sendSetDelegationTx'] =>\n  async (addresses: string[]) => {\n    if (addresses.length !== 1) {\n      throw new Error('Compound delegation only supports one delegate');\n    }\n    const tx = await sendTransaction(\n      auth.web3,\n      space.delegationPortal.delegationContract,\n      ['function delegate(address delegatee)'],\n      'delegate',\n      [addresses[0]]\n    );\n    return tx;\n  };\n\nexport const getDelegationWriter = (\n  space: ExtendedSpace,\n  auth: any\n): DelegationWriter => ({\n  sendSetDelegationTx: sendSetDelegationTx(space, auth)\n});\n"
  },
  {
    "path": "src/helpers/delegationV2/index.ts",
    "content": "import {\n  DelegationReader,\n  DelegationWriter\n} from '@/helpers/delegationV2/types';\nimport * as compound from '@/helpers/delegationV2/compound';\nimport * as splitDelegation from '@/helpers/delegationV2/splitDelegation';\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\nexport enum DelegationTypes {\n  COMPOUND = 'compound-governor',\n  SPLIT_DELEGATION = 'split-delegation'\n}\n\nexport function setupDelegation(\n  space: ExtendedSpace,\n  auth?: any\n): {\n  reader: DelegationReader;\n  writer: DelegationWriter;\n} {\n  if (\n    space.delegationPortal?.delegationType ===\n      DelegationTypes.SPLIT_DELEGATION &&\n    space.strategies.some(\n      ({ name }) => name === DelegationTypes.SPLIT_DELEGATION\n    )\n  ) {\n    return {\n      reader: splitDelegation.getDelegationReader(space),\n      writer: splitDelegation.getDelegationWriter(space, auth)\n    };\n  }\n\n  return {\n    reader: compound.getDelegationReader(space),\n    writer: compound.getDelegationWriter(space, auth)\n  };\n}\n"
  },
  {
    "path": "src/helpers/delegationV2/splitDelegation/abi.ts",
    "content": "export const abi = [\n  {\n    inputs: [{ internalType: 'string', name: 'context', type: 'string' }],\n    name: 'clearDelegation',\n    outputs: [],\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    inputs: [\n      { internalType: 'string', name: 'context', type: 'string' },\n      { internalType: 'bool', name: '_optout', type: 'bool' }\n    ],\n    name: 'optout',\n    outputs: [],\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    inputs: [\n      { internalType: 'string', name: 'context', type: 'string' },\n      {\n        components: [\n          { internalType: 'bytes32', name: 'delegate', type: 'bytes32' },\n          { internalType: 'uint256', name: 'ratio', type: 'uint256' }\n        ],\n        internalType: 'struct Delegation[]',\n        name: 'delegation',\n        type: 'tuple[]'\n      },\n      {\n        internalType: 'uint256',\n        name: 'expirationTimestamp',\n        type: 'uint256'\n      }\n    ],\n    name: 'setDelegation',\n    outputs: [],\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    inputs: [\n      { internalType: 'string', name: 'context', type: 'string' },\n      {\n        internalType: 'uint256',\n        name: 'expirationTimestamp',\n        type: 'uint256'\n      }\n    ],\n    name: 'setExpiration',\n    outputs: [],\n    stateMutability: 'nonpayable',\n    type: 'function'\n  }\n];\n"
  },
  {
    "path": "src/helpers/delegationV2/splitDelegation/index.ts",
    "content": "export { getDelegationReader } from './read';\nexport { getDelegationWriter } from './write';\n"
  },
  {
    "path": "src/helpers/delegationV2/splitDelegation/read.ts",
    "content": "import { DelegateWithPercent, ExtendedSpace } from '@/helpers/interfaces';\nimport {\n  DelegateTreeItem,\n  DelegationReader,\n  DelegatorTreeItem\n} from '@/helpers/delegationV2/types';\n\nconst SPLIT_DELEGATE_BACKEND_URL = 'https://delegate-api.gnosisguild.org';\n\ntype DelegateFromSD = {\n  address: string;\n  delegatorCount: number;\n  percentOfDelegators: number;\n  votingPower: number;\n  percentOfVotingPower: number;\n};\n\ntype AddressResponse = {\n  chainId: number;\n  blockNumber: number;\n  address: string;\n  votingPower: Record<string, number>;\n  percentOfVotingPower: number;\n  percentOfDelegators: number;\n  delegates: string[];\n  delegateTree: DelegateTreeItem[];\n  delegators: string[];\n  delegatorTree: DelegatorTreeItem[];\n};\n\n// const emptyDelegate = (address: string): DelegateWithPercent => ({\n//   id: address,\n//   delegatedVotes: '0',\n//   tokenHoldersRepresentedAmount: 0,\n//   delegatorsPercentage: 0,\n//   votesPercentage: 0\n// });\n\nconst bpsToPercent = (bps: number): number => bps / 10000;\n\nconst getDelegations =\n  (space: ExtendedSpace): DelegationReader['getDelegates'] =>\n  async (first: number, skip: number, matchFilter: string) => {\n    let orderBy = 'power';\n    if (matchFilter === 'tokenHoldersRepresentedAmount') {\n      orderBy = 'count';\n    }\n\n    const splitDelStrategy = space.strategies.find(\n      strat => strat.name === 'split-delegation'\n    );\n\n    const response = (await fetch(\n      `${\n        space.delegationPortal.delegationApi || SPLIT_DELEGATE_BACKEND_URL\n      }/api/v1/${\n        space.id\n      }/pin/top-delegates?by=${orderBy}&limit=${first}&offset=${skip}`,\n      {\n        method: 'POST',\n        body: JSON.stringify({\n          strategy: splitDelStrategy\n        })\n      }\n    ).then(res => res.json())) as { delegates: DelegateFromSD[] };\n\n    const formatted: DelegateWithPercent[] = response.delegates.map(d => ({\n      id: d.address,\n      delegatedVotes: d.votingPower.toString(),\n      tokenHoldersRepresentedAmount: d.delegatorCount,\n      delegatorsPercentage: bpsToPercent(d.percentOfDelegators),\n      votesPercentage: bpsToPercent(d.percentOfVotingPower)\n    }));\n\n    return formatted;\n  };\n\nconst getDelegate =\n  (space: ExtendedSpace): DelegationReader['getDelegate'] =>\n  async (address: string) => {\n    const splitDelStrategy = space.strategies.find(\n      strat => strat.name === 'split-delegation'\n    );\n\n    const response = (await fetch(\n      `${\n        space.delegationPortal.delegationApi || SPLIT_DELEGATE_BACKEND_URL\n      }/api/v1/${space.id}/pin/${address}`,\n      {\n        method: 'POST',\n        body: JSON.stringify({\n          strategy: splitDelStrategy\n        })\n      }\n    ).then(res => res.json())) as AddressResponse;\n\n    const formatted: DelegateWithPercent = {\n      id: address,\n      delegatedVotes: response.votingPower.toString(),\n      tokenHoldersRepresentedAmount: response.delegators.length,\n      delegatorsPercentage: bpsToPercent(response.percentOfDelegators),\n      votesPercentage: bpsToPercent(response.percentOfVotingPower)\n    };\n\n    return formatted;\n  };\n\nconst getBalance =\n  (space: ExtendedSpace): DelegationReader['getBalance'] =>\n  async (address: string) => {\n    const splitDelStrategy = space.strategies.find(\n      strat => strat.name === 'split-delegation'\n    );\n\n    const response = (await fetch(\n      `${\n        space.delegationPortal.delegationApi || SPLIT_DELEGATE_BACKEND_URL\n      }/api/v1/${space.id}/pin/${address}`,\n      {\n        method: 'POST',\n        body: JSON.stringify({\n          strategy: splitDelStrategy\n        })\n      }\n    ).then(res => res.json())) as DelegateFromSD;\n\n    return response.votingPower.toString();\n  };\n\nconst getDelegatingTo =\n  (space: ExtendedSpace): DelegationReader['getDelegatingTo'] =>\n  async (address: string) => {\n    const splitDelStrategy = space.strategies.find(\n      strat => strat.name === 'split-delegation'\n    );\n\n    const response = (await fetch(\n      `${\n        space.delegationPortal.delegationApi || SPLIT_DELEGATE_BACKEND_URL\n      }/api/v1/${space.id}/pin/${address}`,\n      {\n        method: 'POST',\n        body: JSON.stringify({\n          strategy: splitDelStrategy\n        })\n      }\n    ).then(res => res.json())) as AddressResponse;\n\n    return {\n      delegates: response.delegates,\n      delegateTree: response.delegateTree\n    };\n  };\n\nexport const getDelegationReader = (\n  space: ExtendedSpace\n): DelegationReader => ({\n  getDelegates: getDelegations(space),\n  getDelegate: getDelegate(space),\n  getBalance: getBalance(space),\n  getDelegatingTo: getDelegatingTo(space)\n});\n"
  },
  {
    "path": "src/helpers/delegationV2/splitDelegation/write.ts",
    "content": "import { sendTransaction } from '@snapshot-labs/snapshot.js/src/utils';\nimport { hexZeroPad } from '@ethersproject/bytes';\nimport { DelegationWriter } from '@/helpers/delegationV2/types';\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport { abi } from './abi';\n\nconst DELEGATION_CONTRACT = '0xDE1e8A7E184Babd9F0E3af18f40634e9Ed6F0905'; //All chains\n\nconst sendSetDelegationTx =\n  (space: ExtendedSpace, auth: any): DelegationWriter['sendSetDelegationTx'] =>\n  async (addresses, ratio, expirationTimestamp) => {\n    const delegationContract =\n      space.delegationPortal.delegationContract || DELEGATION_CONTRACT;\n    console.log('sendSetDelegationTx', addresses, ratio, expirationTimestamp);\n    if (addresses.length <= 0) {\n      throw new Error('Delegation must have at least one delegate');\n    }\n\n    if (addresses.length !== ratio?.length) {\n      throw new Error(\n        'Delegation must have the same number of delegates and ratios'\n      );\n    }\n\n    if (expirationTimestamp == null) {\n      throw new Error('Delegation must have an expiration timestamp');\n    }\n\n    if (\n      expirationTimestamp &&\n      expirationTimestamp < Math.floor(Date.now() / 1000)\n    ) {\n      throw new Error('Delegation expiration must be in the future');\n    }\n\n    const delegations = addresses\n      .map((address, index) => ({\n        delegate: hexZeroPad(address, 32),\n        ratio: ratio[index]\n      }))\n      .sort((a, b) => {\n        return BigInt(a.delegate) < BigInt(b.delegate) ? -1 : 1;\n      });\n    console.log('delegations', delegations);\n    const tx = await sendTransaction(\n      auth.web3,\n      delegationContract,\n      abi,\n      'setDelegation',\n      [space.id, delegations, expirationTimestamp] //space.id should be the ENS name\n    );\n    return tx;\n  };\n\nconst sendClearDelegationsTx =\n  (\n    space: ExtendedSpace,\n    auth: any\n  ): DelegationWriter['sendClearDelegationsTx'] =>\n  async () => {\n    const delegationContract =\n      space.delegationPortal.delegationContract || DELEGATION_CONTRACT;\n    const tx = await sendTransaction(\n      auth.web3,\n      delegationContract,\n      abi,\n      'clearDelegation',\n      [space.id] //space.id should be the ENS name\n    );\n    return tx;\n  };\n\nexport const getDelegationWriter = (\n  space: ExtendedSpace,\n  auth: any\n): DelegationWriter => ({\n  sendSetDelegationTx: sendSetDelegationTx(space, auth),\n  sendClearDelegationsTx: sendClearDelegationsTx(space, auth)\n});\n"
  },
  {
    "path": "src/helpers/delegationV2/types.ts",
    "content": "import { DelegateWithPercent } from '@/helpers/interfaces';\n\nexport type DelegationReader = {\n  getDelegates(\n    first: number,\n    skip: number,\n    orderBy: string\n  ): Promise<DelegateWithPercent[]>;\n  getDelegate(id: string): Promise<DelegateWithPercent>;\n  getBalance(id: string): Promise<string>;\n  getDelegatingTo(address: string): Promise<DelegatingTo>;\n};\n\nexport type DelegationWriter = {\n  sendSetDelegationTx: (\n    addresses: string[],\n    ratio?: number[],\n    expirationTimestamp?: number\n  ) => Promise<any>;\n  sendClearDelegationsTx?: () => Promise<any>;\n};\n\nexport type DelegatingTo = {\n  delegates: string[];\n  delegateTree?: DelegateTreeItem[];\n};\n\nexport type DelegateTreeItem = {\n  delegate: string;\n  weight: number;\n  delegatedPower: number;\n  children: DelegateTreeItem[];\n};\n\nexport type DelegatorTreeItem = {\n  delegator: string;\n  weight: number;\n  delegatedPower: number;\n  parents: [];\n};\n"
  },
  {
    "path": "src/helpers/ens.ts",
    "content": "import networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport {\n  ApolloClient,\n  createHttpLink,\n  InMemoryCache\n} from '@apollo/client/core';\n\nconst env = import.meta.env.VITE_DEFAULT_NETWORK;\nconst uri = networks[env].ensSubgraph;\n\nconst httpLink = createHttpLink({ uri });\n\nexport const ensApolloClient = new ApolloClient({\n  link: httpLink,\n  cache: new InMemoryCache({\n    addTypename: false\n  }),\n  defaultOptions: {\n    query: {\n      fetchPolicy: 'no-cache'\n    }\n  }\n});\n"
  },
  {
    "path": "src/helpers/i18n.ts",
    "content": "import { createI18n } from 'vue-i18n';\nimport en from '@/locales/default.json';\nimport languages from '@/locales/languages.json';\nimport { lsRemove } from '@/helpers/utils';\n\nexport let defaultLocale = 'en-US';\n\nexport function getBrowserLocale() {\n  if (typeof navigator !== 'undefined') {\n    return (\n      navigator['userLanguage'] ||\n      navigator['language'] ||\n      (navigator.languages?.[0] ? navigator.languages[0] : undefined)\n    );\n  }\n  return undefined;\n}\n\nconst browserLocale = getBrowserLocale();\nObject.keys(languages).forEach(locale => {\n  if (locale.slice(0, 2) === browserLocale.slice(0, 2)) defaultLocale = locale;\n});\n\nexport function setI18nLanguage(i18n, locale) {\n  if (i18n.mode === 'legacy') {\n    i18n.global.locale = locale;\n  } else {\n    i18n.global.locale.value = locale;\n  }\n  document.querySelector('html')?.setAttribute('lang', locale);\n}\n\nexport async function loadLocaleMessages(i18n, locale) {\n  if (!Object.keys(languages).includes(locale)) {\n    lsRemove('locale');\n    locale = 'default';\n  }\n  if (locale === 'en-US') locale = 'default';\n\n  try {\n    // load locale messages with dynamic import\n    const messages = await import(\n      /* webpackChunkName: \"locale-[request]\" */ `../locales/${locale}.json`\n    );\n\n    // set locale and locale message\n    i18n.global.setLocaleMessage(locale, messages.default);\n  } catch (e) {\n    console.log(e);\n  }\n\n  return nextTick();\n}\n\nconst i18n = createI18n({\n  locale: defaultLocale,\n  datetimeFormats: {\n    'en-US': {\n      short: {\n        year: 'numeric',\n        month: 'short',\n        day: 'numeric',\n        hour: 'numeric',\n        minute: 'numeric'\n      }\n    }\n  },\n  messages: { 'en-US': en },\n  fallbackLocale: 'en-US'\n});\n\nsetI18nLanguage(i18n, defaultLocale);\n\nexport default i18n;\n"
  },
  {
    "path": "src/helpers/interfaces.ts",
    "content": "import { BigNumber } from '@ethersproject/bignumber';\nimport { Fragment, JsonFragment } from '@ethersproject/abi';\nimport { DelegationTypes } from '@/helpers/delegationV2';\n\nexport interface Strategy {\n  id: string;\n  spacesCount: number;\n  author: string;\n  version: string;\n  about?: string;\n  schema?: StrategySchema | null;\n  examples?: StrategyExample[];\n}\n\ninterface StrategyExample {\n  name: string;\n  strategy: Record<string, any>;\n  network: string;\n  addresses: string[];\n  snapshot: number;\n  space?: string;\n}\n\ninterface StrategySchema {\n  $schema: string;\n  $ref: string;\n  definitions: {\n    Strategy: Record<string, unknown>;\n  };\n}\n\nexport interface StrategyDefinitionProperties {\n  type: string;\n  title: string;\n  default?: any;\n  examples?: string[];\n  description?: string;\n  minLength?: number;\n  maxLength?: number;\n  pattern?: string;\n}\nexport interface StrategyDefinition {\n  title: string;\n  type: string;\n  default?: any;\n  description?: string;\n  required?: string[];\n  additionalProperties?: boolean;\n  properties?: StrategyDefinitionProperties;\n}\n\nexport interface Profile {\n  id: string;\n  name: string;\n  ens: string;\n  about?: string;\n  avatar?: string;\n  created?: number;\n}\n\nexport interface ProfileActivity {\n  id: string;\n  created: number;\n  type: string;\n  title: string;\n  space: { id: string; avatar: string };\n  vote?: {\n    proposalId: string;\n    choice: string;\n    type: string;\n  };\n}\n\nexport interface TreasuryAsset {\n  contract_name: string;\n  contract_ticker_symbol: string;\n  contract_address: string;\n  contract_decimals: number;\n  logo_url: string;\n  balance: string;\n  balance_24h: string;\n  quote: number;\n  quote_24h: number;\n}\n\nexport interface TreasuryWallet {\n  name: string;\n  address: string;\n  network: string;\n}\n\nexport interface ExploreSpace {\n  id: string;\n  name: string;\n  private?: boolean;\n  terms?: string;\n  network?: string;\n  networks?: string[];\n  categories?: string[];\n  proposals?: number;\n  proposals_active?: number;\n  proposals_7d?: number;\n  votes?: number;\n  votes_7d?: number;\n  followers?: number;\n  followers_7d?: number;\n}\n\nexport interface Space {\n  id: string;\n  name: string;\n  avatar: string;\n  verified: boolean;\n  turbo: boolean;\n  activeProposals: number;\n  followersCount: number;\n  flagged: boolean;\n  hibernated: boolean;\n  terms: string;\n}\n\nexport interface RankedSpace {\n  id: string;\n  name: string;\n  avatar: string;\n  verified: boolean;\n  turbo: boolean;\n  rank: number;\n  categories: string[];\n  activeProposals: number;\n  proposalsCount: number;\n  proposalsCount7d: number;\n  followersCount: number;\n  followersCount7d: number;\n  votesCount: number;\n  votesCount7d: number;\n  terms: string;\n}\n\nexport interface ExtendedSpace {\n  id: string;\n  name: string;\n  symbol: string;\n  network: string;\n  strategies: SpaceStrategy[];\n  delegationPortal: DelegatesConfig;\n  about: string;\n  avatar: string;\n  skin: string;\n  domain: string | null;\n  website: string | null;\n  terms: string | null;\n  coingecko: string | null;\n  github: string | null;\n  twitter: string | null;\n  followersCount: number;\n  private: boolean;\n  admins: string[];\n  moderators: string[];\n  members: string[];\n  categories: string[];\n  parent: ExtendedSpace | null;\n  children: ExtendedSpace[];\n  filters: { minScore: number; onlyMembers: boolean };\n  plugins: Record<string, any>;\n  validation: SpaceValidation;\n  voteValidation: VoteValidation;\n  treasuries: TreasuryWallet[];\n  template: string;\n  guidelines: string;\n  verified: boolean;\n  flagged: boolean;\n  hibernated: boolean;\n  turbo: boolean;\n  boost: {\n    enabled: boolean;\n    bribeEnabled: boolean;\n  };\n  voting: {\n    delay: number | null;\n    hideAbstain: boolean;\n    period: number | null;\n    quorum: number | null;\n    quorumType: 'default' | 'rejection';\n    type: string | null;\n    privacy: string | null;\n  };\n}\n\nexport interface DelegatesConfig {\n  delegationType: DelegationTypes;\n  delegationContract: string;\n  delegationNetwork: string;\n  delegationApi: string;\n}\nexport interface SpaceValidation {\n  name: string;\n  params: Record<string, any>;\n}\n\nexport interface SpaceStrategy {\n  name: string;\n  network: string;\n  params: Record<string, unknown>;\n}\n\nexport interface ProposalSpace {\n  id: string;\n  name: string;\n  members: string[];\n  avatar: string;\n  symbol: string;\n  verified: boolean;\n  turbo: boolean;\n}\n\nexport interface Proposal {\n  id: string;\n  title: string;\n  ipfs: string;\n  network: string;\n  choices: string[];\n  type: string;\n  snapshot: string;\n  author: string;\n  body: string;\n  created: number;\n  start: number;\n  end: number;\n  state: string;\n  symbol: string;\n  privacy: string;\n  validation: VoteValidation;\n  discussion: string;\n  quorum: number;\n  quorumType: 'default' | 'rejection';\n  scores: number[];\n  scores_state: string;\n  scores_total: number;\n  scores_by_strategy: number[][];\n  votes: number;\n  plugins: Record<string, any>;\n  space: ExtendedSpace;\n  strategies: SpaceStrategy[];\n  flagged: boolean;\n}\n\nexport interface VoteValidation {\n  name: string;\n  params: Record<string, any>;\n}\n\nexport interface Results {\n  scoresByStrategy: number[][];\n  scores: number[];\n  scoresTotal: number;\n}\n\nexport type Choice = number | number[] | Record<string, any>;\n\nexport interface Vote {\n  ipfs: string;\n  voter: string;\n  choice: Choice;\n  balance: number;\n  scores: number[];\n  vp: number;\n  vp_by_strategy: number[];\n  reason: string;\n  created: number;\n}\n\nexport interface VoteFilters {\n  orderDirection: string;\n  onlyWithReason: boolean;\n}\n\n// Execution\n\nexport type ABI = string | Array<Fragment | JsonFragment | string>;\n\nexport interface PendingTransaction {\n  id: string;\n  network: string;\n  createdAt: number;\n  hash: string | null;\n}\n\nexport interface SafeTransaction {\n  to: string;\n  value: string;\n  data: string;\n  operation: string;\n  nonce: string;\n}\n\nexport interface RealityOracleProposal {\n  dao: string;\n  oracle: string;\n  cooldown: number;\n  expiration: number;\n  proposalId: string;\n  questionId: string | undefined;\n  executionApproved: boolean;\n  finalizedAt: number | undefined;\n  nextTxIndex: number | undefined;\n  transactions: SafeTransaction[];\n  txHashes: string[];\n  currentBond: BigNumber | undefined;\n  isApproved: boolean;\n  endTime: number | undefined;\n}\n\nexport interface UmaOracleProposal {\n  dao: string;\n  oracle: string;\n  rules: string;\n  expiration: number;\n  proposalId: string;\n  transactions: SafeTransaction[];\n  minimumBond: BigNumber | number | undefined;\n  explanation: string;\n  allowance: BigNumber | number | undefined;\n  collateral: string;\n  decimals: number;\n  symbol: string;\n  userBalance: BigNumber | number | undefined;\n}\n\nexport interface SafeAsset {\n  address: string;\n  name: string;\n  logoUri?: string;\n}\n\nexport interface CollectableAsset extends SafeAsset {\n  id: string;\n  tokenName?: string;\n}\n\nexport interface TokenAsset extends SafeAsset {\n  symbol: string;\n  decimals: number;\n  balance: string;\n  verified?: any;\n  chainId?: number;\n}\n\nexport interface CollectableAssetTransaction extends SafeTransaction {\n  type: 'transferNFT';\n  recipient: string;\n  collectable: CollectableAsset;\n}\n\nexport interface TokenAssetTransaction extends SafeTransaction {\n  type: 'transferFunds';\n  amount: string;\n  recipient: any;\n  token: TokenAsset;\n}\n\nexport interface CustomContractTransaction extends SafeTransaction {\n  type: 'contractInteraction';\n  abi: string[];\n}\n\nexport interface SafeModuleTransactionBatch {\n  hash: string;\n  transactions: SafeTransaction[];\n}\n\nexport interface SafeExecutionData {\n  hash: string | null;\n  txs: SafeModuleTransactionBatch[];\n  network: string;\n  realityModule: string;\n}\n\nexport interface Plugin {\n  name: string;\n  author: string;\n  version: string;\n  defaults?: any;\n  icon?: string;\n  description?: string;\n  website?: string;\n}\n\nexport interface PluginIndex extends Plugin {\n  key: string;\n}\n\nexport interface FormError {\n  message: string;\n  push?: boolean;\n}\n\nexport interface Delegate {\n  id: string;\n  delegatedVotes: string;\n  tokenHoldersRepresentedAmount: number;\n}\n\nexport interface DelegateWithPercent extends Delegate {\n  delegatorsPercentage: number;\n  votesPercentage: number;\n}\n\nexport interface Statement {\n  delegate: string;\n  space: string;\n  statement: string;\n  about: string;\n  ipfs: string;\n  id: string;\n}\n\nexport type DelegatesVote = {\n  created: number;\n  voter: string;\n  choice: any;\n  vp: number;\n};\n\nexport type DelegatesProposal = {\n  created: number;\n  author: string;\n  title: string;\n};\n"
  },
  {
    "path": "src/helpers/pin.ts",
    "content": "import { create } from 'kubo-rpc-client';\nimport { pin } from '@snapshot-labs/pineapple';\n\nconst client = create({ url: 'https://api.thegraph.com/ipfs/api/v0' });\n\nexport async function pinGraph(payload: any) {\n  const res = await client.add(JSON.stringify(payload), { pin: true });\n\n  return {\n    provider: 'graph',\n    cid: res.cid.toV0().toString()\n  };\n}\n\nexport async function pinPineapple(payload: any) {\n  const pinned = await pin(payload);\n  if (!pinned) throw new Error('Failed to pin');\n\n  return {\n    provider: pinned.provider,\n    cid: pinned.cid\n  };\n}\n"
  },
  {
    "path": "src/helpers/queries.ts",
    "content": "import gql from 'graphql-tag';\n\nexport const VOTES_QUERY = gql`\n  query Votes(\n    $id: String!\n    $first: Int\n    $skip: Int\n    $orderBy: String\n    $orderDirection: OrderDirection\n    $reason_not: String\n    $voter: String\n    $space: String\n    $created_gte: Int\n  ) {\n    votes(\n      first: $first\n      skip: $skip\n      where: {\n        proposal: $id\n        vp_gt: 0\n        voter: $voter\n        space: $space\n        reason_not: $reason_not\n        created_gte: $created_gte\n      }\n      orderBy: $orderBy\n      orderDirection: $orderDirection\n    ) {\n      ipfs\n      voter\n      choice\n      vp\n      vp_by_strategy\n      reason\n      created\n    }\n  }\n`;\n\nexport const PROPOSAL_QUERY = gql`\n  query Proposal($id: String!) {\n    proposal(id: $id) {\n      id\n      ipfs\n      title\n      body\n      discussion\n      choices\n      labels\n      start\n      end\n      snapshot\n      state\n      author\n      created\n      plugins\n      network\n      type\n      quorum\n      quorumType\n      symbol\n      privacy\n      validation {\n        name\n        params\n      }\n      strategies {\n        name\n        network\n        params\n      }\n      space {\n        id\n        name\n      }\n      scores_state\n      scores\n      scores_by_strategy\n      scores_total\n      votes\n      flagged\n    }\n  }\n`;\n\nexport const PROPOSALS_QUERY = gql`\n  query Proposals(\n    $first: Int!\n    $skip: Int!\n    $state: String!\n    $space: String\n    $space_in: [String]\n    $author_in: [String]\n    $title_contains: String\n    $space_verified: Boolean\n    $flagged: Boolean\n  ) {\n    proposals(\n      first: $first\n      skip: $skip\n      where: {\n        space: $space\n        state: $state\n        space_in: $space_in\n        author_in: $author_in\n        title_contains: $title_contains\n        space_verified: $space_verified\n        flagged: $flagged\n      }\n    ) {\n      id\n      ipfs\n      title\n      body\n      start\n      end\n      state\n      author\n      created\n      choices\n      space {\n        id\n        name\n        members\n        avatar\n        symbol\n        verified\n        turbo\n        plugins\n      }\n      scores_state\n      scores_total\n      scores\n      votes\n      quorum\n      quorumType\n      symbol\n      flagged\n    }\n  }\n`;\n\nexport const NOTIFICATION_PROPOSALS_QUERY = gql`\n  query Proposals(\n    $first: Int!\n    $state: String!\n    $space_in: [String]\n    $start_gte: Int\n  ) {\n    proposals(\n      first: $first\n      where: { state: $state, space_in: $space_in, start_gte: $start_gte }\n    ) {\n      id\n      title\n      start\n      end\n      state\n      space {\n        id\n        name\n        avatar\n      }\n    }\n  }\n`;\n\nexport const FOLLOWS_QUERY = gql`\n  query Follows($space_in: [String], $follower_in: [String]) {\n    follows(\n      where: { space_in: $space_in, follower_in: $follower_in }\n      first: 500\n    ) {\n      id\n      follower\n      space {\n        id\n      }\n    }\n  }\n`;\n\nexport const SUBSCRIPTIONS_QUERY = gql`\n  query Subscriptions($space: String, $address: String) {\n    subscriptions(where: { space: $space, address: $address }) {\n      id\n      address\n      space {\n        id\n      }\n    }\n  }\n`;\n\nexport const ALIASES_QUERY = gql`\n  query Aliases($address: String!, $alias: String!, $created_gt: Int) {\n    aliases(\n      where: { address: $address, alias: $alias, created_gt: $created_gt }\n    ) {\n      address\n      alias\n    }\n  }\n`;\n\nexport const ENS_DOMAINS_BY_ACCOUNT_QUERY = gql`\n  query Domain($id: String!) {\n    account(id: $id) {\n      domains {\n        name\n        expiryDate\n      }\n      wrappedDomains {\n        name\n        expiryDate\n      }\n    }\n  }\n`;\n\nexport const ENS_DOMAIN_BY_HASH_QUERY = gql`\n  query Registration($id: String!) {\n    registration(id: $id) {\n      domain {\n        name\n        labelName\n      }\n    }\n  }\n`;\n\nexport const SPACE_SKIN_QUERY = gql`\n  query Space($id: String!) {\n    space(id: $id) {\n      skin\n    }\n  }\n`;\n\nexport const SPACE_DELEGATE_QUERY = gql`\n  query Space($id: String!) {\n    space(id: $id) {\n      id\n      symbol\n      network\n      strategies {\n        name\n        network\n        params\n      }\n    }\n  }\n`;\n\nexport const SKINS_COUNT_QUERY = gql`\n  query Skins {\n    skins {\n      id\n      spacesCount\n    }\n  }\n`;\n\nexport const NETWORKS_COUNT_QUERY = gql`\n  query Networks {\n    networks {\n      id\n      spacesCount\n    }\n  }\n`;\n\nexport const PLUGINS_COUNT_QUERY = gql`\n  query Plugins {\n    plugins {\n      id\n      spacesCount\n    }\n  }\n`;\n\nexport const VALIDATIONS_COUNT_QUERY = gql`\n  query Validations {\n    validations {\n      id\n      spacesCount\n    }\n  }\n`;\n\nexport const STRATEGIES_QUERY = gql`\n  query Strategies {\n    strategies {\n      id\n      author\n      version\n      spacesCount\n    }\n  }\n`;\n\nexport const EXTENDED_STRATEGY_QUERY = gql`\n  query Strategy($id: String!) {\n    strategy(id: $id) {\n      id\n      author\n      version\n      spacesCount\n      about\n      schema\n      examples\n    }\n  }\n`;\n\nexport const ACTIVITY_VOTES_QUERY = gql`\n  query Votes(\n    $voter: String!\n    $first: Int\n    $skip: Int\n    $orderBy: String\n    $orderDirection: OrderDirection\n  ) {\n    votes(\n      first: $first\n      skip: $skip\n      where: { voter: $voter }\n      orderBy: $orderBy\n      orderDirection: $orderDirection\n    ) {\n      id\n      created\n      choice\n      proposal {\n        id\n        title\n        choices\n        type\n      }\n      space {\n        id\n        avatar\n      }\n    }\n  }\n`;\n\nexport const PROFILES_QUERY = gql`\n  query Users($addresses: [String]!, $first: Int, $skip: Int) {\n    users(first: $first, skip: $skip, where: { id_in: $addresses }) {\n      id\n      name\n      about\n      avatar\n      created\n    }\n  }\n`;\n\nexport const USER_VOTED_PROPOSAL_IDS_QUERY = gql`\n  query Votes($voter: String!, $proposals: [String]!) {\n    votes(first: 1000, where: { voter: $voter, proposal_in: $proposals }) {\n      proposal {\n        id\n      }\n    }\n  }\n`;\n\nexport const SPACES_RANKING_QUERY = gql`\n  query Ranking(\n    $first: Int\n    $skip: Int\n    $search: String\n    $network: String\n    $category: String\n  ) {\n    ranking(\n      first: $first\n      skip: $skip\n      where: { search: $search, network: $network, category: $category }\n    ) {\n      metrics {\n        total\n        categories\n      }\n      items {\n        id\n        name\n        avatar\n        private\n        verified\n        turbo\n        categories\n        rank\n        activeProposals\n        proposalsCount\n        proposalsCount7d\n        followersCount\n        followersCount7d\n        votesCount\n        votesCount7d\n        terms\n      }\n    }\n  }\n`;\n\nexport const SPACES_QUERY = gql`\n  query Spaces($id_in: [String], $first: Int, $skip: Int) {\n    spaces(first: $first, skip: $skip, where: { id_in: $id_in }) {\n      id\n      name\n      avatar\n      verified\n      turbo\n      activeProposals\n      followersCount\n      terms\n      flagged\n      hibernated\n    }\n  }\n`;\n\nexport const STATEMENTS_QUERY = gql`\n  query Statements($space: String!, $delegate_in: [String]!) {\n    statements(where: { space: $space, delegate_in: $delegate_in }) {\n      delegate\n      space\n      statement\n      about\n      ipfs\n      id\n      discourse\n      network\n      status\n    }\n  }\n`;\n\nexport const SPACE_QUERY = gql`\n  query Space($id: String!) {\n    space(id: $id) {\n      id\n      name\n      about\n      network\n      symbol\n      network\n      terms\n      skin\n      avatar\n      twitter\n      website\n      github\n      coingecko\n      private\n      domain\n      admins\n      moderators\n      members\n      categories\n      labels {\n        id\n        name\n        description\n        color\n      }\n      plugins\n      followersCount\n      template\n      guidelines\n      verified\n      turbo\n      flagged\n      hibernated\n      parent {\n        id\n        name\n        avatar\n        followersCount\n        children {\n          id\n        }\n      }\n      children {\n        id\n        name\n        avatar\n        followersCount\n        parent {\n          id\n        }\n      }\n      voting {\n        delay\n        period\n        type\n        quorum\n        quorumType\n        privacy\n        hideAbstain\n      }\n      strategies {\n        name\n        network\n        params\n      }\n      validation {\n        name\n        params\n      }\n      voteValidation {\n        name\n        params\n      }\n      filters {\n        minScore\n        onlyMembers\n      }\n      delegationPortal {\n        delegationType\n        delegationContract\n        delegationNetwork\n        delegationApi\n      }\n      treasuries {\n        name\n        address\n        network\n      }\n      boost {\n        enabled\n        bribeEnabled\n      }\n    }\n  }\n`;\n\nexport const LEADERBOARD_QUERY = gql`\n  query Leaderboard($space: String!, $user_in: [String]) {\n    leaderboards(where: { space: $space, user_in: $user_in }) {\n      space\n      user\n      proposalsCount\n      votesCount\n      lastVote\n    }\n  }\n`;\n"
  },
  {
    "path": "src/helpers/shutter.ts",
    "content": "import { randomBytes } from '@ethersproject/random';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { arrayify, hexlify } from '@ethersproject/bytes';\nimport { toUtf8Bytes, formatBytes32String } from '@ethersproject/strings';\nimport shutterWasm from '@shutter-network/shutter-crypto/dist/shutter-crypto.wasm?url';\nimport { init, encrypt } from '@shutter-network/shutter-crypto';\n\nexport default async function encryptChoice(\n  choice: string,\n  id: string\n): Promise<string | null> {\n  await init(shutterWasm);\n\n  const bytesChoice = toUtf8Bytes(choice);\n  const message = arrayify(bytesChoice);\n  const eonPublicKey = arrayify(import.meta.env.VITE_SHUTTER_EON_PUBKEY);\n\n  const is32ByteString = id.substring(0, 2) === '0x';\n  const proposalId = arrayify(is32ByteString ? id : formatBytes32String(id));\n\n  const sigma = arrayify(BigNumber.from(randomBytes(32)));\n\n  const encryptedMessage = await encrypt(\n    message,\n    eonPublicKey,\n    proposalId,\n    sigma\n  );\n\n  return hexlify(encryptedMessage) ?? null;\n}\n"
  },
  {
    "path": "src/helpers/sign.ts",
    "content": "import { getAddress } from '@ethersproject/address';\nimport type { Web3Provider } from '@ethersproject/providers';\nimport type { Wallet } from '@ethersproject/wallet';\n\nconst domain = {\n  name: 'snapshot',\n  version: '0.1.4'\n};\n\nexport type DataType = Record<string, { name: string; type: string }[]>;\n\nexport type ISubscribe = {\n  address: string;\n} & Record<string, any>;\n\nexport default async function sign(\n  web3: Web3Provider | Wallet,\n  address: string,\n  message: ISubscribe,\n  types: DataType\n) {\n  const signer = 'getSigner' in web3 ? web3.getSigner() : web3;\n  message.address = getAddress(address);\n  return await signer._signTypedData(domain, types, message);\n}\n"
  },
  {
    "path": "src/helpers/snapshot.ts",
    "content": "import { getVp, validate } from '@snapshot-labs/snapshot.js/src/utils';\nimport { apolloClient } from '@/helpers/apollo';\nimport { PROPOSAL_QUERY, VOTES_QUERY } from '@/helpers/queries';\nimport { ExtendedSpace, Proposal, Vote } from '@/helpers/interfaces';\nimport { isAddress } from '@ethersproject/address';\nimport cloneDeep from 'lodash/cloneDeep';\n\nexport async function getProposalVotes(\n  proposalId: string,\n  {\n    first = 1000,\n    voter = '',\n    skip = 0,\n    space = '',\n    orderBy = 'vp',\n    orderDirection = 'desc',\n    created_gte = 0\n  } = {}\n): Promise<Vote[] | []> {\n  try {\n    console.time('getProposalVotes');\n    const response = await apolloClient.query({\n      query: VOTES_QUERY,\n      variables: {\n        id: proposalId,\n        orderBy,\n        orderDirection,\n        first,\n        voter: isAddress(voter) ? voter : undefined,\n        skip,\n        space: space || undefined,\n        created_gte\n      }\n    });\n    console.timeEnd('getProposalVotes');\n    const votesResClone = cloneDeep(response);\n    return votesResClone.data.votes || [];\n  } catch (e) {\n    console.log(e);\n    return [];\n  }\n}\n\nexport async function getProposal(id) {\n  try {\n    console.time('getProposal');\n    const response = await apolloClient.query({\n      query: PROPOSAL_QUERY,\n      variables: {\n        id\n      }\n    });\n    console.timeEnd('getProposal');\n\n    const proposalResClone = cloneDeep(response);\n    const proposal = proposalResClone.data.proposal;\n\n    if (proposal?.plugins?.daoModule) {\n      // The Dao Module has been renamed to SafeSnap\n      // Previous proposals have to be renamed\n      proposal.plugins.safeSnap = proposal.plugins.daoModule;\n      delete proposal.plugins.daoModule;\n    }\n\n    return proposal;\n  } catch (e) {\n    console.log(e);\n    return e;\n  }\n}\n\nexport async function getPower(space, address, proposal) {\n  console.log('[score] getPower');\n  const options: any = {};\n  if (import.meta.env.VITE_SCORES_URL)\n    options.url = import.meta.env.VITE_SCORES_URL;\n  return getVp(\n    address,\n    proposal.network,\n    proposal.strategies,\n    parseInt(proposal.snapshot),\n    space.id,\n    proposal.delegation === 1,\n    options\n  );\n}\n\nexport async function voteValidation(\n  space: ExtendedSpace,\n  address: string,\n  proposal: Proposal\n): Promise<boolean> {\n  console.log('[score] getValidation');\n  const options: any = {};\n  if (import.meta.env.VITE_SCORES_URL)\n    options.url = import.meta.env.VITE_SCORES_URL;\n\n  const params = cloneDeep(proposal.validation?.params) || {};\n  if (proposal.validation.name === 'basic') {\n    params.strategies = params.strategies ?? proposal.strategies;\n  }\n\n  const validateRes = await validate(\n    proposal.validation.name,\n    address,\n    space.id,\n    proposal.network,\n    parseInt(proposal.snapshot),\n    params,\n    options\n  );\n  if (typeof validateRes !== 'boolean') {\n    console.error('Vote validation failed', validateRes);\n    return false;\n  }\n  return validateRes;\n}\n\nexport async function proposalValidation(\n  space: ExtendedSpace,\n  address: string\n): Promise<boolean> {\n  console.log('[score] getProposalValidation');\n  const options: any = {};\n  if (import.meta.env.VITE_SCORES_URL)\n    options.url = import.meta.env.VITE_SCORES_URL;\n\n  const params = space.validation?.params || {};\n  if (space.validation.name === 'basic') {\n    params.minScore =\n      space.validation?.params?.minScore || space.filters.minScore;\n    params.strategies =\n      space.validation?.params?.strategies || space.strategies;\n  }\n\n  const validateRes = await validate(\n    space.validation.name,\n    address,\n    space.id,\n    space.network,\n    'latest',\n    params,\n    options\n  );\n  if (typeof validateRes !== 'boolean') {\n    console.error('Proposal validation failed', validateRes);\n    return false;\n  }\n  return validateRes;\n}\n"
  },
  {
    "path": "src/helpers/transaction.ts",
    "content": "import { ERC20ABI } from '@/helpers/constants';\nimport { sendTransaction } from '@snapshot-labs/snapshot.js/src/utils';\n\nexport function sendApprovalTransaction(\n  provider: any,\n  token: string,\n  contract: string,\n  amount: string\n) {\n  return sendTransaction(\n    provider,\n    token,\n    ERC20ABI,\n    'approve',\n    [contract, amount],\n    {}\n  );\n}\n"
  },
  {
    "path": "src/helpers/utils.test.js",
    "content": "import { describe, expect, it } from 'vitest';\nimport { calcFromSeconds, calcToSeconds } from './utils';\n\ndescribe('calcFromSeconds', () => {\n  it('should return 3 for seconds from 10800 to 14399 and unit h', () => {\n    expect(calcFromSeconds(60 * 60 * 3, 'h')).toBe(3);\n    expect(calcFromSeconds(60 * 60 * 4 - 1, 'h')).toBe(3);\n  });\n\n  it('should return 3 for seconds from 259200 to 345599 and unit d', () => {\n    expect(calcFromSeconds(60 * 60 * 24 * 3, 'd')).toBe(3);\n    expect(calcFromSeconds(60 * 60 * 24 * 4 - 1, 'd')).toBe(3);\n  });\n\n  it('should return 3600 for 1 hour and 5400 for 1.5 hours', () => {\n    expect(calcToSeconds(1, 'h')).toBe(3600);\n    expect(calcToSeconds(1.5, 'h')).toBe(5400);\n  });\n\n  it('should return 86400 for 1 day and 129600 for 1.5 days', () => {\n    expect(calcToSeconds(1, 'd')).toBe(86400);\n    expect(calcToSeconds(1.5, 'd')).toBe(129600);\n  });\n});\n"
  },
  {
    "path": "src/helpers/utils.ts",
    "content": "import pkg from '@/../package.json';\nimport { formatEther } from '@ethersproject/units';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport voting from '@snapshot-labs/snapshot.js/src/voting';\nimport { getUrl } from '@snapshot-labs/snapshot.js/src/utils';\nimport { getAddress } from '@ethersproject/address';\n\nexport function shortenAddress(str = '') {\n  return `${str.slice(0, 6)}...${str.slice(str.length - 4)}`;\n}\n\nexport function shorten(str: string, key?: any): string {\n  if (!str) return str;\n  let limit;\n  if (typeof key === 'number') limit = key;\n  if (key === 'symbol') limit = 6;\n  if (key === 'name') limit = 64;\n  if (key === 'choice') limit = 12;\n  if (limit)\n    return str.length > limit ? `${str.slice(0, limit).trim()}...` : str;\n  return shortenAddress(str);\n}\n\nexport function getChoiceString(proposal, selected) {\n  const votingClass = new voting[proposal.type](proposal, '', '', selected);\n  return votingClass.getChoiceString();\n}\n\nexport function jsonParse(input, fallback?) {\n  if (typeof input !== 'string') {\n    return fallback || {};\n  }\n  try {\n    return JSON.parse(input);\n  } catch (err) {\n    return fallback || {};\n  }\n}\n\nexport function lsSet(key: string, value: any) {\n  return localStorage.setItem(`${pkg.name}.${key}`, JSON.stringify(value));\n}\n\nexport function lsGet(key: string, fallback?: any) {\n  const item = localStorage.getItem(`${pkg.name}.${key}`);\n  return jsonParse(item, fallback);\n}\n\nexport function lsRemove(key: string) {\n  return localStorage.removeItem(`${pkg.name}.${key}`);\n}\n\nexport function mapOldPluginNames(space) {\n  // The Dao Module has been renamed to SafeSnap\n  // Previous spaces plugins have to be renamed\n  if (space.plugins?.daoModule) {\n    space.plugins.safeSnap = space.plugins.daoModule;\n    delete space.plugins.daoModule;\n  }\n\n  return space;\n}\n\nexport function formatAmount(amount, maxDecimals) {\n  let out = formatEther(amount);\n  if (maxDecimals && out.includes('.')) {\n    const parts = out.split('.');\n    if (parts[1].length > maxDecimals) {\n      out = `~${parts[0]}.${parts[1].slice(0, maxDecimals)}`;\n    }\n  }\n  return `${out} ETH`;\n}\n\nexport function parseAmount(input) {\n  return BigNumber.from(input).toString();\n}\n\nexport function parseValueInput(input) {\n  try {\n    return parseAmount(input);\n  } catch (e) {\n    return input;\n  }\n}\n\nexport function getNumberWithOrdinal(n) {\n  const s = ['th', 'st', 'nd', 'rd'],\n    v = n % 100;\n  return n + (s[(v - 20) % 10] || s[v] || s[0]);\n}\n\nexport function explorerUrl(network, str: string, type = 'address'): string {\n  return `${networks[network].explorer.url}/${type}/${str}`;\n}\n\nexport function openProfile(address: string, domain: string, router: any) {\n  return domain\n    ? window.open(`https://snapshot.org/#/profile/${address}`, '_blank')\n    : router.push({\n        name: 'profileActivity',\n        params: { address: address }\n      });\n}\n\nexport function calcFromSeconds(value, unit) {\n  if (unit === 'm') return Math.floor(value / 60);\n  if (unit === 'h') return Math.floor(value / (60 * 60));\n  if (unit === 'd') return Math.floor(value / (60 * 60 * 24));\n}\n\nexport function calcToSeconds(value, unit) {\n  if (unit === 'm') return value * 60;\n  if (unit === 'h') return value * 60 * 60;\n  if (unit === 'd') return value * 60 * 60 * 24;\n}\n\nexport function getIpfsUrl(url: string) {\n  const gateway: any =\n    import.meta.env.VITE_IPFS_GATEWAY || 'cloudflare-ipfs.com';\n  return getUrl(url, gateway);\n}\n\nexport async function clearStampCache(id: string, type = 'space') {\n  if (type === 'space')\n    return await fetch(`https://cdn.stamp.fyi/clear/space/${id}`);\n\n  if (type === 'avatar')\n    return await fetch(`https://cdn.stamp.fyi/clear/avatar/eth:${id}`);\n}\n\nexport async function resolveHandle(handle: string) {\n  try {\n    const results = await fetch(import.meta.env.VITE_STAMP_URL, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify({ method: 'resolve_names', params: [handle] })\n    });\n\n    return (await results.json()).result?.[handle];\n  } catch (e) {\n    console.error('Error resolving handle:', handle, e);\n    return null;\n  }\n}\n\nexport async function lookupAddress(\n  addresses: string[]\n): Promise<Record<string, string>> {\n  if (addresses.length === 0) {\n    return {};\n  }\n\n  try {\n    const response = await fetch(import.meta.env.VITE_STAMP_URL, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify({\n        method: 'lookup_addresses',\n        params: addresses.slice(0, 50)\n      })\n    });\n\n    const results = (await response.json()).result;\n\n    return Object.fromEntries(\n      addresses.map(address => [address, results[address] || ''])\n    );\n  } catch (e) {\n    console.error('Error resolving addresses:', e);\n    return {};\n  }\n}\n\nexport function isSnapshotUrl(url: string) {\n  let parsedUrl;\n  try {\n    parsedUrl = new URL(url);\n  } catch (err) {\n    console.error('Invalid URL', err);\n    return;\n  }\n\n  if (parsedUrl.hostname === 'snapshot.org') {\n    return true;\n  }\n\n  return false;\n}\n\nexport function toChecksumAddress(address: string) {\n  try {\n    return getAddress(address.toLowerCase());\n  } catch (e) {\n    return address;\n  }\n}\n\nexport function addressEqual(address1: string, address2: string) {\n  return address1.toLowerCase() === address2.toLowerCase();\n}\n"
  },
  {
    "path": "src/helpers/validation.ts",
    "content": "import snapshot from '@snapshot-labs/snapshot.js';\n\nconst { env } = useApp();\n\nfunction getErrorMessage(errorObject): string {\n  if (!errorObject.message) return 'Invalid field.';\n\n  if (errorObject.keyword === 'format') {\n    switch (errorObject.params.format) {\n      case 'address':\n        return 'Must be a valid address.';\n      case 'ethValue':\n        return 'Must be a number.';\n      case 'customUrl':\n        return 'Must be a valid URL.';\n      case 'uri':\n        return 'Must be a valid URL.';\n      case 'percentage':\n        return 'Percentage must be between 0 and 100.';\n      default:\n        return 'Invalid format.';\n    }\n  }\n\n  return `${errorObject.message\n    .charAt(0)\n    .toLocaleUpperCase()}${errorObject.message.slice(1)}.`;\n}\nexport function validateForm(\n  schema: Record<string, any>,\n  form: Record<string, any>,\n  options = {\n    spaceType: 'default'\n  }\n): Record<string, any> {\n  const valid = snapshot.utils.validateSchema(schema, form, {\n    spaceType: options.spaceType || 'default',\n    snapshotEnv: env === 'production' ? 'mainnet' : 'default'\n  });\n  if (!Array.isArray(valid)) return {};\n  return transformAjvErrors(valid);\n}\n\ninterface ValidationErrorOutput {\n  [key: string]: ValidationErrorOutput | string;\n}\nfunction transformAjvErrors(errors): ValidationErrorOutput {\n  errors = errors.map(error => {\n    if (error.instancePath) return error;\n    const propertyName = error.params.missingProperty;\n    if (!propertyName) return error;\n    const path = `/${propertyName}`;\n    return {\n      ...error,\n      instancePath: path\n    };\n  });\n\n  return errors.reduce((output: ValidationErrorOutput, error) => {\n    const path: string[] = extractPathFromError(error);\n\n    // Skip the current error if the path is empty\n    if (path.length === 0) {\n      return output;\n    }\n\n    const targetObject: ValidationErrorOutput = findOrCreateNestedObject(\n      output,\n      path\n    );\n\n    targetObject[path[path.length - 1]] = getErrorMessage(error);\n    return output;\n  }, {});\n}\n\nfunction extractPathFromError(error): string[] {\n  if (!error.instancePath) {\n    return [];\n  }\n  return error.instancePath.split('/').slice(1);\n}\n\nfunction findOrCreateNestedObject(\n  output: ValidationErrorOutput,\n  path: string[]\n): ValidationErrorOutput {\n  const parentPath: string[] = path.slice(0, path.length - 1);\n  const parentObject: ValidationErrorOutput = parentPath.reduce(\n    (current: ValidationErrorOutput, subpath: string) => {\n      if (!current[subpath]) {\n        current[subpath] = {} as ValidationErrorOutput;\n      }\n      return current[subpath] as ValidationErrorOutput;\n    },\n    output\n  );\n\n  return parentObject;\n}\n"
  },
  {
    "path": "src/helpers/vitePlugins.ts",
    "content": "import vue from '@vitejs/plugin-vue';\nimport ViteComponents from 'unplugin-vue-components/vite';\nimport visualizer from 'rollup-plugin-visualizer';\nimport Icons from 'unplugin-icons/vite';\nimport IconsResolver from 'unplugin-icons/resolver';\nimport { FileSystemIconLoader } from 'unplugin-icons/loaders';\nimport { sentryVitePlugin } from '@sentry/vite-plugin';\nimport AutoImport from 'unplugin-auto-import/vite';\n\nexport const getPlugins = () => [\n  vue({ reactivityTransform: true }),\n  AutoImport({\n    dts: true,\n    imports: ['vue', 'vue-router'],\n    dirs: ['./src/composables'],\n    eslintrc: {\n      enabled: true\n    }\n  }),\n  ViteComponents({\n    globs: ['src/components/**/*.vue', '!src/components/Tune/_Form/*.vue'],\n    resolvers: [\n      IconsResolver({\n        customCollections: ['s'],\n        alias: {\n          ho: 'heroicons-outline'\n        }\n      })\n    ]\n  }),\n  visualizer({\n    filename: './dist/stats.html',\n    template: 'sunburst',\n    gzipSize: true\n  }),\n  Icons({\n    compiler: 'vue3',\n    customCollections: {\n      s: FileSystemIconLoader('./src/assets/icons', svg =>\n        svg.replace(/^<svg /, '<svg fill=\"currentColor\" ')\n      )\n    }\n  }),\n  // ATTENTION: Keep the Sentry plugin last\n  sentryVitePlugin({\n    org: process.env.SENTRY_ORG,\n    project: 'snapshot',\n    authToken: process.env.SENTRY_AUTH_TOKEN,\n    disable: process.env.VITE_ENV !== 'production'\n  })\n];\n"
  },
  {
    "path": "src/locales/ar-SA.json",
    "content": "{\n  \"searchPlaceholder\": \"بحث\",\n  \"spaceCount\": \"{0} مساحة\",\n  \"createSpace\": \"إنشاء مساحة\",\n  \"backToHome\": \"الصفحه الرئيسيه\",\n  \"actions\": \"الإجراءات\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"النتائج\",\n  \"resultsError\": \"Results could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"resultsCalculating\": \"Final results are being calculated. If you still see this message after a few minutes contact the space admin.\",\n  \"votingPowerFailedMessage\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"votingValidationFailedMessage\": \"There was an error on our side and we could not verify if you are eligible to vote. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n  \"notValidVoterMessage\": \"Oops, you don't seem to be eligible to vote on this proposal.\",\n  \"getHelp\": \"Get help\",\n  \"retry\": \"Retry\",\n  \"currentResults\": \"إعرض النتائج الحالية\",\n  \"reset\": \"إعادة ضبط\",\n  \"close\": \"Close\",\n  \"save\": \"حفظ\",\n  \"author\": \"المؤلف\",\n  \"next\": \"التالي\",\n  \"choice\": \"Choice\",\n  \"submit\": \"إعتماد\",\n  \"plugins\": \"الإضافات\",\n  \"information\": \"المعلومات\",\n  \"confirm\": \"تأكيد\",\n  \"snapshot\": \"لمحة(سنابشوت)\",\n  \"strategies\": \"الإستراتيجيات (تخطيط)\",\n  \"strategiesPage\": \"الإستراتيجية (تخطيط)\",\n  \"space\": \"المساحة\",\n  \"spaces\": \"المساحات\",\n  \"verifiedSpace\": \"Verified space\",\n  \"warningSpace\": \"This space has been flagged as potentially malicious. Proceed with caution.\",\n  \"version\": \"الاصدار\",\n  \"timeline\": \"الجدول الزمني\",\n  \"ended\": \"ended\",\n  \"started\": \"started\",\n  \"filters\": \"المرشحات\",\n  \"allSpaces\": \"جميع المساحات\",\n  \"submitOnchain\": \"إرسال على السلسلة\",\n  \"inSpaces\": \"في {0} مساحة(مساحات)\",\n  \"votes\": \"الأصوات\",\n  \"seeMore\": \"الاطّلاع على المزيد\",\n  \"seeAll\": \"See all\",\n  \"network\": \"الشبكة\",\n  \"networks\": \"الشبكات\",\n  \"skins\": \"المظاهر\",\n  \"spaceMembers\": \"Members\",\n  \"members\": \"لا يوجد أعضاء | {count} أعضاء | {count} أعضاء\",\n  \"editStrategy\": \"تعديل الاستراتيجية\",\n  \"invalidProposals\": \"اقتراحات غير صالحة\",\n  \"account\": \"الحساب\",\n  \"create3box\": \"إنشاء ملف شخصي في 3Box\",\n  \"view3box\": \"عرض الملف الشخصي في 3Box\",\n  \"edit3box\": \"تعديل الملف الشخصي في 3Box\",\n  \"connectWallet\": \"الاتصال بالمحفظة\",\n  \"toggleSkin\": \"Toggle skin\",\n  \"about\": \"عن البرنامج\",\n  \"license\": \"الترخيص\",\n  \"showMore\": \"Show more\",\n  \"voted\": \"Voted\",\n  \"reload\": \"Reload\",\n  \"ipfsServer\": \"سيرفر IPFS\",\n  \"hub\": \"مكان التجمع\",\n  \"cancel\": \"إلغاء\",\n  \"delete\": \"Delete\",\n  \"demoSite\": \"هذا هو الموقع التجريبي, قم بتجربتة!\",\n  \"removeDelegation\": \"إزالة التفويض\",\n  \"confirmRemove\": \"هل انت متأكد من رغبتك بالغاء التفويض\",\n  \"removeSpace\": \"للمساحة {0}\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum reached\",\n  \"options\": \"خيار(ات)\",\n  \"votingPower\": \"قوة التصويت الخاصة بك\",\n  \"comment\": {\n    \"placeholder\": \"Share your reason (optional)\"\n  },\n  \"receipt\": \"الإيصال\",\n  \"relayer\": \"المرحّل\",\n  \"verifyOnMycrypto\": \"تحقق من الإيصال على MyCrypto\",\n  \"verifyOnSignatorio\": \"تحقق على موقع\",\n  \"isCore\": \"النواة\",\n  \"notificationsBlocked\": \"متصفحك يحظر الإشعارات\",\n  \"notificationsNotSupported\": \"المتصفح الخاص بك لا يدعم الإشعارات\",\n  \"walletNotSupported\": \"Wallet is not supported\",\n  \"seeInExplorer\": \"See explorer\",\n  \"learnMore\": \"لمعرفة المزيد\",\n  \"logout\": \"تسجيل خروج\",\n  \"continue\": \"متابعة\",\n  \"add\": \"إضافة\",\n  \"edit\": \"تعديل\",\n  \"strategyParameters\": \"معايير الاستراتيجية\",\n  \"addAction\": \"أضف إجراء\",\n  \"removeAction\": \"إزالة الإجراء\",\n  \"yourChoice\": \"خيار {0}\",\n  \"targetAddress\": \"عنوان الهدف\",\n  \"value\": \"القيمة\",\n  \"date\": \"البيانات\",\n  \"marketDetails\": \"تفاصيل السوق\",\n  \"addMarket\": \"إضافة سوق\",\n  \"selectNetwork\": \"تحديد الشبكة\",\n  \"conditionId\": \"الشرط\",\n  \"basetokenAddress\": \"عنوان الرمز المميز الأساسي\",\n  \"quoteAddress\": \"اقتباس عنوان العملة\",\n  \"removeMarket\": \"إزالة السوق\",\n  \"back\": \"الرجوع للخلف\",\n  \"loading\": \"جاري التحميل...\",\n  \"predictedImpact\": \"الأثر المتوقع\",\n  \"marketSymbol\": \"سوق {0}\",\n  \"twoChoicesRequired\": \"هناك خياران مطلوبان لهذه الإضافة.\",\n  \"noResultsFound\": \"عفواً، لا يمكننا العثور على أي نتائج\",\n  \"createFirstProposal\": \"دعنا نقوم بإنشاء المنتج الأول الخاص بك\",\n  \"noSpacesJoined\": \"عفواً، لم تنضم إلى أي مساحات بعد\",\n  \"addFavorites\": \"إضافة للمفضلة\",\n  \"createdBy\": \"بواسطة: {0}\",\n  \"startIn\": \"البدء {0}\",\n  \"endIn\": \"إنهاء {0}\",\n  \"proposalTimeLeft\": \"{0} left\",\n  \"endedAgo\": \"ended {0}\",\n  \"proposalBy\": \"بواسطة: {0}\",\n  \"endDate\": \"نهاية {0}\",\n  \"defaultSkin\": \"المضهر الافتراضي\",\n  \"select\": \"حدد\",\n  \"language\": \"اللغة\",\n  \"agree\": \"أوافق\",\n  \"moderators\": \"Moderators\",\n  \"playground\": \"ساحة التجريب\",\n  \"strategyParams\": \"معايير الاستراتيجية\",\n  \"addresses\": \"العناوين\",\n  \"networkErrorPlayground\": \"خطأ في الشبكة - الرجاء فتح وحدة تحكم المتصفح للمزيد من المعلومات\",\n  \"upload\": \"تحميل\",\n  \"join\": \"إنضمام\",\n  \"joined\": \"انضم\",\n  \"leave\": \"غادِر\",\n  \"subspaces\": \"Sub-spaces\",\n  \"mainspace\": \"Main space\",\n  \"copyLink\": \"نسخ الرابط\",\n  \"duplicate\": \"Duplicate\",\n  \"joinedSpaces\": \"المساحات المشترك فيها\",\n  \"joinSpaces\": \"الانضمام إلى المساحات\",\n  \"setDelegationToSpace\": \"Limit delegation to a specific space\",\n  \"theCurrentNetwork\": \"the current network\",\n  \"optional\": \"(optional)\",\n  \"homeLoadmore\": \"Load more\",\n  \"confirmAction\": \"Confirm action\",\n  \"or\": \"or\",\n  \"share\": \"Share\",\n  \"shareOnTwitter\": \"Share on Twitter\",\n  \"shareOnLenster\": \"Share on Lenster\",\n  \"createButton\": \"Create\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Change wallet\",\n  \"createASpace\": \"Create a space\",\n  \"getStarted\": \"Get started\",\n  \"skip\": \"Skip\",\n  \"newSpaceNotice\": {\n    \"header\": \"Your space is live!\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Learn more in the {documentation} or join Snapshot {discord} for help.\",\n    \"gotIt\": \"Got it!\"\n  },\n  \"errors\": {\n    \"required\": \"حقل مطلوب\",\n    \"minLength\": \"Field is required\",\n    \"maxLength\": \"الحد الاعلى لعدد الاحرف {0}\",\n    \"pattern\": \"رمز غير صحيح\",\n    \"minItems\": \"الحد الأدنى {0} عنصر(ار) مطلوب\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"minStrategy\": \"تلزم خطة واحدة على الأقل.\",\n    \"website\": \"URL should be in the format https://www.example.com\",\n    \"format\": \"تنسيق غير صحيح\",\n    \"type\": \"الرمز غير صحيح\",\n    \"unsupportedImageType\": \"File type not supported, Supported formats are jpeg, jpg and png\",\n    \"invalidAddress\": \"Invalid address\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Title\",\n    \"discussion\": \"Discussion (optional)\",\n    \"categorie(s)\": \"حدد حتى 2 فئة (فئات)\",\n    \"proposalDescription\": \"Description (optional)\",\n    \"preview\": \"معاينة\",\n    \"choices\": \"الخيارات\",\n    \"addChoice\": \"إضافة اختيار\",\n    \"startDate\": \"حدد تاريخ البدء\",\n    \"endDate\": \"حدد تاريخ انتهاء\",\n    \"startTime\": \"حدد موعد البدء\",\n    \"endTime\": \"حدد موعد الانتهاء\",\n    \"publish\": \"نشر\",\n    \"untitled\": \"Untitled\",\n    \"snapshotBlock\": \"رقم القطعة (البلوك) في لمحة سلسلة البلوكشين\",\n    \"voting\": \"Voting\",\n    \"votingSystem\": \"Voting system\",\n    \"choice\": \"Choice {0}\",\n    \"period\": \"Voting period\",\n    \"start\": \"Start\",\n    \"end\": \"End\",\n    \"days\": \"Days\",\n    \"hours\": \"Hours\",\n    \"minutes\": \"Minutes\",\n    \"schedule\": \"Schedule proposal\",\n    \"delayEnforced\": \"The space enforces a delay until voting can start\",\n    \"periodEnforced\": \"The space enforces the duration of the voting period\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"Edit\",\n    \"continue\": \"Continue\",\n    \"now\": \"Now\",\n    \"votingPeriodExplainer\": \"This is the time period in which users will be able to vote. The proposal will be visible and pending before the start of the voting period.\",\n    \"uploadImageExplainer\": \"Attach images by dragging & dropping, selecting or pasting them.\",\n    \"uploading\": \"Uploading image\",\n    \"markdown\": \"Styling with Markdown is supported\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"يجب أن تكون عضوا في المساحة لتقديم اقتراح.\",\n        \"minScore\": \"تحتاج إلى الحد الأدنى من {0} {1} لتقديم اقتراح.\"\n      },\n      \"customValidation\": \"تحتاج إلى اجتياز المصادقة على الاقتراح من أجل تقديم اقتراح.\",\n      \"executionError\": \"Verifying your eligibility to create proposals in this space has failed. This is likely due to a misconfigured strategy.\"\n    },\n    \"errorGettingSnapshot\": \"We encountered an error while fetching the snapshot block number which is needed to calculate your voting power. Please try again later.\"\n  },\n  \"delegate\": {\n    \"header\": \"تفويض\",\n    \"selectDelegate\": \"اختر تفويض\",\n    \"to\": \"إلى\",\n    \"addressPlaceholder\": \"عنوان أو اسم ENS\",\n    \"delegations\": \"التفويض(التفويضات)\",\n    \"allSpaces\": \"لجميع المساحات\",\n    \"delegated\": \"التفويض لنفسك\",\n    \"pendingTransaction\": \"لا توجد معاملات معلقة | 1 معاملات معلقة | {count} معاملات معلقة\",\n    \"topDelegates\": \"المندوب الأعلى\",\n    \"noDelegatesFoundFor\": \"No delegates found for {0}\",\n    \"noValidEns\": \"Not a valid ENS address.\",\n    \"noValidAddress\": \"Not a valid address\",\n    \"delegateToSelf\": \"You cannot delegate to yourself\",\n    \"delegateToSelfAddress\": \"You cannot delegate to your own ENS address\",\n    \"noValidSpaceId\": \"Not a valid space ID\",\n    \"noDelegationsAndDelegates\": \"Can't find your delegations and delegates? Make sure you are connected to the correct network.\",\n    \"delegateNotSupported\": \"Delegation is currently not supported for {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"سجل صوتك\",\n    \"vote\": \"صوت\",\n    \"startDate\": \"تاريخ البدء\",\n    \"endDate\": \"تاريخ الانتهاء\",\n    \"votingSystem\": \"نظام التصويت\",\n    \"privacy\": \"Privacy\",\n    \"invalidChoice\": \"Invalid choice\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Your vote is in!\",\n      \"gnosisSafeTitle\": \"Your vote is pending...\",\n      \"gnosisSafeDescription\": \" Votes with a Safe require additional signers and will be visible once the transaction is confirmed\",\n      \"seeQueue\": \"See queued transactions\",\n      \"tips\": {\n        \"1\": \"Votes can be changed while the proposal is active\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"المقترحات\",\n    \"new\": \"اقتراح جديد\",\n    \"noProposals\": \"لا توجد أي اقتراحات هنا حتى الآن!\",\n    \"createProposal\": \"إنشاء إقتراح\",\n    \"showMore\": \"Show more\",\n    \"showLess\": \"Show less\",\n    \"states\": {\n      \"all\": \"الكل\",\n      \"core\": \"النواة\",\n      \"community\": \"المجتمع\",\n      \"active\": \"نشط\",\n      \"pending\": \"في إنتظار المُراجعة\",\n      \"closed\": \"مغلق\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Notifications\",\n    \"noNotifications\": \"You have no notifications\",\n    \"proposalStarted\": \"proposal has started:\",\n    \"proposalEnded\": \"proposal has ended:\",\n    \"markAllAsRead\": \"Mark all as read\",\n    \"all\": \"All\",\n    \"unread\": \"Unread\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"join\",\n    \"actionCreate\": \"create a proposal in\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"الإعدادات\",\n    \"editController\": \"Edit controller\",\n    \"connectWithSpaceOwner\": \"You are in view only mode, to modify space settings connect with a controller or admin wallet.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"edit the space settings\",\n      \"create\": \"create a proposal\",\n      \"vote\": \"vote on this proposal\"\n    },\n    \"currentSpaceControllerIs\": \"The current space controller is {address}\",\n    \"newController\": \"New controller\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Set\",\n    \"profile\": \"الملف الشخصي\",\n    \"avatar\": \"الصورة الرمزية\",\n    \"name\": {\n      \"label\": \"Name\",\n      \"placeholder\": \"e.g. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"About\",\n      \"placeholder\": \"What is your organisation about?\"\n    },\n    \"categories\": {\n      \"label\": \"Categorie(s)\",\n      \"select\": \"Select categorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Terms of service\",\n      \"information\": \"Users will be required to accept these terms once before they can create a proposal or cast a vote\"\n    },\n    \"hideSpace\": \"إخفاء المساحة من الصفحة الرئيسية\",\n    \"links\": \"Social accounts\",\n    \"subspaces\": {\n      \"label\": \"Sub-spaces\",\n      \"information\": \"Sub-spaces will only be shown once configured on both the main space and the sub-space(s). {docs}\",\n      \"parent\": {\n        \"label\": \"Main space\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"The space that this space is a sub-space of will be displayed on the space page\"\n      },\n      \"children\": {\n        \"label\": \"Sub-spaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Related Sub-spaces listed here will be displayed on the space page\"\n      }\n    },\n    \"website\": \"Website\",\n    \"strategies\": {\n      \"label\": \"Strategie(s)\",\n      \"information\": \"Strategies are used to determine voting power or whether a user is eligible to create a proposal\"\n    },\n    \"network\": {\n      \"label\": \"Network\",\n      \"information\": \"The defaul network used for this space. Networks can also be specified in individual strategies\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbol\",\n      \"information\": \"The default symbol used for this space, usually the token symbol i.e. BAL for Balancer\"\n    },\n    \"strategiesList\": \"Select up to 8 strategies\",\n    \"votingPowerIsCumulative\": \"Voting power is cumulative\",\n    \"addStrategy\": \"إضافة استراتيجية\",\n    \"testInPlayground\": \"Test in playground\",\n    \"admins\": {\n      \"label\": \"Admins\",\n      \"information\": \"Admins are able to modify the space settings and manage the space's proposals\"\n    },\n    \"authors\": {\n      \"label\": \"Authors\",\n      \"information\": \"Authors are always able to create proposals\"\n    },\n    \"proposalValidation\": \"المصادقة على المقترح\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Threshold\",\n      \"information\": \"The minimum amount of voting power required to create a proposal\"\n    },\n    \"allowOnlyAuthors\": \"Allow only authors to submit a proposal\",\n    \"editValidation\": \"تعديل المصادقة\",\n    \"selectValidation\": \"اختر المصادقة\",\n    \"validationParameters\": \"معايير المصادقة\",\n    \"voting\": \"التصويت\",\n    \"votingDelay\": \"تأخير التصويت\",\n    \"votingPeriod\": \"فترة التصويت\",\n    \"hours\": \"Hours\",\n    \"days\": \"Days\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"The minimum amount of voting power required for the proposal to pass\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"The type of voting system used for this space. (Enforced on all future proposals)\"\n    },\n    \"anyType\": \"أيّما\",\n    \"hideAbstain\": \"Ignore abstain votes in basic voting results\",\n    \"customDomain\": \"نطاق مخصص\",\n    \"domain\": {\n      \"label\": \"Domain name\",\n      \"placeholder\": \"e.g. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"المظهر\",\n    \"treasuries\": {\n      \"label\": \"Treasury\",\n      \"add\": \"Add treasury\",\n      \"edit\": \"Edit treasury\",\n      \"information\": \"Add treasuries of your organization to show them in your space\"\n    },\n    \"addPlugin\": \"أضف إضافة\",\n    \"editPlugin\": \"تعديل إضافة\",\n    \"pluginParameters\": \"إعدادات الإضافة\",\n    \"proposal\": {\n      \"title\": \"Proposal\",\n      \"guidelines\": {\n        \"title\": \"Guidelines\",\n        \"information\": \"Display a link to your guidelines on proposal creation to help users understand what constitutes a good/valid proposal\"\n      },\n      \"template\": {\n        \"title\": \"Template\",\n        \"information\": \"Start every proposal with a template to help users understand what information is required\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"مثال yam.eth\",\n    \"chooseExistingEns\": \"Choose one of your existing ENS domains to create a space with:\",\n    \"useSingleExistingEns\": \"Use your existing ENS domain:\",\n    \"orRegisterNewEns\": \"Or register a new domain:\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"To create a space, you first need an ENS domain. Enter one below and follow the ENS registration instructions.\",\n    \"createASpace\": \"إنشاء مساحة\",\n    \"registerEnsButton\": \"Register\",\n    \"supportedEnsTLDs\": \"Supported domain endings\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or join Snapshot {discord}.\",\n    \"setSpaceController\": \"Space controller\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \" The space controller is the account that will be able to manage the space settings. Additional space controllers (admins) can be added later.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"When creating a space with a Gnosis Safe it's recommended to set the safe address as the space controller. If you don't, you need to follow some additional steps. {link}\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"Setting the controller requires a transaction on the {network} which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choose ENS address\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. {address}\",\n    \"controllerAddress\": \"Controller address\",\n    \"updateController\": \"Update controller\",\n    \"seeOnEns\": \"See on ENS\",\n    \"goToSettings\": \"Go to settings\",\n    \"setSpaceProfile\": \"Customize your space\",\n    \"waitForTransaction\": \"The transaction need to confirm before you can create your space. {txUrl}\",\n    \"pleaseWaitMessage\": \"This can take a few minutes, please wait while the transaction is being confirmed\",\n    \"notControllerAddress\": \"Please connect with the controller address {wallet} to create the space.\",\n    \"fillCurrentAccount\": \"Use currently logged in account\",\n    \"domain\": {\n      \"title\": \"Setup your space domain\",\n      \"ensMessage\": \" One thing you need before you can create your own space, is an ENS domain on Ethereum mainnet.\",\n      \"ensMessageTestnet\": \"You can also {link} on the Goerli testnet and mess with things there first.\",\n      \"tryDemo\": \"try the demo\",\n      \"yourExistingSpaces\": \"Your existing spaces\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"How would you like to setup your voting strategy?\",\n      \"subtitle\": \"You can change your strategy settings any time.\",\n      \"blockTitle\": \"Setup voting strategy\",\n      \"onePersonOneVote\": {\n        \"title\": \"One person, one vote\",\n        \"description\": \"Manage a whitelist of people who can vote or simply allow any address to vote. Every vote is equal and no token is required\",\n        \"whitelistInformation\": \"Specify a number of accounts that can vote\",\n        \"ticketInformation\": \"Any account can vote\",\n        \"votesEqualInfo\": \"Each vote is equal and no token is required\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Token weighted voting\",\n        \"description\": \"Votes are weighted by a token. The token can be an ERC-20, ERC-721 or ERC-1155 token standard\",\n        \"tokenNotFound\": \"Token not found\",\n        \"seeOnEtherscan\": \"See on Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Custom setup\",\n        \"description\": \"Select up to 8 strategies with a wide range of options. If you can't find the right strategy for your use case, you can create your own\"\n      }\n    },\n    \"validationTitle\": \"Who can manage this space and create proposals?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Edit profile\",\n    \"viewProfile\": \"View profile\",\n    \"about\": {\n      \"header\": \"About\",\n      \"joinedSpaces\": \"Joined spaces\",\n      \"createdSpaces\": \"Created spaces\",\n      \"biography\": \"Bio\",\n      \"notJoinSpacesYet\": \"Hasn't joined any spaces yet\",\n      \"notCreatedSpacesYet\": \"Hasn't created any spaces yet\",\n      \"delegatorNetworkInfo\": \"Change by switching network in your wallet\",\n      \"delegate\": \"Delegate\",\n      \"delegated\": \"Delegated\",\n      \"delegateTo\": \"Delegate to\",\n      \"delegateFor\": \"Delegator for\",\n      \"noDelegatorsMessage\": \"No delegators on {network}\",\n      \"notSupportedNetwork\": \"Delegation currently isn't supported on {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activity\",\n      \"votedFor\": \"Voted {choice}\",\n      \"today\": \"Today\",\n      \"thisWeek\": \"This week\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"No activity yet\"\n    },\n    \"settings\": {\n      \"header\": \"Edit profile\",\n      \"name\": \"Name\",\n      \"biography\": \"Bio\",\n      \"namePlaceholder\": \"Enter name\",\n      \"bioPlaceholder\": \"Tell your story\",\n      \"change\": \"Change\",\n      \"remove\": \"Remove\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"لقد نجحت!\",\n    \"copied\": \"تم النسخ!\",\n    \"proposalDeleted\": \"تم حذف الاقتراح\",\n    \"somethingWentWrong\": \"عفوا، حدث خطأ ما!\",\n    \"saved\": \"حفظ!\",\n    \"delegationSuccess\": \"تم التفويض بنجاح\",\n    \"delegationRemoved\": \"تمت إزالة التفويض\",\n    \"proposalCreated\": \"خلقت عمدا\",\n    \"voteSuccessful\": \"تم اعتماد{0} الخاص بك!\",\n    \"ensSet\": \"ENS text record was successfully set\",\n    \"transactionSent\": \"Transaction sent\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"إنشاء استراتيجية\",\n    \"createSkin\": \"انشاء مظهر\",\n    \"addNetwork\": \"إضافة شبكة\",\n    \"createPlugin\": \"إنشاء إضافة\",\n    \"strategies\": \"استراتيجية(استراتيجيات)\",\n    \"skins\": \"المظهر(المظاهر)\",\n    \"networks\": \"شبكة (شبكات)\",\n    \"plugins\": \"ملحق(ات)\",\n    \"results\": \"النتيجة (النتائج)\",\n    \"category\": \"Category\",\n    \"categories\": {\n      \"all\": \"All\",\n      \"protocol\": \"Protocol\",\n      \"social\": \"Social\",\n      \"investment\": \"Investment\",\n      \"grant\": \"Grant\",\n      \"service\": \"Service\",\n      \"media\": \"Media\",\n      \"creator\": \"Creator\",\n      \"collector\": \"Collector\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"اختيار نظام التصويت\",\n    \"single-choice\": \"تصويت الخيار الواحد\",\n    \"approval\": \"الموافقة على التصويت\",\n    \"quadratic\": \"التصويت الرباعي\",\n    \"ranked-choice\": \"تصويت خيار التصنيف\",\n    \"weighted\": \"التصويت المرجح\",\n    \"basic\": \"التصويت الأساسي\",\n    \"description\": {\n      \"single-choice\": \"ويجوز لكل ناخب أن يختار خيارا واحدا فقط.\",\n      \"approval\": \"ويجوز لكل ناخب أن يختار أي عدد من الخيارات.\",\n      \"quadratic\": \"يمكن لكل ناخب أن يوزع قوة التصويت على أي عدد من الخيارات. يتم حساب النتائج بالنظام الرباعي.\",\n      \"ranked-choice\": \"يمكن لكل ناخب اختيار وترتيب أي عدد من الخيارات. يتم حساب النتائج بطريقة العد الفوري.\",\n      \"weighted\": \"ويمكن لكل ناخب أن يوزع قوة التصويت على أي عدد من الخيارات.\",\n      \"basic\": \"التصويت بخيار واحد من ثلاثة خيارات: مع،ضد أو الامتناع عن التصويت\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Privacy\",\n    \"title\": \"Select voting privacy\",\n    \"information\": \"The type of privacy used on proposals. (Enforced on all future proposals)\",\n    \"any\": \"Any\",\n    \"none\": \"None\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Choices are encrypted and only visible once the voting period is over\",\n      \"tooltip\": \"This proposal has Shutter privacy enabled. All votes will be encrypted until the voting period has ended and the final score is calculated\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select voting validation\",\n    \"information\": \"The type of validation used to determine if a user can vote. (Enforced on all future proposals)\",\n    \"any\": {\n      \"label\": \"Anyone can vote\",\n      \"description\": \"Anyone with voting power can cast a vote.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement to vote on this proposal. \"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your proposals from spam and vote manipulation by requiring users to have a Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold} and {operator} of the following stamps to vote on this proposal: {stamps}. \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"النتيجة الحالية\",\n    \"currentBond\": \"السندات الحالية\",\n    \"finalizedIn\": \"تم الانتهاء من {0}\",\n    \"executableIn\": \"قابل للتنفيذ {0}\",\n    \"finalOutcome\": \"النتائج\",\n    \"nextBond\": \"السندات التي تحدد النتائج\",\n    \"setOutcomeTo\": \"تعيين النتيجة إلى\",\n    \"claimBond\": \"المطالبة بالسندات\",\n    \"addBatch\": \"أضف دفعة المعاملات\",\n    \"batch\": \"دفعة المعاملات\",\n    \"transactions\": \"العمليات المالية\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"العنوان غير صحيح\",\n    \"invalidAmount\": \"مبلغ غير صحيح\",\n    \"invalidValue\": \"قيمة غير صحيحة\",\n    \"invalidAbi\": \"ABI غير صحيح\",\n    \"invalidData\": \"بيانات غير صحيحة\",\n    \"value\": \"القيمة (wei)\",\n    \"data\": \"البيانات\",\n    \"noCollectibles\": \"لا توجد مقتنيات\",\n    \"asset\": \"الأصل\",\n    \"amount\": \"مبلغ\",\n    \"type\": \"النوع\",\n    \"transferFunds\": \"تحويل الأموال\",\n    \"transferNFT\": \"تحويل NFT\",\n    \"contractInteraction\": \"تفاعل العقد\",\n    \"rawTransaction\": \"المعاملة الخام\",\n    \"addTransaction\": \"أضف معاملة\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei إلى {address}\",\n      \"transferFunds\": \"نقل {amount} {tokenSymbol} إلى {address}\",\n      \"transferNFT\": \"إرسال {name} #{id} إلى {address}\",\n      \"raw\": \"إرسال {amount} wei إلى {address}\"\n    },\n    \"labels\": {\n      \"request\": \"اطلب التنفيذ\",\n      \"setOutcome\": \"تعيين النتيجة\",\n      \"changeOutcome\": \"تغيير النتيجة\",\n      \"executeTxs\": \"تنفيذ دفعة المعاملات {0} من {1}\",\n      \"executed\": \"تم تنفيذ جميع المعاملات\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"حدث خطأ ما\",\n      \"connectWallet\": \"قم بتوصيل المحفظة لرؤية تفاصيل التنفيذ\",\n      \"switchChain\": \"قم بتغيير محفظتك إلى {0} لطلب التنفيذ\",\n      \"question\": \"هل تم إقرار هذا الاقتراح وهل استوفى\",\n      \"criteria\": \"معايير القبول؟\",\n      \"proposalPassed\": \"Did the proposal pass?\",\n      \"expired\": \"The proposal has expired\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to confirm the proposal passed\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"requestToolTip\": \"This will propose the transactions from this Snapshot vote on-chain. After a challenge window, if the proposal is valid, the transactions can be executed and your bond will be returned.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"لم يتم إعداد POAP لهذا الاقتراح بعد :'(\",\n    \"no_voted_header\": \"التصويت للحصول على هذا POAP\",\n    \"unclaimed_header\": \"Mint your I voted POAP\",\n    \"claimed_header\": \"Congratulations! The POAP has been minted to your collection\",\n    \"loading_header\": \"يتم سك العملة POAP في مجموعتك\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"تصفح المجموعة\",\n    \"success_claim\": \"تم سك العملة POAP إلى مجموعتك\",\n    \"error_claim\": \"حدثت مشكلة في سك العملة الرمزية\"\n  },\n  \"progress\": {\n    \"progress\": \"Progress\",\n    \"inProgress\": \"In Progress\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"New Step\",\n    \"description\": \"Description\",\n    \"add\": \"Add\",\n    \"deleteStep\": \"Delete Step\",\n    \"deleteConfirm\": \"Are you sure you want to delete?\",\n    \"delete\": \"Delete\",\n    \"cancel\": \"Cancel\",\n    \"comeBack\": \"Come back after voting is complete to see how this proposal is progressing!\",\n    \"confirmSignature\": \"Signing this message will allow us to authorize your request to update the progress of your proposal.\",\n    \"wentWrong\": \"Oops something went wrong.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"الرسوم البيانية\",\n    \"noVotesYet\": \"لا توجد تصويتات لعرضها حتى الآن.\",\n    \"totalVotesPerDay\": \"إجمالي الأصوات في اليوم\",\n    \"shareOfVotingPower\": \"مشاركة قوة التصويت\",\n    \"votingPowerPerDay\": \"قوة التصويت في اليوم\"\n  },\n  \"comment_box\": {\n    \"title\": \"صندوق التعليق\",\n    \"add\": \"أضف تعليقك هنا\",\n    \"submit\": \"ارسال\",\n    \"preview\": \"المعاينة\",\n    \"continue_editing\": \"الاستمرار في التعديل\",\n    \"edit\": \"عدل ردك هنا\",\n    \"edit_button\": \"تعديل\",\n    \"dismiss\": \"رفض\",\n    \"delete\": \"حذف\",\n    \"add_reply\": \"أضف ردك هنا\",\n    \"edit_comment\": \"تعديل التعليق\",\n    \"edit_modal\": \"هل أنت متأكد أنك تريد تعديل؟\",\n    \"yes\": \"نعم\",\n    \"no\": \"لا\",\n    \"delete_comment\": \"حذف التعليق\",\n    \"delete_modal\": \"هل أنت متأكد من أنك تريد حذف؟\",\n    \"error\": \"عفوا، حدث خطأ ما\",\n    \"replies\": \"ردود\",\n    \"hide\": \"إخفاء\",\n    \"show\": \"إظهار\",\n    \"reply\": \"رد\",\n    \"load_more\": \"تحميل المزيد\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Create a space\",\n      \"timeline\": \"Timeline\",\n      \"notifications\": \"Notifications\",\n      \"explore\": \"Explore\",\n      \"playground\": \"Playground\",\n      \"space\": {\n        \"create\": \"Create {space} proposal\",\n        \"about\": \"About {space}\",\n        \"proposals\": \"{space} Proposals\",\n        \"proposal\": \"{space} proposal: {proposal}\",\n        \"settings\": \"{space} Settings\"\n      },\n      \"strategy\": \"{key} strategy\",\n      \"delegate\": \"Delegate\",\n      \"ranking\": \"Ranking\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Track proposals for {spaceName}\",\n    \"text\": \"Receive notifications every time a new proposal is created or ends\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 second | {n} seconds\",\n    \"minute\": \"1 minute | {n} minutes\",\n    \"hour\": \"1 hour | {n} hours\",\n    \"day\": \"1 day | {n} days\",\n    \"week\": \"1 week | {n} weeks\",\n    \"month\": \"1 month | {n} months\",\n    \"year\": \"1 year | {n} years\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Unsupported network\",\n    \"switchNetworkToNetwork\": \"To continue, you need to change the network in your wallet to {network}.\",\n    \"switchToNetwork\": \"Switch to {network}\",\n    \"goToDemoSite\": \"Go to demo website\"\n  },\n  \"treasury\": {\n    \"title\": \"Treasury\",\n    \"wallets\": {\n      \"title\": \"Wallets\",\n      \"empty\": \"This space doesn't have a treasury yet\",\n      \"addTreasury\": \"Add a treasury\"\n    },\n    \"assets\": {\n      \"title\": \"Assets\",\n      \"empty\": \"There are no assets in this contract\"\n    },\n    \"24hChange\": \"24h change\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Your email\",\n    \"title\": \"Get the latest Snapshot updates\"\n  },\n  \"joinCommunity\": \"Join Snapshot community\",\n  \"header\": {\n    \"title\": \"Where decisions get made\",\n    \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot is a decentralized governance platform that makes it easy to create and vote on proposals - all without spending a fortune on gas fees! Plus, our flexible system supports various voting types and strategies, so you can tailor the voting process to your needs.\",\n    \"subHeader\": \"Governance should be a snap\",\n    \"subDescription\": \"Web3 governance doesn't have to be complicated. Snapshot is the perfect solution for organizations looking for an easy and efficient way to govern their community or organization.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Resources\",\n    \"about\": \"About\",\n    \"blog\": \"Blog\",\n    \"jobs\": \"Jobs\",\n    \"discussions\": \"Discussions\",\n    \"github\": \"GitHub\",\n    \"docs\": \"Docs\",\n    \"support\": \"Support\",\n    \"hiring\": \"Join us!\"\n  }\n}\n"
  },
  {
    "path": "src/locales/de-DE.json",
    "content": "{\n  \"searchPlaceholder\": \"Suche\",\n  \"spaceCount\": \"bereich(e) {0}\",\n  \"createSpace\": \"Bereich erstellen\",\n  \"backToHome\": \"Startseite\",\n  \"actions\": \"Aktionen\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"Resultate\",\n  \"resultsError\": \"Die Ergebnisse konnten nicht berechnet werden. Dies liegt oft an einer falsch konfigurierten Strategie oder einem nicht reagierenden RPC-Knoten, der an der Strategie beteiligt ist.\",\n  \"resultsCalculating\": \"Final results are being calculated. If you still see this message after a few minutes contact the space admin.\",\n  \"votingPowerFailedMessage\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"votingValidationFailedMessage\": \"There was an error on our side and we could not verify if you are eligible to vote. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n  \"notValidVoterMessage\": \"Oops, you don't seem to be eligible to vote on this proposal.\",\n  \"getHelp\": \"Hilfe\",\n  \"retry\": \"Erneut versuchen\",\n  \"currentResults\": \"Aktuelle Ergebnisse\",\n  \"reset\": \"Zurücksetzen\",\n  \"close\": \"Close\",\n  \"save\": \"Speichern\",\n  \"author\": \"Autor\",\n  \"next\": \"Weiter\",\n  \"choice\": \"Choice\",\n  \"submit\": \"Absenden\",\n  \"plugins\": \"Plugins\",\n  \"information\": \"Informationen\",\n  \"confirm\": \"Bestätigen\",\n  \"snapshot\": \"Momentaufnahme\",\n  \"strategies\": \"Strategie(n)\",\n  \"strategiesPage\": \"Strategien\",\n  \"space\": \"Bereiche\",\n  \"spaces\": \"Bereiche\",\n  \"verifiedSpace\": \"Verifizierter Space\",\n  \"warningSpace\": \"This space has been flagged as potentially malicious. Proceed with caution.\",\n  \"version\": \"Version\",\n  \"timeline\": \"Timeline\",\n  \"ended\": \"beendet\",\n  \"started\": \"begonnen\",\n  \"filters\": \"Filter\",\n  \"allSpaces\": \"Alle Bereiche\",\n  \"submitOnchain\": \"On-chain senden\",\n  \"inSpaces\": \"In {0} Bereich(en)\",\n  \"votes\": \"Stimmen\",\n  \"seeMore\": \"Mehr anzeigen\",\n  \"seeAll\": \"Alle anzeigen\",\n  \"network\": \"Netzwerk\",\n  \"networks\": \"Netzwerke\",\n  \"skins\": \"Skins\",\n  \"spaceMembers\": \"Members\",\n  \"members\": \"Keine Mitglieder| {count} Mitglied | {count} Mitglied\",\n  \"editStrategy\": \"Strategie bearbeiten\",\n  \"invalidProposals\": \"Ungültige Vorschläge\",\n  \"account\": \"Konto\",\n  \"create3box\": \"Profil auf 3Box erstellen\",\n  \"view3box\": \"Profil auf 3Box ansehen\",\n  \"edit3box\": \"Profil auf 3Box bearbeiten\",\n  \"connectWallet\": \"Wallet verbinden\",\n  \"toggleSkin\": \"Skin ein/aus\",\n  \"about\": \"About\",\n  \"license\": \"Lizenz\",\n  \"showMore\": \"Show more\",\n  \"voted\": \"Voted\",\n  \"reload\": \"Reload\",\n  \"ipfsServer\": \"IPFS server\",\n  \"hub\": \"Hub\",\n  \"cancel\": \"Abbrechen\",\n  \"delete\": \"Delete\",\n  \"demoSite\": \"Dies ist eine Demo-Seite, probiere es aus!\",\n  \"removeDelegation\": \"Delegation entfernen\",\n  \"confirmRemove\": \"Sind Sie sicher, dass Sie Ihre Delegation entfernen möchten zu\",\n  \"removeSpace\": \"für den Bereich {0}\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum reached\",\n  \"options\": \"Option(en)\",\n  \"votingPower\": \"Deine Stimmmacht\",\n  \"comment\": {\n    \"placeholder\": \"Share your reason (optional)\"\n  },\n  \"receipt\": \"Beleg\",\n  \"relayer\": \"Übertrager\",\n  \"verifyOnMycrypto\": \"Beleg auf MyCrypto überprüfen\",\n  \"verifyOnSignatorio\": \"Auf signator.io überprüfen\",\n  \"isCore\": \"Kern\",\n  \"notificationsBlocked\": \"Dein Browser blockiert Benachrichtigungen\",\n  \"notificationsNotSupported\": \"Dein Browser unterstützt keine Benachrichtigungen\",\n  \"walletNotSupported\": \"Wallet wird nicht unterstützt\",\n  \"seeInExplorer\": \"Im Explorer ansehen\",\n  \"learnMore\": \"Mehr erfahren\",\n  \"logout\": \"Ausloggen\",\n  \"continue\": \"Weiter\",\n  \"add\": \"Hinzufügen\",\n  \"edit\": \"Bearbeiten\",\n  \"strategyParameters\": \"Strategieparameter\",\n  \"addAction\": \"Aktion hinzufügen\",\n  \"removeAction\": \"Aktion entfernen\",\n  \"yourChoice\": \"Auswahl {0}\",\n  \"targetAddress\": \"Zieladresse\",\n  \"value\": \"Betrag\",\n  \"date\": \"Daten\",\n  \"marketDetails\": \"Marktdetails\",\n  \"addMarket\": \"Markt hinzufügen\",\n  \"selectNetwork\": \"Netzwerk auswählen\",\n  \"conditionId\": \"Bedingungs-ID\",\n  \"basetokenAddress\": \"Basis-Token-Adresse\",\n  \"quoteAddress\": \"Adresse der Angebotswährung\",\n  \"removeMarket\": \"Markt entfernen\",\n  \"back\": \"Zurück\",\n  \"loading\": \"Lädt...\",\n  \"predictedImpact\": \"Vorhergesehener Einfluss\",\n  \"marketSymbol\": \"{0} Markt\",\n  \"twoChoicesRequired\": \"Für dieses Plugin sind zwei Optionen erforderlich.\",\n  \"noResultsFound\": \"Hoppla, wir konnten keine Ergebnisse finden\",\n  \"createFirstProposal\": \"Erstelle deine erste Abstimmung\",\n  \"noSpacesJoined\": \"Hoppla, du bist noch keinem Bereich beigetreten\",\n  \"addFavorites\": \"Favoriten hinzufügen\",\n  \"createdBy\": \"Von {0}\",\n  \"startIn\": \"start {0}\",\n  \"endIn\": \"ende {0}\",\n  \"proposalTimeLeft\": \"{0} verbleibend\",\n  \"endedAgo\": \"endete {0}\",\n  \"proposalBy\": \"von {0}\",\n  \"endDate\": \"ende {0}\",\n  \"defaultSkin\": \"Standard-Skin\",\n  \"select\": \"Auswählen\",\n  \"language\": \"Sprache\",\n  \"agree\": \"Ich stimme zu\",\n  \"moderators\": \"Moderators\",\n  \"playground\": \"Spielplatz\",\n  \"strategyParams\": \"Strategieparameter\",\n  \"addresses\": \"Adressen\",\n  \"networkErrorPlayground\": \"Netzwerkfehler - Bitte öffne deine Browserkonsole für weitere Informationen\",\n  \"upload\": \"Hochladen\",\n  \"join\": \"Beitreten\",\n  \"joined\": \"Beigetreten\",\n  \"leave\": \"Verlassen\",\n  \"subspaces\": \"Sub-spaces\",\n  \"mainspace\": \"Main space\",\n  \"copyLink\": \"Link kopieren\",\n  \"duplicate\": \"Duplicate\",\n  \"joinedSpaces\": \"Beigetretene Bereiche\",\n  \"joinSpaces\": \"Bereiche beitreten\",\n  \"setDelegationToSpace\": \"Delegation auf einen bestimmten Space beschränken\",\n  \"theCurrentNetwork\": \"aktuelles Netzwerk\",\n  \"optional\": \"(optional)\",\n  \"homeLoadmore\": \"Mehr\",\n  \"confirmAction\": \"Bestätigen\",\n  \"or\": \"oder\",\n  \"share\": \"Teilen\",\n  \"shareOnTwitter\": \"Share on Twitter\",\n  \"shareOnLenster\": \"Share on Lenster\",\n  \"createButton\": \"Create\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Change wallet\",\n  \"createASpace\": \"Create a space\",\n  \"getStarted\": \"Get started\",\n  \"skip\": \"Skip\",\n  \"newSpaceNotice\": {\n    \"header\": \"Your space is live!\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Learn more in the {documentation} or join Snapshot {discord} for help.\",\n    \"gotIt\": \"Got it!\"\n  },\n  \"errors\": {\n    \"required\": \"Dieses Feld ist ein Pflichtfeld\",\n    \"minLength\": \"Field is required\",\n    \"maxLength\": \"Maximallänge ist {0}\",\n    \"pattern\": \"Ungültiges Zeichen\",\n    \"minItems\": \"Mindestens {0} Element(e) erforderlich\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"minStrategy\": \"Mindestens eine Strategie ist erforderlich.\",\n    \"website\": \"URL should be in the format https://www.example.com\",\n    \"format\": \"Ungültiges Format\",\n    \"type\": \"Invalid type\",\n    \"unsupportedImageType\": \"File type not supported, Supported formats are jpeg, jpg and png\",\n    \"invalidAddress\": \"Invalid address\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Title\",\n    \"discussion\": \"Discussion (optional)\",\n    \"categorie(s)\": \"Select up to 2 categorie(s)\",\n    \"proposalDescription\": \"Description (optional)\",\n    \"preview\": \"Vorschau\",\n    \"choices\": \"Auswahl\",\n    \"addChoice\": \"Auswahl hinzufügen\",\n    \"startDate\": \"Startdatum auswählen\",\n    \"endDate\": \"Enddatum auswählen\",\n    \"startTime\": \"Startzeit auswählen\",\n    \"endTime\": \"Endzeit auswählen\",\n    \"publish\": \"Veröffentlichen\",\n    \"untitled\": \"Unbenannt\",\n    \"snapshotBlock\": \"Momentaufnahme Blocknummer\",\n    \"voting\": \"Abstimmung\",\n    \"votingSystem\": \"Art der Abstimmung\",\n    \"choice\": \"Möglichkeit {0}\",\n    \"period\": \"Zeitraum der Abstimmung\",\n    \"start\": \"Start\",\n    \"end\": \"Ende\",\n    \"days\": \"Tage\",\n    \"hours\": \"Stunden\",\n    \"minutes\": \"Minuten\",\n    \"schedule\": \"Vorschlag planen\",\n    \"delayEnforced\": \"Der Space verlangt eine Verzögerung bevor die Abstimmung beginnen kann\",\n    \"periodEnforced\": \"Dieser Space benötigt eine Abstimmungsdauer\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"Bearbeiten\",\n    \"continue\": \"Weiter\",\n    \"now\": \"Jetzt\",\n    \"votingPeriodExplainer\": \"Dies ist der Zeitraum, in dem Benutzer abstimmen können. Der Vorschlag wird bereits vor Beginn der Abstimmung sichtbar sein.\",\n    \"uploadImageExplainer\": \"Attach images by dragging & dropping, selecting or pasting them.\",\n    \"uploading\": \"Uploading image\",\n    \"markdown\": \"Styling with Markdown is supported\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"You need to be an author of the space in order to submit a proposal.\",\n        \"minScore\": \"Sie müssen mindestens {0} {1} haben, um einen Vorschlag einzureichen.\"\n      },\n      \"customValidation\": \"Sie müssen Zugang haben, um einen Vorschlag einzureichen.\",\n      \"executionError\": \"Verifying your eligibility to create proposals in this space has failed. This is likely due to a misconfigured strategy.\"\n    },\n    \"errorGettingSnapshot\": \"We encountered an error while fetching the snapshot block number which is needed to calculate your voting power. Please try again later.\"\n  },\n  \"delegate\": {\n    \"header\": \"Delegieren\",\n    \"selectDelegate\": \"Wählen Sie eine Delegation\",\n    \"to\": \"An\",\n    \"addressPlaceholder\": \"Adresse oder ENS Name\",\n    \"delegations\": \"Ihre Delegation(en)\",\n    \"allSpaces\": \"Für alle Bereiche\",\n    \"delegated\": \"An dich delegiert\",\n    \"pendingTransaction\": \"keine ausstehende Transaktion | 1 ausstehende Transaktion | {count} ausstehende Transaktionen\",\n    \"topDelegates\": \"Top delegates\",\n    \"noDelegatesFoundFor\": \"Keine Delegierten für {0} gefunden\",\n    \"noValidEns\": \"Keine gültige ENS Adresse.\",\n    \"noValidAddress\": \"Keine gültige Adresse\",\n    \"delegateToSelf\": \"Du kannst nicht an dich selbst delegieren\",\n    \"delegateToSelfAddress\": \"Du kannst nicht an deine eigene ENS Adresse delegieren\",\n    \"noValidSpaceId\": \"Keine gültige Space ID\",\n    \"noDelegationsAndDelegates\": \"Du kannst keine Delegationen und Delegierten finden? Stelle sicher, dass du mit dem richtigen Netzwerk verbunden bist.\",\n    \"delegateNotSupported\": \"Auf {network} wird Delegation derzeit nicht unterstützt.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Deine Stimme abgeben\",\n    \"vote\": \"Abstimmen\",\n    \"startDate\": \"Anfangsdatum\",\n    \"endDate\": \"Enddatum\",\n    \"votingSystem\": \"Wahlsystem\",\n    \"privacy\": \"Privacy\",\n    \"invalidChoice\": \"Invalid choice\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Your vote is in!\",\n      \"gnosisSafeTitle\": \"Your vote is pending...\",\n      \"gnosisSafeDescription\": \" Votes with a Safe require additional signers and will be visible once the transaction is confirmed\",\n      \"seeQueue\": \"See queued transactions\",\n      \"tips\": {\n        \"1\": \"Votes can be changed while the proposal is active\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"Vorschläge\",\n    \"new\": \"Neuer Vorschlag\",\n    \"noProposals\": \"Es gibt noch keine Vorschläge!\",\n    \"createProposal\": \"Create proposal\",\n    \"showMore\": \"Mehr\",\n    \"showLess\": \"Weniger\",\n    \"states\": {\n      \"all\": \"Alle\",\n      \"core\": \"Kern\",\n      \"community\": \"Community\",\n      \"active\": \"Aktiv\",\n      \"pending\": \"Ausstehend\",\n      \"closed\": \"Geschlossen\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Notifications\",\n    \"noNotifications\": \"You have no notifications\",\n    \"proposalStarted\": \"proposal has started:\",\n    \"proposalEnded\": \"proposal has ended:\",\n    \"markAllAsRead\": \"Mark all as read\",\n    \"all\": \"All\",\n    \"unread\": \"Unread\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"join\",\n    \"actionCreate\": \"create a proposal in\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"Einstellungen\",\n    \"editController\": \"Edit controller\",\n    \"connectWithSpaceOwner\": \"You are in view only mode, to modify space settings connect with a controller or admin wallet.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"edit the space settings\",\n      \"create\": \"create a proposal\",\n      \"vote\": \"vote on this proposal\"\n    },\n    \"currentSpaceControllerIs\": \"The current space controller is {address}\",\n    \"newController\": \"New controller\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Set\",\n    \"profile\": \"Profil\",\n    \"avatar\": \"Avatar\",\n    \"name\": {\n      \"label\": \"Name\",\n      \"placeholder\": \"e.g. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"About\",\n      \"placeholder\": \"What is your organisation about?\"\n    },\n    \"categories\": {\n      \"label\": \"Categorie(s)\",\n      \"select\": \"Select categorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Terms of service\",\n      \"information\": \"Users will be required to accept these terms once before they can create a proposal or cast a vote\"\n    },\n    \"hideSpace\": \"Raum auf der Startseite ausblenden\",\n    \"links\": \"Social accounts\",\n    \"subspaces\": {\n      \"label\": \"Sub-spaces\",\n      \"information\": \"Sub-spaces will only be shown once configured on both the main space and the sub-space(s). {docs}\",\n      \"parent\": {\n        \"label\": \"Main space\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"The space that this space is a sub-space of will be displayed on the space page\"\n      },\n      \"children\": {\n        \"label\": \"Sub-spaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Related Sub-spaces listed here will be displayed on the space page\"\n      }\n    },\n    \"website\": \"Website\",\n    \"strategies\": {\n      \"label\": \"Strategie(s)\",\n      \"information\": \"Strategies are used to determine voting power or whether a user is eligible to create a proposal\"\n    },\n    \"network\": {\n      \"label\": \"Network\",\n      \"information\": \"The defaul network used for this space. Networks can also be specified in individual strategies\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbol\",\n      \"information\": \"The default symbol used for this space, usually the token symbol i.e. BAL for Balancer\"\n    },\n    \"strategiesList\": \"Select up to 8 strategies\",\n    \"votingPowerIsCumulative\": \"Voting power is cumulative\",\n    \"addStrategy\": \"Strategie hinzufügen\",\n    \"testInPlayground\": \"Test in playground\",\n    \"admins\": {\n      \"label\": \"Admins\",\n      \"information\": \"Admins are able to modify the space settings and manage the space's proposals\"\n    },\n    \"authors\": {\n      \"label\": \"Authors\",\n      \"information\": \"Authors are always able to create proposals\"\n    },\n    \"proposalValidation\": \"Vorschlagsvalidierung\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Threshold\",\n      \"information\": \"The minimum amount of voting power required to create a proposal\"\n    },\n    \"allowOnlyAuthors\": \"Nur Mitgliedern erlauben, einen Vorschlag einzureichen\",\n    \"editValidation\": \"Validierung bearbeiten\",\n    \"selectValidation\": \"Validierung auswählen\",\n    \"validationParameters\": \"Validierungsparameter\",\n    \"voting\": \"Voting\",\n    \"votingDelay\": \"Voting delay\",\n    \"votingPeriod\": \"Voting period\",\n    \"hours\": \"Hours\",\n    \"days\": \"Days\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"The minimum amount of voting power required for the proposal to pass\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"The type of voting system used for this space. (Enforced on all future proposals)\"\n    },\n    \"anyType\": \"Any\",\n    \"hideAbstain\": \"Ignore abstain votes in basic voting results\",\n    \"customDomain\": \"Custom domain\",\n    \"domain\": {\n      \"label\": \"Domain name\",\n      \"placeholder\": \"e.g. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"Skin\",\n    \"treasuries\": {\n      \"label\": \"Treasury\",\n      \"add\": \"Add treasury\",\n      \"edit\": \"Edit treasury\",\n      \"information\": \"Add treasuries of your organization to show them in your space\"\n    },\n    \"addPlugin\": \"Plugin hinzufügen\",\n    \"editPlugin\": \"Plugin bearbeiten\",\n    \"pluginParameters\": \"Plugin Parameter\",\n    \"proposal\": {\n      \"title\": \"Proposal\",\n      \"guidelines\": {\n        \"title\": \"Guidelines\",\n        \"information\": \"Display a link to your guidelines on proposal creation to help users understand what constitutes a good/valid proposal\"\n      },\n      \"template\": {\n        \"title\": \"Template\",\n        \"information\": \"Start every proposal with a template to help users understand what information is required\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"z.B. yam.eth\",\n    \"chooseExistingEns\": \"Choose one of your existing ENS domains to create a space with:\",\n    \"useSingleExistingEns\": \"Use your existing ENS domain:\",\n    \"orRegisterNewEns\": \"Or register a new domain:\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"To create a space, you first need an ENS domain. Enter one below and follow the ENS registration instructions.\",\n    \"createASpace\": \"Bereich erstellen\",\n    \"registerEnsButton\": \"Register\",\n    \"supportedEnsTLDs\": \"Supported domain endings\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or join Snapshot {discord}.\",\n    \"setSpaceController\": \"Space controller\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \" The space controller is the account that will be able to manage the space settings. Additional space controllers (admins) can be added later.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"When creating a space with a Gnosis Safe it's recommended to set the safe address as the space controller. If you don't, you need to follow some additional steps. {link}\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"Setting the controller requires a transaction on the {network} which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"END Adresse auswählen\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. {address}\",\n    \"controllerAddress\": \"Controller-Adresse\",\n    \"updateController\": \"Update controller\",\n    \"seeOnEns\": \"Auf ENS ansehen\",\n    \"goToSettings\": \"Go to settings\",\n    \"setSpaceProfile\": \"Customize your space\",\n    \"waitForTransaction\": \"The transaction need to confirm before you can create your space. {txUrl}\",\n    \"pleaseWaitMessage\": \"This can take a few minutes, please wait while the transaction is being confirmed\",\n    \"notControllerAddress\": \"Please connect with the controller address {wallet} to create the space.\",\n    \"fillCurrentAccount\": \"Use currently logged in account\",\n    \"domain\": {\n      \"title\": \"Setup your space domain\",\n      \"ensMessage\": \" One thing you need before you can create your own space, is an ENS domain on Ethereum mainnet.\",\n      \"ensMessageTestnet\": \"You can also {link} on the Goerli testnet and mess with things there first.\",\n      \"tryDemo\": \"try the demo\",\n      \"yourExistingSpaces\": \"Your existing spaces\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"How would you like to setup your voting strategy?\",\n      \"subtitle\": \"You can change your strategy settings any time.\",\n      \"blockTitle\": \"Setup voting strategy\",\n      \"onePersonOneVote\": {\n        \"title\": \"One person, one vote\",\n        \"description\": \"Manage a whitelist of people who can vote or simply allow any address to vote. Every vote is equal and no token is required\",\n        \"whitelistInformation\": \"Specify a number of accounts that can vote\",\n        \"ticketInformation\": \"Any account can vote\",\n        \"votesEqualInfo\": \"Each vote is equal and no token is required\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Token weighted voting\",\n        \"description\": \"Votes are weighted by a token. The token can be an ERC-20, ERC-721 or ERC-1155 token standard\",\n        \"tokenNotFound\": \"Token not found\",\n        \"seeOnEtherscan\": \"See on Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Custom setup\",\n        \"description\": \"Select up to 8 strategies with a wide range of options. If you can't find the right strategy for your use case, you can create your own\"\n      }\n    },\n    \"validationTitle\": \"Who can manage this space and create proposals?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Edit profile\",\n    \"viewProfile\": \"View profile\",\n    \"about\": {\n      \"header\": \"About\",\n      \"joinedSpaces\": \"Joined spaces\",\n      \"createdSpaces\": \"Created spaces\",\n      \"biography\": \"Bio\",\n      \"notJoinSpacesYet\": \"Hasn't joined any spaces yet\",\n      \"notCreatedSpacesYet\": \"Hasn't created any spaces yet\",\n      \"delegatorNetworkInfo\": \"Change by switching network in your wallet\",\n      \"delegate\": \"Delegate\",\n      \"delegated\": \"Delegated\",\n      \"delegateTo\": \"Delegate to\",\n      \"delegateFor\": \"Delegator for\",\n      \"noDelegatorsMessage\": \"No delegators on {network}\",\n      \"notSupportedNetwork\": \"Delegation currently isn't supported on {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activity\",\n      \"votedFor\": \"Voted {choice}\",\n      \"today\": \"Today\",\n      \"thisWeek\": \"This week\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"No activity yet\"\n    },\n    \"settings\": {\n      \"header\": \"Edit profile\",\n      \"name\": \"Name\",\n      \"biography\": \"Bio\",\n      \"namePlaceholder\": \"Enter name\",\n      \"bioPlaceholder\": \"Tell your story\",\n      \"change\": \"Change\",\n      \"remove\": \"Remove\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"Du hast es geschafft!\",\n    \"copied\": \"Kopiert!\",\n    \"proposalDeleted\": \"Vorschlag gelöscht\",\n    \"somethingWentWrong\": \"Hoppla, da ist etwas schiefgelaufen!\",\n    \"saved\": \"Saved!\",\n    \"delegationSuccess\": \"Delegation successful\",\n    \"delegationRemoved\": \"Delegation removed\",\n    \"proposalCreated\": \"Proposal created\",\n    \"voteSuccessful\": \"Your vote is in!\",\n    \"ensSet\": \"ENS Text Eintrag wurde erfolgreich gesetzt\",\n    \"transactionSent\": \"Transaction sent\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Strategie erstellen\",\n    \"createSkin\": \"Neuen Skin erstellen\",\n    \"addNetwork\": \"Netzwerk hinzufügen\",\n    \"createPlugin\": \"Plugin erstellen\",\n    \"strategies\": \"Strategie(n)\",\n    \"skins\": \"skin(s)\",\n    \"networks\": \"Netzwerk(e)\",\n    \"plugins\": \"Plugin(s)\",\n    \"results\": \"Ergebnis(se)\",\n    \"category\": \"Category\",\n    \"categories\": {\n      \"all\": \"All\",\n      \"protocol\": \"Protocol\",\n      \"social\": \"Social\",\n      \"investment\": \"Investment\",\n      \"grant\": \"Grant\",\n      \"service\": \"Service\",\n      \"media\": \"Media\",\n      \"creator\": \"Creator\",\n      \"collector\": \"Collector\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Wähle das Abstimmungssystem aus\",\n    \"single-choice\": \"Einzelwahl-Abstimmung\",\n    \"approval\": \"Zustimmungswahl\",\n    \"quadratic\": \"Quadratische Abstimmung\",\n    \"ranked-choice\": \"Rangfolgewahl\",\n    \"weighted\": \"Gewichtete Abstimmung\",\n    \"basic\": \"Einfache Abstimmung\",\n    \"description\": {\n      \"single-choice\": \"Jeder Wähler kann nur eine Wahlmöglichkeit wählen.\",\n      \"approval\": \"Jeder Wähler kann eine beliebige Anzahl von Wahlmöglichkeiten wählen.\",\n      \"quadratic\": \"Jeder Wähler kann seine Stimmrechte auf eine beliebige Anzahl von Wahlmöglichkeiten verteilen. Das Ergebnis wird quadratisch berechnet.\",\n      \"ranked-choice\": \"Jeder Wähler kann eine beliebige Anzahl von Wahlmöglichkeiten auswählen und ihnen eine Priorität zuordnen. Das Ergebnis wird mit der Methode \\\"sofortige Stichwahl\\\" ermittelt.\",\n      \"weighted\": \"Jeder Wähler kann die Stimmrechte auf eine beliebige Anzahl von Wahlmöglichkeiten verteilen.\",\n      \"basic\": \"Single choice voting with three choices: For, Against or Abstain\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Privacy\",\n    \"title\": \"Select voting privacy\",\n    \"information\": \"The type of privacy used on proposals. (Enforced on all future proposals)\",\n    \"any\": \"Any\",\n    \"none\": \"None\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Choices are encrypted and only visible once the voting period is over\",\n      \"tooltip\": \"This proposal has Shutter privacy enabled. All votes will be encrypted until the voting period has ended and the final score is calculated\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select voting validation\",\n    \"information\": \"The type of validation used to determine if a user can vote. (Enforced on all future proposals)\",\n    \"any\": {\n      \"label\": \"Anyone can vote\",\n      \"description\": \"Anyone with voting power can cast a vote.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement to vote on this proposal. \"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your proposals from spam and vote manipulation by requiring users to have a Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold} and {operator} of the following stamps to vote on this proposal: {stamps}. \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Current outcome\",\n    \"currentBond\": \"Current bond\",\n    \"finalizedIn\": \"{0} fertiggestellt\",\n    \"executableIn\": \"Ausführbar {0}\",\n    \"finalOutcome\": \"Outcome\",\n    \"nextBond\": \"Bond to set outcome\",\n    \"setOutcomeTo\": \"Set outcome to\",\n    \"claimBond\": \"Bond einfordern\",\n    \"addBatch\": \"Add transaction batch\",\n    \"batch\": \"Transaction batch\",\n    \"transactions\": \"Transactions\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"Invalid address\",\n    \"invalidAmount\": \"Invalid amount\",\n    \"invalidValue\": \"Invalid value\",\n    \"invalidAbi\": \"Invalid ABI\",\n    \"invalidData\": \"Invalid data\",\n    \"value\": \"Value (wei)\",\n    \"data\": \"Data\",\n    \"noCollectibles\": \"No collectibles\",\n    \"asset\": \"Asset\",\n    \"amount\": \"Amount\",\n    \"type\": \"Type\",\n    \"transferFunds\": \"Transfer funds\",\n    \"transferNFT\": \"Transfer NFT\",\n    \"contractInteraction\": \"Contract interaction\",\n    \"rawTransaction\": \"Raw transaction\",\n    \"addTransaction\": \"Add transaction\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei to {address}\",\n      \"transferFunds\": \"Transfer {amount} {tokenSymbol} to {address}\",\n      \"transferNFT\": \"Send {name} #{id} to {address}\",\n      \"raw\": \"Send {amount} wei to {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Ausführung anfordern\",\n      \"setOutcome\": \"Ergebnis festlegen\",\n      \"changeOutcome\": \"Ergebnis ändern\",\n      \"executeTxs\": \"Execute transaction batch {0} of {1}\",\n      \"executed\": \"All transactions have been executed\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"Something went wrong\",\n      \"connectWallet\": \"Connect wallet to see execution details\",\n      \"switchChain\": \"Switch your wallet to {0} to request execution\",\n      \"question\": \"Ist dieser Vorschlag durchgegangen und erfüllt er die\",\n      \"criteria\": \"Akzeptanzkriterien?\",\n      \"proposalPassed\": \"Wurde der Vorschlag angenommen?\",\n      \"expired\": \"The proposal has expired\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to confirm the proposal passed\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"requestToolTip\": \"This will propose the transactions from this Snapshot vote on-chain. After a challenge window, if the proposal is valid, the transactions can be executed and your bond will be returned.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"Ein POAP wurde für diesen Vorschlag noch nicht eingerichtet :'(\",\n    \"no_voted_header\": \"Stimme ab um diesen POAP zu erhalten\",\n    \"unclaimed_header\": \"Mint your I voted POAP\",\n    \"claimed_header\": \"Congratulations! The POAP has been minted to your collection\",\n    \"loading_header\": \"The POAP is being minted to your collection\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"Browse collection\",\n    \"success_claim\": \"The POAP has been minted to your collection\",\n    \"error_claim\": \"There was a problem minting the token\"\n  },\n  \"progress\": {\n    \"progress\": \"Progress\",\n    \"inProgress\": \"In Progress\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"New Step\",\n    \"description\": \"Description\",\n    \"add\": \"Add\",\n    \"deleteStep\": \"Delete Step\",\n    \"deleteConfirm\": \"Are you sure you want to delete?\",\n    \"delete\": \"Delete\",\n    \"cancel\": \"Cancel\",\n    \"comeBack\": \"Come back after voting is complete to see how this proposal is progressing!\",\n    \"confirmSignature\": \"Signing this message will allow us to authorize your request to update the progress of your proposal.\",\n    \"wentWrong\": \"Oops something went wrong.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"Charts\",\n    \"noVotesYet\": \"There are no votes to visualize yet.\",\n    \"totalVotesPerDay\": \"Total votes per day\",\n    \"shareOfVotingPower\": \"Share of voting power\",\n    \"votingPowerPerDay\": \"Voting power per day\"\n  },\n  \"comment_box\": {\n    \"title\": \"Comment box\",\n    \"add\": \"Add your comment here\",\n    \"submit\": \"Submit\",\n    \"preview\": \"Preview\",\n    \"continue_editing\": \"Continue editing\",\n    \"edit\": \"Edit your reply here\",\n    \"edit_button\": \"Edit\",\n    \"dismiss\": \"Dismiss\",\n    \"delete\": \"Delete\",\n    \"add_reply\": \"Add your reply here\",\n    \"edit_comment\": \"Edit comment\",\n    \"edit_modal\": \"Are you sure you want to edit?\",\n    \"yes\": \"Yes\",\n    \"no\": \"No\",\n    \"delete_comment\": \"Delete comment\",\n    \"delete_modal\": \"Are you sure you want to delete?\",\n    \"error\": \"Oops, something went wrong\",\n    \"replies\": \"replies\",\n    \"hide\": \"Hide\",\n    \"show\": \"Show\",\n    \"reply\": \"Reply\",\n    \"load_more\": \"Load more\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Create a space\",\n      \"timeline\": \"Timeline\",\n      \"notifications\": \"Notifications\",\n      \"explore\": \"Explore\",\n      \"playground\": \"Playground\",\n      \"space\": {\n        \"create\": \"Create {space} proposal\",\n        \"about\": \"About {space}\",\n        \"proposals\": \"{space} Proposals\",\n        \"proposal\": \"{space} proposal: {proposal}\",\n        \"settings\": \"{space} Settings\"\n      },\n      \"strategy\": \"{key} strategy\",\n      \"delegate\": \"Delegate\",\n      \"ranking\": \"Ranking\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Track proposals for {spaceName}\",\n    \"text\": \"Receive notifications every time a new proposal is created or ends\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 second | {n} seconds\",\n    \"minute\": \"1 minute | {n} minutes\",\n    \"hour\": \"1 hour | {n} hours\",\n    \"day\": \"1 day | {n} days\",\n    \"week\": \"1 week | {n} weeks\",\n    \"month\": \"1 month | {n} months\",\n    \"year\": \"1 year | {n} years\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Nicht unterstütztes Netzwerk\",\n    \"switchNetworkToNetwork\": \"To continue, you need to change the network in your wallet to {network}.\",\n    \"switchToNetwork\": \"Switch to {network}\",\n    \"goToDemoSite\": \"Go to demo website\"\n  },\n  \"treasury\": {\n    \"title\": \"Treasury\",\n    \"wallets\": {\n      \"title\": \"Wallets\",\n      \"empty\": \"This space doesn't have a treasury yet\",\n      \"addTreasury\": \"Add a treasury\"\n    },\n    \"assets\": {\n      \"title\": \"Assets\",\n      \"empty\": \"There are no assets in this contract\"\n    },\n    \"24hChange\": \"24h change\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Your email\",\n    \"title\": \"Get the latest Snapshot updates\"\n  },\n  \"joinCommunity\": \"Join Snapshot community\",\n  \"header\": {\n    \"title\": \"Where decisions get made\",\n    \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot is a decentralized governance platform that makes it easy to create and vote on proposals - all without spending a fortune on gas fees! Plus, our flexible system supports various voting types and strategies, so you can tailor the voting process to your needs.\",\n    \"subHeader\": \"Governance should be a snap\",\n    \"subDescription\": \"Web3 governance doesn't have to be complicated. Snapshot is the perfect solution for organizations looking for an easy and efficient way to govern their community or organization.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Resources\",\n    \"about\": \"About\",\n    \"blog\": \"Blog\",\n    \"jobs\": \"Jobs\",\n    \"discussions\": \"Discussions\",\n    \"github\": \"GitHub\",\n    \"docs\": \"Docs\",\n    \"support\": \"Support\",\n    \"hiring\": \"Join us!\"\n  }\n}\n"
  },
  {
    "path": "src/locales/default.json",
    "content": "{\n  \"searchPlaceholder\": \"Search\",\n  \"searchPlaceholderVotes\": \"Search: Address, ENS, or Lens\",\n  \"searchPlaceholderTokens\": \"Search: Name, Symbol, or Address\",\n  \"searchVotingPower\": \"Voting power\",\n  \"searchChoice\": \"Choice\",\n  \"spaceCount\": \"{0} space(s)\",\n  \"createSpace\": \"Create space\",\n  \"backToHome\": \"Home\",\n  \"actions\": \"Actions\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"Results\",\n  \"failed\": \"Failed\",\n  \"applyChanges\": \"Apply changes\",\n  \"resultsError\": \"Results could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"ticketWithAnyOrBasicError\": \"In order to use the \\\"ticket\\\" strategy you are required to set a voting validation strategy. This combination reduces the risk of spam and sybil attacks. {article}\",\n  \"ticketWithAnyOrBasicErrorSetup\": \"Use of the ticket strategy requires setting a voting validation strategy. To continue go one step back and select the \\\"One person, one vote\\\" option. {article}\",\n  \"missingProposalValidationError\": \"Proposal validation is required to prevent unauthorized proposals and spam. If you don't want proposal validation you can toggle only the \\\"Allow only authors to submit a proposal\\\" option.\",\n  \"resultsCalculating\": \"Final results are being calculated. If you still see this message after a few minutes contact the space admin.\",\n  \"votingPowerFailedMessage\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy. If the problem persists, consider contacting our support team on {help}\",\n  \"votingValidationFailedMessage\": \"We were not able to validate your eligibility to vote. This is often due to a misconfigured vote validation setting or an unresponsive RPC node. If the problem persists, consider contacting our support team on {help}\",\n  \"getHelp\": \"Get help\",\n  \"retry\": \"Retry\",\n  \"currentResults\": \"Current results\",\n  \"reset\": \"Reset\",\n  \"strategy\": \"Strategy\",\n  \"addStrategy\": \"Add a strategy\",\n  \"copyVotingStrategies\": \"Copy voting strategies\",\n  \"close\": \"Close\",\n  \"save\": \"Save\",\n  \"author\": \"Author\",\n  \"next\": \"Next\",\n  \"choice\": \"Choice\",\n  \"submit\": \"Submit\",\n  \"plugins\": \"Plugins\",\n  \"information\": \"Information\",\n  \"confirm\": \"Confirm\",\n  \"snapshot\": \"Snapshot\",\n  \"strategies\": \"Strategie(s)\",\n  \"strategiesPage\": \"Strategies\",\n  \"space\": \"Space\",\n  \"spaces\": \"Spaces\",\n  \"verified\": \"Verified\",\n  \"verifiedSpace\": \"Verified space\",\n  \"warningSpace\": \"This space has been flagged as potentially malicious. Proceed with caution.\",\n  \"warningShortUrl\": \"Short URLs are not allowed. Please use the full URL.\",\n  \"warningFlaggedProposal\": \"This proposal might contain scams, offensive material, or be malicious in nature. Please proceed with caution.\",\n  \"warningFlaggedSpace\": \"This space might contain scams, offensive material, or be malicious in nature. Please proceed with caution.\",\n  \"warningFlaggedActionShow\": \"Show\",\n  \"linkPreview\": \"This link will take you to {url}.</br> Be careful, this link could be malicious. Are you sure you want to continue? \",\n  \"version\": \"Version\",\n  \"timeline\": \"Timeline\",\n  \"ended\": \"ended\",\n  \"started\": \"started\",\n  \"filters\": \"Filters\",\n  \"allSpaces\": \"All spaces\",\n  \"submitOnchain\": \"Submit on-chain\",\n  \"inSpaces\": \"In {0} space(s)\",\n  \"votes\": \"Votes\",\n  \"seeMore\": \"See more\",\n  \"seeAll\": \"See all\",\n  \"network\": \"Network\",\n  \"networks\": \"Networks\",\n  \"skins\": \"Skins\",\n  \"spaceMembers\": \"Members\",\n  \"members\": \"No members | {count} member | {count} members\",\n  \"editStrategy\": \"Edit strategy\",\n  \"invalidProposals\": \"Invalid proposals\",\n  \"account\": \"Account\",\n  \"create3box\": \"Create profile on 3Box\",\n  \"view3box\": \"View profile on 3Box\",\n  \"edit3box\": \"Edit profile on 3Box\",\n  \"connectWallet\": \"Connect wallet\",\n  \"toggleSkin\": \"Toggle skin\",\n  \"about\": \"About\",\n  \"license\": \"License\",\n  \"showMore\": \"Show more\",\n  \"voted\": \"Voted\",\n  \"reload\": \"Reload\",\n  \"ipfsServer\": \"IPFS server\",\n  \"hub\": \"Hub\",\n  \"cancel\": \"Cancel\",\n  \"delete\": \"Delete\",\n  \"demoSite\": \"This is the demo site, give it a try!\",\n  \"removeDelegation\": \"Remove delegation\",\n  \"confirmRemove\": \"Are you sure you want to remove your delegation to\",\n  \"removeSpace\": \"for the space {0}\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum reached\",\n  \"options\": \"Option(s)\",\n  \"votingPower\": \"Your voting power\",\n  \"comment\": {\n    \"placeholder\": \"Share your reason (optional)\"\n  },\n  \"receipt\": \"Receipt\",\n  \"relayer\": \"Relayer\",\n  \"verifyOnMycrypto\": \"Verify receipt on MyCrypto\",\n  \"verifyOnSignatorio\": \"Verify on Signator.io\",\n  \"isCore\": \"Core\",\n  \"notificationsBlocked\": \"Your browser is blocking notifications\",\n  \"notificationsNotSupported\": \"Your browser does not support notifications\",\n  \"walletNotSupported\": \"Wallet is not supported\",\n  \"seeInExplorer\": \"See explorer\",\n  \"learnMore\": \"Learn more\",\n  \"logout\": \"Log out\",\n  \"continue\": \"Continue\",\n  \"add\": \"Add\",\n  \"edit\": \"Edit\",\n  \"strategyParameters\": \"Strategy parameters\",\n  \"addAction\": \"Add action\",\n  \"removeAction\": \"Remove action\",\n  \"yourChoice\": \"Choice {0}\",\n  \"targetAddress\": \"Target address\",\n  \"value\": \"Value\",\n  \"date\": \"Data\",\n  \"marketDetails\": \"Market details\",\n  \"addMarket\": \"Add market\",\n  \"selectNetwork\": \"Select network\",\n  \"conditionId\": \"Condition ID\",\n  \"basetokenAddress\": \"Base token address\",\n  \"quoteAddress\": \"Quote currency address\",\n  \"removeMarket\": \"Remove market\",\n  \"back\": \"Back\",\n  \"set\": \"Set\",\n  \"loading\": \"Loading...\",\n  \"predictedImpact\": \"Predicted impact\",\n  \"marketSymbol\": \"{0} market\",\n  \"twoChoicesRequired\": \"Two choices are required for this plugin.\",\n  \"noResultsFound\": \"Oops, we can't find any results\",\n  \"createFirstProposal\": \"Let's create your first proposal\",\n  \"noSpacesJoined\": \"Oops, you haven't joined any spaces yet\",\n  \"addFavorites\": \"Add favorites\",\n  \"createdBy\": \"By {0}\",\n  \"startIn\": \"start {0}\",\n  \"endIn\": \"ends {0}\",\n  \"proposalTimeLeft\": \"{0} left\",\n  \"endedAgo\": \"ended {0}\",\n  \"proposalBy\": \"by {0}\",\n  \"endDate\": \"end {0}\",\n  \"defaultSkin\": \"Default skin\",\n  \"select\": \"Select\",\n  \"language\": \"Language\",\n  \"agree\": \"I agree\",\n  \"moderators\": \"Moderators\",\n  \"playground\": \"Playground\",\n  \"strategyDetails\": \"Strategy details\",\n  \"strategyParams\": \"Strategy params\",\n  \"useCustomStrategies\": \"Use custom strategies\",\n  \"addresses\": \"Addresses\",\n  \"networkErrorPlayground\": \"Network error - please open your browser console for more information\",\n  \"upload\": \"Upload\",\n  \"join\": \"Join\",\n  \"joined\": \"Joined\",\n  \"leave\": \"Leave\",\n  \"subspaces\": \"Sub-spaces\",\n  \"mainspace\": \"Main space\",\n  \"copyLink\": \"Copy link\",\n  \"duplicate\": \"Duplicate\",\n  \"report\": \"Report\",\n  \"flag\": \"Flag\",\n  \"joinedSpaces\": \"Joined spaces\",\n  \"joinSpaces\": \"Join spaces\",\n  \"setDelegationToSpace\": \"Limit delegation to a specific space\",\n  \"theCurrentNetwork\": \"the current network\",\n  \"optional\": \"(optional)\",\n  \"homeLoadmore\": \"Load more\",\n  \"confirmAction\": \"Confirm action\",\n  \"or\": \"or\",\n  \"share\": \"Share\",\n  \"shareOnTwitter\": \"Share on Twitter\",\n  \"shareOnHey\": \"Share on Hey\",\n  \"createButton\": \"Create\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Change wallet\",\n  \"createASpace\": \"Create a space\",\n  \"getStarted\": \"Get started\",\n  \"skip\": \"Skip\",\n  \"reactivateSpace\": \"Reactivate space\",\n  \"newSpaceNotice\": {\n    \"header\": \"Your space is live!\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Learn more in the {documentation} or contact support on {help}.\",\n    \"gotIt\": \"Got it!\"\n  },\n  \"errors\": {\n    \"required\": \"Field is required\",\n    \"minLength\": \"Field is required\",\n    \"maxLength\": \"Maximum length is {0}\",\n    \"pattern\": \"Invalid character\",\n    \"minItems\": \"Minimum {0} item(s) required\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"members\": {\n      \"maxItems\": \"Maximum {limit} {role} allowed\"\n    },\n    \"minStrategy\": \"At least one strategy is required.\",\n    \"website\": \"URL should be in the format https://www.example.com\",\n    \"format\": \"Invalid format\",\n    \"type\": \"Invalid type\",\n    \"unsupportedImageType\": \"File type not supported, Supported formats are jpeg, jpg and png\",\n    \"fileTooBig\": \"File size should be smaller than 1MB\",\n    \"invalidAddress\": \"Invalid address\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Title\",\n    \"discussion\": \"Discussion (optional)\",\n    \"categorie(s)\": \"Select up to 2 categorie(s)\",\n    \"proposalDescription\": \"Description (optional)\",\n    \"preview\": \"Preview\",\n    \"choices\": \"Choices\",\n    \"addChoice\": \"Add choice\",\n    \"startDate\": \"Select start date\",\n    \"endDate\": \"Select end date\",\n    \"startTime\": \"Select start time\",\n    \"endTime\": \"Select end time\",\n    \"publish\": \"Publish\",\n    \"untitled\": \"Untitled\",\n    \"snapshotBlock\": \"Snapshot block number\",\n    \"voting\": \"Voting\",\n    \"votingSystem\": \"Voting system\",\n    \"choice\": \"Choice {0}\",\n    \"period\": \"Voting period\",\n    \"start\": \"Start\",\n    \"end\": \"End\",\n    \"days\": \"Days\",\n    \"hours\": \"Hours\",\n    \"minutes\": \"Minutes\",\n    \"schedule\": \"Schedule proposal\",\n    \"delayEnforced\": \"The space enforces a delay until voting can start\",\n    \"periodEnforced\": \"The space enforces the duration of the voting period\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"Edit\",\n    \"continue\": \"Continue\",\n    \"now\": \"Now\",\n    \"votingPeriodExplainer\": \"This is the time period in which users will be able to vote. The proposal will be visible and pending before the start of the voting period.\",\n    \"uploadImageExplainer\": \"Attach images by dragging & dropping, selecting or pasting them.\",\n    \"uploading\": \"Uploading image\",\n    \"markdown\": \"Styling with Markdown is supported\",\n    \"errorGettingSnapshot\": \"We encountered an error while fetching the snapshot block number which is needed to calculate your voting power. Please try again later.\",\n    \"errorTimeInPast\": \"Entered time is in the past. Please select a future time.\",\n    \"errorSpaceHibernatedAdmin\": \"Admins your attention is required. This space has been hibernated due to a lack of activity, and as a result, proposal creation is currently disabled. To re-enable your space, please go to the settings page and click \\\"Reactivate\\\".\",\n    \"errorSpaceHibernatedUsers\": \"This space has been hibernated due to a lack of activity. As a result, the creation of proposals has been temporarily disabled. To resume your ability to create proposals, the space needs to be reactivated by an admin. Please check back later or contact your space admin for further information.\"\n  },\n  \"delegates\": {\n    \"header\": \"Delegates\",\n    \"filters\": {\n      \"mostVotingPower\": \"Most voting power\",\n      \"mostDelegators\": \"Most delegators\",\n      \"mostProposals\": \"Most proposals\",\n      \"mostVotes\": \"Most votes\"\n    },\n    \"delegateModal\": {\n      \"title\": \"Delegate\",\n      \"sub\": \"You are about to delegate all of your voting power.\"\n    },\n    \"profileModal\": {\n      \"title\": \"Profile\"\n    }\n  },\n  \"delegate\": {\n    \"header\": \"Delegate\",\n    \"selectDelegate\": \"Select delegate\",\n    \"to\": \"To\",\n    \"addressPlaceholder\": \"Address or ENS name\",\n    \"delegations\": \"Your delegation(s)\",\n    \"allSpaces\": \"For all spaces\",\n    \"delegated\": \"Delegated to you\",\n    \"pendingTransaction\": \"no pending transaction | 1 pending transaction | {count} pending transactions\",\n    \"topDelegates\": \"Top delegates\",\n    \"noDelegatesFoundFor\": \"No delegates found for {0}\",\n    \"noValidEns\": \"Not a valid ENS address.\",\n    \"noValidAddress\": \"Not a valid address\",\n    \"delegateToSelf\": \"You cannot delegate to yourself\",\n    \"delegateToSelfAddress\": \"You cannot delegate to your own ENS address\",\n    \"noValidSpaceId\": \"Not a valid space ID\",\n    \"noDelegationsAndDelegates\": \"Can't find your delegations and delegates? Make sure you are connected to the correct network.\",\n    \"delegateNotSupported\": \"Delegation is currently not supported for {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Cast your vote\",\n    \"vote\": \"Vote\",\n    \"startDate\": \"Start date\",\n    \"endDate\": \"End date\",\n    \"votingSystem\": \"Voting system\",\n    \"privacy\": \"Privacy\",\n    \"invalidChoice\": \"Invalid choice\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Your vote is in!\",\n      \"gnosisSafeTitle\": \"Your vote is pending...\",\n      \"gnosisSafeDescription\": \" Votes with a Safe require additional signers and will be visible once the transaction is confirmed\",\n      \"seeQueue\": \"See queued transactions\",\n      \"tips\": {\n        \"1\": \"Votes can be changed while the proposal is active\"\n      },\n      \"subscribe\": \"Subscribe with email\"\n    },\n    \"downloadCsvVotes\": {\n      \"title\": \"Download as CSV\",\n      \"postDownloadModal\": {\n        \"title\": \"Generating votes report\",\n        \"message\": {\n          \"pendingGeneration\": {\n            \"title\": \"Your report is currently being generated\",\n            \"description\": \"It may take a few minutes. Please check back shortly.\"\n          },\n          \"unsupportedEnv\": {\n            \"title\": \"Unsupported environment\",\n            \"description\": \"Votes report are not available on demo.\"\n          },\n          \"unknownError\": {\n            \"title\": \"We're having trouble connecting to the server responsible for downloads\",\n            \"description\": \"Please try again in a few moments. If the problem persists, consider contacting our support team.\"\n          }\n        }\n      }\n    },\n    \"votesModal\": {\n      \"title\": \"Votes\",\n      \"filtersPopover\": {\n        \"title\": \"Filters\",\n        \"votingPower\": \"Voting power\",\n        \"onlyVotesWithReason\": \"Only show votes with a reason\",\n        \"more\": \"More\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"Proposals\",\n    \"new\": \"New proposal\",\n    \"noProposals\": \"There aren't any proposals here yet!\",\n    \"createProposal\": \"Create proposal\",\n    \"showMore\": \"Show more\",\n    \"showLess\": \"Show less\",\n    \"states\": {\n      \"all\": \"All\",\n      \"core\": \"Core\",\n      \"community\": \"Community\",\n      \"active\": \"Active\",\n      \"pending\": \"Pending\",\n      \"closed\": \"Closed\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Notifications\",\n    \"noNotifications\": \"You have no notifications\",\n    \"proposalStarted\": \"proposal has started:\",\n    \"proposalEnded\": \"proposal has ended:\",\n    \"markAllAsRead\": \"Mark all as read\",\n    \"all\": \"All\",\n    \"unread\": \"Unread\"\n  },\n  \"modalNotifications\": {\n    \"wrong timestamp\": {\n      \"title\": \"Wrong timestamp\",\n      \"message\": \"The message timestamp is not valid. Please check the clock on your device, make sure it is set to the correct date and time and try again.\\n\\n If you need further assistance, feel free to reach out to our support team on {help}.\"\n    },\n    \"space with ticket requires voting validation\": {\n      \"title\": \"Missing voting validation\",\n      \"message\": \"Your proposal cannot be submitted due to a missing voting validation rule required with the 'ticket' strategy. Please adjust your settings by either using another strategy or adding the required validation.\\n\\n If further assistance is needed, contact our support team on {help}.\"\n    },\n    \"space missing proposal validation\": {\n      \"title\": \"Missing proposal validation\",\n      \"message\": \"Your proposal cannot be submitted due to a missing proposal validation rule. To prevent unauthorized proposals and spam, add a validation rule in your space settings.\\n\\n For assistance, reach out to our support team on {help}.\"\n    }\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"join\",\n    \"actionCreate\": \"create a proposal in\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"Settings\",\n    \"confirmLeaveMessage\": \" Do you really want to leave? You have unsaved changes that will be lost.\",\n    \"confirmDeleteSpace\": \" Do you really want to delete this space? This action cannot be undone and you will not be able to use {name} again to create another space.\",\n    \"validationErrorsMessage\": \"Please fix the following fields before saving:\",\n    \"confirmInputDeleteSpace\": \"Enter {space} to continue:\",\n    \"noticeSavedTitle\": \"Settings saved\",\n    \"noticeSavedText\": \"New settings only affect future proposals, not existing ones.\",\n    \"noticeSavedInputCheckboxLabel\": \"Don't show again\",\n    \"navigation\": {\n      \"general\": \"General\",\n      \"strategies\": \"Strategies\",\n      \"proposal\": \"Proposal\",\n      \"voting\": \"Voting\",\n      \"delegation\": \"Delegation\",\n      \"members\": \"Members\",\n      \"advanced\": \"Advanced\"\n    },\n    \"editController\": \"Edit controller\",\n    \"connectWithSpaceOwner\": \"You are in view only mode, to modify space settings connect with a controller or admin wallet.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"edit the space settings\",\n      \"create\": \"create a proposal\",\n      \"vote\": \"vote on this proposal\"\n    },\n    \"currentSpaceControllerIs\": \"The current space controller is {address}\",\n    \"newController\": \"New controller\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Set\",\n    \"profile\": \"Profile\",\n    \"avatar\": \"Avatar\",\n    \"name\": {\n      \"label\": \"Name\",\n      \"placeholder\": \"e.g. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"About\",\n      \"placeholder\": \"What is your organisation about?\"\n    },\n    \"categories\": {\n      \"label\": \"Categorie(s)\",\n      \"select\": \"Select categorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Terms of service\",\n      \"information\": \"Users will be required to accept these terms once before they can create a proposal or cast a vote\"\n    },\n    \"hideSpace\": \"Hide space from homepage\",\n    \"links\": \"Social accounts\",\n    \"subspaces\": {\n      \"label\": \"Sub-spaces\",\n      \"information\": \"Add a sub-space to display its proposals within your space. If you want the current space to be displayed on the sub-space's page, the space needs to be added as main space in the sub-space settings to make relation mutual. {docs}\",\n      \"parent\": {\n        \"label\": \"Main space\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"The space that this space is a sub-space of will be displayed on the space page\"\n      },\n      \"children\": {\n        \"label\": \"Sub-spaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Related Sub-spaces listed here will be displayed on the space page\"\n      }\n    },\n    \"website\": \"Website\",\n    \"strategies\": {\n      \"label\": \"Strategie(s)\",\n      \"information\": \"Strategies are used to determine voting power or whether a user is eligible to create a proposal\"\n    },\n    \"network\": {\n      \"label\": \"Network\",\n      \"information\": \"The default network used for this space. Networks can also be specified in individual strategies\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbol\",\n      \"information\": \"The default symbol used for this space, usually the token symbol i.e. BAL for Balancer\"\n    },\n    \"strategiesList\": \"Select up to {0} strategies\",\n    \"votingPowerIsCumulative\": \"Voting power is cumulative\",\n    \"addStrategy\": \"Add strategy\",\n    \"testInPlayground\": \"Test in playground\",\n    \"members\": {\n      \"title\": \"Members\",\n      \"information\": \"Members have different roles and permissions within the space.\",\n      \"addMembers\": \"Add members\",\n      \"addMembersInformation\": \"Paste an address to add a member. You can add multiple members at once by separating them with a comma.\",\n      \"alreadyExists\": \"Member already exists\",\n      \"invalidAddress\": \"Invalid address\",\n      \"membersAdded\": \"Members added\",\n      \"admin\": {\n        \"description\": \"Able to modify the space settings, manage the space's proposals and create proposals\"\n      },\n      \"moderator\": {\n        \"description\": \"Able to manage the space's proposals and create proposals\"\n      },\n      \"author\": {\n        \"description\": \"Able to create proposals without having to go through proposal validation\"\n      }\n    },\n    \"proposalValidation\": \"Proposal validation\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Threshold\",\n      \"information\": \"The minimum amount of voting power required to create a proposal\"\n    },\n    \"allowOnlyAuthors\": \"Allow only authors to submit a proposal\",\n    \"editValidation\": \"Edit validation\",\n    \"selectValidation\": \"Select validation\",\n    \"validationParameters\": \"Validation parameters\",\n    \"voting\": \"Voting\",\n    \"votingDelay\": \"Voting delay\",\n    \"votingPeriod\": \"Voting period\",\n    \"hours\": \"Hours\",\n    \"days\": \"Days\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"The minimum amount of voting power required for the proposal to pass\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"The type of voting system used for this space. (Enforced on all future proposals)\"\n    },\n    \"anyType\": \"Any\",\n    \"hideAbstain\": \"Ignore abstain votes in basic voting results\",\n    \"customDomain\": \"Custom domain\",\n    \"domain\": {\n      \"label\": \"Domain name\",\n      \"placeholder\": \"e.g. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"Skin\",\n    \"treasuries\": {\n      \"label\": \"Treasury\",\n      \"add\": \"Add treasury\",\n      \"edit\": \"Edit treasury\",\n      \"information\": \"Add treasuries of your organization to show them in your space\"\n    },\n    \"addPlugin\": \"Add plugin\",\n    \"editPlugin\": \"Edit plugin\",\n    \"pluginParameters\": \"Plugin parameters\",\n    \"proposal\": {\n      \"title\": \"Proposal\",\n      \"guidelines\": {\n        \"title\": \"Guidelines\",\n        \"information\": \"Display a link to your guidelines on proposal creation to help users understand what constitutes a good/valid proposal\"\n      },\n      \"template\": {\n        \"title\": \"Template\",\n        \"information\": \"Start every proposal with a template to help users understand what information is required\"\n      }\n    },\n    \"dangerZone\": {\n      \"title\": \"Danger zone\",\n      \"changeController\": {\n        \"title\": \"Change space controller\",\n        \"information\": \"Change the controller of this space. This can only be done by the ENS controller.\",\n        \"button\": \"Change controller\",\n        \"disabledInformation\": \"You need to be the ENS owner ({owner}) to change the space controller.\"\n      },\n      \"deleteSpace\": {\n        \"title\": \"Delete space\",\n        \"information\": \"Delete this space and all its content. This cannot be undone and you will not be able to create a new space with the same ENS domain name.\",\n        \"button\": \"Delete space\",\n        \"disabledInformation\": \"You need to be the space controller to delete the space\"\n      }\n    },\n    \"delegationPortal\": {\n      \"title\": \"Delegation portal\",\n      \"information\": \"Please ensure your token adheres to a compatible delegation standard to enable delegate discovery and activity within your Snapshot space.\"\n    },\n    \"reactivatingHibernatedSpace\": {\n      \"information\": \"Your space is currently in hibernation due to six months of inactivity. To re-enable proposal creation, please click the button below to reactivate your space. There are no penalties for hibernation, but the space will remain inactive for proposal creation until reactivation by an admin.\",\n      \"disabledInformation\": \"Your space contains invalid settings since your last update. Fix the errors below and your space will be reactivated automatically upon save.\"\n    }\n  },\n  \"setup\": {\n    \"example\": \"e.g. yam.eth\",\n    \"chooseExistingEns\": \"Choose one of your existing ENS domains to create a space with:\",\n    \"useSingleExistingEns\": \"Use your existing ENS domain:\",\n    \"orRegisterNewEns\": \"Or register a new domain:\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"To create a space, you first need an ENS domain. Enter one below and follow the ENS registration instructions.\",\n    \"createASpace\": \"Create a space\",\n    \"registerEnsButton\": \"Register\",\n    \"supportedEnsTLDs\": \"Supported domain endings\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or contact support on {help}.\",\n    \"setSpaceController\": \"Space controller\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \" The space controller is the account that will be able to manage the space settings. Additional space controllers (admins) can be added later.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"When creating a space with a Gnosis Safe it's recommended to set the safe address as the space controller. If you don't, you need to follow some additional steps. {link}\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"Setting the controller requires a transaction on the {network} which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choose ENS address\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. {address}\",\n    \"controllerAddress\": \"Controller address\",\n    \"updateController\": \"Update controller\",\n    \"seeOnEns\": \"See on ENS\",\n    \"goToSettings\": \"Go to settings\",\n    \"setSpaceProfile\": \"Customize your space\",\n    \"waitForTransaction\": \"The transaction need to confirm before you can create your space. {txUrl}\",\n    \"pendingTransactions\": \"Pending transactions\",\n    \"pleaseWaitMessage\": \"This can take a few minutes, please wait while the transaction is being confirmed\",\n    \"notControllerAddress\": \"Please connect with the controller address {wallet} to create the space.\",\n    \"fillCurrentAccount\": \"Use currently logged in account\",\n    \"domain\": {\n      \"title\": \"Setup your space domain\",\n      \"ensMessage\": \" One thing you need before you can create your own space, is an ENS domain on Ethereum mainnet.\",\n      \"ensMessageTestnet\": \"You can also {link} - a Sepolia testnet playground dedicated to testing before creating your space or proposals on Snapshot\",\n      \"tryDemo\": \"try the testnet.v1.snapshot.org\",\n      \"yourExistingSpaces\": \"Your existing spaces\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"How would you like to setup voting?\",\n      \"subtitle\": \"You can change these settings any time.\",\n      \"blockTitle\": \"Setup voting strategy\",\n      \"onePersonOneVote\": {\n        \"title\": \"One person, one vote\",\n        \"description\": \"Manage a whitelist of people who can vote or simply allow any address to vote. Every vote is equal and no token is required\",\n        \"whitelistInformation\": \"Specify a number of accounts that can vote\",\n        \"ticketInformation\": \"Any account can vote\",\n        \"votesEqualInfo\": \"Each vote is equal and no token is required\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Token weighted voting\",\n        \"description\": \"Votes are weighted by a token. The token can be an ERC-20, ERC-721 or ERC-1155 token standard\",\n        \"tokenNotFound\": \"Token not found\",\n        \"seeOnEtherscan\": \"See on Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Custom setup\",\n        \"description\": \"Select up to 8 strategies with a wide range of options. If you can't find the right strategy for your use case, you can create your own\"\n      }\n    },\n    \"validationTitle\": \"Who can manage this space and create proposals?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Edit profile\",\n    \"viewProfile\": \"View profile\",\n    \"about\": {\n      \"header\": \"About\",\n      \"joinedSpaces\": \"Joined spaces\",\n      \"createdSpaces\": \"Created spaces\",\n      \"biography\": \"Bio\",\n      \"notJoinSpacesYet\": \"Hasn't joined any spaces yet\",\n      \"notCreatedSpacesYet\": \"Hasn't created any spaces yet\",\n      \"delegatorNetworkInfo\": \"Change by switching network in your wallet\",\n      \"delegate\": \"Delegate\",\n      \"delegated\": \"Delegated\",\n      \"delegateTo\": \"Delegate to\",\n      \"delegateFor\": \"Delegator for\",\n      \"noDelegatorsMessage\": \"No delegators on {network}\",\n      \"notSupportedNetwork\": \"Delegation currently isn't supported on {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activity\",\n      \"votedFor\": \"Voted {choice}\",\n      \"today\": \"Today\",\n      \"thisWeek\": \"This week\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"No activity yet\"\n    },\n    \"settings\": {\n      \"header\": \"Edit profile\",\n      \"name\": \"Name\",\n      \"biography\": \"Bio\",\n      \"namePlaceholder\": \"Enter name\",\n      \"bioPlaceholder\": \"Tell your story\",\n      \"change\": \"Change\",\n      \"remove\": \"Remove\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"You did it!\",\n    \"copied\": \"Copied!\",\n    \"proposalDeleted\": \"Proposal deleted\",\n    \"somethingWentWrong\": \"Oops, something went wrong!\",\n    \"saved\": \"Saved!\",\n    \"delegationSuccess\": \"Delegation successful\",\n    \"delegationRemoved\": \"Delegation removed\",\n    \"proposalCreated\": \"Proposal created\",\n    \"proposalUpdated\": \"Proposal updated\",\n    \"waitingForOtherSigners\": \"Waiting for other signers\",\n    \"voteSuccessful\": \"Your vote is in!\",\n    \"ensSet\": \"ENS text record was successfully set\",\n    \"transactionSent\": \"Transaction sent\",\n    \"emailPreferencesUpdated\": \"Email preferences updated\",\n    \"delegationAdded\": \"Well done. Delegate was successfully added\",\n    \"spaceReactivated\": \"The space has been reactivated\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Create strategy\",\n    \"createSkin\": \"Create skin\",\n    \"addNetwork\": \"Add network\",\n    \"createPlugin\": \"Create plugin\",\n    \"strategies\": \"strategie(s)\",\n    \"skins\": \"skin(s)\",\n    \"networks\": \"network(s)\",\n    \"plugins\": \"plugin(s)\",\n    \"results\": \"result(s)\",\n    \"category\": \"Category\",\n    \"categories\": {\n      \"all\": \"All\",\n      \"protocol\": \"Protocol\",\n      \"social\": \"Social\",\n      \"investment\": \"Investment\",\n      \"grant\": \"Grant\",\n      \"service\": \"Service\",\n      \"media\": \"Media\",\n      \"creator\": \"Creator\",\n      \"collector\": \"Collector\",\n      \"ai-agent\": \"AI agent\",\n      \"gaming\": \"Gaming\",\n      \"wallet\": \"Wallet\",\n      \"music\": \"Music\",\n      \"layer-2\": \"Layer 2\",\n      \"defai\": \"DeFi AI\",\n      \"defi\": \"DeFi\",\n      \"rwa\": \"RWA\",\n      \"depin\": \"DePin\",\n      \"meme\": \"Meme\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Select voting system\",\n    \"single-choice\": {\n      \"label\": \"Single choice voting\",\n      \"description\": \"Each voter may select only one choice.\"\n    },\n    \"approval\": {\n      \"label\": \"Approval voting\",\n      \"description\": \"Each voter may select any number of choices.\"\n    },\n    \"quadratic\": {\n      \"label\": \"Quadratic voting\",\n      \"description\": \"Each voter may spread voting power across any number of choices. Results are calculated quadratically.\"\n    },\n    \"ranked-choice\": {\n      \"label\": \"Ranked choice voting\",\n      \"description\": \"Each voter may select and rank any number of choices. Results are calculated by instant-runoff counting method.\"\n    },\n    \"weighted\": {\n      \"label\": \"Weighted voting\",\n      \"description\": \"Each voter may spread voting power across any number of choices.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic voting\",\n      \"description\": \"Single choice voting with three choices: For, Against or Abstain\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Privacy\",\n    \"title\": \"Select voting privacy\",\n    \"information\": \"The type of privacy used on proposals. (Enforced on all future proposals)\",\n    \"any\": \"No privacy\",\n    \"none\": \"None\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Choices are encrypted and only visible once the voting period is over\",\n      \"tooltip\": \"This proposal has Shutter privacy enabled. All votes will be encrypted until the voting period has ended and the final score is calculated\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"proposalValidation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select validation\",\n    \"settingsTitle\": \"Configure validation\",\n    \"paramPlaceholder\": \"Validation parameters\",\n    \"information\": \"The type of validation used to determine if a user can create a proposal. (Enforced on all future proposals)\",\n    \"executionError\": \"There was an error on our side and we could not verify if you are eligible to create a proposal. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n    \"notValidMessage\": \"Oops, you don't seem to be eligible to submit a proposal.\",\n    \"onlyMemberMessage\": \"You need to be a core member of the space in order to submit a proposal.\",\n    \"notConnectedMessage\": \"You need to connect your wallet in order to submit a proposal.\",\n    \"any\": {\n      \"label\": \"Anyone can create\",\n      \"description\": \"Anyone can create a proposal.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use a minimum score along with any strategy to determine if a user can create a proposal.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement of {amount} {symbol} to create a proposal.\",\n      \"minScoreHint\": \"The score is calculated with Voting Strategies used by the space.\",\n      \"customStrategiesHint\": \"Calculate the score with a different configuration of Voting Strategies\"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your space from spam by requiring users to have a Gitcoin Passport to create a proposal.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold}\",\n      \"invalidMessageStamps\": \" and {operator} of the following stamps to create a proposal: {stamps} \"\n    },\n    \"arbitrum\": {\n      \"label\": \"Arbitrum DAO votable supply\",\n      \"description\": \"Use with erc20-votes to validate by percentage of votable supply.\"\n    },\n    \"karma-eas-attestation\": {\n      \"label\": \"Karma EAS Attestation\",\n      \"description\": \"Use EAS attest.sh to determine if user can create a proposal.\"\n    }\n  },\n  \"votingValidation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select validation\",\n    \"settingsTitle\": \"Configure validation\",\n    \"paramPlaceholder\": \"Validation parameters\",\n    \"information\": \"The type of validation used to determine if a user can vote. (Enforced on all future proposals)\",\n    \"executionError\": \"There was an error on our side and we could not verify if you are eligible to vote. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n    \"notValidMessage\": \"Oops, you don't seem to be eligible to vote on this proposal.\",\n    \"any\": {\n      \"label\": \"Anyone can vote\",\n      \"description\": \"Anyone with voting power can cast a vote.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use a minimum score along with any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement of {amount} {symbol} to vote on this proposal.\",\n      \"minScoreHint\": \"The score is calculated with Voting Strategies used by the space.\",\n      \"customStrategiesHint\": \"Calculate the score with a different configuration of Voting Strategies\"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your proposals from spam and vote manipulation by requiring users to have a Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold}\",\n      \"invalidMessageStamps\": \" and {operator} of the following stamps to vote on this proposal: {stamps} \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Current outcome\",\n    \"currentBond\": \"Current bond\",\n    \"finalizedIn\": \"Finalized {0}\",\n    \"executableIn\": \"Executable {0}\",\n    \"finalOutcome\": \"Outcome\",\n    \"nextBond\": \"Bond to set outcome\",\n    \"setOutcomeTo\": \"Set outcome to\",\n    \"claimBond\": \"Claim bond\",\n    \"addBatch\": \"Add transaction batch\",\n    \"batch\": \"Transaction batch\",\n    \"transactions\": \"Transactions\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"Invalid address\",\n    \"invalidAmount\": \"Invalid amount\",\n    \"invalidValue\": \"Invalid value\",\n    \"invalidAbi\": \"Invalid ABI\",\n    \"invalidData\": \"Invalid data\",\n    \"value\": \"Value (wei)\",\n    \"data\": \"Data\",\n    \"noCollectibles\": \"No collectibles\",\n    \"asset\": \"Asset\",\n    \"amount\": \"Amount\",\n    \"type\": \"Type\",\n    \"transferFunds\": \"Transfer funds\",\n    \"transferNFT\": \"Transfer NFT\",\n    \"contractInteraction\": \"Contract interaction\",\n    \"rawTransaction\": \"Raw transaction\",\n    \"addTransaction\": \"Add transaction\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei to {address}\",\n      \"transferFunds\": \"Transfer {amount} {tokenSymbol} to {address}\",\n      \"transferNFT\": \"Send {name} #{id} to {address}\",\n      \"raw\": \"Send {amount} wei to {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Request execution\",\n      \"setOutcome\": \"Set outcome\",\n      \"changeOutcome\": \"Change outcome\",\n      \"executeTxs\": \"Execute transaction batch {0} of {1}\",\n      \"executed\": \"All transactions have been executed\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"Something went wrong\",\n      \"connectWallet\": \"Connect wallet to see execution details\",\n      \"switchChain\": \"Switch your wallet to {0} to request execution\",\n      \"question\": \"Did this proposal pass and does it meet the\",\n      \"criteria\": \"acceptance criteria?\",\n      \"proposalPassed\": \"Did the proposal pass?\",\n      \"expired\": \"The proposal has expired\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to request proposal execution\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\",\n      \"bondWarning\": \"Bond is greater than users balance\",\n      \"quorumWarning\": \"Quorum did not reach space requirement\",\n      \"requiredBond\": \"Required Bond:\",\n      \"challengePeriod\": \"Challenge Period:\",\n      \"disputeProposal\": \"UMA Oracle URL to dispute\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"A POAP hasn't been setup for this proposal yet :'(\",\n    \"no_voted_header\": \"Vote to get this POAP\",\n    \"unclaimed_header\": \"Mint your I voted POAP\",\n    \"claimed_header\": \"Congratulations! The POAP has been minted to your collection\",\n    \"loading_header\": \"The POAP is being minted to your collection\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"Browse collection\",\n    \"success_claim\": \"The POAP has been minted to your collection\",\n    \"error_claim\": \"There was a problem minting the token\"\n  },\n  \"progress\": {\n    \"progress\": \"Progress\",\n    \"inProgress\": \"In Progress\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"New Step\",\n    \"description\": \"Description\",\n    \"add\": \"Add\",\n    \"deleteStep\": \"Delete Step\",\n    \"deleteConfirm\": \"Are you sure you want to delete?\",\n    \"delete\": \"Delete\",\n    \"cancel\": \"Cancel\",\n    \"comeBack\": \"Come back after voting is complete to see how this proposal is progressing!\",\n    \"confirmSignature\": \"Signing this message will allow us to authorize your request to update the progress of your proposal.\",\n    \"wentWrong\": \"Oops something went wrong.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"Charts\",\n    \"noVotesYet\": \"There are no votes to visualize yet.\",\n    \"totalVotesPerDay\": \"Total votes per day\",\n    \"shareOfVotingPower\": \"Share of voting power\",\n    \"votingPowerPerDay\": \"Voting power per day\"\n  },\n  \"metaInfo\": {\n    \"home\": {\n      \"title\": \"Snapshot - Where decisions get made\",\n      \"description\": \"Snapshot is an off-chain voting platform that allows DAOs, DeFi protocols, or NFT communities to participate in the decentralized governance easily and without gas fees.\"\n    },\n    \"setup\": {\n      \"title\": \"Create a space\",\n      \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n    },\n    \"timeline\": {\n      \"title\": \"Timeline\",\n      \"description\": \"See what's happening in the Snapshot community.\"\n    },\n    \"playground\": {\n      \"title\": \"Playground\",\n      \"description\": \"{strategy}\"\n    },\n    \"strategy\": {\n      \"title\": \"{strategy} strategy\",\n      \"description\": \"\"\n    },\n    \"space\": {\n      \"create\": {\n        \"title\": \"{space} Create proposal\",\n        \"description\": \"Create a proposal and start a vote.\"\n      },\n      \"about\": {\n        \"title\": \"{space} About \",\n        \"description\": \"{about}\"\n      },\n      \"proposals\": {\n        \"title\": \"{space} Proposals\",\n        \"description\": \"{about}\"\n      },\n      \"proposal\": {\n        \"title\": \"{space} Proposal: {proposal}\",\n        \"description\": \"{body}\"\n      },\n      \"settings\": {\n        \"title\": \"{space} Settings\",\n        \"description\": \"Change how proposals and voting works in this space.\"\n      }\n    }\n  },\n  \"domino\": {\n    \"title\": \"Automations\",\n    \"information\": \"Create automated workflows with just a single click using Domino.\",\n    \"viewMore\": \"View More\",\n    \"createCustomWorkflow\": \"Create custom workflow\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 second | {n} seconds\",\n    \"minute\": \"1 minute | {n} minutes\",\n    \"hour\": \"1 hour | {n} hours\",\n    \"day\": \"1 day | {n} days\",\n    \"week\": \"1 week | {n} weeks\",\n    \"month\": \"1 month | {n} months\",\n    \"year\": \"1 year | {n} years\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Unsupported network\",\n    \"switchNetworkToNetwork\": \"To continue, you need to change the network in your wallet to {network}.\",\n    \"switchToNetwork\": \"Switch to {network}\",\n    \"goToDemoSite\": \"Go to demo website\"\n  },\n  \"treasury\": {\n    \"title\": \"Treasury\",\n    \"wallets\": {\n      \"title\": \"Wallets\",\n      \"empty\": \"This space doesn't have a treasury yet\",\n      \"addTreasury\": \"Add a treasury\"\n    },\n    \"assets\": {\n      \"title\": \"Assets\",\n      \"empty\": \"There are no assets in this contract\"\n    },\n    \"24hChange\": \"24h change\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Your email\",\n    \"title\": \"Get the latest Snapshot updates\",\n    \"join\": \"Join Snapshot newsletter\"\n  },\n  \"emailSubscription\": {\n    \"title\": \"Email subscriptions\",\n    \"subscribe\": \"Subscribe\",\n    \"manage\": \"Manage subscriptions\",\n    \"description\": \"Subscribe to receive email notifications about your joined spaces and proposals activities.\",\n    \"inputCaption\": \"You may be asked to sign a message to verify wallet ownership\",\n    \"inputPlaceholder\": \"Your email\",\n    \"postSubscribeMessage\": {\n      \"successThanks\": \"Thanks for subscribing!\",\n      \"successConfirmation\": \"Please click the confirmation link that has been sent to your email.\"\n    }\n  },\n  \"emailManagement\": {\n    \"title\": \"Subscription management\",\n    \"subtitle\": \"Choose the types of email updates that matter to you:\",\n    \"optionNewProposal\": \"Proposal creation\",\n    \"optionNewProposalDescription\": \"Get informed when a new proposal is submitted in your followed spaces.\",\n    \"optionClosedProposal\": \"Proposal closure\",\n    \"optionClosedProposalDescription\": \"Get informed when a proposal is closed in your followed spaces.\",\n    \"optionSummary\": \"Weekly summary\",\n    \"optionSummaryDescription\": \"Get a weekly report detailing the activity in your followed spaces.\",\n    \"updatePreferences\": \"Update preferences\"\n  },\n  \"emailResend\": {\n    \"title\": \"Verify email\",\n    \"description\": \"Email validation letter has been sent to your email address. Please click the confirmation link to complete the subscription.\"\n  },\n  \"joinCommunity\": \"Join Snapshot community\",\n  \"header\": {\n    \"title\": \"Where decisions get made\",\n    \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot is a decentralized governance platform that makes it easy to create and vote on proposals - all without spending a fortune on gas fees! Plus, our flexible system supports various voting types and strategies, so you can tailor the voting process to your needs.\",\n    \"subHeader\": \"Governance should be a snap\",\n    \"subDescription\": \"Web3 governance doesn't have to be complicated. Snapshot is the perfect solution for organizations looking for an easy and efficient way to govern their community or organization.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Resources\",\n    \"about\": \"About\",\n    \"blog\": \"Blog\",\n    \"network-support\": \"Network support\",\n    \"terms\": \"Terms and conditions\",\n    \"jobs\": \"Jobs\",\n    \"faqs\": \"FAQs\",\n    \"github\": \"GitHub\",\n    \"featureRequests\": \"Request a feature\",\n    \"docs\": \"Docs\",\n    \"support\": \"Support\",\n    \"hiring\": \"Join us!\"\n  }\n}\n"
  },
  {
    "path": "src/locales/es-ES.json",
    "content": "{\n  \"searchPlaceholder\": \"Buscar\",\n  \"spaceCount\": \"{0} espacio(s)\",\n  \"createSpace\": \"Crear espacio\",\n  \"backToHome\": \"Inicio\",\n  \"actions\": \"Acciones\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"Resultados\",\n  \"resultsError\": \"Results could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"resultsCalculating\": \"Final results are being calculated. If you still see this message after a few minutes contact the space admin.\",\n  \"votingPowerFailedMessage\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"votingValidationFailedMessage\": \"There was an error on our side and we could not verify if you are eligible to vote. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n  \"notValidVoterMessage\": \"Oops, you don't seem to be eligible to vote on this proposal.\",\n  \"getHelp\": \"Get help\",\n  \"retry\": \"Retry\",\n  \"currentResults\": \"Resultados actuales\",\n  \"reset\": \"Restablecer\",\n  \"close\": \"Close\",\n  \"save\": \"Guardar\",\n  \"author\": \"Autor\",\n  \"next\": \"Siguente\",\n  \"choice\": \"Choice\",\n  \"submit\": \"Enviar\",\n  \"plugins\": \"Plugins\",\n  \"information\": \"Información\",\n  \"confirm\": \"Confirmar\",\n  \"snapshot\": \"Snapshot\",\n  \"strategies\": \"Estrategia(s)\",\n  \"strategiesPage\": \"Estrategias\",\n  \"space\": \"Espacio\",\n  \"spaces\": \"Espacios\",\n  \"verifiedSpace\": \"Verified space\",\n  \"warningSpace\": \"This space has been flagged as potentially malicious. Proceed with caution.\",\n  \"version\": \"Versión\",\n  \"timeline\": \"Línea de tiempo\",\n  \"ended\": \"ended\",\n  \"started\": \"started\",\n  \"filters\": \"Filtros\",\n  \"allSpaces\": \"Todos los espacios\",\n  \"submitOnchain\": \"Enviar on-chain\",\n  \"inSpaces\": \"En {0} espacio(s)\",\n  \"votes\": \"Votos\",\n  \"seeMore\": \"Ver más\",\n  \"seeAll\": \"See all\",\n  \"network\": \"Red\",\n  \"networks\": \"Redes\",\n  \"skins\": \"Skins\",\n  \"spaceMembers\": \"Members\",\n  \"members\": \"Sin miembros | {count} member | {count} members\",\n  \"editStrategy\": \"Editar estrategia\",\n  \"invalidProposals\": \"Propuestas invalidas\",\n  \"account\": \"Cuenta\",\n  \"create3box\": \"Crear perfil en 3Box\",\n  \"view3box\": \"Ver perfil en 3Box\",\n  \"edit3box\": \"Editar perfil en 3Box\",\n  \"connectWallet\": \"Conectar cartera\",\n  \"toggleSkin\": \"Toggle skin\",\n  \"about\": \"Acerca de\",\n  \"license\": \"Licencia\",\n  \"showMore\": \"Show more\",\n  \"voted\": \"Voted\",\n  \"reload\": \"Reload\",\n  \"ipfsServer\": \"Servidor IPFS\",\n  \"hub\": \"Hub\",\n  \"cancel\": \"Cancelar\",\n  \"delete\": \"Delete\",\n  \"demoSite\": \"Este es el sitio de demostración, ¡pruébalo!\",\n  \"removeDelegation\": \"Eliminar delegación\",\n  \"confirmRemove\": \"¿Está seguro de que desea eliminar su delegación a\",\n  \"removeSpace\": \"para el espacio {0}\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum reached\",\n  \"options\": \"Opción(es)\",\n  \"votingPower\": \"Tu poder de voto\",\n  \"comment\": {\n    \"placeholder\": \"Share your reason (optional)\"\n  },\n  \"receipt\": \"Recibo\",\n  \"relayer\": \"Retransmisor\",\n  \"verifyOnMycrypto\": \"Verificar recibo en MyCrypto\",\n  \"verifyOnSignatorio\": \"Verifica en Signator.io\",\n  \"isCore\": \"Core\",\n  \"notificationsBlocked\": \"Tu navegador está bloqueando las notificaciones\",\n  \"notificationsNotSupported\": \"Your browser does not support notifications\",\n  \"walletNotSupported\": \"Wallet is not supported\",\n  \"seeInExplorer\": \"See explorer\",\n  \"learnMore\": \"Conoce más\",\n  \"logout\": \"Cerrar sesión\",\n  \"continue\": \"Continuar\",\n  \"add\": \"Agregar\",\n  \"edit\": \"Editar\",\n  \"strategyParameters\": \"Parámetros de estrategia\",\n  \"addAction\": \"Añadir acción\",\n  \"removeAction\": \"Eliminar acción\",\n  \"yourChoice\": \"Elegir {0}\",\n  \"targetAddress\": \"Dirección de destino\",\n  \"value\": \"Valor\",\n  \"date\": \"Datos\",\n  \"marketDetails\": \"Detalles del mercado\",\n  \"addMarket\": \"Añadir mercado\",\n  \"selectNetwork\": \"Seleccionar red\",\n  \"conditionId\": \"ID de condición\",\n  \"basetokenAddress\": \"Dirección de token base\",\n  \"quoteAddress\": \"Dirección de la moneda\",\n  \"removeMarket\": \"Eliminar mercado\",\n  \"back\": \"Atrás\",\n  \"loading\": \"Cargando...\",\n  \"predictedImpact\": \"Impacto previsto\",\n  \"marketSymbol\": \"{0} mercado\",\n  \"twoChoicesRequired\": \"Se requieren dos opciones para este plugin.\",\n  \"noResultsFound\": \"Oops, no podemos encontrar ningún resultado\",\n  \"createFirstProposal\": \"Creemos tu primer propuesta\",\n  \"noSpacesJoined\": \"Oops, no has unido a ningún espacio todavía\",\n  \"addFavorites\": \"Añadir favoritos\",\n  \"createdBy\": \"Por {0}\",\n  \"startIn\": \"iniciar {0}\",\n  \"endIn\": \"finalizar {0}\",\n  \"proposalTimeLeft\": \"{0} left\",\n  \"endedAgo\": \"ended {0}\",\n  \"proposalBy\": \"por {0}\",\n  \"endDate\": \"acaba {0}\",\n  \"defaultSkin\": \"Aspecto predeterminado\",\n  \"select\": \"Selecciona\",\n  \"language\": \"Idioma\",\n  \"agree\": \"Acepto\",\n  \"moderators\": \"Moderators\",\n  \"playground\": \"Centro de Pruebas\",\n  \"strategyParams\": \"Parámetros de estrategia\",\n  \"addresses\": \"Direcciones\",\n  \"networkErrorPlayground\": \"Error de red - por favor, abra la consola de su navegador para obtener más información\",\n  \"upload\": \"Subir\",\n  \"join\": \"Unirse\",\n  \"joined\": \"Registrado\",\n  \"leave\": \"Salir\",\n  \"subspaces\": \"Sub-spaces\",\n  \"mainspace\": \"Main space\",\n  \"copyLink\": \"Copiar enlace\",\n  \"duplicate\": \"Duplicate\",\n  \"joinedSpaces\": \"Espacios a los que te has unido\",\n  \"joinSpaces\": \"Únete a los espacios\",\n  \"setDelegationToSpace\": \"Limit delegation to a specific space\",\n  \"theCurrentNetwork\": \"the current network\",\n  \"optional\": \"(optional)\",\n  \"homeLoadmore\": \"Load more\",\n  \"confirmAction\": \"Confirm action\",\n  \"or\": \"or\",\n  \"share\": \"Share\",\n  \"shareOnTwitter\": \"Share on Twitter\",\n  \"shareOnLenster\": \"Share on Lenster\",\n  \"createButton\": \"Create\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Change wallet\",\n  \"createASpace\": \"Create a space\",\n  \"getStarted\": \"Get started\",\n  \"skip\": \"Skip\",\n  \"newSpaceNotice\": {\n    \"header\": \"Your space is live!\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Learn more in the {documentation} or join Snapshot {discord} for help.\",\n    \"gotIt\": \"Got it!\"\n  },\n  \"errors\": {\n    \"required\": \"Este campo es obligatorio\",\n    \"minLength\": \"Field is required\",\n    \"maxLength\": \"La cantidad máxima es {0}\",\n    \"pattern\": \"Carácter inválido\",\n    \"minItems\": \"Mínimo de {0} artículo(s) requeridos\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"minStrategy\": \"Se requiere de al menos una estrategia.\",\n    \"website\": \"URL should be in the format https://www.example.com\",\n    \"format\": \"Formato inválido\",\n    \"type\": \"Type invalido\",\n    \"unsupportedImageType\": \"File type not supported, Supported formats are jpeg, jpg and png\",\n    \"invalidAddress\": \"Invalid address\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Title\",\n    \"discussion\": \"Discussion (optional)\",\n    \"categorie(s)\": \"Select up to 2 categorie(s)\",\n    \"proposalDescription\": \"Description (optional)\",\n    \"preview\": \"Vista previa\",\n    \"choices\": \"Opciones\",\n    \"addChoice\": \"Añadir opción\",\n    \"startDate\": \"Selecciona una fecha de inicio\",\n    \"endDate\": \"Seleccionar fecha de finalización\",\n    \"startTime\": \"Selecciona una hora de inicio\",\n    \"endTime\": \"Seleccionar hora de finalización\",\n    \"publish\": \"Publicar\",\n    \"untitled\": \"Untitled\",\n    \"snapshotBlock\": \"Número de bloque Snapshot\",\n    \"voting\": \"Voting\",\n    \"votingSystem\": \"Voting system\",\n    \"choice\": \"Choice {0}\",\n    \"period\": \"Voting period\",\n    \"start\": \"Start\",\n    \"end\": \"End\",\n    \"days\": \"Days\",\n    \"hours\": \"Hours\",\n    \"minutes\": \"Minutes\",\n    \"schedule\": \"Schedule proposal\",\n    \"delayEnforced\": \"The space enforces a delay until voting can start\",\n    \"periodEnforced\": \"The space enforces the duration of the voting period\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"Edit\",\n    \"continue\": \"Continue\",\n    \"now\": \"Now\",\n    \"votingPeriodExplainer\": \"This is the time period in which users will be able to vote. The proposal will be visible and pending before the start of the voting period.\",\n    \"uploadImageExplainer\": \"Attach images by dragging & dropping, selecting or pasting them.\",\n    \"uploading\": \"Uploading image\",\n    \"markdown\": \"Styling with Markdown is supported\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"Necesitas ser un autor del espacio para poder presentar una propuesta.\",\n        \"minScore\": \"Necesitas tener un mínimo de {0} {1} para poder presentar una propuesta.\"\n      },\n      \"customValidation\": \"Es necesario aprobar la validación de la propuesta para presentarla.\",\n      \"executionError\": \"Verifying your eligibility to create proposals in this space has failed. This is likely due to a misconfigured strategy.\"\n    },\n    \"errorGettingSnapshot\": \"We encountered an error while fetching the snapshot block number which is needed to calculate your voting power. Please try again later.\"\n  },\n  \"delegate\": {\n    \"header\": \"Delegar\",\n    \"selectDelegate\": \"Seleccionar delegado\",\n    \"to\": \"Para\",\n    \"addressPlaceholder\": \"Dirección o nombre de ENS\",\n    \"delegations\": \"Tu(s) delegación(es)\",\n    \"allSpaces\": \"Para todos los espacios\",\n    \"delegated\": \"Delegado a ti\",\n    \"pendingTransaction\": \"no hay transacciones pendientes | 1 transacción pendiente | {count} transacciones pendientes\",\n    \"topDelegates\": \"Los mejores delegados\",\n    \"noDelegatesFoundFor\": \"No delegates found for {0}\",\n    \"noValidEns\": \"Not a valid ENS address.\",\n    \"noValidAddress\": \"Not a valid address\",\n    \"delegateToSelf\": \"You cannot delegate to yourself\",\n    \"delegateToSelfAddress\": \"You cannot delegate to your own ENS address\",\n    \"noValidSpaceId\": \"Not a valid space ID\",\n    \"noDelegationsAndDelegates\": \"Can't find your delegations and delegates? Make sure you are connected to the correct network.\",\n    \"delegateNotSupported\": \"Delegation is currently not supported for {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Emite tu voto\",\n    \"vote\": \"Votar\",\n    \"startDate\": \"Fecha de inicio\",\n    \"endDate\": \"Fecha final\",\n    \"votingSystem\": \"Sistema de votación\",\n    \"privacy\": \"Privacy\",\n    \"invalidChoice\": \"Invalid choice\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Your vote is in!\",\n      \"gnosisSafeTitle\": \"Your vote is pending...\",\n      \"gnosisSafeDescription\": \" Votes with a Safe require additional signers and will be visible once the transaction is confirmed\",\n      \"seeQueue\": \"See queued transactions\",\n      \"tips\": {\n        \"1\": \"Votes can be changed while the proposal is active\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"Propuestas\",\n    \"new\": \"Nueva propuesta\",\n    \"noProposals\": \"¡No hay ninguna propuesta aquí todavía!\",\n    \"createProposal\": \"Crea una propuesta\",\n    \"showMore\": \"Show more\",\n    \"showLess\": \"Show less\",\n    \"states\": {\n      \"all\": \"Todo\",\n      \"core\": \"Core\",\n      \"community\": \"Comunidad\",\n      \"active\": \"Activo\",\n      \"pending\": \"Pendiente\",\n      \"closed\": \"Cerrado\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Notifications\",\n    \"noNotifications\": \"You have no notifications\",\n    \"proposalStarted\": \"proposal has started:\",\n    \"proposalEnded\": \"proposal has ended:\",\n    \"markAllAsRead\": \"Mark all as read\",\n    \"all\": \"All\",\n    \"unread\": \"Unread\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"join\",\n    \"actionCreate\": \"create a proposal in\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"Ajustes\",\n    \"editController\": \"Edit controller\",\n    \"connectWithSpaceOwner\": \"You are in view only mode, to modify space settings connect with a controller or admin wallet.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"edit the space settings\",\n      \"create\": \"create a proposal\",\n      \"vote\": \"vote on this proposal\"\n    },\n    \"currentSpaceControllerIs\": \"The current space controller is {address}\",\n    \"newController\": \"New controller\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Set\",\n    \"profile\": \"Perfil\",\n    \"avatar\": \"Avatar\",\n    \"name\": {\n      \"label\": \"Name\",\n      \"placeholder\": \"e.g. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"About\",\n      \"placeholder\": \"What is your organisation about?\"\n    },\n    \"categories\": {\n      \"label\": \"Categorie(s)\",\n      \"select\": \"Select categorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Terms of service\",\n      \"information\": \"Users will be required to accept these terms once before they can create a proposal or cast a vote\"\n    },\n    \"hideSpace\": \"Ocultar espacio de la página de inicio\",\n    \"links\": \"Social accounts\",\n    \"subspaces\": {\n      \"label\": \"Sub-spaces\",\n      \"information\": \"Sub-spaces will only be shown once configured on both the main space and the sub-space(s). {docs}\",\n      \"parent\": {\n        \"label\": \"Main space\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"The space that this space is a sub-space of will be displayed on the space page\"\n      },\n      \"children\": {\n        \"label\": \"Sub-spaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Related Sub-spaces listed here will be displayed on the space page\"\n      }\n    },\n    \"website\": \"Website\",\n    \"strategies\": {\n      \"label\": \"Strategie(s)\",\n      \"information\": \"Strategies are used to determine voting power or whether a user is eligible to create a proposal\"\n    },\n    \"network\": {\n      \"label\": \"Network\",\n      \"information\": \"The defaul network used for this space. Networks can also be specified in individual strategies\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbol\",\n      \"information\": \"The default symbol used for this space, usually the token symbol i.e. BAL for Balancer\"\n    },\n    \"strategiesList\": \"Select up to 8 strategies\",\n    \"votingPowerIsCumulative\": \"Voting power is cumulative\",\n    \"addStrategy\": \"Añadir estrategia\",\n    \"testInPlayground\": \"Test in playground\",\n    \"admins\": {\n      \"label\": \"Admins\",\n      \"information\": \"Admins are able to modify the space settings and manage the space's proposals\"\n    },\n    \"authors\": {\n      \"label\": \"Authors\",\n      \"information\": \"Authors are always able to create proposals\"\n    },\n    \"proposalValidation\": \"Validación de oferta\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Threshold\",\n      \"information\": \"The minimum amount of voting power required to create a proposal\"\n    },\n    \"allowOnlyAuthors\": \"Habilita que sólo autores puedan enviar una propuesta\",\n    \"editValidation\": \"Editar validación\",\n    \"selectValidation\": \"Seleccionar validación\",\n    \"validationParameters\": \"Parámetros de validación\",\n    \"voting\": \"Votación\",\n    \"votingDelay\": \"Retraso en la votación\",\n    \"votingPeriod\": \"Periodo de votación\",\n    \"hours\": \"Hours\",\n    \"days\": \"Days\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"The minimum amount of voting power required for the proposal to pass\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"The type of voting system used for this space. (Enforced on all future proposals)\"\n    },\n    \"anyType\": \"Cualquiera\",\n    \"hideAbstain\": \"Ignore abstain votes in basic voting results\",\n    \"customDomain\": \"Dominio personalizado\",\n    \"domain\": {\n      \"label\": \"Domain name\",\n      \"placeholder\": \"e.g. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"Tema\",\n    \"treasuries\": {\n      \"label\": \"Treasury\",\n      \"add\": \"Add treasury\",\n      \"edit\": \"Edit treasury\",\n      \"information\": \"Add treasuries of your organization to show them in your space\"\n    },\n    \"addPlugin\": \"Agregar extensión\",\n    \"editPlugin\": \"Editar extensión\",\n    \"pluginParameters\": \"Parámetros de la extensión\",\n    \"proposal\": {\n      \"title\": \"Proposal\",\n      \"guidelines\": {\n        \"title\": \"Guidelines\",\n        \"information\": \"Display a link to your guidelines on proposal creation to help users understand what constitutes a good/valid proposal\"\n      },\n      \"template\": {\n        \"title\": \"Template\",\n        \"information\": \"Start every proposal with a template to help users understand what information is required\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"ej. yam.eth\",\n    \"chooseExistingEns\": \"Choose one of your existing ENS domains to create a space with:\",\n    \"useSingleExistingEns\": \"Use your existing ENS domain:\",\n    \"orRegisterNewEns\": \"Or register a new domain:\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"To create a space, you first need an ENS domain. Enter one below and follow the ENS registration instructions.\",\n    \"createASpace\": \"Crear espacio\",\n    \"registerEnsButton\": \"Register\",\n    \"supportedEnsTLDs\": \"Supported domain endings\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or join Snapshot {discord}.\",\n    \"setSpaceController\": \"Space controller\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \" The space controller is the account that will be able to manage the space settings. Additional space controllers (admins) can be added later.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"When creating a space with a Gnosis Safe it's recommended to set the safe address as the space controller. If you don't, you need to follow some additional steps. {link}\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"Setting the controller requires a transaction on the {network} which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choose ENS address\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. {address}\",\n    \"controllerAddress\": \"Controller address\",\n    \"updateController\": \"Update controller\",\n    \"seeOnEns\": \"See on ENS\",\n    \"goToSettings\": \"Go to settings\",\n    \"setSpaceProfile\": \"Customize your space\",\n    \"waitForTransaction\": \"The transaction need to confirm before you can create your space. {txUrl}\",\n    \"pleaseWaitMessage\": \"This can take a few minutes, please wait while the transaction is being confirmed\",\n    \"notControllerAddress\": \"Please connect with the controller address {wallet} to create the space.\",\n    \"fillCurrentAccount\": \"Use currently logged in account\",\n    \"domain\": {\n      \"title\": \"Setup your space domain\",\n      \"ensMessage\": \" One thing you need before you can create your own space, is an ENS domain on Ethereum mainnet.\",\n      \"ensMessageTestnet\": \"You can also {link} on the Goerli testnet and mess with things there first.\",\n      \"tryDemo\": \"try the demo\",\n      \"yourExistingSpaces\": \"Your existing spaces\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"How would you like to setup your voting strategy?\",\n      \"subtitle\": \"You can change your strategy settings any time.\",\n      \"blockTitle\": \"Setup voting strategy\",\n      \"onePersonOneVote\": {\n        \"title\": \"One person, one vote\",\n        \"description\": \"Manage a whitelist of people who can vote or simply allow any address to vote. Every vote is equal and no token is required\",\n        \"whitelistInformation\": \"Specify a number of accounts that can vote\",\n        \"ticketInformation\": \"Any account can vote\",\n        \"votesEqualInfo\": \"Each vote is equal and no token is required\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Token weighted voting\",\n        \"description\": \"Votes are weighted by a token. The token can be an ERC-20, ERC-721 or ERC-1155 token standard\",\n        \"tokenNotFound\": \"Token not found\",\n        \"seeOnEtherscan\": \"See on Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Custom setup\",\n        \"description\": \"Select up to 8 strategies with a wide range of options. If you can't find the right strategy for your use case, you can create your own\"\n      }\n    },\n    \"validationTitle\": \"Who can manage this space and create proposals?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Edit profile\",\n    \"viewProfile\": \"View profile\",\n    \"about\": {\n      \"header\": \"About\",\n      \"joinedSpaces\": \"Joined spaces\",\n      \"createdSpaces\": \"Created spaces\",\n      \"biography\": \"Bio\",\n      \"notJoinSpacesYet\": \"Hasn't joined any spaces yet\",\n      \"notCreatedSpacesYet\": \"Hasn't created any spaces yet\",\n      \"delegatorNetworkInfo\": \"Change by switching network in your wallet\",\n      \"delegate\": \"Delegate\",\n      \"delegated\": \"Delegated\",\n      \"delegateTo\": \"Delegate to\",\n      \"delegateFor\": \"Delegator for\",\n      \"noDelegatorsMessage\": \"No delegators on {network}\",\n      \"notSupportedNetwork\": \"Delegation currently isn't supported on {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activity\",\n      \"votedFor\": \"Voted {choice}\",\n      \"today\": \"Today\",\n      \"thisWeek\": \"This week\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"No activity yet\"\n    },\n    \"settings\": {\n      \"header\": \"Edit profile\",\n      \"name\": \"Name\",\n      \"biography\": \"Bio\",\n      \"namePlaceholder\": \"Enter name\",\n      \"bioPlaceholder\": \"Tell your story\",\n      \"change\": \"Change\",\n      \"remove\": \"Remove\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"¡Lo hiciste!\",\n    \"copied\": \"¡Copiado!\",\n    \"proposalDeleted\": \"Propuesta eliminada\",\n    \"somethingWentWrong\": \"¡Huy! ¡Algo salió mal!\",\n    \"saved\": \"¡Guardado!\",\n    \"delegationSuccess\": \"Delegación exitosa\",\n    \"delegationRemoved\": \"Delegación eliminada\",\n    \"proposalCreated\": \"Propuesta creada\",\n    \"voteSuccessful\": \"¡Tu voto está dentro!\",\n    \"ensSet\": \"ENS text record was successfully set\",\n    \"transactionSent\": \"Transaction sent\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Crear estrategia\",\n    \"createSkin\": \"Crear apecto\",\n    \"addNetwork\": \"Añadir red\",\n    \"createPlugin\": \"Crear extensión\",\n    \"strategies\": \"estrategia(s)\",\n    \"skins\": \"aspecto(s)\",\n    \"networks\": \"red(es)\",\n    \"plugins\": \"extensión(es)\",\n    \"results\": \"resultado(s)\",\n    \"category\": \"Category\",\n    \"categories\": {\n      \"all\": \"All\",\n      \"protocol\": \"Protocol\",\n      \"social\": \"Social\",\n      \"investment\": \"Investment\",\n      \"grant\": \"Grant\",\n      \"service\": \"Service\",\n      \"media\": \"Media\",\n      \"creator\": \"Creator\",\n      \"collector\": \"Collector\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Selecciona sistema de votación\",\n    \"single-choice\": \"Votación de elección única\",\n    \"approval\": \"Votación por aprobación\",\n    \"quadratic\": \"Votación cuadrática\",\n    \"ranked-choice\": \"Voto por ranking\",\n    \"weighted\": \"Votación ponderada\",\n    \"basic\": \"Votación básica\",\n    \"description\": {\n      \"single-choice\": \"Cada votante puede elegir sólo una opción.\",\n      \"approval\": \"Los votantes puede seleccionar cualquier cantidad de opciones.\",\n      \"quadratic\": \"Cada votante puede repartir sus votos entre cualquier cantidad de opciones. Los resultados son calculados de forma cuadrática.\",\n      \"ranked-choice\": \"Cada votante puede seleccionar y clasificar cualquier cantidad de opciones. Los resultados serán calculados con un método de segunda vuelta instantánea.\",\n      \"weighted\": \"Cada votante puede repartir sus votos entre cualquier cantidad de opciones.\",\n      \"basic\": \"Votación individual con tres opciones: A favor, En Contra o Abstenerse\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Privacy\",\n    \"title\": \"Select voting privacy\",\n    \"information\": \"The type of privacy used on proposals. (Enforced on all future proposals)\",\n    \"any\": \"Any\",\n    \"none\": \"None\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Choices are encrypted and only visible once the voting period is over\",\n      \"tooltip\": \"This proposal has Shutter privacy enabled. All votes will be encrypted until the voting period has ended and the final score is calculated\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select voting validation\",\n    \"information\": \"The type of validation used to determine if a user can vote. (Enforced on all future proposals)\",\n    \"any\": {\n      \"label\": \"Anyone can vote\",\n      \"description\": \"Anyone with voting power can cast a vote.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement to vote on this proposal. \"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your proposals from spam and vote manipulation by requiring users to have a Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold} and {operator} of the following stamps to vote on this proposal: {stamps}. \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Resultado actual\",\n    \"currentBond\": \"Bono actual\",\n    \"finalizedIn\": \"Finalizado {0}\",\n    \"executableIn\": \"Ejecutable {0}\",\n    \"finalOutcome\": \"Resultado\",\n    \"nextBond\": \"Vínculo para establecer resultado\",\n    \"setOutcomeTo\": \"Establecer el resultado en\",\n    \"claimBond\": \"Reclamar vínculo\",\n    \"addBatch\": \"Agrega conjunto de transacciones\",\n    \"batch\": \"Conjunto de transacciones\",\n    \"transactions\": \"Transacciones\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"Dirección inválida\",\n    \"invalidAmount\": \"Monto inválido\",\n    \"invalidValue\": \"Valor inválido\",\n    \"invalidAbi\": \"ABI inválido\",\n    \"invalidData\": \"Data invalida\",\n    \"value\": \"Valor (wei)\",\n    \"data\": \"Data\",\n    \"noCollectibles\": \"No coleccionables\",\n    \"asset\": \"Activo\",\n    \"amount\": \"Monto\",\n    \"type\": \"Tipo\",\n    \"transferFunds\": \"Transferir fondos\",\n    \"transferNFT\": \"Transferir NFT\",\n    \"contractInteraction\": \"Contrato de interacción\",\n    \"rawTransaction\": \"Transacción cruda\",\n    \"addTransaction\": \"Añadir transacción\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} comisión para {address}\",\n      \"transferFunds\": \"Transfer {amount} {tokenSymbol} para {address}\",\n      \"transferNFT\": \"Enviar {name} #{id} a {address}\",\n      \"raw\": \"Enviar {amount} comisión a {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Solicitar ejecución\",\n      \"setOutcome\": \"Establecer resultado\",\n      \"changeOutcome\": \"Cambiar resultado\",\n      \"executeTxs\": \"Ejecutar transacción de lote {0} a {1}\",\n      \"executed\": \"Todas las transacciones han sido ejecutadas\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"Algo salió mal\",\n      \"connectWallet\": \"Conecta tu cartera para ver los detalles de la ejecución\",\n      \"switchChain\": \"Cambia tu cartera a {0} para solicitar la ejecución\",\n      \"question\": \"Acaso pasó esta propuesta y cumple los\",\n      \"criteria\": \"criterios de aceptación?\",\n      \"proposalPassed\": \"Did the proposal pass?\",\n      \"expired\": \"The proposal has expired\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to confirm the proposal passed\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"requestToolTip\": \"This will propose the transactions from this Snapshot vote on-chain. After a challenge window, if the proposal is valid, the transactions can be executed and your bond will be returned.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"Aun no se ha creado un POAP para esta oferta :'(\",\n    \"no_voted_header\": \"Vote para obtener este POAP\",\n    \"unclaimed_header\": \"Mint your I voted POAP\",\n    \"claimed_header\": \"Congratulations! The POAP has been minted to your collection\",\n    \"loading_header\": \"The POAP is being minted to your collection\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"Navegar colección\",\n    \"success_claim\": \"The POAP has been minted to your collection\",\n    \"error_claim\": \"Hubo un problema en la acuñación de esta token\"\n  },\n  \"progress\": {\n    \"progress\": \"Progress\",\n    \"inProgress\": \"In Progress\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"New Step\",\n    \"description\": \"Description\",\n    \"add\": \"Add\",\n    \"deleteStep\": \"Delete Step\",\n    \"deleteConfirm\": \"Are you sure you want to delete?\",\n    \"delete\": \"Delete\",\n    \"cancel\": \"Cancel\",\n    \"comeBack\": \"Come back after voting is complete to see how this proposal is progressing!\",\n    \"confirmSignature\": \"Signing this message will allow us to authorize your request to update the progress of your proposal.\",\n    \"wentWrong\": \"Oops something went wrong.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"Gráficos\",\n    \"noVotesYet\": \"No hay votos para visualizar todavía.\",\n    \"totalVotesPerDay\": \"Total de votos por día\",\n    \"shareOfVotingPower\": \"Participación en el poder de voto\",\n    \"votingPowerPerDay\": \"Poder de votación por día\"\n  },\n  \"comment_box\": {\n    \"title\": \"Caja de comentarios\",\n    \"add\": \"Agrega aquí tu comentario\",\n    \"submit\": \"Enviar\",\n    \"preview\": \"Vista previa\",\n    \"continue_editing\": \"Continuar editando\",\n    \"edit\": \"Edita tu respuesta aquí\",\n    \"edit_button\": \"Editar\",\n    \"dismiss\": \"Descartar\",\n    \"delete\": \"Eliminar\",\n    \"add_reply\": \"Agrega tu respuesta aquí\",\n    \"edit_comment\": \"Editar comentario\",\n    \"edit_modal\": \"¿Estás seguro que quieres editar?\",\n    \"yes\": \"Sí\",\n    \"no\": \"No\",\n    \"delete_comment\": \"Eliminar comentario\",\n    \"delete_modal\": \"¿Estás seguro que quieres eliminar?\",\n    \"error\": \"Oops, algo salió mal\",\n    \"replies\": \"respuestas\",\n    \"hide\": \"Esconder\",\n    \"show\": \"Mostrar\",\n    \"reply\": \"Responder\",\n    \"load_more\": \"Cargar más\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Create a space\",\n      \"timeline\": \"Timeline\",\n      \"notifications\": \"Notifications\",\n      \"explore\": \"Explore\",\n      \"playground\": \"Playground\",\n      \"space\": {\n        \"create\": \"Create {space} proposal\",\n        \"about\": \"About {space}\",\n        \"proposals\": \"{space} Proposals\",\n        \"proposal\": \"{space} proposal: {proposal}\",\n        \"settings\": \"{space} Settings\"\n      },\n      \"strategy\": \"{key} strategy\",\n      \"delegate\": \"Delegate\",\n      \"ranking\": \"Ranking\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Track proposals for {spaceName}\",\n    \"text\": \"Receive notifications every time a new proposal is created or ends\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 second | {n} seconds\",\n    \"minute\": \"1 minute | {n} minutes\",\n    \"hour\": \"1 hour | {n} hours\",\n    \"day\": \"1 day | {n} days\",\n    \"week\": \"1 week | {n} weeks\",\n    \"month\": \"1 month | {n} months\",\n    \"year\": \"1 year | {n} years\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Unsupported network\",\n    \"switchNetworkToNetwork\": \"To continue, you need to change the network in your wallet to {network}.\",\n    \"switchToNetwork\": \"Switch to {network}\",\n    \"goToDemoSite\": \"Go to demo website\"\n  },\n  \"treasury\": {\n    \"title\": \"Treasury\",\n    \"wallets\": {\n      \"title\": \"Wallets\",\n      \"empty\": \"This space doesn't have a treasury yet\",\n      \"addTreasury\": \"Add a treasury\"\n    },\n    \"assets\": {\n      \"title\": \"Assets\",\n      \"empty\": \"There are no assets in this contract\"\n    },\n    \"24hChange\": \"24h change\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Your email\",\n    \"title\": \"Get the latest Snapshot updates\"\n  },\n  \"joinCommunity\": \"Join Snapshot community\",\n  \"header\": {\n    \"title\": \"Where decisions get made\",\n    \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot is a decentralized governance platform that makes it easy to create and vote on proposals - all without spending a fortune on gas fees! Plus, our flexible system supports various voting types and strategies, so you can tailor the voting process to your needs.\",\n    \"subHeader\": \"Governance should be a snap\",\n    \"subDescription\": \"Web3 governance doesn't have to be complicated. Snapshot is the perfect solution for organizations looking for an easy and efficient way to govern their community or organization.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Resources\",\n    \"about\": \"About\",\n    \"blog\": \"Blog\",\n    \"jobs\": \"Jobs\",\n    \"discussions\": \"Discussions\",\n    \"github\": \"GitHub\",\n    \"docs\": \"Docs\",\n    \"support\": \"Support\",\n    \"hiring\": \"Join us!\"\n  }\n}\n"
  },
  {
    "path": "src/locales/fil-PH.json",
    "content": "{\n  \"searchPlaceholder\": \"Magsaliksik\",\n  \"spaceCount\": \"{0} puwang\",\n  \"createSpace\": \"Lumikha ng puwang\",\n  \"backToHome\": \"Bahay\",\n  \"actions\": \"Mga aksyon\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"Mga resulta\",\n  \"resultsError\": \"Results could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"resultsCalculating\": \"Final results are being calculated. If you still see this message after a few minutes contact the space admin.\",\n  \"votingPowerFailedMessage\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"votingValidationFailedMessage\": \"There was an error on our side and we could not verify if you are eligible to vote. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n  \"notValidVoterMessage\": \"Oops, you don't seem to be eligible to vote on this proposal.\",\n  \"getHelp\": \"Get help\",\n  \"retry\": \"Retry\",\n  \"currentResults\": \"Mga kasalukuyang resulta\",\n  \"reset\": \"I-reset\",\n  \"close\": \"Close\",\n  \"save\": \"I-save\",\n  \"author\": \"May-akda\",\n  \"next\": \"Susunod\",\n  \"choice\": \"Choice\",\n  \"submit\": \"Ipadala\",\n  \"plugins\": \"Mga plugin\",\n  \"information\": \"Impormasyon\",\n  \"confirm\": \"Kumpirmahin\",\n  \"snapshot\": \"Snapshot\",\n  \"strategies\": \"\\t\\nMga estratehiya\",\n  \"strategiesPage\": \"\\t\\nMga estratehiya\",\n  \"space\": \"Silid\",\n  \"spaces\": \"Mga silid\",\n  \"verifiedSpace\": \"Verified space\",\n  \"warningSpace\": \"This space has been flagged as potentially malicious. Proceed with caution.\",\n  \"version\": \"Bersyon\",\n  \"timeline\": \"Timeline\",\n  \"ended\": \"ended\",\n  \"started\": \"started\",\n  \"filters\": \"Mga filter\",\n  \"allSpaces\": \"Lahat ng mga silid\",\n  \"submitOnchain\": \"Isumite ang on-chain\",\n  \"inSpaces\": \"Sa {0} (mga) espasyo\",\n  \"votes\": \"Mga boto\",\n  \"seeMore\": \"Tumingin pa\",\n  \"seeAll\": \"See all\",\n  \"network\": \"Network\",\n  \"networks\": \"Mga network\",\n  \"skins\": \"Mga Skin\",\n  \"spaceMembers\": \"Members\",\n  \"members\": \"Walang miyembro | {count} miyembro | {count} mga miyembro\",\n  \"editStrategy\": \"Edit strategy\",\n  \"invalidProposals\": \"Mga di-wastong panukala\",\n  \"account\": \"Account\",\n  \"create3box\": \"Gumawa ng profile sa 3Box\",\n  \"view3box\": \"Ipakita ang profile sa 3Box\",\n  \"edit3box\": \"Ibahin ang profile sa 3Box\",\n  \"connectWallet\": \"Ikonekta ang wallet\",\n  \"toggleSkin\": \"Toggle skin\",\n  \"about\": \"Tungkol\",\n  \"license\": \"Lisensya\",\n  \"showMore\": \"Show more\",\n  \"voted\": \"Voted\",\n  \"reload\": \"Reload\",\n  \"ipfsServer\": \"IPFS server\",\n  \"hub\": \"Hub\",\n  \"cancel\": \"Kanselahin\",\n  \"delete\": \"Delete\",\n  \"demoSite\": \"Ito ay isang demo site, subukan!\",\n  \"removeDelegation\": \"Tanggalin ang delegasyon\",\n  \"confirmRemove\": \"Sigurado kaba na na gusto mo tanggalin ang iyong delegasyon sa\",\n  \"removeSpace\": \"para sa puwang {0}\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum reached\",\n  \"options\": \"Opsyon\",\n  \"votingPower\": \"Ang lakas mo sa pagboto\",\n  \"comment\": {\n    \"placeholder\": \"Share your reason (optional)\"\n  },\n  \"receipt\": \"Resibo\",\n  \"relayer\": \"Taga relay\",\n  \"verifyOnMycrypto\": \"I-verify ang receipt sa MyCrypto\",\n  \"verifyOnSignatorio\": \"I-verify sa Signator.io\",\n  \"isCore\": \"Core\",\n  \"notificationsBlocked\": \"Bina-block ng iyong browser ang mga notification\",\n  \"notificationsNotSupported\": \"Your browser does not support notifications\",\n  \"walletNotSupported\": \"Wallet is not supported\",\n  \"seeInExplorer\": \"See explorer\",\n  \"learnMore\": \"Karagdagang impormasyon\",\n  \"logout\": \"I-logout\",\n  \"continue\": \"Ipagpatuloy\",\n  \"add\": \"Idagdag\",\n  \"edit\": \"I-Edit\",\n  \"strategyParameters\": \"Mga parameter ng estratehiya\",\n  \"addAction\": \"Magdagdag ng aksyon\",\n  \"removeAction\": \"Alisin ang pagkilos\",\n  \"yourChoice\": \"Pagpipilian {0}\",\n  \"targetAddress\": \"Target na address\",\n  \"value\": \"Halaga\",\n  \"date\": \"Datos\",\n  \"marketDetails\": \"Market details\",\n  \"addMarket\": \"Magdagdag ng merkado\",\n  \"selectNetwork\": \"Pumili ng network\",\n  \"conditionId\": \"Condition ID\",\n  \"basetokenAddress\": \"Base token address\",\n  \"quoteAddress\": \"I-quote ang address ng pera\",\n  \"removeMarket\": \"Alisin ang merkado\",\n  \"back\": \"Bumalik\",\n  \"loading\": \"Naglo-load...\",\n  \"predictedImpact\": \"Hinulaang epekto\",\n  \"marketSymbol\": \"{0} market\",\n  \"twoChoicesRequired\": \"Dalawang choice ang kailangan para sa plugin na ito.\",\n  \"noResultsFound\": \"Oops, wala kaming makitang mga resulta\",\n  \"createFirstProposal\": \"Gawin natin ang iyong unang panukala\",\n  \"noSpacesJoined\": \"Oops, hindi ka pa nakakasali sa anumang space\",\n  \"addFavorites\": \"Magdagdag ng mga paborito\",\n  \"createdBy\": \"By {0}\",\n  \"startIn\": \"simula {0}\",\n  \"endIn\": \"pagtatapos {0}\",\n  \"proposalTimeLeft\": \"{0} left\",\n  \"endedAgo\": \"ended {0}\",\n  \"proposalBy\": \"by {0}\",\n  \"endDate\": \"pagtatapos {0}\",\n  \"defaultSkin\": \"Default skin\",\n  \"select\": \"Piliin\",\n  \"language\": \"Lingwahe\",\n  \"agree\": \"Sang-ayon ako\",\n  \"moderators\": \"Moderators\",\n  \"playground\": \"Palaruan\",\n  \"strategyParams\": \"Param ng estratehiya\",\n  \"addresses\": \"Mga address\",\n  \"networkErrorPlayground\": \"Error sa network - mangyaring buksan ang iyong browser console para sa higit pang impormasyon\",\n  \"upload\": \"I-upload\",\n  \"join\": \"Sumali\",\n  \"joined\": \"Sumali\",\n  \"leave\": \"Umalis\",\n  \"subspaces\": \"Sub-spaces\",\n  \"mainspace\": \"Main space\",\n  \"copyLink\": \"Kopyahin ang link\",\n  \"duplicate\": \"Duplicate\",\n  \"joinedSpaces\": \"Pinagsamang mga puwang\",\n  \"joinSpaces\": \"Sumali sa mga puwang\",\n  \"setDelegationToSpace\": \"Limit delegation to a specific space\",\n  \"theCurrentNetwork\": \"the current network\",\n  \"optional\": \"(optional)\",\n  \"homeLoadmore\": \"Load more\",\n  \"confirmAction\": \"Confirm action\",\n  \"or\": \"or\",\n  \"share\": \"Share\",\n  \"shareOnTwitter\": \"Share on Twitter\",\n  \"shareOnLenster\": \"Share on Lenster\",\n  \"createButton\": \"Create\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Change wallet\",\n  \"createASpace\": \"Create a space\",\n  \"getStarted\": \"Get started\",\n  \"skip\": \"Skip\",\n  \"newSpaceNotice\": {\n    \"header\": \"Your space is live!\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Learn more in the {documentation} or join Snapshot {discord} for help.\",\n    \"gotIt\": \"Got it!\"\n  },\n  \"errors\": {\n    \"required\": \"Kailangang punan ang patlang\",\n    \"minLength\": \"Field is required\",\n    \"maxLength\": \"Ang maximum na haba ay {0}\",\n    \"pattern\": \"Di-wastong character\",\n    \"minItems\": \"Kinakailangan ang minimum na {0} (mga) item\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"minStrategy\": \"Hindi bababa sa isang diskarte ang kinakailangan.\",\n    \"website\": \"URL should be in the format https://www.example.com\",\n    \"format\": \"Di-wastong format\",\n    \"type\": \"Di-wastong uri\",\n    \"unsupportedImageType\": \"File type not supported, Supported formats are jpeg, jpg and png\",\n    \"invalidAddress\": \"Invalid address\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Title\",\n    \"discussion\": \"Discussion (optional)\",\n    \"categorie(s)\": \"Select up to 2 categorie(s)\",\n    \"proposalDescription\": \"Description (optional)\",\n    \"preview\": \"Ipakita\",\n    \"choices\": \"Mga pagpipilian\",\n    \"addChoice\": \"Magdagdag ng pagpipilian\",\n    \"startDate\": \"Pumili ng petsa ng pagsisimula\",\n    \"endDate\": \"Pumili ng petsa ng pagtatapos\",\n    \"startTime\": \"Pumili ng oras ng pagsisimula\",\n    \"endTime\": \"Pumili ng oras ng pagtatapos\",\n    \"publish\": \"Ilathala\",\n    \"untitled\": \"Untitled\",\n    \"snapshotBlock\": \"Snapshot block number\",\n    \"voting\": \"Voting\",\n    \"votingSystem\": \"Voting system\",\n    \"choice\": \"Choice {0}\",\n    \"period\": \"Voting period\",\n    \"start\": \"Start\",\n    \"end\": \"End\",\n    \"days\": \"Days\",\n    \"hours\": \"Hours\",\n    \"minutes\": \"Minutes\",\n    \"schedule\": \"Schedule proposal\",\n    \"delayEnforced\": \"The space enforces a delay until voting can start\",\n    \"periodEnforced\": \"The space enforces the duration of the voting period\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"Edit\",\n    \"continue\": \"Continue\",\n    \"now\": \"Now\",\n    \"votingPeriodExplainer\": \"This is the time period in which users will be able to vote. The proposal will be visible and pending before the start of the voting period.\",\n    \"uploadImageExplainer\": \"Attach images by dragging & dropping, selecting or pasting them.\",\n    \"uploading\": \"Uploading image\",\n    \"markdown\": \"Styling with Markdown is supported\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"Kailangan mo maging miyembro ng ispasyo para makapag sumite ng proposal.\",\n        \"minScore\": \"Kailangan mong magkaroon ng minimum na {0} {1} upang makapagsumite ng panukala.\"\n      },\n      \"customValidation\": \"Kailangan mong ipasa ang pagpapatunay ng panukala upang makapagsumite ng isang panukala.\",\n      \"executionError\": \"Verifying your eligibility to create proposals in this space has failed. This is likely due to a misconfigured strategy.\"\n    },\n    \"errorGettingSnapshot\": \"We encountered an error while fetching the snapshot block number which is needed to calculate your voting power. Please try again later.\"\n  },\n  \"delegate\": {\n    \"header\": \"Delegasyon\",\n    \"selectDelegate\": \"Pumili ng delegado\",\n    \"to\": \"Sa\",\n    \"addressPlaceholder\": \"Address o pangalan ng ENS\",\n    \"delegations\": \"Ang iyong delegasyon\",\n    \"allSpaces\": \"Para sa lahat ng mga puwang\",\n    \"delegated\": \"Pinagkatiwala kay\",\n    \"pendingTransaction\": \"walang nakabinbing transaksyon | 1 nakabinbing transaksyon | {count} mga nakabinbing transaksyon\",\n    \"topDelegates\": \"Nangungunang mga delegado\",\n    \"noDelegatesFoundFor\": \"No delegates found for {0}\",\n    \"noValidEns\": \"Not a valid ENS address.\",\n    \"noValidAddress\": \"Not a valid address\",\n    \"delegateToSelf\": \"You cannot delegate to yourself\",\n    \"delegateToSelfAddress\": \"You cannot delegate to your own ENS address\",\n    \"noValidSpaceId\": \"Not a valid space ID\",\n    \"noDelegationsAndDelegates\": \"Can't find your delegations and delegates? Make sure you are connected to the correct network.\",\n    \"delegateNotSupported\": \"Delegation is currently not supported for {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Ilagay ang iyong boto\",\n    \"vote\": \"Iboto\",\n    \"startDate\": \"Simula ng petsa\",\n    \"endDate\": \"Pagtatapos ng petsa\",\n    \"votingSystem\": \"Sistema ng pagboto\",\n    \"privacy\": \"Privacy\",\n    \"invalidChoice\": \"Invalid choice\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Your vote is in!\",\n      \"gnosisSafeTitle\": \"Your vote is pending...\",\n      \"gnosisSafeDescription\": \" Votes with a Safe require additional signers and will be visible once the transaction is confirmed\",\n      \"seeQueue\": \"See queued transactions\",\n      \"tips\": {\n        \"1\": \"Votes can be changed while the proposal is active\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"Mga Panukala\",\n    \"new\": \"Bagong panukala\",\n    \"noProposals\": \"Wala pang masyadong kadaming mga panukala ang naririto!\",\n    \"createProposal\": \"Gumawa ng panukala\",\n    \"showMore\": \"Show more\",\n    \"showLess\": \"Show less\",\n    \"states\": {\n      \"all\": \"Lahat\",\n      \"core\": \"Core\",\n      \"community\": \"Komunidad\",\n      \"active\": \"Aktibo\",\n      \"pending\": \"Pending\",\n      \"closed\": \"Nakasara\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Notifications\",\n    \"noNotifications\": \"You have no notifications\",\n    \"proposalStarted\": \"proposal has started:\",\n    \"proposalEnded\": \"proposal has ended:\",\n    \"markAllAsRead\": \"Mark all as read\",\n    \"all\": \"All\",\n    \"unread\": \"Unread\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"join\",\n    \"actionCreate\": \"create a proposal in\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"Mga Setting\",\n    \"editController\": \"Edit controller\",\n    \"connectWithSpaceOwner\": \"You are in view only mode, to modify space settings connect with a controller or admin wallet.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"edit the space settings\",\n      \"create\": \"create a proposal\",\n      \"vote\": \"vote on this proposal\"\n    },\n    \"currentSpaceControllerIs\": \"The current space controller is {address}\",\n    \"newController\": \"New controller\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Set\",\n    \"profile\": \"Profile\",\n    \"avatar\": \"Avatar\",\n    \"name\": {\n      \"label\": \"Name\",\n      \"placeholder\": \"e.g. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"About\",\n      \"placeholder\": \"What is your organisation about?\"\n    },\n    \"categories\": {\n      \"label\": \"Categorie(s)\",\n      \"select\": \"Select categorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Terms of service\",\n      \"information\": \"Users will be required to accept these terms once before they can create a proposal or cast a vote\"\n    },\n    \"hideSpace\": \"Itago ang espasyo sa homepage\",\n    \"links\": \"Social accounts\",\n    \"subspaces\": {\n      \"label\": \"Sub-spaces\",\n      \"information\": \"Sub-spaces will only be shown once configured on both the main space and the sub-space(s). {docs}\",\n      \"parent\": {\n        \"label\": \"Main space\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"The space that this space is a sub-space of will be displayed on the space page\"\n      },\n      \"children\": {\n        \"label\": \"Sub-spaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Related Sub-spaces listed here will be displayed on the space page\"\n      }\n    },\n    \"website\": \"Website\",\n    \"strategies\": {\n      \"label\": \"Strategie(s)\",\n      \"information\": \"Strategies are used to determine voting power or whether a user is eligible to create a proposal\"\n    },\n    \"network\": {\n      \"label\": \"Network\",\n      \"information\": \"The defaul network used for this space. Networks can also be specified in individual strategies\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbol\",\n      \"information\": \"The default symbol used for this space, usually the token symbol i.e. BAL for Balancer\"\n    },\n    \"strategiesList\": \"Select up to 8 strategies\",\n    \"votingPowerIsCumulative\": \"Voting power is cumulative\",\n    \"addStrategy\": \"Add strategy\",\n    \"testInPlayground\": \"Test in playground\",\n    \"admins\": {\n      \"label\": \"Admins\",\n      \"information\": \"Admins are able to modify the space settings and manage the space's proposals\"\n    },\n    \"authors\": {\n      \"label\": \"Authors\",\n      \"information\": \"Authors are always able to create proposals\"\n    },\n    \"proposalValidation\": \"Pagpapatunay ng panukala\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Threshold\",\n      \"information\": \"The minimum amount of voting power required to create a proposal\"\n    },\n    \"allowOnlyAuthors\": \"Pahintulutan lamang ang mga may-akda na magsumite ng isang panukala\",\n    \"editValidation\": \"I-edit ang pagpapatunay\",\n    \"selectValidation\": \"Piliin ang pagpapatunay\",\n    \"validationParameters\": \"Mga parameter ng pagpapatunay\",\n    \"voting\": \"Pagboto\",\n    \"votingDelay\": \"Pagkaantala ng pagboto\",\n    \"votingPeriod\": \"Panahon ng pagboto\",\n    \"hours\": \"Hours\",\n    \"days\": \"Days\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"The minimum amount of voting power required for the proposal to pass\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"The type of voting system used for this space. (Enforced on all future proposals)\"\n    },\n    \"anyType\": \"Kahit Ano\",\n    \"hideAbstain\": \"Ignore abstain votes in basic voting results\",\n    \"customDomain\": \"Custom na domain\",\n    \"domain\": {\n      \"label\": \"Domain name\",\n      \"placeholder\": \"e.g. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"Skin\",\n    \"treasuries\": {\n      \"label\": \"Treasury\",\n      \"add\": \"Add treasury\",\n      \"edit\": \"Edit treasury\",\n      \"information\": \"Add treasuries of your organization to show them in your space\"\n    },\n    \"addPlugin\": \"Magdagdag ng Plugin\",\n    \"editPlugin\": \"I-edit ang plugin\",\n    \"pluginParameters\": \"Plugin parameters\",\n    \"proposal\": {\n      \"title\": \"Proposal\",\n      \"guidelines\": {\n        \"title\": \"Guidelines\",\n        \"information\": \"Display a link to your guidelines on proposal creation to help users understand what constitutes a good/valid proposal\"\n      },\n      \"template\": {\n        \"title\": \"Template\",\n        \"information\": \"Start every proposal with a template to help users understand what information is required\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"e.g. yam.eth\",\n    \"chooseExistingEns\": \"Choose one of your existing ENS domains to create a space with:\",\n    \"useSingleExistingEns\": \"Use your existing ENS domain:\",\n    \"orRegisterNewEns\": \"Or register a new domain:\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"To create a space, you first need an ENS domain. Enter one below and follow the ENS registration instructions.\",\n    \"createASpace\": \"Create a space\",\n    \"registerEnsButton\": \"Register\",\n    \"supportedEnsTLDs\": \"Supported domain endings\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or join Snapshot {discord}.\",\n    \"setSpaceController\": \"Space controller\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \" The space controller is the account that will be able to manage the space settings. Additional space controllers (admins) can be added later.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"When creating a space with a Gnosis Safe it's recommended to set the safe address as the space controller. If you don't, you need to follow some additional steps. {link}\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"Setting the controller requires a transaction on the {network} which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choose ENS address\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. {address}\",\n    \"controllerAddress\": \"Controller address\",\n    \"updateController\": \"Update controller\",\n    \"seeOnEns\": \"See on ENS\",\n    \"goToSettings\": \"Go to settings\",\n    \"setSpaceProfile\": \"Customize your space\",\n    \"waitForTransaction\": \"The transaction need to confirm before you can create your space. {txUrl}\",\n    \"pleaseWaitMessage\": \"This can take a few minutes, please wait while the transaction is being confirmed\",\n    \"notControllerAddress\": \"Please connect with the controller address {wallet} to create the space.\",\n    \"fillCurrentAccount\": \"Use currently logged in account\",\n    \"domain\": {\n      \"title\": \"Setup your space domain\",\n      \"ensMessage\": \" One thing you need before you can create your own space, is an ENS domain on Ethereum mainnet.\",\n      \"ensMessageTestnet\": \"You can also {link} on the Goerli testnet and mess with things there first.\",\n      \"tryDemo\": \"try the demo\",\n      \"yourExistingSpaces\": \"Your existing spaces\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"How would you like to setup your voting strategy?\",\n      \"subtitle\": \"You can change your strategy settings any time.\",\n      \"blockTitle\": \"Setup voting strategy\",\n      \"onePersonOneVote\": {\n        \"title\": \"One person, one vote\",\n        \"description\": \"Manage a whitelist of people who can vote or simply allow any address to vote. Every vote is equal and no token is required\",\n        \"whitelistInformation\": \"Specify a number of accounts that can vote\",\n        \"ticketInformation\": \"Any account can vote\",\n        \"votesEqualInfo\": \"Each vote is equal and no token is required\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Token weighted voting\",\n        \"description\": \"Votes are weighted by a token. The token can be an ERC-20, ERC-721 or ERC-1155 token standard\",\n        \"tokenNotFound\": \"Token not found\",\n        \"seeOnEtherscan\": \"See on Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Custom setup\",\n        \"description\": \"Select up to 8 strategies with a wide range of options. If you can't find the right strategy for your use case, you can create your own\"\n      }\n    },\n    \"validationTitle\": \"Who can manage this space and create proposals?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Edit profile\",\n    \"viewProfile\": \"View profile\",\n    \"about\": {\n      \"header\": \"About\",\n      \"joinedSpaces\": \"Joined spaces\",\n      \"createdSpaces\": \"Created spaces\",\n      \"biography\": \"Bio\",\n      \"notJoinSpacesYet\": \"Hasn't joined any spaces yet\",\n      \"notCreatedSpacesYet\": \"Hasn't created any spaces yet\",\n      \"delegatorNetworkInfo\": \"Change by switching network in your wallet\",\n      \"delegate\": \"Delegate\",\n      \"delegated\": \"Delegated\",\n      \"delegateTo\": \"Delegate to\",\n      \"delegateFor\": \"Delegator for\",\n      \"noDelegatorsMessage\": \"No delegators on {network}\",\n      \"notSupportedNetwork\": \"Delegation currently isn't supported on {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activity\",\n      \"votedFor\": \"Voted {choice}\",\n      \"today\": \"Today\",\n      \"thisWeek\": \"This week\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"No activity yet\"\n    },\n    \"settings\": {\n      \"header\": \"Edit profile\",\n      \"name\": \"Name\",\n      \"biography\": \"Bio\",\n      \"namePlaceholder\": \"Enter name\",\n      \"bioPlaceholder\": \"Tell your story\",\n      \"change\": \"Change\",\n      \"remove\": \"Remove\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"You did it!\",\n    \"copied\": \"Copied!\",\n    \"proposalDeleted\": \"Proposal deleted\",\n    \"somethingWentWrong\": \"Oops, may maling nangyari!\",\n    \"saved\": \"Na-save!\",\n    \"delegationSuccess\": \"Tagumpay ng Delegasyon\",\n    \"delegationRemoved\": \"Inalis ang delegasyon\",\n    \"proposalCreated\": \"Nagawa ang panukala\",\n    \"voteSuccessful\": \"Ang iyong boto ay nasa!\",\n    \"ensSet\": \"ENS text record was successfully set\",\n    \"transactionSent\": \"Transaction sent\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Lumikha ng diskarte\",\n    \"createSkin\": \"Lumikha ng skin\",\n    \"addNetwork\": \"Magdagdag ng network\",\n    \"createPlugin\": \"Lumikha ng plugin\",\n    \"strategies\": \"\\t\\nmga estratehiya\",\n    \"skins\": \"mga Skin\",\n    \"networks\": \"mga network\",\n    \"plugins\": \"mga plugin\",\n    \"results\": \"(mga) resulta\",\n    \"category\": \"Category\",\n    \"categories\": {\n      \"all\": \"All\",\n      \"protocol\": \"Protocol\",\n      \"social\": \"Social\",\n      \"investment\": \"Investment\",\n      \"grant\": \"Grant\",\n      \"service\": \"Service\",\n      \"media\": \"Media\",\n      \"creator\": \"Creator\",\n      \"collector\": \"Collector\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Piliin ang sistema ng pagboto\",\n    \"single-choice\": \"Single choice na pagboto\",\n    \"approval\": \"Pagboto sa pag-apruba\",\n    \"quadratic\": \"Quadratic na pagboto\",\n    \"ranked-choice\": \"Ranggo na pagpipiliang pagboto\",\n    \"weighted\": \"Timbang na pagboto\",\n    \"basic\": \"Basic na pagboto\",\n    \"description\": {\n      \"single-choice\": \"Ang bawat botante ay maaaring pumili lamang ng isang pagpipilian.\",\n      \"approval\": \"Ang bawat botante ay maaaring pumili ng anumang bilang ng mga pagpipilian.\",\n      \"quadratic\": \"Ang bawat botante ay maaaring magpakalat ng kapangyarihan sa pagboto sa anumang bilang ng mga pagpipilian. Ang mga resulta ay kinakalkula nang quadratically.\",\n      \"ranked-choice\": \"Ang bawat botante ay maaaring pumili at magranggo ng anumang bilang ng mga pagpipilian. Kinakalkula ang mga resulta sa pamamagitan ng instant-runoff counting method.\",\n      \"weighted\": \"Ang bawat botante ay maaaring magpakalat ng kapangyarihan sa pagboto sa anumang bilang ng mga pagpipilian.\",\n      \"basic\": \"Single choice na pagboto na may tatlong pagpipilian: Para sa, Laban o Abstain\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Privacy\",\n    \"title\": \"Select voting privacy\",\n    \"information\": \"The type of privacy used on proposals. (Enforced on all future proposals)\",\n    \"any\": \"Any\",\n    \"none\": \"None\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Choices are encrypted and only visible once the voting period is over\",\n      \"tooltip\": \"This proposal has Shutter privacy enabled. All votes will be encrypted until the voting period has ended and the final score is calculated\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select voting validation\",\n    \"information\": \"The type of validation used to determine if a user can vote. (Enforced on all future proposals)\",\n    \"any\": {\n      \"label\": \"Anyone can vote\",\n      \"description\": \"Anyone with voting power can cast a vote.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement to vote on this proposal. \"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your proposals from spam and vote manipulation by requiring users to have a Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold} and {operator} of the following stamps to vote on this proposal: {stamps}. \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Kasalukuyang kinalabasan\",\n    \"currentBond\": \"Kasalukuyang bono\",\n    \"finalizedIn\": \"Finalized {0}\",\n    \"executableIn\": \"Naipapatupad {0}\",\n    \"finalOutcome\": \"Kinalabasan\",\n    \"nextBond\": \"Bond para itakda ang kinalabasan\",\n    \"setOutcomeTo\": \"Itakda ang kinalabasan sa\",\n    \"claimBond\": \"Mag-claim ng bono\",\n    \"addBatch\": \"Magdagdag ng batch ng transaksyon\",\n    \"batch\": \"Batch ng transaksyon\",\n    \"transactions\": \"Transaksyon\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"Di-wastong address\",\n    \"invalidAmount\": \"Di-wastong halaga\",\n    \"invalidValue\": \"Hindi tama ang halaga\",\n    \"invalidAbi\": \"Di-wastong ABI\",\n    \"invalidData\": \"Di-wastong data\",\n    \"value\": \"Halaga (wei)\",\n    \"data\": \"Datos\",\n    \"noCollectibles\": \"Walang mga collectible\",\n    \"asset\": \"Ang Asset\",\n    \"amount\": \"Halaga\",\n    \"type\": \"Uri\",\n    \"transferFunds\": \"Maglipat ng pondo\",\n    \"transferNFT\": \"Paglipat ng NFT\",\n    \"contractInteraction\": \"Pakikipag-ugnayan sa kontrata\",\n    \"rawTransaction\": \"Raw transaksyon\",\n    \"addTransaction\": \"Magdagdag ng transaksyon\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei sa {address}\",\n      \"transferFunds\": \"Transfer {amount}{tokenSymbol} sa {address}\",\n      \"transferNFT\": \"Ipadala {name} #{id} sa {address}\",\n      \"raw\": \"Ipadala {amount} wei sa {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Humiling ng pagpapatupad\",\n      \"setOutcome\": \"Itakda ang kinalabasan\",\n      \"changeOutcome\": \"Baguhin ang kinalabasan\",\n      \"executeTxs\": \"Isagawa ang batch ng transaksyon {0} ng {1}\",\n      \"executed\": \"Ang lahat ng mga transaksyon ay naisakatuparan\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"May nangyaring mali\",\n      \"connectWallet\": \"Ikonekta ang wallet upang makita ang mga detalye ng pagpapatupad\",\n      \"switchChain\": \"Ilipat ang iyong wallet sa {0} upang humiling ng pagpapatupad\",\n      \"question\": \"Pumasa ba ang panukala ito at natutugunan ba nito ang\",\n      \"criteria\": \"pamantayan sa pagtanggap?\",\n      \"proposalPassed\": \"Did the proposal pass?\",\n      \"expired\": \"The proposal has expired\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to confirm the proposal passed\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"requestToolTip\": \"This will propose the transactions from this Snapshot vote on-chain. After a challenge window, if the proposal is valid, the transactions can be executed and your bond will be returned.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"Hindi pa nase-setup ang POAP para sa panukalang ito :'(\",\n    \"no_voted_header\": \"Bumoto para makuha ang POAP na ito\",\n    \"unclaimed_header\": \"Mint your I voted POAP\",\n    \"claimed_header\": \"Congratulations! The POAP has been minted to your collection\",\n    \"loading_header\": \"The POAP is being minted to your collection\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"I-browse ang koleksyon\",\n    \"success_claim\": \"The POAP has been minted to your collection\",\n    \"error_claim\": \"Nagkaroon ng problema sa pag-minting ng token\"\n  },\n  \"progress\": {\n    \"progress\": \"Progress\",\n    \"inProgress\": \"In Progress\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"New Step\",\n    \"description\": \"Description\",\n    \"add\": \"Add\",\n    \"deleteStep\": \"Delete Step\",\n    \"deleteConfirm\": \"Are you sure you want to delete?\",\n    \"delete\": \"Delete\",\n    \"cancel\": \"Cancel\",\n    \"comeBack\": \"Come back after voting is complete to see how this proposal is progressing!\",\n    \"confirmSignature\": \"Signing this message will allow us to authorize your request to update the progress of your proposal.\",\n    \"wentWrong\": \"Oops something went wrong.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"Charts\",\n    \"noVotesYet\": \"There are no votes to visualize yet.\",\n    \"totalVotesPerDay\": \"Total votes per day\",\n    \"shareOfVotingPower\": \"Share of voting power\",\n    \"votingPowerPerDay\": \"Voting power per day\"\n  },\n  \"comment_box\": {\n    \"title\": \"Kahon ng komento\",\n    \"add\": \"Magdagdag ng iyong mga komento dito\",\n    \"submit\": \"Ipadala\",\n    \"preview\": \"I-preview\",\n    \"continue_editing\": \"Ipagpatuloy an pag-edit\",\n    \"edit\": \"I-edit ang iyong tugon dito\",\n    \"edit_button\": \"I-Edit\",\n    \"dismiss\": \"I-dismiss\",\n    \"delete\": \"Burahin\",\n    \"add_reply\": \"Idagdag ang iyong tugon dito\",\n    \"edit_comment\": \"I-edit ang komento\",\n    \"edit_modal\": \"Sigurado ka bang gusto mong i-edit?\",\n    \"yes\": \"Oo\",\n    \"no\": \"Hindi\",\n    \"delete_comment\": \"I-delete ang komento\",\n    \"delete_modal\": \"Sigurado ka bang gusto mong tanggalin?\",\n    \"error\": \"Oops, may maling nangyari\",\n    \"replies\": \"mga sagot\",\n    \"hide\": \"Tago\",\n    \"show\": \"Ipakita\",\n    \"reply\": \"Tumugon\",\n    \"load_more\": \"Mag load pa\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Create a space\",\n      \"timeline\": \"Timeline\",\n      \"notifications\": \"Notifications\",\n      \"explore\": \"Explore\",\n      \"playground\": \"Playground\",\n      \"space\": {\n        \"create\": \"Create {space} proposal\",\n        \"about\": \"About {space}\",\n        \"proposals\": \"{space} Proposals\",\n        \"proposal\": \"{space} proposal: {proposal}\",\n        \"settings\": \"{space} Settings\"\n      },\n      \"strategy\": \"{key} strategy\",\n      \"delegate\": \"Delegate\",\n      \"ranking\": \"Ranking\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Track proposals for {spaceName}\",\n    \"text\": \"Receive notifications every time a new proposal is created or ends\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 second | {n} seconds\",\n    \"minute\": \"1 minute | {n} minutes\",\n    \"hour\": \"1 hour | {n} hours\",\n    \"day\": \"1 day | {n} days\",\n    \"week\": \"1 week | {n} weeks\",\n    \"month\": \"1 month | {n} months\",\n    \"year\": \"1 year | {n} years\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Unsupported network\",\n    \"switchNetworkToNetwork\": \"To continue, you need to change the network in your wallet to {network}.\",\n    \"switchToNetwork\": \"Switch to {network}\",\n    \"goToDemoSite\": \"Go to demo website\"\n  },\n  \"treasury\": {\n    \"title\": \"Treasury\",\n    \"wallets\": {\n      \"title\": \"Wallets\",\n      \"empty\": \"This space doesn't have a treasury yet\",\n      \"addTreasury\": \"Add a treasury\"\n    },\n    \"assets\": {\n      \"title\": \"Assets\",\n      \"empty\": \"There are no assets in this contract\"\n    },\n    \"24hChange\": \"24h change\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Your email\",\n    \"title\": \"Get the latest Snapshot updates\"\n  },\n  \"joinCommunity\": \"Join Snapshot community\",\n  \"header\": {\n    \"title\": \"Where decisions get made\",\n    \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot is a decentralized governance platform that makes it easy to create and vote on proposals - all without spending a fortune on gas fees! Plus, our flexible system supports various voting types and strategies, so you can tailor the voting process to your needs.\",\n    \"subHeader\": \"Governance should be a snap\",\n    \"subDescription\": \"Web3 governance doesn't have to be complicated. Snapshot is the perfect solution for organizations looking for an easy and efficient way to govern their community or organization.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Resources\",\n    \"about\": \"About\",\n    \"blog\": \"Blog\",\n    \"jobs\": \"Jobs\",\n    \"discussions\": \"Discussions\",\n    \"github\": \"GitHub\",\n    \"docs\": \"Docs\",\n    \"support\": \"Support\",\n    \"hiring\": \"Join us!\"\n  }\n}\n"
  },
  {
    "path": "src/locales/fr-FR.json",
    "content": "{\n  \"searchPlaceholder\": \"Rechercher\",\n  \"spaceCount\": \"{0} espace(s)\",\n  \"createSpace\": \"Créer un espace\",\n  \"backToHome\": \"Accueil\",\n  \"actions\": \"Actions\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"Résultats\",\n  \"resultsError\": \"Les résultats n’ont pas pu être calculés. C’est habituellement dû à une stratégie mal configurée ou à un RPC désynchronisé impliqué dans la stratégie.\",\n  \"resultsCalculating\": \"Les résultats finaux sont en cours de calcul. Si vous voyez toujours ce message après quelques minutes, contactez l'administrateur de l'espace.\",\n  \"votingPowerFailedMessage\": \"Votre voting power n’a pas pu être calculé. C’est habituellement dû à une stratégie mal configurée ou à un RPC désynchronisé impliqué dans la stratégie.\",\n  \"votingValidationFailedMessage\": \"Il y a eu une erreur de notre côté et nous n'avons pas pu vérifier si vous êtes éligible au vote. Cela est souvent dû à une validation de vote mal configurée ou à une API non réactive impliquée dans la validation.\",\n  \"notValidVoterMessage\": \"Oups, vous ne semblez pas être éligible au vote sur cette proposition.\",\n  \"getHelp\": \"Obtenir de l’aide\",\n  \"retry\": \"Réessayer\",\n  \"currentResults\": \"Résultats actuels\",\n  \"reset\": \"Réinitialiser\",\n  \"close\": \"Fermer\",\n  \"save\": \"Enregistrer\",\n  \"author\": \"Auteur\",\n  \"next\": \"Suivant\",\n  \"choice\": \"Choix\",\n  \"submit\": \"Valider\",\n  \"plugins\": \"Plugins\",\n  \"information\": \"Informations\",\n  \"confirm\": \"Confirmer\",\n  \"snapshot\": \"Snapshot\",\n  \"strategies\": \"Stratégie(s)\",\n  \"strategiesPage\": \"Stratégies\",\n  \"space\": \"Espace\",\n  \"spaces\": \"Espaces\",\n  \"verifiedSpace\": \"Espace vérifié\",\n  \"warningSpace\": \"Cet espace a été signalé comme potentiellement malveillant. Faites preuve de prudence.\",\n  \"version\": \"Version\",\n  \"timeline\": \"Timeline\",\n  \"ended\": \"terminé\",\n  \"started\": \"commencé\",\n  \"filters\": \"Filtres\",\n  \"allSpaces\": \"Tous les espaces\",\n  \"submitOnchain\": \"Envoyer on-chain\",\n  \"inSpaces\": \"Dans {0} espace(s)\",\n  \"votes\": \"Votes\",\n  \"seeMore\": \"Voir plus\",\n  \"seeAll\": \"Voir tout\",\n  \"network\": \"Réseau\",\n  \"networks\": \"Réseaux\",\n  \"skins\": \"Thèmes\",\n  \"spaceMembers\": \"Membres\",\n  \"members\": \"Aucun membre | {count} membre | {count} membres\",\n  \"editStrategy\": \"Modifier la stratégie\",\n  \"invalidProposals\": \"Propositions incorrectes\",\n  \"account\": \"Compte\",\n  \"create3box\": \"Créer un profil sur 3Box\",\n  \"view3box\": \"Voir le profil sur 3Box\",\n  \"edit3box\": \"Éditer le profil sur 3Box\",\n  \"connectWallet\": \"Connexion\",\n  \"toggleSkin\": \"Activer le thème\",\n  \"about\": \"À propos\",\n  \"license\": \"Licence\",\n  \"showMore\": \"Voir plus\",\n  \"voted\": \"Voté\",\n  \"reload\": \"Rafraîchir\",\n  \"ipfsServer\": \"Serveur IPFS\",\n  \"hub\": \"Hub\",\n  \"cancel\": \"Annuler\",\n  \"delete\": \"Supprimer\",\n  \"demoSite\": \"Ceci est le site de démonstration, testez-le !\",\n  \"removeDelegation\": \"Révoquer la délégation\",\n  \"confirmRemove\": \"Êtes-vous sûr de vouloir révoquer votre délégation à\",\n  \"removeSpace\": \"pour l'espace {0}\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum atteint\",\n  \"options\": \"Option(s)\",\n  \"votingPower\": \"Votre voting power\",\n  \"comment\": {\n    \"placeholder\": \"Partagez votre raison (facultatif)\"\n  },\n  \"receipt\": \"Reçu\",\n  \"relayer\": \"Relayer\",\n  \"verifyOnMycrypto\": \"Vérifier le reçu sur MyCrypto\",\n  \"verifyOnSignatorio\": \"Vérifier sur Signator.io\",\n  \"isCore\": \"Core\",\n  \"notificationsBlocked\": \"Votre navigateur bloque les notifications\",\n  \"notificationsNotSupported\": \"Votre navigateur ne prend pas en charge les notifications\",\n  \"walletNotSupported\": \"Le wallet n’est pas compatible\",\n  \"seeInExplorer\": \"Explorer\",\n  \"learnMore\": \"En savoir plus\",\n  \"logout\": \"Se déconnecter\",\n  \"continue\": \"Continuer\",\n  \"add\": \"Ajouter\",\n  \"edit\": \"Éditer\",\n  \"strategyParameters\": \"Paramètres de la stratégie\",\n  \"addAction\": \"Ajouter une action\",\n  \"removeAction\": \"Supprimer l'action\",\n  \"yourChoice\": \"Choix {0}\",\n  \"targetAddress\": \"Adresse de destination\",\n  \"value\": \"Valeur\",\n  \"date\": \"Données\",\n  \"marketDetails\": \"Détails du marché\",\n  \"addMarket\": \"Ajouter un marché\",\n  \"selectNetwork\": \"Sélectionnez un réseau\",\n  \"conditionId\": \"ID de condition\",\n  \"basetokenAddress\": \"Adresse du token de base\",\n  \"quoteAddress\": \"Adresse de la monnaie de cotation\",\n  \"removeMarket\": \"Supprimer le marché\",\n  \"back\": \"Retour\",\n  \"loading\": \"Chargement...\",\n  \"predictedImpact\": \"Impact prévu\",\n  \"marketSymbol\": \"{0} marché\",\n  \"twoChoicesRequired\": \"Deux choix sont requis pour ce plugin.\",\n  \"noResultsFound\": \"Oups, nous n'avons trouvé aucun résultat\",\n  \"createFirstProposal\": \"Créer votre première proposition\",\n  \"noSpacesJoined\": \"Oups, vous n'avez pas encore rejoint d'espaces\",\n  \"addFavorites\": \"Ajouter aux favoris\",\n  \"createdBy\": \"Par {0}\",\n  \"startIn\": \"début {0}\",\n  \"endIn\": \"fin {0}\",\n  \"proposalTimeLeft\": \"{0} restant(s)\",\n  \"endedAgo\": \"terminé {0}\",\n  \"proposalBy\": \"par {0}\",\n  \"endDate\": \"fin {0}\",\n  \"defaultSkin\": \"Thème par défaut\",\n  \"select\": \"Sélectionnez\",\n  \"language\": \"Langage\",\n  \"agree\": \"J'accepte\",\n  \"moderators\": \"Modérateurs\",\n  \"playground\": \"Playground\",\n  \"strategyParams\": \"Paramètres de la stratégie\",\n  \"addresses\": \"Adresses\",\n  \"networkErrorPlayground\": \"Erreur réseau - veuillez ouvrir la console de votre navigateur pour plus d'informations\",\n  \"upload\": \"Upload\",\n  \"join\": \"Rejoindre\",\n  \"joined\": \"Abonné\",\n  \"leave\": \"Se désabonner\",\n  \"subspaces\": \"Sous-espaces\",\n  \"mainspace\": \"Espace principal\",\n  \"copyLink\": \"Copier le lien\",\n  \"duplicate\": \"Dupliquer\",\n  \"joinedSpaces\": \"Espaces rejoints\",\n  \"joinSpaces\": \"Rejoindre un espace\",\n  \"setDelegationToSpace\": \"Limiter la délégation à un espace précis\",\n  \"theCurrentNetwork\": \"le réseau actuel\",\n  \"optional\": \"(facultatif)\",\n  \"homeLoadmore\": \"Afficher plus\",\n  \"confirmAction\": \"Confirmer l’action\",\n  \"or\": \"ou\",\n  \"share\": \"Partager\",\n  \"shareOnTwitter\": \"Partager sur Twitter\",\n  \"shareOnLenster\": \"Partager sur Lenster\",\n  \"createButton\": \"Créer\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Changer de wallet\",\n  \"createASpace\": \"Créer un espace\",\n  \"getStarted\": \"Commencer\",\n  \"skip\": \"Ignorer\",\n  \"newSpaceNotice\": {\n    \"header\": \"Votre espace est live !\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Apprenez-en plus dans la {documentation} ou joignez-vous au {discord} Snapshot pour obtenir de l’aide.\",\n    \"gotIt\": \"Compris !\"\n  },\n  \"errors\": {\n    \"required\": \"Le champ est requis\",\n    \"minLength\": \"Le champ est requis\",\n    \"maxLength\": \"Longueur maximale de {0}\",\n    \"pattern\": \"Caractère invalide\",\n    \"minItems\": \"Minimum {0} élément(s) requis\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"minStrategy\": \"Au moins une stratégie est requise.\",\n    \"website\": \"L’URL doit être dans le format https://www.example.com\",\n    \"format\": \"Format invalide\",\n    \"type\": \"Type invalide\",\n    \"unsupportedImageType\": \"Type de fichier incompatible, Formats compatibles sont jpeg, jpg et png\",\n    \"invalidAddress\": \"Adresse invalide\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Titre\",\n    \"discussion\": \"Discussion (facultatif)\",\n    \"categorie(s)\": \"Sélectionnez jusqu’à 2 catégorie(s)\",\n    \"proposalDescription\": \"Description (facultatif)\",\n    \"preview\": \"Aperçu\",\n    \"choices\": \"Choix\",\n    \"addChoice\": \"Ajouter un choix\",\n    \"startDate\": \"Sélectionnez la date de début\",\n    \"endDate\": \"Sélectionnez la date de fin\",\n    \"startTime\": \"Sélectionnez l’heure de début\",\n    \"endTime\": \"Sélectionnez l'heure de fin\",\n    \"publish\": \"Publier\",\n    \"untitled\": \"Sans titre\",\n    \"snapshotBlock\": \"Numéro du bloc Snapshot\",\n    \"voting\": \"Vote\",\n    \"votingSystem\": \"Système de vote\",\n    \"choice\": \"Choix {0}\",\n    \"period\": \"Période de vote\",\n    \"start\": \"Début\",\n    \"end\": \"Fin\",\n    \"days\": \"Jours\",\n    \"hours\": \"Heures\",\n    \"minutes\": \"Minutes\",\n    \"schedule\": \"Planifier une proposition\",\n    \"delayEnforced\": \"L’espace impose un délai jusqu’à ce que le vote puisse commencer\",\n    \"periodEnforced\": \"L’espace impose la durée de la période de vote\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"Modifier\",\n    \"continue\": \"Continuer\",\n    \"now\": \"Maintenant\",\n    \"votingPeriodExplainer\": \"Ceci représente la période pendant laquelle les utilisateurs pourront voter. La proposition sera visible et en attente avant le début de la période de vote.\",\n    \"uploadImageExplainer\": \"Ajouter des images en les glissant-déposant, en les sélectionnant, ou en les collant.\",\n    \"uploading\": \"Image en cours d’envoi\",\n    \"markdown\": \"Le style de Markdown est compatible\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"Vous devez être auteur dans cet espace pour soumettre une proposition.\",\n        \"minScore\": \"Vous devez posséder un minimum de {0} {1} pour soumettre une proposition.\"\n      },\n      \"customValidation\": \"Vous devez approuver la validation de la proposition pour soumettre une proposition.\",\n      \"executionError\": \"La vérification de votre admissibilité à créer des propositions dans cet espace a échoué. C’est probablement dû à une stratégie mal configurée.\"\n    },\n    \"errorGettingSnapshot\": \"Nous avons rencontré une erreur lors de la récupération du numéro de bloc nécessaire pour calculer votre voting power. Veuillez réessayer plus tard.\"\n  },\n  \"delegate\": {\n    \"header\": \"Déléguer\",\n    \"selectDelegate\": \"Sélectionner une délégation\",\n    \"to\": \"Vers\",\n    \"addressPlaceholder\": \"Adresse ou ENS\",\n    \"delegations\": \"Vos délégation(s)\",\n    \"allSpaces\": \"Pour tous les espaces\",\n    \"delegated\": \"Vous délègue\",\n    \"pendingTransaction\": \"aucune transaction en attente | 1 transaction en attente | {count} transactions en attente\",\n    \"topDelegates\": \"Meilleurs délégués\",\n    \"noDelegatesFoundFor\": \"Pas de délégués trouvés pour {0}\",\n    \"noValidEns\": \"Adresse ENS invalide.\",\n    \"noValidAddress\": \"Adresse invalide\",\n    \"delegateToSelf\": \"Vous ne pouvez pas déléguer à vous-même\",\n    \"delegateToSelfAddress\": \"Vous ne pouvez pas déléguer à votre propre adresse ENS\",\n    \"noValidSpaceId\": \"ID d’espace invalide\",\n    \"noDelegationsAndDelegates\": \"Vous ne trouvez pas vos délégations ni vos délégués ? Assurez-vous d’être connecté au bon réseau.\",\n    \"delegateNotSupported\": \"Actuellement, la délégation n’est pas compatible pour {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Votez\",\n    \"vote\": \"Vote\",\n    \"startDate\": \"Date de début \",\n    \"endDate\": \"Date de fin\",\n    \"votingSystem\": \"Système de vote\",\n    \"privacy\": \"Confidentialité\",\n    \"invalidChoice\": \"Choix invalide\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Votre vote est enregistré !\",\n      \"gnosisSafeTitle\": \"Votre vote est en attente...\",\n      \"gnosisSafeDescription\": \"Les votes avec un Safe nécessitent des signataires supplémentaires et seront visibles une fois la transaction confirmée.\",\n      \"seeQueue\": \"Voir les transactions en attente\",\n      \"tips\": {\n        \"1\": \"Les votes peuvent être modifiés tant que la proposition est active\"\n      }\n    },\n    \"downloadCsvVotes\": \"Télécharger en tant que CSV\",\n    \"preparingCsvVotes\": \"Préparation du fichier\"\n  },\n  \"proposals\": {\n    \"header\": \"Propositions\",\n    \"new\": \"Nouvelle proposition\",\n    \"noProposals\": \"Il n'y a pas encore de propositions ici !\",\n    \"createProposal\": \"Créer une proposition\",\n    \"showMore\": \"Afficher plus\",\n    \"showLess\": \"Afficher moins\",\n    \"states\": {\n      \"all\": \"Tous\",\n      \"core\": \"Officiel\",\n      \"community\": \"Communauté\",\n      \"active\": \"Actif\",\n      \"pending\": \"En attente\",\n      \"closed\": \"Clos\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Notifications\",\n    \"noNotifications\": \"Vous n’avez pas de notifications\",\n    \"proposalStarted\": \"La proposition a commencé :\",\n    \"proposalEnded\": \"La proposition est close :\",\n    \"markAllAsRead\": \"Marquer tous comme lu\",\n    \"all\": \"Tout\",\n    \"unread\": \"Non lus\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"rejoindre\",\n    \"actionCreate\": \"créer une proposition dans\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"Réglages\",\n    \"editController\": \"Modifier le controller\",\n    \"connectWithSpaceOwner\": \"Vous êtes en mode lecture seule, pour modifier les paramètres de l'espace, connectez-vous avec un contrôleur ou un wallet administrateur.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"éditer les paramètres de l'espace\",\n      \"create\": \"créer une proposition\",\n      \"vote\": \"voter sur cette proposition\"\n    },\n    \"currentSpaceControllerIs\": \"Le controller de l’espace actuel est {address}\",\n    \"newController\": \"Nouveau controller\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Appliquer\",\n    \"profile\": \"Profil\",\n    \"avatar\": \"Avatar\",\n    \"name\": {\n      \"label\": \"Nom\",\n      \"placeholder\": \"p. ex. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"À propos\",\n      \"placeholder\": \"Quel est le but de votre organisation ?\"\n    },\n    \"categories\": {\n      \"label\": \"Catégorie(s)\",\n      \"select\": \"Sélectionner la/les catégorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Conditions d'utilisation\",\n      \"information\": \"Les utilisateurs devront accepter ces conditions une fois avant de pouvoir créer une proposition ou voter\"\n    },\n    \"hideSpace\": \"Cacher l'espace de la page d'accueil\",\n    \"links\": \"Réseaux Sociaux\",\n    \"subspaces\": {\n      \"label\": \"Sous-espaces\",\n      \"information\": \"Sub-spaces will only be shown once configured on both the main space and the sub-space(s). {docs}\",\n      \"parent\": {\n        \"label\": \"Espace principal\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"L'espace dont cet espace est un sous-espace sera affiché sur la page de l'espace\"\n      },\n      \"children\": {\n        \"label\": \"Sous-espaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Les sous-espaces apparentés listés ici seront affichés sur la page d'espace\"\n      }\n    },\n    \"website\": \"Site web\",\n    \"strategies\": {\n      \"label\": \"Stratégie(s)\",\n      \"information\": \"Les stratégies sont utilisées pour déterminer le voting power ou si un utilisateur est autorisé à créer une proposition\"\n    },\n    \"network\": {\n      \"label\": \"Réseau\",\n      \"information\": \"Le réseau par défaut utilisé pour cet espace. Les réseaux peuvent également être spécifiés dans les stratégies individuelles\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbole\",\n      \"information\": \"Le symbole par défaut utilisé sur cet espace, généralement le symbole du jeton, p. ex. BAL pour Balancer\"\n    },\n    \"strategiesList\": \"Sélectionnez jusqu'à 8 stratégies\",\n    \"votingPowerIsCumulative\": \"Le voting power est cumulatif\",\n    \"addStrategy\": \"Ajouter une stratégie\",\n    \"testInPlayground\": \"Tester sur playground\",\n    \"admins\": {\n      \"label\": \"Administrateurs\",\n      \"information\": \"Les administrateurs peuvent modifier les paramètres et gérer les propositions de l'espace\"\n    },\n    \"authors\": {\n      \"label\": \"Auteurs\",\n      \"information\": \"Les auteurs sont toujours en mesure de créer des propositions\"\n    },\n    \"proposalValidation\": \"Validation de la proposition\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Seuil\",\n      \"information\": \"Le voting power minimum requis pour créer une proposition\"\n    },\n    \"allowOnlyAuthors\": \"Permettre uniquement aux auteurs de soumettre une proposition\",\n    \"editValidation\": \"Modifier la validation\",\n    \"selectValidation\": \"Sélectionner la validation\",\n    \"validationParameters\": \"Paramètres de validation\",\n    \"voting\": \"Vote\",\n    \"votingDelay\": \"Délai avant vote\",\n    \"votingPeriod\": \"Période de vote\",\n    \"hours\": \"Heures\",\n    \"days\": \"Jours\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"Le voting power minimum requis pour que la proposition passe\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"Le type de système de vote utilisé pour cet espace. (Appliqué à toutes les propositions futures)\"\n    },\n    \"anyType\": \"Tous\",\n    \"hideAbstain\": \"Ignorer les votes par abstention dans les résultats de vote basiques\",\n    \"customDomain\": \"Nom de domaine personnalisé\",\n    \"domain\": {\n      \"label\": \"Nom de domaine\",\n      \"placeholder\": \"p. ex. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"Thème\",\n    \"treasuries\": {\n      \"label\": \"Trésorerie\",\n      \"add\": \"Ajouter une trésorerie\",\n      \"edit\": \"Modifier la trésorerie\",\n      \"information\": \"Ajoutez des trésoreries à votre organisation pour les afficher dans votre espace\"\n    },\n    \"addPlugin\": \"Ajouter un plugin\",\n    \"editPlugin\": \"Modifier le plugin\",\n    \"pluginParameters\": \"Paramètres du plugin\",\n    \"proposal\": {\n      \"title\": \"Proposition\",\n      \"guidelines\": {\n        \"title\": \"Instructions\",\n        \"information\": \"Afficher un lien vers vos directives sur la création de propositions pour aider les utilisateurs à comprendre ce qui constitue une proposition valide\"\n      },\n      \"template\": {\n        \"title\": \"Modèle\",\n        \"information\": \"Créer chaque proposition avec un modèle pour aider les utilisateurs à comprendre quelles informations sont nécessaires\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"ex : yam.eth\",\n    \"chooseExistingEns\": \"Choisissez un de vos domaines ENS existants pour créer un espace avec :\",\n    \"useSingleExistingEns\": \"Utiliser votre domaine ENS existant :\",\n    \"orRegisterNewEns\": \"Ou enregistrer un nouveau domaine :\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"Pour créer un espace, premièrement vous avez besoin d’un domaine ENS. En saisir un ci-dessous et suivre les instructions d’enregistrement ENS.\",\n    \"createASpace\": \"Créer un espace\",\n    \"registerEnsButton\": \"Enregistrer\",\n    \"supportedEnsTLDs\": \"Extensions de domaine prises en charge\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or join Snapshot {discord}.\",\n    \"setSpaceController\": \"Controller de l’espace\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \"Le contrôleur d'espace est le compte qui sera en mesure de gérer les paramètres de l'espace. Des contrôleurs d'espace supplémentaires (admins) peuvent être ajoutés ultérieurement.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"Lors de la création d'un espace avec un Gnosis Safe, il est recommandé de définir l'adresse du Safe comme controller de l'espace. Si non, vous devez suivre quelques étapes supplémentaires. {link}\",\n    \"editSpaceController\": \"Modifier le controller sur ENS\",\n    \"setController\": \"Définir le controller\",\n    \"explainControllerAndEns\": \"Cette action nécessite une transaction sur {network} qui ajoutera un \\\"snapshot\\\" TEXT record à votre domaine ENS.\",\n    \"confirmToSetAddress\": \"Êtes-vous sûr de vouloir choisir {address} comme le controller de l’espace?\",\n    \"controllerHasAuthority\": \"Le controller a toute autorité sur les paramètres de l’espace\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choisir l’adresse ENS\",\n    \"spaceOwnerAddressPlaceHolder\": \"ex : {address}\",\n    \"controllerAddress\": \"L’adresse du controller\",\n    \"updateController\": \"Mettre à jour le controller\",\n    \"seeOnEns\": \"Voir sur ENS\",\n    \"goToSettings\": \"Accéder les paramètres\",\n    \"setSpaceProfile\": \"Personnaliser votre espace\",\n    \"waitForTransaction\": \"La transaction doit être confirmée avant de pouvoir créer votre espace. {txUrl}\",\n    \"pleaseWaitMessage\": \"Cela peut prendre quelques minutes, veuillez attendre pendant que la transaction est confirmée\",\n    \"notControllerAddress\": \"Veuillez vous connecter avec l'adresse du controller {wallet} pour créer l'espace.\",\n    \"fillCurrentAccount\": \"Utiliser le compte connecté\",\n    \"domain\": {\n      \"title\": \"Configurez votre domaine d'espace\",\n      \"ensMessage\": \"Avant de pouvoir créer votre propre espace, vous avez besoin d'un domaine ENS sur le réseau principal Ethereum.\",\n      \"ensMessageTestnet\": \"You can also {link} on the Goerli testnet and mess with things there first.\",\n      \"tryDemo\": \"Essayer la démo\",\n      \"yourExistingSpaces\": \"Vos espaces existants\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"Comment souhaitez-vous configurer votre stratégie de vote ?\",\n      \"subtitle\": \"Vous pouvez changer vos paramètres de stratégie à tout moment.\",\n      \"blockTitle\": \"Configurer la stratégie de vote\",\n      \"onePersonOneVote\": {\n        \"title\": \"Une personne, un vote\",\n        \"description\": \"Gérer une liste blanche de personnes qui peuvent voter ou simplement permettre à n'importe quelle adresse de voter. Chaque vote est égal et aucun jeton n'est requis\",\n        \"whitelistInformation\": \"Spécifiez les comptes pouvant voter\",\n        \"ticketInformation\": \"N'importe quel compte peut voter\",\n        \"votesEqualInfo\": \"Chaque vote est égal et aucun jeton n'est requis\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Vote pondéré par jeton\",\n        \"description\": \"Les votes sont pondérés par un jeton. Le jeton peut être un jeton ERC-20, ERC-721 ou ERC-1155\",\n        \"tokenNotFound\": \"Jeton introuvable\",\n        \"seeOnEtherscan\": \"Voir sur Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Paramètres personnalisés\",\n        \"description\": \"Sélectionnez jusqu'à 8 stratégies avec un vaste choix d'options. Si vous ne trouvez pas de stratégie adaptée à votre cas d'utilisation, vous pouvez créer la vôtre\"\n      }\n    },\n    \"validationTitle\": \"Qui peut gérer cet espace et créer des propositions ?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Modifier le profil\",\n    \"viewProfile\": \"Afficher le profil\",\n    \"about\": {\n      \"header\": \"À propos\",\n      \"joinedSpaces\": \"Espaces rejoints\",\n      \"createdSpaces\": \"Espaces créés\",\n      \"biography\": \"Biographie\",\n      \"notJoinSpacesYet\": \"N'a pas encore rejoint d'espaces\",\n      \"notCreatedSpacesYet\": \"N'a pas encore créé d'espaces\",\n      \"delegatorNetworkInfo\": \"Changer de réseau dans votre wallet\",\n      \"delegate\": \"Déléguer\",\n      \"delegated\": \"Délégué\",\n      \"delegateTo\": \"Déléguer à\",\n      \"delegateFor\": \"Délégue à\",\n      \"noDelegatorsMessage\": \"Aucune délégation sur {network}\",\n      \"notSupportedNetwork\": \"Actuellement, la délégation n’est pas compatible sur {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activité\",\n      \"votedFor\": \"Voté {choice}\",\n      \"today\": \"Aujourd'hui\",\n      \"thisWeek\": \"Cette semaine\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"Pas encore d'activité\"\n    },\n    \"settings\": {\n      \"header\": \"Modifier le profil\",\n      \"name\": \"Nom\",\n      \"biography\": \"Biographie\",\n      \"namePlaceholder\": \"Saisir le nom\",\n      \"bioPlaceholder\": \"Raconter votre histoire\",\n      \"change\": \"Changer\",\n      \"remove\": \"Supprimer\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"Tout est bon !\",\n    \"copied\": \"Copié !\",\n    \"proposalDeleted\": \"Proposition supprimée\",\n    \"somethingWentWrong\": \"Oups, une erreur s'est produite !\",\n    \"saved\": \"Enregistré !\",\n    \"delegationSuccess\": \"Délégation réussie\",\n    \"delegationRemoved\": \"Délégation retirée\",\n    \"proposalCreated\": \"Proposition créée\",\n    \"voteSuccessful\": \"Votre vote est enregistré !\",\n    \"ensSet\": \"ENS text record défini avec succès\",\n    \"transactionSent\": \"Transaction envoyée\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Créer une stratégie\",\n    \"createSkin\": \"Créer un thème\",\n    \"addNetwork\": \"Ajouter un réseau\",\n    \"createPlugin\": \"Créer un plugin\",\n    \"strategies\": \"stratégie(s)\",\n    \"skins\": \"thème(s)\",\n    \"networks\": \"réseau(x)\",\n    \"plugins\": \"plugin(s)\",\n    \"results\": \"résultat(s)\",\n    \"category\": \"Catégorie\",\n    \"categories\": {\n      \"all\": \"Tout\",\n      \"protocol\": \"Protocole\",\n      \"social\": \"Réseaux sociaux\",\n      \"investment\": \"Investissement\",\n      \"grant\": \"Bourse\",\n      \"service\": \"Service\",\n      \"media\": \"Média\",\n      \"creator\": \"Créateur/Créatrice\",\n      \"collector\": \"Collectionneur/Collectionneuse\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Sélectionner le système de vote\",\n    \"single-choice\": \"Vote à choix simple\",\n    \"approval\": \"Vote d'approbation\",\n    \"quadratic\": \"Vote quadratique\",\n    \"ranked-choice\": \"Vote préférentiel\",\n    \"weighted\": \"Vote pondéré\",\n    \"basic\": \"Vote basique\",\n    \"description\": {\n      \"single-choice\": \"Chaque votant ne peut effectuer qu'un seul choix.\",\n      \"approval\": \"Chaque votant peut effectuer un nombre indéfini de choix.\",\n      \"quadratic\": \"Chaque votant peut répartir ses droits de vote entre les différents choix. Les résultats sont calculés de manière quadratique.\",\n      \"ranked-choice\": \"Chaque votant peut effectuer plusieurs choix et les classer par préférence. Le système du second tour instantané est utilisé pour calculer les résultats.\",\n      \"weighted\": \"Chaque votant peut répartir ses droits de vote entre les différents choix.\",\n      \"basic\": \"Choix de vote unique avec trois choix: Pour, Contre ou S’abstenir\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Confidentialité\",\n    \"title\": \"Sélectionner la confidentialité des votes\",\n    \"information\": \"Le type de confidentialité utilisé pour les propositions. (Appliqué à toutes les propositions futures)\",\n    \"any\": \"Any\",\n    \"none\": \"Aucun\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Les choix sont chiffrés et visibles seulement après la fin de la période de vote\",\n      \"tooltip\": \"La confidentialité est activée pour cette proposition. Tous les votes seront cryptés jusqu'à ce que la période de vote soit terminée et le score final soit calculé\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select voting validation\",\n    \"information\": \"Le type de validation utilisé pour déterminer si un utilisateur peut voter. (Appliqué à toutes les propositions futures)\",\n    \"any\": {\n      \"label\": \"Ouvert à tous\",\n      \"description\": \"Toute personne ayant du voting power peut voter.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement to vote on this proposal. \"\n    },\n    \"passport-gated\": {\n      \"label\": \"Accès réservé au Gitcoin Passport\",\n      \"description\": \"Protégez vos propositions contre le spam et la manipulation des votes en exigeant que les utilisateurs aient un Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold} and {operator} of the following stamps to vote on this proposal: {stamps}. \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Résultats actuels\",\n    \"currentBond\": \"Caution actuelle\",\n    \"finalizedIn\": \"Terminé {0}\",\n    \"executableIn\": \"Exécutable {0}\",\n    \"finalOutcome\": \"Résultat\",\n    \"nextBond\": \"Ajouter une caution au résultat défini\",\n    \"setOutcomeTo\": \"Définir le résultat\",\n    \"claimBond\": \"Récupérer la caution\",\n    \"addBatch\": \"Ajouter un lot de transactions\",\n    \"batch\": \"Lot de transactions\",\n    \"transactions\": \"Transactions\",\n    \"to\": \"Vers (adresse)\",\n    \"invalidAddress\": \"Adresse invalide\",\n    \"invalidAmount\": \"Montant invalide\",\n    \"invalidValue\": \"Valeur incorrecte\",\n    \"invalidAbi\": \"ABI invalide\",\n    \"invalidData\": \"Données invalides\",\n    \"value\": \"Valeur (wei)\",\n    \"data\": \"Données\",\n    \"noCollectibles\": \"Aucun objet de collection\",\n    \"asset\": \"Actif\",\n    \"amount\": \"Montant \",\n    \"type\": \"Type\",\n    \"transferFunds\": \"Virer des fonds\",\n    \"transferNFT\": \"Transférer un NFT\",\n    \"contractInteraction\": \"Interaction avec le contrat\",\n    \"rawTransaction\": \"Transaction brute\",\n    \"addTransaction\": \"Ajouter une transaction\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei vers {address}\",\n      \"transferFunds\": \"Transférer {amount} {tokenSymbol} vers {address}\",\n      \"transferNFT\": \"Envoyer {name} #{id} vers {address}\",\n      \"raw\": \"Envoyer {amount} wei vers {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Exécuter la requête\",\n      \"setOutcome\": \"Définir un résultat\",\n      \"changeOutcome\": \"Modifier le résultat\",\n      \"executeTxs\": \"Exécuter le lot de transactions {0} sur {1}\",\n      \"executed\": \"Toutes les transactions ont été exécutées\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"Une erreur s'est produite\",\n      \"connectWallet\": \"Connectez le wallet pour voir les détails de l'exécution\",\n      \"switchChain\": \"Passez au wallet {0} pour demander l'exécution\",\n      \"question\": \"Cette proposition a-t-elle été adoptée et satisfait-elle aux\",\n      \"criteria\": \"critères d'acceptation ?\",\n      \"proposalPassed\": \"Did the proposal pass?\",\n      \"expired\": \"La proposition a expiré\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to confirm the proposal passed\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"requestToolTip\": \"This will propose the transactions from this Snapshot vote on-chain. After a challenge window, if the proposal is valid, the transactions can be executed and your bond will be returned.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"Il n'y a pas encore de POAP pour cette proposition :'(\",\n    \"no_voted_header\": \"Votez pour obtenir ce POAP\",\n    \"unclaimed_header\": \"Mint votre POAP «J’ai voté»\",\n    \"claimed_header\": \"Félicitations! Le POAP a été réclamé à votre collection\",\n    \"loading_header\": \"Le POAP est en cours d’ajout à votre collection\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"Parcourir la collection\",\n    \"success_claim\": \"Le POAP a été réclamé à votre collection\",\n    \"error_claim\": \"Un problème est survenu lors de la création du token\"\n  },\n  \"progress\": {\n    \"progress\": \"Progression\",\n    \"inProgress\": \"En cours\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"Nouvelle étape\",\n    \"description\": \"Description\",\n    \"add\": \"Ajouter\",\n    \"deleteStep\": \"Supprimer l’étape\",\n    \"deleteConfirm\": \"Confirmez-vous la suppression ?\",\n    \"delete\": \"Supprimer\",\n    \"cancel\": \"Annuler\",\n    \"comeBack\": \"Revenez une fois le vote terminé pour voir comment cette proposition progresse!\",\n    \"confirmSignature\": \"Signer ce message nous permettra d'autoriser votre demande de mise à jour de la progression de votre proposition.\",\n    \"wentWrong\": \"Une erreur s'est produite.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"Tableaux\",\n    \"noVotesYet\": \"Il n’y a pas encore de votes à visualiser.\",\n    \"totalVotesPerDay\": \"Nombre total de votes par jour\",\n    \"shareOfVotingPower\": \"Partage du voting power\",\n    \"votingPowerPerDay\": \"Voting power par jour\"\n  },\n  \"comment_box\": {\n    \"title\": \"Boîte de commentaires\",\n    \"add\": \"Ajoutez vos commentaires ici\",\n    \"submit\": \"Valider\",\n    \"preview\": \"Aperçu\",\n    \"continue_editing\": \"Poursuivre la modification\",\n    \"edit\": \"Modifiez votre réponse ici\",\n    \"edit_button\": \"Modifier\",\n    \"dismiss\": \"Ignorer\",\n    \"delete\": \"Supprimer\",\n    \"add_reply\": \"Ajoutez votre réponse ici\",\n    \"edit_comment\": \"Modifier le commentaire\",\n    \"edit_modal\": \"Confirmez-vous la modification ?\",\n    \"yes\": \"Oui\",\n    \"no\": \"Non\",\n    \"delete_comment\": \"Supprimer le commentaire\",\n    \"delete_modal\": \"Confirmez-vous la suppression ?\",\n    \"error\": \"Oups, une erreur s'est produite\",\n    \"replies\": \"réponses\",\n    \"hide\": \"Masquer\",\n    \"show\": \"Voir\",\n    \"reply\": \"Répondre\",\n    \"load_more\": \"Afficher plus\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Créer un espace\",\n      \"timeline\": \"Chronologie\",\n      \"notifications\": \"Notifications\",\n      \"explore\": \"Explorer\",\n      \"playground\": \"Playground\",\n      \"space\": {\n        \"create\": \"Créer une proposition {space}\",\n        \"about\": \"À propos de {space}\",\n        \"proposals\": \"Propositions {space}\",\n        \"proposal\": \"Proposition {space}: {proposal}\",\n        \"settings\": \"Paramètres {space}\"\n      },\n      \"strategy\": \"Stratégie {key}\",\n      \"delegate\": \"Déléguer\",\n      \"ranking\": \"Classement\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Suivre les propositions pour {spaceName}\",\n    \"text\": \"Recevoir des notifications chaque fois qu’une nouvelle proposition est créée ou se termine\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 seconde | {n} secondes\",\n    \"minute\": \"1 minute | {n} minutes\",\n    \"hour\": \"1 heure | {n} heures\",\n    \"day\": \"1 jour | {n} jours\",\n    \"week\": \"1 semaine | {n} semaines\",\n    \"month\": \"1 mois | {n} mois\",\n    \"year\": \"1 an | {n} ans\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Réseau incompatible\",\n    \"switchNetworkToNetwork\": \"Pour continuer, vous devez changer le réseau de votre wallet à {network}.\",\n    \"switchToNetwork\": \"Passer au réseau {network}\",\n    \"goToDemoSite\": \"Accéder le site démo\"\n  },\n  \"treasury\": {\n    \"title\": \"Trésorerie\",\n    \"wallets\": {\n      \"title\": \"Portefeuilles\",\n      \"empty\": \"Cet espace n'a pas encore de trésorerie\",\n      \"addTreasury\": \"Ajouter une trésorerie\"\n    },\n    \"assets\": {\n      \"title\": \"Actifs\",\n      \"empty\": \"Ce contrat ne contient aucun actif\"\n    },\n    \"24hChange\": \"Changement sur 24h\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Votre e-mail\",\n    \"title\": \"Recevoir les dernières mises à jour de Snapshot\"\n  },\n  \"joinCommunity\": \"Rejoindre la communauté Snapshot\",\n  \"header\": {\n    \"title\": \"Là où les décisions sont prises\",\n    \"description\": \"Snapshot est une plateforme de gouvernance communautaire gratuite et open-source. Créez votre propre espace dès maintenant et commencez à prendre des décisions !\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot est une plateforme de gouvernance décentralisée qui facilite la création et le vote sur les propositions - le tout sans dépenser une fortune en frais de gaz ! De plus, notre système flexible prend en charge différents types et stratégies de vote, afin que vous puissiez adapter le processus de vote à vos besoins.\",\n    \"subHeader\": \"La gouvernance devrait être simple\",\n    \"subDescription\": \"La gouvernance Web3 ne doit pas être compliquée. Snapshot est la solution idéale pour les organisations qui cherchent un moyen simple et efficace pour organiser leur communauté ou leur organisation.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Ressources\",\n    \"about\": \"À propos\",\n    \"blog\": \"Blog\",\n    \"jobs\": \"Jobs\",\n    \"discussions\": \"Discussions\",\n    \"github\": \"GitHub\",\n    \"docs\": \"Documentation\",\n    \"support\": \"Support\",\n    \"hiring\": \"Rejoignez-nous !\"\n  }\n}\n"
  },
  {
    "path": "src/locales/hi-IN.json",
    "content": "{\n  \"searchPlaceholder\": \"खोजें\",\n  \"spaceCount\": \"{0} स्पेसेस\",\n  \"createSpace\": \"स्थान बनाएं\",\n  \"backToHome\": \"होम\",\n  \"actions\": \"एक्शन\",\n  \"results\": \"परिणाम\",\n  \"resultsError\": \"Results could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"vpError\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"getHelp\": \"Get help\",\n  \"retry\": \"Retry\",\n  \"currentResults\": \"वर्तमान परिणाम (रिज़ल्ट्स)\",\n  \"reset\": \"रीसेट करें\",\n  \"save\": \"सेव करें\",\n  \"author\": \"लेखक\\nरिलेयर\",\n  \"next\": \"आगे\",\n  \"submit\": \"जमा करें\",\n  \"plugins\": \"प्लगइन्स\",\n  \"information\": \"सुचना\",\n  \"confirm\": \"पुष्टि करें\",\n  \"snapshot\": \"स्नैपशॉट\",\n  \"strategies\": \"स्ट्रेटेजीज\",\n  \"strategiesPage\": \"स्ट्रेटेजीज\",\n  \"space\": \"जगह\",\n  \"spaces\": \"जगह\",\n  \"verifiedSpace\": \"Verified space\",\n  \"version\": \"संस्करण\",\n  \"timeline\": \"टाइमलाइन\",\n  \"filters\": \"छांटे\",\n  \"allSpaces\": \"सभी जगहें\",\n  \"submitOnchain\": \"चेन पर जमा करें\",\n  \"inSpaces\": \"रिक्त स्थान में\",\n  \"votes\": \"वोट\",\n  \"seeMore\": \"और देखें\",\n  \"network\": \"नेटवर्क\",\n  \"networks\": \"नेटवर्क\",\n  \"skins\": \"स्किन\",\n  \"members\": \"कोई सदस्य नहीं | {count} सदस्य | {count} सदस्य\",\n  \"editStrategy\": \"रणनीति संपादित करें\",\n  \"invalidProposals\": \"अमान्य प्रस्ताव\",\n  \"account\": \"खाता\",\n  \"create3box\": \"3बॉक्स पर प्रोफाइल बनाए\",\n  \"view3box\": \"3 बॉक्स का प्रोफ़ाइल देखें\",\n  \"edit3box\": \"3 बॉक्स का प्रोफाइल संपादित करें\",\n  \"connectWallet\": \"खता जोड़े\",\n  \"toggleSkin\": \"Toggle skin\",\n  \"about\": \"विवरण\",\n  \"license\": \"लाइसेंस\",\n  \"ipfsServer\": \"आईपीएफएस सर्वर\",\n  \"hub\": \"केंद्र\",\n  \"cancel\": \"रद्द करें\",\n  \"deleteProposal\": \"प्रस्ताव मिटाएँ\",\n  \"demoSite\": \"यहाँ एक डेमो साइट है, संदर्भ के लिए\",\n  \"removeDelegation\": \"डेलिगेशन हटाएं\",\n  \"confirmRemove\": \"क्या आप वाकई अपना डेलिगेशन हटाना चाहते हैं?\",\n  \"removeSpace\": \"अंतर के लिए {0}\",\n  \"confirmVote\": \"वोट की पुष्टी करें\",\n  \"sureToVote\": \"Are you sure you want to cast this vote?\",\n  \"cannotBeUndone\": \"इस क्रिया को पूर्ववत नहीं किया जा सकता है।\",\n  \"options\": \"विकल्प\",\n  \"votingPower\": \"आपकी वोटिंग पावर:\",\n  \"receipt\": \"रसीद\",\n  \"relayer\": \"रिलेयर\",\n  \"verifyOnMycrypto\": \"माइक्रिप्टो पर रसीद सत्यापित करें\",\n  \"verifyOnSignatorio\": \"Verify on Signator.io\",\n  \"isCore\": \"कोर\",\n  \"notificationsBlocked\": \"Your browser is blocking notifications\",\n  \"notificationsNotSupported\": \"Your browser does not support notifications\",\n  \"walletNotSupported\": \"Wallet is not supported\",\n  \"seeInExplorer\": \"एक्सप्लोरर पर देखें\",\n  \"learnMore\": \"और पढ़ें\",\n  \"logout\": \"लॉग आउट\",\n  \"continue\": \"जारी रखें\",\n  \"add\": \"जोड़ें\",\n  \"edit\": \"संपादित करें\",\n  \"strategyParameters\": \"स्ट्रेटेजी पैरामीटर\",\n  \"addAction\": \"एक्शन जोड़ें\",\n  \"removeAction\": \"एक्शन हटाएं\",\n  \"yourChoice\": \"पसंद{0}\",\n  \"targetAddress\": \"टारगेट एड्रेस\",\n  \"value\": \"मूल्य\",\n  \"date\": \"डेटा\",\n  \"marketDetails\": \"मारकेट का विवरण\",\n  \"addMarket\": \"मारकेट जोड़ें\",\n  \"selectNetwork\": \"नेटवर्क चुनें\",\n  \"conditionId\": \"कंडीशन आय डी\",\n  \"basetokenAddress\": \"बेस टोकन आय डी\",\n  \"quoteAddress\": \"करेंसी का एड्रेस बताएं\",\n  \"removeMarket\": \"मार्किट हटाए\",\n  \"back\": \"वापस जाएँ\",\n  \"loading\": \"लोड हो रहा है...\",\n  \"predictedImpact\": \"होने वाला प्रभाव\",\n  \"marketSymbol\": \"{0} मारकेट\",\n  \"twoChoicesRequired\": \"इस प्लगइन के लिए २ विकल्प जरुरी है\",\n  \"noResultsFound\": \"कोई परिणाम नहीं मिल पा रहा है\",\n  \"createFirstProposal\": \"Let's create your first proposal\",\n  \"noSpacesJoined\": \"ओह, आप अभी तक किसी जगह से नही जुडे।\",\n  \"addFavorites\": \"पसंदीदा में जोड़ें\",\n  \"createdBy\": \"तक {0}\",\n  \"startIn\": \"शुरू {0}\",\n  \"endIn\": \"ख़त्म {0}\",\n  \"proposalTimeLeft\": \"{0} left\",\n  \"endedAgo\": \"ended {0}\",\n  \"proposalBy\": \"द्वारा{0}\",\n  \"endDate\": \"ख़त्म {0}\",\n  \"defaultSkin\": \"डिफ़ॉल्ट स्किन\",\n  \"select\": \"चुनें\",\n  \"language\": \"भाषा\",\n  \"agree\": \"मैं सहमत हूँ\",\n  \"mustAgreeToTerms\": \"इससे पहले कि आप इस कार्रवाई को पूरा कर सकें, आपको यहां {0} सेवा की शर्तों से सहमत होना होगा\",\n  \"playground\": \"प्लेग्राउंड\",\n  \"strategyParams\": \"स्ट्रेटेजी पैरामीटर\",\n  \"addresses\": \"पते\",\n  \"networkErrorPlayground\": \"नेटवर्क त्रुटि - अधिक जानकारी के लिए कृपया अपना ब्राउज़र कंसोल खोलें\",\n  \"upload\": \"अपलोड करें\",\n  \"join\": \"शामिल हों\",\n  \"joined\": \"शामिल हो गए\",\n  \"leave\": \"छोड़ें\",\n  \"copyLink\": \"लिंक कॉपी करें\",\n  \"duplicateProposal\": \"डुप्लीकेट प्रस्ताव\",\n  \"joinedSpaces\": \"स्पेस में शामिल हों\",\n  \"joinSpaces\": \"स्पेस में शामिल हों\",\n  \"setDelegationToSpace\": \"Limit delegation to a specific space\",\n  \"theCurrentNetwork\": \"the current network\",\n  \"optional\": \"(optional)\",\n  \"homeLoadmore\": \"Load more\",\n  \"confirmAction\": \"Confirm action\",\n  \"or\": \"or\",\n  \"share\": \"Share\",\n  \"errors\": {\n    \"required\": \"यह जानकारी जरुरी है\",\n    \"minLength\": \"न्यूनतम लंबाई {0}\",\n    \"maxLength\": \"अधिकतम लंबाई {0}\",\n    \"pattern\": \"अमान्य वर्ण\",\n    \"minItems\": \"न्यूनतम {0} आइटम्स जरुरी है\",\n    \"minStrategy\": \"कम से कम एक स्ट्रेटेजी की आवश्यकता है\",\n    \"format\": \"अवैध फॉर्मेट\",\n    \"type\": \"Invalid type\"\n  },\n  \"create\": {\n    \"question\": \"Ask a question...\",\n    \"categorie(s)\": \"Select up to 2 categorie(s)\",\n    \"content\": \"Tell us more about your proposal (optional)\",\n    \"preview\": \"पूर्वावलोकन/पूर्वदर्शन\",\n    \"choices\": \"विकल्प\",\n    \"addChoice\": \"विकल्प जोड़े\",\n    \"startDate\": \"प्रारंभ तिथि चुनें\",\n    \"endDate\": \"अंतिम तिथि चुनें\",\n    \"startTime\": \"समाप्ति का समय\",\n    \"endTime\": \"समाप्ति का समय\",\n    \"publish\": \"प्रकाशित करें\",\n    \"untitled\": \"Untitled\",\n    \"snapshotBlock\": \"स्नैपशॉट ब्लॉक नंबर\",\n    \"voting\": \"Voting\",\n    \"votingSystem\": \"Voting system\",\n    \"choice\": \"Choice {0}\",\n    \"period\": \"Voting period\",\n    \"start\": \"Start\",\n    \"end\": \"End\",\n    \"days\": \"Days\",\n    \"hours\": \"Hours\",\n    \"minutes\": \"Minutes\",\n    \"schedule\": \"Schedule proposal\",\n    \"delayEnforced\": \"The space enforces a delay until voting can start\",\n    \"periodEnforced\": \"The space enforces the duration of the voting period\",\n    \"edit\": \"Edit\",\n    \"continue\": \"Continue\",\n    \"now\": \"Now\",\n    \"votingPeriodExplainer\": \"This is the time period in which users will be able to vote. The proposal will be visible and pending before the start of the voting period.\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"प्रस्ताव सबमिट करने के लिए आपको स्पेस का लेखक होना आवश्यक है।\",\n        \"minScore\": \"प्रपोजल सबमिट करने के लिए काम से काम {0} {1} होना जरुरी है\"\n      },\n      \"customValidation\": \"प्रस्ताव जमा करने के लिए आपको प्रस्ताव सत्यापन पास करना होगा\",\n      \"executionError\": \"Verifying your eligibility to create proposals in this space has failed. This is likely due to a misconfigured strategy.\"\n    }\n  },\n  \"delegate\": {\n    \"header\": \"डेलीगेट\",\n    \"selectDelegate\": \"प्रतिनिधि चुनें\",\n    \"to\": \"को\",\n    \"addressPlaceholder\": \"पता या ईएनएस नाम\",\n    \"delegations\": \"आपके डेलीगेशन\",\n    \"allSpaces\": \"सब जगहों के लिए\",\n    \"delegated\": \"आपको दिए गए डेलीगेशन\",\n    \"pendingTransaction\": \"कोई लंबित लेनदेन नहीं | 1 लंबित लेनदेन | {count} लंबित लेनदेन\",\n    \"topDelegates\": \"Top delegates\",\n    \"noDelegatesFoundFor\": \"No delegates found for {0}\",\n    \"noValidEns\": \"Not a valid ENS address.\",\n    \"noValidAddress\": \"Not a valid address\",\n    \"delegateToSelf\": \"You cannot delegate to yourself\",\n    \"delegateToSelfAddress\": \"You cannot delegate to your own ENS address\",\n    \"noValidSpaceId\": \"Not a valid space ID\",\n    \"noDelegationsAndDelegates\": \"Can't find your delegations and delegates? Make sure you are connected to the correct network.\",\n    \"delegateNotSupported\": \"Delegation is currently not supported for {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"अपना वोट दे\",\n    \"vote\": \"वोट\",\n    \"startDate\": \"आरंभ तिथि\",\n    \"endDate\": \"समाप्ति दिनांक\",\n    \"votingSystem\": \"मतदान प्रणाली\"\n  },\n  \"proposals\": {\n    \"header\": \"प्रपोजल्स\",\n    \"new\": \"नए प्रपोजल्स\",\n    \"noProposals\": \"यहाँ अभी प्रोपोज़ल्स नहीं है\",\n    \"createProposal\": \"Create proposal\",\n    \"showMore\": \"Show more\",\n    \"showLess\": \"Show less\",\n    \"states\": {\n      \"all\": \"सभी\",\n      \"core\": \"कोर\",\n      \"community\": \"कम्यूनिटी\",\n      \"active\": \"सक्रिय\",\n      \"pending\": \"बाकी\",\n      \"closed\": \"बंद\"\n    }\n  },\n  \"settings\": {\n    \"header\": \"सेटिंग्स\",\n    \"setEnsTextRecord\": \"Set ENS text record\",\n    \"editController\": \"Edit controller\",\n    \"needToSetEnsText\": \"Your space settings will be stored in a file on IPFS. Before you can edit them, you need to set an ENS text record pointing to that file.\",\n    \"profile\": \"प्रोफ़ाइल\",\n    \"avatar\": \"अवतार\",\n    \"name\": \"नाम\",\n    \"about\": \"जानकारी\",\n    \"terms\": \"शर्तें\",\n    \"network\": \"नेटवर्क\",\n    \"symbol\": \"चिन्ह\",\n    \"skin\": \"स्किन\",\n    \"customDomain\": \"कस्टम डोमेन\",\n    \"domain\": \"डोमेन नाम\",\n    \"strategies\": \"स्ट्रेटेजीज\",\n    \"categories\": \"Categorie(s)\",\n    \"selectCategories\": \"Select categories\",\n    \"hideSpace\": \"होमपेज से स्पेस छुपाये\",\n    \"addStrategy\": \"स्ट्रेटेजी जोड़े\",\n    \"authors\": \"लेखक\",\n    \"admins\": \"एडमिन\",\n    \"defaultTab\": \"डिफ़ॉल्ट टैब\",\n    \"proposalThreshold\": \"प्रपोजल थ्रेसहोल्ड\",\n    \"allowOnlyAuthors\": \"केवल लेखकों को प्रस्ताव प्रस्तुत करने की अनुमति है|\",\n    \"editPlugin\": \"प्लगइन संपादित करें\",\n    \"addPlugin\": \"प्लगइन जोड़ें\",\n    \"pluginParameters\": \"प्लगइन पैरामीटर्स\",\n    \"proposalValidation\": \"प्रस्ताव सत्यापन\",\n    \"validation\": \"मान्यकरण\",\n    \"editValidation\": \"प्रमाणीकरण जोड़ें/संपादित करें\",\n    \"selectValidation\": \"मान्यता का चयन करें\",\n    \"validationParameters\": \"इन मापदंडों को मान्य करें\",\n    \"warningTextRecord\": \"आपको ENS डोमेन पर एक टेक्स्ट रिकॉर्ड सेट करना होगा।\",\n    \"votingDelay\": \"Voting delay\",\n    \"votingPeriod\": \"Voting period\",\n    \"voting\": \"Voting\",\n    \"quorum\": \"Quorum\",\n    \"type\": \"Type\",\n    \"anyType\": \"Any\",\n    \"hideAbstain\": \"Ignore abstain votes in basic voting results\",\n    \"connectWithSpaceOwner\": \"To modify space settings, connect with a controller or admin wallet.\"\n  },\n  \"setup\": {\n    \"example\": \"उदाहरण. yam.eth\",\n    \"chooseExistingEns\": \"Choose one of your existing ENS domains to create a space with:\",\n    \"useSingleExistingEns\": \"Use your existing ENS domain:\",\n    \"orRegisterNewEns\": \"Or register a new domain:\",\n    \"toCreateASpace\": \"To create a space, you first need an ENS domain. Enter one below and follow the ENS registration instructions.\",\n    \"createASpace\": \"स्पेस तैयार करे\",\n    \"registerEnsButton\": \"Register\",\n    \"supportedEnsTLDs\": \"Supported domain endings\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to create a space? Learn more in the <a target='_blank' href='https://docs.snapshot.org/spaces/create'> documentation </a> or join Snapshot <a target='_blank' href='https://discord.snapshot.org/'>Discord</a>.\",\n    \"setSpaceController\": \"Set space controller address on ENS\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"This action requires a transaction on the Ethereum Mainnet which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choose ENS address\",\n    \"textRecordExists\": \"Currently the space controller is {address}. You can update the space controller by using the form below.\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. 0xF78108c9BBaF466dd96BE41be728Fe3220b37119\",\n    \"controllerAddress\": \"Controller address\",\n    \"updateController\": \"Update controller\",\n    \"connectWithEnsController\": \"To modify the space controller you need to connect with the wallet that owns {ens}\",\n    \"seeOnEns\": \"See on ENS\",\n    \"goToSettings\": \"Go to settings\"\n  },\n  \"notify\": {\n    \"youDidIt\": \"संपूर्ण हुआ\",\n    \"copied\": \"कॉपी किया गया!\",\n    \"proposalDeleted\": \"प्रपोजल मिटा दिया गया\",\n    \"somethingWentWrong\": \"ओह! कुछ गलत हो गया है\",\n    \"saved\": \"Saved!\",\n    \"delegationSuccess\": \"Delegation successful\",\n    \"delegationRemoved\": \"Delegation removed\",\n    \"proposalCreated\": \"Proposal created\",\n    \"voteSuccessful\": \"Your vote is in!\",\n    \"ensSet\": \"ENS text record was successfully set\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"स्ट्रेटेजी बनाये\",\n    \"createSkin\": \"स्किन बनाये\",\n    \"addNetwork\": \"नेटवर्क बनाये\",\n    \"createPlugin\": \"प्लगइन बनाये\",\n    \"strategies\": \"स्ट्रेटेजीज\",\n    \"skins\": \"स्किन\",\n    \"networks\": \"नेटवर्क\",\n    \"plugins\": \"प्लगइन्स\",\n    \"results\": \"परिणाम\",\n    \"categories\": {\n      \"all\": \"All\",\n      \"protocol\": \"Protocol\",\n      \"social\": \"Social\",\n      \"investment\": \"Investment\",\n      \"grant\": \"Grant\",\n      \"service\": \"Service\",\n      \"media\": \"Media\",\n      \"creator\": \"Creator\",\n      \"collector\": \"Collector\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"मतदान प्रणाली का चयन करें\",\n    \"single-choice\": \"एकल विकल्प मतदान\",\n    \"approval\": \"स्वीकृति मतदान\",\n    \"quadratic\": \"त्रैमासिक मतदान\",\n    \"ranked-choice\": \"रैंकिंग विकल्प मतदान\",\n    \"weighted\": \"भारित मतदान\",\n    \"basic\": \"Basic voting\",\n    \"description\": {\n      \"single-choice\": \"प्रत्येक मतदाता केवल एक विकल्प का चयन कर सकता है\",\n      \"approval\": \"प्रत्येक मतदाता किसी भी संख्या में विकल्पों का चयन कर सकता है\",\n      \"quadratic\": \"प्रत्येक मतदाता किसी भी संख्या में विकल्पों में मतदान शक्ति का प्रसार कर सकता है। परिणामों की गणना त्रैमासिक रूप से की जाती है\",\n      \"ranked-choice\": \"प्रत्येक मतदाता किसी भी विकल्प का चयन और रैंक कर सकता है। परिणामों की गणना तत्काल गणना पद्धति द्वारा की जाती है\",\n      \"weighted\": \"प्रत्येक मतदाता किसी भी संख्या में विकल्पों में मतदान शक्ति का प्रसार कर सकता है\",\n      \"basic\": \"Single choice voting with three choices: For, Against or Abstain\"\n    },\n    \"choices\": {\n      \"for\": \"For\",\n      \"against\": \"Against\",\n      \"abstain\": \"Abstain\"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"वर्तमान परिणाम\",\n    \"currentBond\": \"वर्तमान बॉन्ड\",\n    \"finalizedIn\": \"फायनलाइस{0}\",\n    \"executableIn\": \"निष्पादन{0}\",\n    \"finalOutcome\": \"परिणाम\",\n    \"nextBond\": \"परिणाम निर्धारित करने के लिए बॉन्ड\",\n    \"setOutcomeTo\": \"परिणाम सेट करें\",\n    \"claimBond\": \"बॉन्ड का दावा करे\",\n    \"addBatch\": \"लेनदेन बैच जोड़ें\",\n    \"batch\": \"लेनदेन बैच\",\n    \"transactions\": \"लेनदेन\",\n    \"to\": \"भेजने का एड्रेस\",\n    \"invalidAddress\": \"अमान्य एड्रेस\",\n    \"invalidAmount\": \"अमान्य रकम\",\n    \"invalidValue\": \"अमान्य मूल्य\",\n    \"invalidAbi\": \"अमान्य एबीआई\",\n    \"invalidData\": \"अमान्य डेटा\",\n    \"value\": \"मूल्य (wei)\",\n    \"data\": \"डेटा\",\n    \"noCollectibles\": \"कोई संग्रहणीय नहीं\",\n    \"asset\": \"परिसंपत्ति\",\n    \"amount\": \"रकम\",\n    \"type\": \"प्रकार\",\n    \"transferFunds\": \"निधि अंतरण\",\n    \"transferNFT\": \"NFT अंतरण\",\n    \"contractInteraction\": \"अनुबंध बातचीत\",\n    \"rawTransaction\": \"कच्चा लेनदेन\",\n    \"addTransaction\": \"लेनदेन जोड़ें\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei से {address}\",\n      \"transferFunds\": \"ट्रांसफ़र {amount} {tokenSymbol} में {address}\",\n      \"transferNFT\": \"भेजें {name} #{id} पर {address}\",\n      \"raw\": \"भेजें {amount} wei पर {address}\"\n    },\n    \"labels\": {\n      \"request\": \"निष्पादन का अनुरोध करें\",\n      \"setOutcome\": \"परिणाम सेट करें:\",\n      \"changeOutcome\": \"परिणाम बदले\",\n      \"executeTxs\": \"निष्पादित करें लेनदेन बैच {0} का {1}\",\n      \"executed\": \"सभी लेनदेन निष्पादित किए गए हैं\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"कुछ गलत हो गया\",\n      \"connectWallet\": \"निष्पादन विवरण देखने के लिए वॉलेट कनेक्ट करें\",\n      \"switchChain\": \"Switch your wallet to {0} to request execution\",\n      \"question\": \"क्या यह प्रस्ताव पारित हुआ और क्या यह पूरा करता है\",\n      \"criteria\": \"स्वीकृति क्रिटेरिऑन?\",\n      \"proposalPassed\": \"Did the proposal pass?\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"इस प्रस्ताव के लिए अभी तक POAP सेटअप नहीं किया गया है :'(\",\n    \"no_voted_header\": \"इस POAP को पाने के लिए वोट करें\",\n    \"unclaimed_header\": \"Mint your I voted POAP\",\n    \"claimed_header\": \"Congratulations! The POAP has been minted to your collection\",\n    \"loading_header\": \"The POAP is being minted to your collection\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"Browse collection\",\n    \"success_claim\": \"The POAP has been minted to your collection\",\n    \"error_claim\": \"टोकन बनाने में एक समस्या हुई\"\n  },\n  \"charts\": {\n    \"charts\": \"Charts\",\n    \"noVotesYet\": \"There are no votes to visualize yet.\",\n    \"totalVotesPerDay\": \"Total votes per day\",\n    \"shareOfVotingPower\": \"Share of voting power\",\n    \"votingPowerPerDay\": \"Voting power per day\"\n  },\n  \"comment_box\": {\n    \"title\": \"टिप्पणी बॉक्स\",\n    \"add\": \"अपनी टिप्पणी यहाँ लिखें\",\n    \"submit\": \"जमा करें\",\n    \"preview\": \"पूर्वावलोकन\",\n    \"continue_editing\": \"एडिटिंग जारी रखें\",\n    \"edit\": \"अपना उत्तर यहां एडिट करें\",\n    \"edit_button\": \"बदले\",\n    \"dismiss\": \"ख़ारिज करें\",\n    \"delete\": \"हटाएं\",\n    \"add_reply\": \"अपना उत्तर यहाँ लिखें\",\n    \"edit_comment\": \"टिप्पणी बदले\",\n    \"edit_modal\": \"क्या आप वाकई एडिट करना चाहते हैं?\",\n    \"yes\": \"हाँ\",\n    \"no\": \"नहीं\",\n    \"delete_comment\": \"टिप्पणी हटाएँ\",\n    \"delete_modal\": \"क्या आप वाकई हटाने के इच्छुक हैं?\",\n    \"error\": \"ओह! कुछ गलत हो गया है\",\n    \"replies\": \"जवाब\",\n    \"hide\": \"छुपाएँ\",\n    \"show\": \"दिखाएँ\",\n    \"reply\": \"जवाब दें\",\n    \"load_more\": \"अधिक लोड करें\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Create a space\",\n      \"timeline\": \"Timeline\",\n      \"explore\": \"Explore\",\n      \"playground\": \"Playground\",\n      \"space\": {\n        \"create\": \"Create {space} proposal\",\n        \"about\": \"About {space}\",\n        \"proposals\": \"{space} Proposals\",\n        \"proposal\": \"{space} proposal: {proposal}\",\n        \"settings\": \"{space} Settings\"\n      },\n      \"strategy\": \"{key} strategy\",\n      \"delegate\": \"Delegate\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Track proposals for {spaceName}\",\n    \"text\": \"Receive notifications every time a new proposal is created or ends\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 second | {n} seconds\",\n    \"minute\": \"1 minute | {n} minutes\",\n    \"hour\": \"1 hour | {n} hours\",\n    \"day\": \"1 day | {n} days\",\n    \"week\": \"1 week | {n} weeks\",\n    \"month\": \"1 month | {n} months\",\n    \"year\": \"1 year | {n} years\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Unsupported network\",\n    \"ensOnlyMainnet\": \"Snapshot only supports ENS on the ethereum mainnet. Once the space is created, you can use it across multiple chains.\",\n    \"switchNetworkToMainnet\": \"To continue, you need to change the network in your wallet to Ethereum Mainnet.\",\n    \"switchToMainnet\": \"Switch to Mainnet\"\n  }\n}\n"
  },
  {
    "path": "src/locales/id-ID.json",
    "content": "{\n  \"searchPlaceholder\": \"Cari\",\n  \"spaceCount\": \"{0} ruang(an)\",\n  \"createSpace\": \"Buat ruang\",\n  \"backToHome\": \"Beranda\",\n  \"actions\": \"Aksi\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"Hasil\",\n  \"resultsError\": \"Hasil tidak dapat dihitung. Hal ini sering disebabkan oleh strategi yang salah dikonfigurasi atau node RPC yang tidak responsif dalam strategi.\",\n  \"resultsCalculating\": \"Final results are being calculated. If you still see this message after a few minutes contact the space admin.\",\n  \"votingPowerFailedMessage\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"votingValidationFailedMessage\": \"There was an error on our side and we could not verify if you are eligible to vote. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n  \"notValidVoterMessage\": \"Oops, you don't seem to be eligible to vote on this proposal.\",\n  \"getHelp\": \"Dapatkan bantuan\",\n  \"retry\": \"Coba lagi\",\n  \"currentResults\": \"Hasil saat ini\",\n  \"reset\": \"Atur ulang\",\n  \"close\": \"Close\",\n  \"save\": \"Simpan\",\n  \"author\": \"Penulis\",\n  \"next\": \"Selanjutnya\",\n  \"choice\": \"Choice\",\n  \"submit\": \"Kirim\",\n  \"plugins\": \"Plugins\",\n  \"information\": \"Informasi\",\n  \"confirm\": \"Konfirmasi\",\n  \"snapshot\": \"Snapshot\",\n  \"strategies\": \"Strategi\",\n  \"strategiesPage\": \"Strategi\",\n  \"space\": \"Ruang\",\n  \"spaces\": \"Ruang\",\n  \"verifiedSpace\": \"Ruang terverifikasi\",\n  \"warningSpace\": \"This space has been flagged as potentially malicious. Proceed with caution.\",\n  \"version\": \"Versi\",\n  \"timeline\": \"Lini Masa\",\n  \"ended\": \"berakhir\",\n  \"started\": \"dimulai\",\n  \"filters\": \"Filter\",\n  \"allSpaces\": \"Semua ruang\",\n  \"submitOnchain\": \"Simpan on-chain\",\n  \"inSpaces\": \"Di {0} ruang\",\n  \"votes\": \"Suara\",\n  \"seeMore\": \"Lihat lebih banyak\",\n  \"seeAll\": \"Lihat semua\",\n  \"network\": \"Jaringan\",\n  \"networks\": \"Jaringan\",\n  \"skins\": \"Tampilan\",\n  \"spaceMembers\": \"Members\",\n  \"members\": \"Tidak ada anggota | {count} anggota | {count} anggota\",\n  \"editStrategy\": \"Edit strategi\",\n  \"invalidProposals\": \"Proposal tidak valid\",\n  \"account\": \"Akun\",\n  \"create3box\": \"Buat profil di 3box\",\n  \"view3box\": \"Lihat profil di 3Box\",\n  \"edit3box\": \"Edit profil di 3Box\",\n  \"connectWallet\": \"Hubungkan dompet\",\n  \"toggleSkin\": \"Atur skin\",\n  \"about\": \"Tentang\",\n  \"license\": \"Lisensi\",\n  \"showMore\": \"Show more\",\n  \"voted\": \"Voted\",\n  \"reload\": \"Reload\",\n  \"ipfsServer\": \"Server IPFS\",\n  \"hub\": \"Hub\",\n  \"cancel\": \"Batal\",\n  \"delete\": \"Delete\",\n  \"demoSite\": \"Ini adalah situs demo, cobalah!\",\n  \"removeDelegation\": \"Hapus delegasi\",\n  \"confirmRemove\": \"Apakah anda yakin untuk menghapus delegasi\",\n  \"removeSpace\": \"untuk ruang {0}\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum reached\",\n  \"options\": \"Pilihan\",\n  \"votingPower\": \"Kekuatan voting anda\",\n  \"comment\": {\n    \"placeholder\": \"Share your reason (optional)\"\n  },\n  \"receipt\": \"Bukti penerimaan\",\n  \"relayer\": \"Penyampai\",\n  \"verifyOnMycrypto\": \"Verifikasi tanda terima di MyCrypto\",\n  \"verifyOnSignatorio\": \"Verifikasi di Signator.io\",\n  \"isCore\": \"Inti\",\n  \"notificationsBlocked\": \"Browser Anda memblokir notifikasi\",\n  \"notificationsNotSupported\": \"Browser anda tidak mendukung pemberitahuan\",\n  \"walletNotSupported\": \"Wallet tidak didukung\",\n  \"seeInExplorer\": \"Lihat di explorer\",\n  \"learnMore\": \"Pelajari lebih lanjut\",\n  \"logout\": \"Keluar\",\n  \"continue\": \"Lanjut\",\n  \"add\": \"Tambah\",\n  \"edit\": \"Edit\",\n  \"strategyParameters\": \"Parameter strategi\",\n  \"addAction\": \"Tambahkan tindakan\",\n  \"removeAction\": \"Hapus tindakan\",\n  \"yourChoice\": \"Pilihan {0}\",\n  \"targetAddress\": \"Alamat tujuan\",\n  \"value\": \"Nilai\",\n  \"date\": \"Data\",\n  \"marketDetails\": \"Rincian pasar\",\n  \"addMarket\": \"Tambahkan pasar\",\n  \"selectNetwork\": \"Pilih jaringan\",\n  \"conditionId\": \"ID kondisi\",\n  \"basetokenAddress\": \"Alamat basis token\",\n  \"quoteAddress\": \"Kutip alamat mata uang\",\n  \"removeMarket\": \"Hapus pasar\",\n  \"back\": \"Kembali\",\n  \"loading\": \"Memuat...\",\n  \"predictedImpact\": \"Dampak yang diprediksi\",\n  \"marketSymbol\": \"{0} pasar\",\n  \"twoChoicesRequired\": \"Dua pilihan diperlukan untuk plugin ini.\",\n  \"noResultsFound\": \"Maaf, kami tidak dapat menemukan hasil apapun\",\n  \"createFirstProposal\": \"Mari buat proposal pertama anda\",\n  \"noSpacesJoined\": \"Ups, Anda belum bergabung dengan ruang apa pun\",\n  \"addFavorites\": \"Tambahkan favorit\",\n  \"createdBy\": \"Oleh {0}\",\n  \"startIn\": \"mulai {0}\",\n  \"endIn\": \"akhir {0}\",\n  \"proposalTimeLeft\": \"{0} tersisa\",\n  \"endedAgo\": \"berakhir {0}\",\n  \"proposalBy\": \"oleh {0}\",\n  \"endDate\": \"akhir {0}\",\n  \"defaultSkin\": \"Skin bawaan\",\n  \"select\": \"Pilih\",\n  \"language\": \"Bahasa\",\n  \"agree\": \"Saya setuju\",\n  \"moderators\": \"Moderators\",\n  \"playground\": \"Playground\",\n  \"strategyParams\": \"Parameter strategi\",\n  \"addresses\": \"Alamat\",\n  \"networkErrorPlayground\": \"Kesalahan jaringan - buka konsol browser Anda untuk informasi lebih lanjut\",\n  \"upload\": \"Unggah\",\n  \"join\": \"Bergabung\",\n  \"joined\": \"Tergabung\",\n  \"leave\": \"Keluar\",\n  \"subspaces\": \"Sub-spaces\",\n  \"mainspace\": \"Main space\",\n  \"copyLink\": \"Salin tautan\",\n  \"duplicate\": \"Duplicate\",\n  \"joinedSpaces\": \"Ruangan yang diikuti\",\n  \"joinSpaces\": \"Gabung ke ruang\",\n  \"setDelegationToSpace\": \"Batasi delegasi untuk ruang tertentu\",\n  \"theCurrentNetwork\": \"jaringan saat ini\",\n  \"optional\": \"(opsional)\",\n  \"homeLoadmore\": \"Muat lebih banyak\",\n  \"confirmAction\": \"Konfirmasi tindakan\",\n  \"or\": \"atau\",\n  \"share\": \"Bagikan\",\n  \"shareOnTwitter\": \"Share on Twitter\",\n  \"shareOnLenster\": \"Share on Lenster\",\n  \"createButton\": \"Create\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Change wallet\",\n  \"createASpace\": \"Create a space\",\n  \"getStarted\": \"Get started\",\n  \"skip\": \"Skip\",\n  \"newSpaceNotice\": {\n    \"header\": \"Your space is live!\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Learn more in the {documentation} or join Snapshot {discord} for help.\",\n    \"gotIt\": \"Got it!\"\n  },\n  \"errors\": {\n    \"required\": \"Baris diperlukan\",\n    \"minLength\": \"Field is required\",\n    \"maxLength\": \"Panjang maksimum adalah {0}\",\n    \"pattern\": \"Karakter tidak valid\",\n    \"minItems\": \"Minimal {0} item diperlukan\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"minStrategy\": \"Setidaknya satu strategi diperlukan.\",\n    \"website\": \"URL should be in the format https://www.example.com\",\n    \"format\": \"Format tidak valid\",\n    \"type\": \"Tipe tidak valid\",\n    \"unsupportedImageType\": \"File type not supported, Supported formats are jpeg, jpg and png\",\n    \"invalidAddress\": \"Invalid address\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Title\",\n    \"discussion\": \"Discussion (optional)\",\n    \"categorie(s)\": \"Pilih lebih dari 2 kategori\",\n    \"proposalDescription\": \"Description (optional)\",\n    \"preview\": \"Pratinjau\",\n    \"choices\": \"Pilihan\",\n    \"addChoice\": \"Tambahkan pilihan\",\n    \"startDate\": \"Pilih tanggal mulai\",\n    \"endDate\": \"Pilih tanggal selesai\",\n    \"startTime\": \"Pilih waktu mulai\",\n    \"endTime\": \"Pilih waktu selesai\",\n    \"publish\": \"Publikasikan\",\n    \"untitled\": \"Tanpa judul\",\n    \"snapshotBlock\": \"Jumlah blok Snapshot\",\n    \"voting\": \"Voting\",\n    \"votingSystem\": \"Sistem voting\",\n    \"choice\": \"Pilihan {0}\",\n    \"period\": \"Periode voting\",\n    \"start\": \"Mulai\",\n    \"end\": \"Selesai\",\n    \"days\": \"Hari\",\n    \"hours\": \"Jam\",\n    \"minutes\": \"Menit\",\n    \"schedule\": \"Jadwal proposal\",\n    \"delayEnforced\": \"The space enforces a delay until voting can start\",\n    \"periodEnforced\": \"The space enforces the duration of the voting period\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"Sunting\",\n    \"continue\": \"Lanjut\",\n    \"now\": \"Sekarang\",\n    \"votingPeriodExplainer\": \"Ini adalah periode waktu di mana pengguna dapat memilih. Proposal akan terlihat dan tertunda sebelum dimulainya periode pemungutan suara.\",\n    \"uploadImageExplainer\": \"Attach images by dragging & dropping, selecting or pasting them.\",\n    \"uploading\": \"Uploading image\",\n    \"markdown\": \"Styling with Markdown is supported\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"Anda harus menjadi penulis di ruangan ini untuk bisa menulis proposal.\",\n        \"minScore\": \"Anda harus memiliki setidaknya minimal {0} {1} untuk mengajukan sebuah proposal.\"\n      },\n      \"customValidation\": \"Anda harus lulus validasi proposal untuk mengajukan proposal.\",\n      \"executionError\": \"Verifikasi hak anda untuk membuat proposal di ruang ini telah gagal. Ini mungkin disebabkan karna kesalahan konfigurasi strategi.\"\n    },\n    \"errorGettingSnapshot\": \"We encountered an error while fetching the snapshot block number which is needed to calculate your voting power. Please try again later.\"\n  },\n  \"delegate\": {\n    \"header\": \"Perwakilan\",\n    \"selectDelegate\": \"Pilih delegasi\",\n    \"to\": \"Kepada\",\n    \"addressPlaceholder\": \"Alamat atau nama ENS\",\n    \"delegations\": \"Perwakilan Anda(s)\",\n    \"allSpaces\": \"Untuk semua ruang\",\n    \"delegated\": \"Diwakilkan kepada Anda\",\n    \"pendingTransaction\": \"tidak ada transaksi yang tertunda | 1 transaksi yang tertunda | {count} transaksi yang tertunda\",\n    \"topDelegates\": \"Delegasi teratas\",\n    \"noDelegatesFoundFor\": \"Tidak ada delegasi yang ditemukan untuk {0}\",\n    \"noValidEns\": \"Bukan alamat ENS yang valid.\",\n    \"noValidAddress\": \"Bukan alamat yang valid\",\n    \"delegateToSelf\": \"Anda tidak bisa mendelegasikan diri sendiri\",\n    \"delegateToSelfAddress\": \"Anda tidak bisa mendelegasikan alamat ENS anda sendiri\",\n    \"noValidSpaceId\": \"Bukan ID ruang yang valid\",\n    \"noDelegationsAndDelegates\": \"Can't find your delegations and delegates? Make sure you are connected to the correct network.\",\n    \"delegateNotSupported\": \"Delegation is currently not supported for {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Berikan voting Anda\",\n    \"vote\": \"Voting\",\n    \"startDate\": \"Tanggal mulai\",\n    \"endDate\": \"Tanggal selesai\",\n    \"votingSystem\": \"Sistem ambil suara\",\n    \"privacy\": \"Privacy\",\n    \"invalidChoice\": \"Invalid choice\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Your vote is in!\",\n      \"gnosisSafeTitle\": \"Your vote is pending...\",\n      \"gnosisSafeDescription\": \" Votes with a Safe require additional signers and will be visible once the transaction is confirmed\",\n      \"seeQueue\": \"See queued transactions\",\n      \"tips\": {\n        \"1\": \"Votes can be changed while the proposal is active\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"Proposal\",\n    \"new\": \"Proposal baru\",\n    \"noProposals\": \"Belum ada proposal di sini!\",\n    \"createProposal\": \"Buat proposal\",\n    \"showMore\": \"Tampilkan lebih banyak\",\n    \"showLess\": \"Tampilkan lebih ringkas\",\n    \"states\": {\n      \"all\": \"Semua\",\n      \"core\": \"Inti\",\n      \"community\": \"Komunitas\",\n      \"active\": \"Aktif\",\n      \"pending\": \"Tertunda\",\n      \"closed\": \"Ditutup\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Pemberitahuan\",\n    \"noNotifications\": \"Anda tidak memiliki pemberitahuan baru\",\n    \"proposalStarted\": \"proposal sudah dimulai:\",\n    \"proposalEnded\": \"proposal telah selesai:\",\n    \"markAllAsRead\": \"Tandai semua sudah dibaca\",\n    \"all\": \"Semua\",\n    \"unread\": \"Belum dibaca\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"join\",\n    \"actionCreate\": \"create a proposal in\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"Pengaturan\",\n    \"editController\": \"Edit controller\",\n    \"connectWithSpaceOwner\": \"You are in view only mode, to modify space settings connect with a controller or admin wallet.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"edit the space settings\",\n      \"create\": \"create a proposal\",\n      \"vote\": \"vote on this proposal\"\n    },\n    \"currentSpaceControllerIs\": \"The current space controller is {address}\",\n    \"newController\": \"New controller\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Set\",\n    \"profile\": \"Profil\",\n    \"avatar\": \"Avatar\",\n    \"name\": {\n      \"label\": \"Name\",\n      \"placeholder\": \"e.g. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"About\",\n      \"placeholder\": \"What is your organisation about?\"\n    },\n    \"categories\": {\n      \"label\": \"Categorie(s)\",\n      \"select\": \"Select categorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Terms of service\",\n      \"information\": \"Users will be required to accept these terms once before they can create a proposal or cast a vote\"\n    },\n    \"hideSpace\": \"Sembunyikan ruang dari halaman beranda\",\n    \"links\": \"Social accounts\",\n    \"subspaces\": {\n      \"label\": \"Sub-spaces\",\n      \"information\": \"Sub-spaces will only be shown once configured on both the main space and the sub-space(s). {docs}\",\n      \"parent\": {\n        \"label\": \"Main space\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"The space that this space is a sub-space of will be displayed on the space page\"\n      },\n      \"children\": {\n        \"label\": \"Sub-spaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Related Sub-spaces listed here will be displayed on the space page\"\n      }\n    },\n    \"website\": \"Website\",\n    \"strategies\": {\n      \"label\": \"Strategie(s)\",\n      \"information\": \"Strategies are used to determine voting power or whether a user is eligible to create a proposal\"\n    },\n    \"network\": {\n      \"label\": \"Network\",\n      \"information\": \"The defaul network used for this space. Networks can also be specified in individual strategies\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbol\",\n      \"information\": \"The default symbol used for this space, usually the token symbol i.e. BAL for Balancer\"\n    },\n    \"strategiesList\": \"Select up to 8 strategies\",\n    \"votingPowerIsCumulative\": \"Voting power is cumulative\",\n    \"addStrategy\": \"Tambahkan strategi\",\n    \"testInPlayground\": \"Test in playground\",\n    \"admins\": {\n      \"label\": \"Admins\",\n      \"information\": \"Admins are able to modify the space settings and manage the space's proposals\"\n    },\n    \"authors\": {\n      \"label\": \"Authors\",\n      \"information\": \"Authors are always able to create proposals\"\n    },\n    \"proposalValidation\": \"Validasi proposal\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Threshold\",\n      \"information\": \"The minimum amount of voting power required to create a proposal\"\n    },\n    \"allowOnlyAuthors\": \"Izinkan hanya penulis yang bisa membuat proposal\",\n    \"editValidation\": \"Sunting validitas\",\n    \"selectValidation\": \"Pilih validitas\",\n    \"validationParameters\": \"Parameter validitas\",\n    \"voting\": \"Pemungutan suara\",\n    \"votingDelay\": \"Penundaan pemungutan suara\",\n    \"votingPeriod\": \"Periode pemungutan suara\",\n    \"hours\": \"Hours\",\n    \"days\": \"Days\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"The minimum amount of voting power required for the proposal to pass\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"The type of voting system used for this space. (Enforced on all future proposals)\"\n    },\n    \"anyType\": \"Apa saja\",\n    \"hideAbstain\": \"Abaikan suara abstain pada hasil pemungutan suara\",\n    \"customDomain\": \"Domain khusus\",\n    \"domain\": {\n      \"label\": \"Domain name\",\n      \"placeholder\": \"e.g. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"Skin\",\n    \"treasuries\": {\n      \"label\": \"Treasury\",\n      \"add\": \"Add treasury\",\n      \"edit\": \"Edit treasury\",\n      \"information\": \"Add treasuries of your organization to show them in your space\"\n    },\n    \"addPlugin\": \"Tambahkan plugin\",\n    \"editPlugin\": \"Edit plugin\",\n    \"pluginParameters\": \"Parameter plugin\",\n    \"proposal\": {\n      \"title\": \"Proposal\",\n      \"guidelines\": {\n        \"title\": \"Guidelines\",\n        \"information\": \"Display a link to your guidelines on proposal creation to help users understand what constitutes a good/valid proposal\"\n      },\n      \"template\": {\n        \"title\": \"Template\",\n        \"information\": \"Start every proposal with a template to help users understand what information is required\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"misalnya yam.eth\",\n    \"chooseExistingEns\": \"Pilih satu domain ENS anda untuk membuat ruang:\",\n    \"useSingleExistingEns\": \"Pakai domain ENS anda:\",\n    \"orRegisterNewEns\": \"Atau daftarkan domain yang baru:\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"Untuk membuat ruang, anda harus punya ENS domain. Masukan satu dibawah dan ikuti instruksi pendaftaran ENS.\",\n    \"createASpace\": \"Buat sebuah ruang\",\n    \"registerEnsButton\": \"Daftar\",\n    \"supportedEnsTLDs\": \"Akhiran domain yang didukung\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or join Snapshot {discord}.\",\n    \"setSpaceController\": \"Space controller\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \" The space controller is the account that will be able to manage the space settings. Additional space controllers (admins) can be added later.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"When creating a space with a Gnosis Safe it's recommended to set the safe address as the space controller. If you don't, you need to follow some additional steps. {link}\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"Setting the controller requires a transaction on the {network} which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choose ENS address\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. {address}\",\n    \"controllerAddress\": \"Controller address\",\n    \"updateController\": \"Update controller\",\n    \"seeOnEns\": \"See on ENS\",\n    \"goToSettings\": \"Go to settings\",\n    \"setSpaceProfile\": \"Customize your space\",\n    \"waitForTransaction\": \"The transaction need to confirm before you can create your space. {txUrl}\",\n    \"pleaseWaitMessage\": \"This can take a few minutes, please wait while the transaction is being confirmed\",\n    \"notControllerAddress\": \"Please connect with the controller address {wallet} to create the space.\",\n    \"fillCurrentAccount\": \"Use currently logged in account\",\n    \"domain\": {\n      \"title\": \"Setup your space domain\",\n      \"ensMessage\": \" One thing you need before you can create your own space, is an ENS domain on Ethereum mainnet.\",\n      \"ensMessageTestnet\": \"You can also {link} on the Goerli testnet and mess with things there first.\",\n      \"tryDemo\": \"try the demo\",\n      \"yourExistingSpaces\": \"Your existing spaces\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"How would you like to setup your voting strategy?\",\n      \"subtitle\": \"You can change your strategy settings any time.\",\n      \"blockTitle\": \"Setup voting strategy\",\n      \"onePersonOneVote\": {\n        \"title\": \"One person, one vote\",\n        \"description\": \"Manage a whitelist of people who can vote or simply allow any address to vote. Every vote is equal and no token is required\",\n        \"whitelistInformation\": \"Specify a number of accounts that can vote\",\n        \"ticketInformation\": \"Any account can vote\",\n        \"votesEqualInfo\": \"Each vote is equal and no token is required\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Token weighted voting\",\n        \"description\": \"Votes are weighted by a token. The token can be an ERC-20, ERC-721 or ERC-1155 token standard\",\n        \"tokenNotFound\": \"Token not found\",\n        \"seeOnEtherscan\": \"See on Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Custom setup\",\n        \"description\": \"Select up to 8 strategies with a wide range of options. If you can't find the right strategy for your use case, you can create your own\"\n      }\n    },\n    \"validationTitle\": \"Who can manage this space and create proposals?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Edit profile\",\n    \"viewProfile\": \"View profile\",\n    \"about\": {\n      \"header\": \"About\",\n      \"joinedSpaces\": \"Joined spaces\",\n      \"createdSpaces\": \"Created spaces\",\n      \"biography\": \"Bio\",\n      \"notJoinSpacesYet\": \"Hasn't joined any spaces yet\",\n      \"notCreatedSpacesYet\": \"Hasn't created any spaces yet\",\n      \"delegatorNetworkInfo\": \"Change by switching network in your wallet\",\n      \"delegate\": \"Delegate\",\n      \"delegated\": \"Delegated\",\n      \"delegateTo\": \"Delegate to\",\n      \"delegateFor\": \"Delegator for\",\n      \"noDelegatorsMessage\": \"No delegators on {network}\",\n      \"notSupportedNetwork\": \"Delegation currently isn't supported on {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activity\",\n      \"votedFor\": \"Voted {choice}\",\n      \"today\": \"Today\",\n      \"thisWeek\": \"This week\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"No activity yet\"\n    },\n    \"settings\": {\n      \"header\": \"Edit profile\",\n      \"name\": \"Name\",\n      \"biography\": \"Bio\",\n      \"namePlaceholder\": \"Enter name\",\n      \"bioPlaceholder\": \"Tell your story\",\n      \"change\": \"Change\",\n      \"remove\": \"Remove\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"Anda berhasil!\",\n    \"copied\": \"Tersalin!\",\n    \"proposalDeleted\": \"Proposal dihapus\",\n    \"somethingWentWrong\": \"Amboy, sesuatu kesalahan telah terjadi!\",\n    \"saved\": \"Disimpan!\",\n    \"delegationSuccess\": \"Delegasi berhasil\",\n    \"delegationRemoved\": \"Delegasi dihapus\",\n    \"proposalCreated\": \"Proposal dibuat\",\n    \"voteSuccessful\": \"Suara Anda telah masuk!\",\n    \"ensSet\": \"ENS text record was successfully set\",\n    \"transactionSent\": \"Transaction sent\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Buat strategi\",\n    \"createSkin\": \"Buat skin\",\n    \"addNetwork\": \"Tambahkan jaringan\",\n    \"createPlugin\": \"Buat plugin\",\n    \"strategies\": \"strategi\",\n    \"skins\": \"skin\",\n    \"networks\": \"jaringan\",\n    \"plugins\": \"plugin\",\n    \"results\": \"hasil\",\n    \"category\": \"Category\",\n    \"categories\": {\n      \"all\": \"Semua\",\n      \"protocol\": \"Protokol\",\n      \"social\": \"Sosial\",\n      \"investment\": \"Investasi\",\n      \"grant\": \"Izinkan\",\n      \"service\": \"Layanan\",\n      \"media\": \"Media\",\n      \"creator\": \"Pembuat\",\n      \"collector\": \"Kolektor\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Pilih sistem ambil suara\",\n    \"single-choice\": \"Ambil suara pilihan tunggal\",\n    \"approval\": \"Ambil suara persetujuan\",\n    \"quadratic\": \"Pemungutan suara kuadrat\",\n    \"ranked-choice\": \"Pilihan peringkat pemungutan suara\",\n    \"weighted\": \"Suara yang berbobot\",\n    \"basic\": \"Pemungutan suara dasar\",\n    \"description\": {\n      \"single-choice\": \"Setiap pemilih hanya boleh memilih satu pilihan.\",\n      \"approval\": \"Setiap pemilih dapat memilih sejumlah pilihan.\",\n      \"quadratic\": \"Setiap pemilih dapat menyebarkan kekuatan suara di sejumlah pilihan. Hasil dihitung secara kuadrat.\",\n      \"ranked-choice\": \"Setiap pemilih dapat memilih dan memberi peringkat ke sejumlah pilihan. Hasil dihitung dengan metode instant-runoff.\",\n      \"weighted\": \"Setiap pemilih dapat menyebarkan hak suara di sejumlah pilihan.\",\n      \"basic\": \"Voting pilihan tunggal dengan tiga pilihan: Untuk, Menentang atau Abstain\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Privacy\",\n    \"title\": \"Select voting privacy\",\n    \"information\": \"The type of privacy used on proposals. (Enforced on all future proposals)\",\n    \"any\": \"Any\",\n    \"none\": \"None\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Choices are encrypted and only visible once the voting period is over\",\n      \"tooltip\": \"This proposal has Shutter privacy enabled. All votes will be encrypted until the voting period has ended and the final score is calculated\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select voting validation\",\n    \"information\": \"The type of validation used to determine if a user can vote. (Enforced on all future proposals)\",\n    \"any\": {\n      \"label\": \"Anyone can vote\",\n      \"description\": \"Anyone with voting power can cast a vote.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement to vote on this proposal. \"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your proposals from spam and vote manipulation by requiring users to have a Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold} and {operator} of the following stamps to vote on this proposal: {stamps}. \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Hasil saat ini\",\n    \"currentBond\": \"Obligasi saat ini\",\n    \"finalizedIn\": \"Selesai{0}\",\n    \"executableIn\": \"Bisa dilaksanakan/dijalankan {0}\",\n    \"finalOutcome\": \"Hasil\",\n    \"nextBond\": \"Obligasikan untuk mendapatkan hasil\",\n    \"setOutcomeTo\": \"Tetapkan hasil ke\",\n    \"claimBond\": \"Klaim obligasi\",\n    \"addBatch\": \"Tambahkan transaksi batch\",\n    \"batch\": \"Transaksi batch\",\n    \"transactions\": \"Transaksi\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"Alamat tidak valid\",\n    \"invalidAmount\": \"Jumlah tidak valid\",\n    \"invalidValue\": \"Nilai tidak valid\",\n    \"invalidAbi\": \"ABI tidak valid\",\n    \"invalidData\": \"Data tidak valid\",\n    \"value\": \"Nilai (wei)\",\n    \"data\": \"Data\",\n    \"noCollectibles\": \"Tidak ada koleksi\",\n    \"asset\": \"Aset\",\n    \"amount\": \"Jumlah\",\n    \"type\": \"Tipe\",\n    \"transferFunds\": \"Transfer dana\",\n    \"transferNFT\": \"Transfer NFT\",\n    \"contractInteraction\": \"Interaksi kontrak\",\n    \"rawTransaction\": \"Transaksi mentah\",\n    \"addTransaction\": \"Tambahkan transaksi\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei ke {address}\",\n      \"transferFunds\": \"Transfer {amount} {tokenSymbol} ke {address}\",\n      \"transferNFT\": \"Kirim {name} #{id} ke {address}\",\n      \"raw\": \"Kirim {amount} wei ke {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Minta eksekusi\",\n      \"setOutcome\": \"Tetapkan hasil\",\n      \"changeOutcome\": \"Ubah hasil\",\n      \"executeTxs\": \"Jalankan transaksi batch {0} dari {1}\",\n      \"executed\": \"Semua transaksi telah dilaksanakan\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"Terjadi suatu kesalahan\",\n      \"connectWallet\": \"Hubungkan dompet untuk melihat detail pelaksanaan\",\n      \"switchChain\": \"Alihkan dompet Anda ke {0} untuk mengajukan pelaksanaan\",\n      \"question\": \"Apaka proposal ini lolos dan sesuai dengan\",\n      \"criteria\": \"kriteria penerimaan?\",\n      \"proposalPassed\": \"Did the proposal pass?\",\n      \"expired\": \"The proposal has expired\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to confirm the proposal passed\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"requestToolTip\": \"This will propose the transactions from this Snapshot vote on-chain. After a challenge window, if the proposal is valid, the transactions can be executed and your bond will be returned.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"POAP belum dipersiapkan untuk proposal ini :'(\",\n    \"no_voted_header\": \"Pilih untuk menerima POAP\",\n    \"unclaimed_header\": \"Klaim POAP voting anda\",\n    \"claimed_header\": \"Selamat! POAP ini telah ditambahkan ke koleksi anda\",\n    \"loading_header\": \"POAP ini sedang ditambahkan ke koleksi anda\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"Jelajahi koleksi\",\n    \"success_claim\": \"POAP ini telah ditambahkan ke koleksi anda\",\n    \"error_claim\": \"Ada masalah dalam pencetakaan token\"\n  },\n  \"progress\": {\n    \"progress\": \"Progress\",\n    \"inProgress\": \"In Progress\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"New Step\",\n    \"description\": \"Description\",\n    \"add\": \"Add\",\n    \"deleteStep\": \"Delete Step\",\n    \"deleteConfirm\": \"Are you sure you want to delete?\",\n    \"delete\": \"Delete\",\n    \"cancel\": \"Cancel\",\n    \"comeBack\": \"Come back after voting is complete to see how this proposal is progressing!\",\n    \"confirmSignature\": \"Signing this message will allow us to authorize your request to update the progress of your proposal.\",\n    \"wentWrong\": \"Oops something went wrong.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"Grafik\",\n    \"noVotesYet\": \"Belum ada pemilih untuk ditampilkan.\",\n    \"totalVotesPerDay\": \"Total pemilih per hari\",\n    \"shareOfVotingPower\": \"Kekuatan voting\",\n    \"votingPowerPerDay\": \"Kekuatan voting per hari\"\n  },\n  \"comment_box\": {\n    \"title\": \"Kotak komentar\",\n    \"add\": \"Tambahkan komentar Anda di sini\",\n    \"submit\": \"Kirimkan\",\n    \"preview\": \"Pratinjau\",\n    \"continue_editing\": \"Lanjutkan mengedit\",\n    \"edit\": \"Edit balasan Anda di sini\",\n    \"edit_button\": \"Edit\",\n    \"dismiss\": \"Abaikan\",\n    \"delete\": \"Hapus\",\n    \"add_reply\": \"Tambahkan balasan Anda di sini\",\n    \"edit_comment\": \"Edit komentar\",\n    \"edit_modal\": \"Apakah anda yakin ingin mengedit?\",\n    \"yes\": \"Ya\",\n    \"no\": \"Tidak\",\n    \"delete_comment\": \"Hapus komentar\",\n    \"delete_modal\": \"Apakah anda yakin ingin menghapus?\",\n    \"error\": \"Ups! Ada yang tidak beres\",\n    \"replies\": \"balasan\",\n    \"hide\": \"Sembunyikan\",\n    \"show\": \"Tampilkan\",\n    \"reply\": \"Balas\",\n    \"load_more\": \"Muat lebih banyak\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Buat sebuah ruang\",\n      \"timeline\": \"Lini masa\",\n      \"notifications\": \"Notifications\",\n      \"explore\": \"Telusuri\",\n      \"playground\": \"Playground\",\n      \"space\": {\n        \"create\": \"Buat proposal {space}\",\n        \"about\": \"Tentang {space}\",\n        \"proposals\": \"Proposal {space}\",\n        \"proposal\": \"Proposal {space}: {proposal}\",\n        \"settings\": \"{space} Pengaturan\"\n      },\n      \"strategy\": \"Strategi {key}\",\n      \"delegate\": \"Delegasikan\",\n      \"ranking\": \"Ranking\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Lacak proposal untuk {spaceName}\",\n    \"text\": \"Dapatkan pemeberitahuan setiap propoaal baru dibuat atau berakhir\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 second | {n} seconds\",\n    \"minute\": \"1 minute | {n} minutes\",\n    \"hour\": \"1 hour | {n} hours\",\n    \"day\": \"1 day | {n} days\",\n    \"week\": \"1 week | {n} weeks\",\n    \"month\": \"1 month | {n} months\",\n    \"year\": \"1 year | {n} years\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Unsupported network\",\n    \"switchNetworkToNetwork\": \"To continue, you need to change the network in your wallet to {network}.\",\n    \"switchToNetwork\": \"Switch to {network}\",\n    \"goToDemoSite\": \"Go to demo website\"\n  },\n  \"treasury\": {\n    \"title\": \"Treasury\",\n    \"wallets\": {\n      \"title\": \"Wallets\",\n      \"empty\": \"This space doesn't have a treasury yet\",\n      \"addTreasury\": \"Add a treasury\"\n    },\n    \"assets\": {\n      \"title\": \"Assets\",\n      \"empty\": \"There are no assets in this contract\"\n    },\n    \"24hChange\": \"24h change\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Your email\",\n    \"title\": \"Get the latest Snapshot updates\"\n  },\n  \"joinCommunity\": \"Join Snapshot community\",\n  \"header\": {\n    \"title\": \"Where decisions get made\",\n    \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot is a decentralized governance platform that makes it easy to create and vote on proposals - all without spending a fortune on gas fees! Plus, our flexible system supports various voting types and strategies, so you can tailor the voting process to your needs.\",\n    \"subHeader\": \"Governance should be a snap\",\n    \"subDescription\": \"Web3 governance doesn't have to be complicated. Snapshot is the perfect solution for organizations looking for an easy and efficient way to govern their community or organization.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Resources\",\n    \"about\": \"About\",\n    \"blog\": \"Blog\",\n    \"jobs\": \"Jobs\",\n    \"discussions\": \"Discussions\",\n    \"github\": \"GitHub\",\n    \"docs\": \"Docs\",\n    \"support\": \"Support\",\n    \"hiring\": \"Join us!\"\n  }\n}\n"
  },
  {
    "path": "src/locales/it-IT.json",
    "content": "{\n  \"searchPlaceholder\": \"Cerca\",\n  \"spaceCount\": \"{0} spazio(i)\",\n  \"createSpace\": \"Crea spazi\",\n  \"backToHome\": \"Inizio\",\n  \"actions\": \"Azioni\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"Risultati\",\n  \"resultsError\": \"Results could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"resultsCalculating\": \"Final results are being calculated. If you still see this message after a few minutes contact the space admin.\",\n  \"votingPowerFailedMessage\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"votingValidationFailedMessage\": \"There was an error on our side and we could not verify if you are eligible to vote. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n  \"notValidVoterMessage\": \"Oops, you don't seem to be eligible to vote on this proposal.\",\n  \"getHelp\": \"Get help\",\n  \"retry\": \"Retry\",\n  \"currentResults\": \"Risultati Correnti\",\n  \"reset\": \"Ripristina predefiniti\",\n  \"close\": \"Close\",\n  \"save\": \"Salva\",\n  \"author\": \"Autore\",\n  \"next\": \"Avanti >\",\n  \"choice\": \"Choice\",\n  \"submit\": \"Invia\",\n  \"plugins\": \"Plugin\",\n  \"information\": \"Informazioni\",\n  \"confirm\": \"Conferma\",\n  \"snapshot\": \"Snapshot\",\n  \"strategies\": \"Strategie\",\n  \"strategiesPage\": \"Strategie\",\n  \"space\": \"Spazio\",\n  \"spaces\": \"Spazi\",\n  \"verifiedSpace\": \"Verified space\",\n  \"warningSpace\": \"This space has been flagged as potentially malicious. Proceed with caution.\",\n  \"version\": \"Versione\",\n  \"timeline\": \"Cronologia\",\n  \"ended\": \"ended\",\n  \"started\": \"started\",\n  \"filters\": \"Filtri\",\n  \"allSpaces\": \"Tutti gli spazi\",\n  \"submitOnchain\": \"Invia on-chain\",\n  \"inSpaces\": \"In {0} spazio(i)\",\n  \"votes\": \"Voti\",\n  \"seeMore\": \"Mostra altro\",\n  \"seeAll\": \"See all\",\n  \"network\": \"Rete\",\n  \"networks\": \"Reti\",\n  \"skins\": \"Temi\",\n  \"spaceMembers\": \"Members\",\n  \"members\": \"Nessun membro | {count} membro | {count} membri\",\n  \"editStrategy\": \"Modifica strategia\",\n  \"invalidProposals\": \"Proposta non valida\",\n  \"account\": \"Profilo\",\n  \"create3box\": \"Crea profilo su 3Box\",\n  \"view3box\": \"Visualizza il profilo su 3Box\",\n  \"edit3box\": \"Modifica profilo su 3Box\",\n  \"connectWallet\": \"Connetti wallet\",\n  \"toggleSkin\": \"Toggle skin\",\n  \"about\": \"Chi siamo\",\n  \"license\": \"Licenza\",\n  \"showMore\": \"Show more\",\n  \"voted\": \"Voted\",\n  \"reload\": \"Reload\",\n  \"ipfsServer\": \"Server IPFS\",\n  \"hub\": \"Hub\",\n  \"cancel\": \"Annulla\",\n  \"delete\": \"Delete\",\n  \"demoSite\": \"Questo è il sito demo, fai una prova!\",\n  \"removeDelegation\": \"Rimuovi delega\",\n  \"confirmRemove\": \"Sei sicuro di voler rimuovere la delega a\",\n  \"removeSpace\": \"per lo spazio {0}\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum reached\",\n  \"options\": \"Opzioni\",\n  \"votingPower\": \"Il tuo potere di voto\",\n  \"comment\": {\n    \"placeholder\": \"Share your reason (optional)\"\n  },\n  \"receipt\": \"Ricevuta\",\n  \"relayer\": \"Relè\",\n  \"verifyOnMycrypto\": \"Verifica ricevuta su MyCrypto\",\n  \"verifyOnSignatorio\": \"Verificati su Signator.io\",\n  \"isCore\": \"Core\",\n  \"notificationsBlocked\": \"Your browser is blocking notifications\",\n  \"notificationsNotSupported\": \"Your browser does not support notifications\",\n  \"walletNotSupported\": \"Wallet is not supported\",\n  \"seeInExplorer\": \"See explorer\",\n  \"learnMore\": \"Scopri di più\",\n  \"logout\": \"Disconnetti\",\n  \"continue\": \"Continua\",\n  \"add\": \"Aggiungi\",\n  \"edit\": \"Modifica\",\n  \"strategyParameters\": \"Parametri strategia\",\n  \"addAction\": \"Aggiungi un'azione\",\n  \"removeAction\": \"Rimuovi azione\",\n  \"yourChoice\": \"Scelta {0}\",\n  \"targetAddress\": \"Indirizzo del destinatario\",\n  \"value\": \"Valore\",\n  \"date\": \"Dati\",\n  \"marketDetails\": \"Dettagli di mercato\",\n  \"addMarket\": \"Aggiungi mercato\",\n  \"selectNetwork\": \"Seleziona la rete\",\n  \"conditionId\": \"Condizione ID\",\n  \"basetokenAddress\": \"Indirizzo del token di base\",\n  \"quoteAddress\": \"Indirizzo valuta citata\",\n  \"removeMarket\": \"Rimuovi mercato\",\n  \"back\": \"Indietro\",\n  \"loading\": \"Caricamento...\",\n  \"predictedImpact\": \"Impatto previsto\",\n  \"marketSymbol\": \"{0} mercati\",\n  \"twoChoicesRequired\": \"Per questo plugin sono necessarie due scelte.\",\n  \"noResultsFound\": \"Oops, non riusciamo a trovare alcun risultato\",\n  \"createFirstProposal\": \"Creiamo la tua prima proposta\",\n  \"noSpacesJoined\": \"Spiacenti, non ti sei ancora iscritto a nessuno spazio\",\n  \"addFavorites\": \"Aggiungi preferiti\",\n  \"createdBy\": \"Da {0}\",\n  \"startIn\": \"inizio {0}\",\n  \"endIn\": \"fine {0}\",\n  \"proposalTimeLeft\": \"{0} left\",\n  \"endedAgo\": \"ended {0}\",\n  \"proposalBy\": \"di {0}\",\n  \"endDate\": \"termina {0}\",\n  \"defaultSkin\": \"Tema predefinito\",\n  \"select\": \"Seleziona\",\n  \"language\": \"Lingua\",\n  \"agree\": \"Accetto\",\n  \"moderators\": \"Moderators\",\n  \"playground\": \"Playground\",\n  \"strategyParams\": \"Parametri strategia\",\n  \"addresses\": \"Indirizzi\",\n  \"networkErrorPlayground\": \"Errore di rete: aprire la console del browser per ulteriori informazioni\",\n  \"upload\": \"Caricare\",\n  \"join\": \"Entra\",\n  \"joined\": \"Iscritto\",\n  \"leave\": \"Abbandona\",\n  \"subspaces\": \"Sub-spaces\",\n  \"mainspace\": \"Main space\",\n  \"copyLink\": \"Copia link\",\n  \"duplicate\": \"Duplicate\",\n  \"joinedSpaces\": \"Iscritto a uno spazio\",\n  \"joinSpaces\": \"Iscritto a uno spazio\",\n  \"setDelegationToSpace\": \"Limit delegation to a specific space\",\n  \"theCurrentNetwork\": \"the current network\",\n  \"optional\": \"(optional)\",\n  \"homeLoadmore\": \"Load more\",\n  \"confirmAction\": \"Confirm action\",\n  \"or\": \"or\",\n  \"share\": \"Share\",\n  \"shareOnTwitter\": \"Share on Twitter\",\n  \"shareOnLenster\": \"Share on Lenster\",\n  \"createButton\": \"Create\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Change wallet\",\n  \"createASpace\": \"Create a space\",\n  \"getStarted\": \"Get started\",\n  \"skip\": \"Skip\",\n  \"newSpaceNotice\": {\n    \"header\": \"Your space is live!\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Learn more in the {documentation} or join Snapshot {discord} for help.\",\n    \"gotIt\": \"Got it!\"\n  },\n  \"errors\": {\n    \"required\": \"Campo necessario\",\n    \"minLength\": \"Field is required\",\n    \"maxLength\": \"La lunghezza massima è {0}\",\n    \"pattern\": \"Carattere non valido\",\n    \"minItems\": \"Minimo {0} elementi richiesti\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"minStrategy\": \"È necessaria almeno una strategia.\",\n    \"website\": \"URL should be in the format https://www.example.com\",\n    \"format\": \"Formato non valido\",\n    \"type\": \"Tipologia non valida\",\n    \"unsupportedImageType\": \"File type not supported, Supported formats are jpeg, jpg and png\",\n    \"invalidAddress\": \"Invalid address\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Title\",\n    \"discussion\": \"Discussion (optional)\",\n    \"categorie(s)\": \"Select up to 2 categorie(s)\",\n    \"proposalDescription\": \"Description (optional)\",\n    \"preview\": \"Anteprima\",\n    \"choices\": \"Scelte\",\n    \"addChoice\": \"Aggiungi scelta\",\n    \"startDate\": \"Seleziona una data di inizio\",\n    \"endDate\": \"Selezionare la data di fine\",\n    \"startTime\": \"Selezionare un orario di inizio\",\n    \"endTime\": \"Selezionare un orario di conclusione\",\n    \"publish\": \"Pubblica\",\n    \"untitled\": \"Untitled\",\n    \"snapshotBlock\": \"Numero blocco istantanea\",\n    \"voting\": \"Voting\",\n    \"votingSystem\": \"Voting system\",\n    \"choice\": \"Choice {0}\",\n    \"period\": \"Voting period\",\n    \"start\": \"Start\",\n    \"end\": \"End\",\n    \"days\": \"Days\",\n    \"hours\": \"Hours\",\n    \"minutes\": \"Minutes\",\n    \"schedule\": \"Schedule proposal\",\n    \"delayEnforced\": \"The space enforces a delay until voting can start\",\n    \"periodEnforced\": \"The space enforces the duration of the voting period\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"Edit\",\n    \"continue\": \"Continue\",\n    \"now\": \"Now\",\n    \"votingPeriodExplainer\": \"This is the time period in which users will be able to vote. The proposal will be visible and pending before the start of the voting period.\",\n    \"uploadImageExplainer\": \"Attach images by dragging & dropping, selecting or pasting them.\",\n    \"uploading\": \"Uploading image\",\n    \"markdown\": \"Styling with Markdown is supported\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"Devi essere un membro dello spazio per inviare una proposta.\",\n        \"minScore\": \"Devi avere un minimo di {0} {1} per inviare una proposta.\"\n      },\n      \"customValidation\": \"È necessario superare la convalida della proposta per inviare una proposta.\",\n      \"executionError\": \"Verifying your eligibility to create proposals in this space has failed. This is likely due to a misconfigured strategy.\"\n    },\n    \"errorGettingSnapshot\": \"We encountered an error while fetching the snapshot block number which is needed to calculate your voting power. Please try again later.\"\n  },\n  \"delegate\": {\n    \"header\": \"Delega\",\n    \"selectDelegate\": \"Seleziona una delega\",\n    \"to\": \"To\",\n    \"addressPlaceholder\": \"Indirizzo o nome ENS\",\n    \"delegations\": \"Le tue deleghe\",\n    \"allSpaces\": \"Per tutti gli spazi\",\n    \"delegated\": \"Delegate a te\",\n    \"pendingTransaction\": \"nessuna transazione in sospeso |1 transazione in attesa  {count} transazioni in attesa\",\n    \"topDelegates\": \"Migliori delegati\",\n    \"noDelegatesFoundFor\": \"No delegates found for {0}\",\n    \"noValidEns\": \"Not a valid ENS address.\",\n    \"noValidAddress\": \"Not a valid address\",\n    \"delegateToSelf\": \"You cannot delegate to yourself\",\n    \"delegateToSelfAddress\": \"You cannot delegate to your own ENS address\",\n    \"noValidSpaceId\": \"Not a valid space ID\",\n    \"noDelegationsAndDelegates\": \"Can't find your delegations and delegates? Make sure you are connected to the correct network.\",\n    \"delegateNotSupported\": \"Delegation is currently not supported for {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Esprimi il tuo voto\",\n    \"vote\": \"Vota\",\n    \"startDate\": \"Data di inizio\",\n    \"endDate\": \"Data di fine\",\n    \"votingSystem\": \"Meccanismo di voto:\",\n    \"privacy\": \"Privacy\",\n    \"invalidChoice\": \"Invalid choice\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Your vote is in!\",\n      \"gnosisSafeTitle\": \"Your vote is pending...\",\n      \"gnosisSafeDescription\": \" Votes with a Safe require additional signers and will be visible once the transaction is confirmed\",\n      \"seeQueue\": \"See queued transactions\",\n      \"tips\": {\n        \"1\": \"Votes can be changed while the proposal is active\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"Proposte\",\n    \"new\": \"Nuova proposta\",\n    \"noProposals\": \"Non ci sono ancora proposte qui!\",\n    \"createProposal\": \"Crea proposta\",\n    \"showMore\": \"Show more\",\n    \"showLess\": \"Show less\",\n    \"states\": {\n      \"all\": \"Tutte\",\n      \"core\": \"Core\",\n      \"community\": \"Community\",\n      \"active\": \"Attive\",\n      \"pending\": \"In sospeso\",\n      \"closed\": \"Chiuse\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Notifications\",\n    \"noNotifications\": \"You have no notifications\",\n    \"proposalStarted\": \"proposal has started:\",\n    \"proposalEnded\": \"proposal has ended:\",\n    \"markAllAsRead\": \"Mark all as read\",\n    \"all\": \"All\",\n    \"unread\": \"Unread\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"join\",\n    \"actionCreate\": \"create a proposal in\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"Impostazioni\",\n    \"editController\": \"Edit controller\",\n    \"connectWithSpaceOwner\": \"You are in view only mode, to modify space settings connect with a controller or admin wallet.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"edit the space settings\",\n      \"create\": \"create a proposal\",\n      \"vote\": \"vote on this proposal\"\n    },\n    \"currentSpaceControllerIs\": \"The current space controller is {address}\",\n    \"newController\": \"New controller\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Set\",\n    \"profile\": \"Profilo\",\n    \"avatar\": \"Avatar\",\n    \"name\": {\n      \"label\": \"Name\",\n      \"placeholder\": \"e.g. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"About\",\n      \"placeholder\": \"What is your organisation about?\"\n    },\n    \"categories\": {\n      \"label\": \"Categorie(s)\",\n      \"select\": \"Select categorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Terms of service\",\n      \"information\": \"Users will be required to accept these terms once before they can create a proposal or cast a vote\"\n    },\n    \"hideSpace\": \"Nascondi lo spazio dalla pagina iniziale\",\n    \"links\": \"Social accounts\",\n    \"subspaces\": {\n      \"label\": \"Sub-spaces\",\n      \"information\": \"Sub-spaces will only be shown once configured on both the main space and the sub-space(s). {docs}\",\n      \"parent\": {\n        \"label\": \"Main space\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"The space that this space is a sub-space of will be displayed on the space page\"\n      },\n      \"children\": {\n        \"label\": \"Sub-spaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Related Sub-spaces listed here will be displayed on the space page\"\n      }\n    },\n    \"website\": \"Website\",\n    \"strategies\": {\n      \"label\": \"Strategie(s)\",\n      \"information\": \"Strategies are used to determine voting power or whether a user is eligible to create a proposal\"\n    },\n    \"network\": {\n      \"label\": \"Network\",\n      \"information\": \"The defaul network used for this space. Networks can also be specified in individual strategies\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbol\",\n      \"information\": \"The default symbol used for this space, usually the token symbol i.e. BAL for Balancer\"\n    },\n    \"strategiesList\": \"Select up to 8 strategies\",\n    \"votingPowerIsCumulative\": \"Voting power is cumulative\",\n    \"addStrategy\": \"Aggiungi strategia\",\n    \"testInPlayground\": \"Test in playground\",\n    \"admins\": {\n      \"label\": \"Admins\",\n      \"information\": \"Admins are able to modify the space settings and manage the space's proposals\"\n    },\n    \"authors\": {\n      \"label\": \"Authors\",\n      \"information\": \"Authors are always able to create proposals\"\n    },\n    \"proposalValidation\": \"Validazione proposta\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Threshold\",\n      \"information\": \"The minimum amount of voting power required to create a proposal\"\n    },\n    \"allowOnlyAuthors\": \"Permetti solo ai membri di presentare una proposta\",\n    \"editValidation\": \"Modifica validatore\",\n    \"selectValidation\": \"Seleziona validazione\",\n    \"validationParameters\": \"Parametri di validazione\",\n    \"voting\": \"Voti\",\n    \"votingDelay\": \"Ritardo delle votazioni\",\n    \"votingPeriod\": \"Periodo di votazione\",\n    \"hours\": \"Hours\",\n    \"days\": \"Days\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"The minimum amount of voting power required for the proposal to pass\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"The type of voting system used for this space. (Enforced on all future proposals)\"\n    },\n    \"anyType\": \"Qualunque\",\n    \"hideAbstain\": \"Ignore abstain votes in basic voting results\",\n    \"customDomain\": \"Dominio personalizzato\",\n    \"domain\": {\n      \"label\": \"Domain name\",\n      \"placeholder\": \"e.g. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"Tema\",\n    \"treasuries\": {\n      \"label\": \"Treasury\",\n      \"add\": \"Add treasury\",\n      \"edit\": \"Edit treasury\",\n      \"information\": \"Add treasuries of your organization to show them in your space\"\n    },\n    \"addPlugin\": \"Aggiungi plugin\",\n    \"editPlugin\": \"Modifica plugin\",\n    \"pluginParameters\": \"Parametri plugin\",\n    \"proposal\": {\n      \"title\": \"Proposal\",\n      \"guidelines\": {\n        \"title\": \"Guidelines\",\n        \"information\": \"Display a link to your guidelines on proposal creation to help users understand what constitutes a good/valid proposal\"\n      },\n      \"template\": {\n        \"title\": \"Template\",\n        \"information\": \"Start every proposal with a template to help users understand what information is required\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"e.g. yam.eth\",\n    \"chooseExistingEns\": \"Choose one of your existing ENS domains to create a space with:\",\n    \"useSingleExistingEns\": \"Use your existing ENS domain:\",\n    \"orRegisterNewEns\": \"Or register a new domain:\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"To create a space, you first need an ENS domain. Enter one below and follow the ENS registration instructions.\",\n    \"createASpace\": \"Crea uno spazio\",\n    \"registerEnsButton\": \"Register\",\n    \"supportedEnsTLDs\": \"Supported domain endings\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or join Snapshot {discord}.\",\n    \"setSpaceController\": \"Space controller\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \" The space controller is the account that will be able to manage the space settings. Additional space controllers (admins) can be added later.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"When creating a space with a Gnosis Safe it's recommended to set the safe address as the space controller. If you don't, you need to follow some additional steps. {link}\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"Setting the controller requires a transaction on the {network} which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choose ENS address\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. {address}\",\n    \"controllerAddress\": \"Controller address\",\n    \"updateController\": \"Update controller\",\n    \"seeOnEns\": \"See on ENS\",\n    \"goToSettings\": \"Go to settings\",\n    \"setSpaceProfile\": \"Customize your space\",\n    \"waitForTransaction\": \"The transaction need to confirm before you can create your space. {txUrl}\",\n    \"pleaseWaitMessage\": \"This can take a few minutes, please wait while the transaction is being confirmed\",\n    \"notControllerAddress\": \"Please connect with the controller address {wallet} to create the space.\",\n    \"fillCurrentAccount\": \"Use currently logged in account\",\n    \"domain\": {\n      \"title\": \"Setup your space domain\",\n      \"ensMessage\": \" One thing you need before you can create your own space, is an ENS domain on Ethereum mainnet.\",\n      \"ensMessageTestnet\": \"You can also {link} on the Goerli testnet and mess with things there first.\",\n      \"tryDemo\": \"try the demo\",\n      \"yourExistingSpaces\": \"Your existing spaces\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"How would you like to setup your voting strategy?\",\n      \"subtitle\": \"You can change your strategy settings any time.\",\n      \"blockTitle\": \"Setup voting strategy\",\n      \"onePersonOneVote\": {\n        \"title\": \"One person, one vote\",\n        \"description\": \"Manage a whitelist of people who can vote or simply allow any address to vote. Every vote is equal and no token is required\",\n        \"whitelistInformation\": \"Specify a number of accounts that can vote\",\n        \"ticketInformation\": \"Any account can vote\",\n        \"votesEqualInfo\": \"Each vote is equal and no token is required\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Token weighted voting\",\n        \"description\": \"Votes are weighted by a token. The token can be an ERC-20, ERC-721 or ERC-1155 token standard\",\n        \"tokenNotFound\": \"Token not found\",\n        \"seeOnEtherscan\": \"See on Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Custom setup\",\n        \"description\": \"Select up to 8 strategies with a wide range of options. If you can't find the right strategy for your use case, you can create your own\"\n      }\n    },\n    \"validationTitle\": \"Who can manage this space and create proposals?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Edit profile\",\n    \"viewProfile\": \"View profile\",\n    \"about\": {\n      \"header\": \"About\",\n      \"joinedSpaces\": \"Joined spaces\",\n      \"createdSpaces\": \"Created spaces\",\n      \"biography\": \"Bio\",\n      \"notJoinSpacesYet\": \"Hasn't joined any spaces yet\",\n      \"notCreatedSpacesYet\": \"Hasn't created any spaces yet\",\n      \"delegatorNetworkInfo\": \"Change by switching network in your wallet\",\n      \"delegate\": \"Delegate\",\n      \"delegated\": \"Delegated\",\n      \"delegateTo\": \"Delegate to\",\n      \"delegateFor\": \"Delegator for\",\n      \"noDelegatorsMessage\": \"No delegators on {network}\",\n      \"notSupportedNetwork\": \"Delegation currently isn't supported on {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activity\",\n      \"votedFor\": \"Voted {choice}\",\n      \"today\": \"Today\",\n      \"thisWeek\": \"This week\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"No activity yet\"\n    },\n    \"settings\": {\n      \"header\": \"Edit profile\",\n      \"name\": \"Name\",\n      \"biography\": \"Bio\",\n      \"namePlaceholder\": \"Enter name\",\n      \"bioPlaceholder\": \"Tell your story\",\n      \"change\": \"Change\",\n      \"remove\": \"Remove\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"Hai finito!\",\n    \"copied\": \"Copiato!\",\n    \"proposalDeleted\": \"Proposta eliminata\",\n    \"somethingWentWrong\": \"Oops, qualcosa è andato storto!\",\n    \"saved\": \"Salvato\",\n    \"delegationSuccess\": \"Delega riuscita\",\n    \"delegationRemoved\": \"Delega rimossa\",\n    \"proposalCreated\": \"Proposta creata\",\n    \"voteSuccessful\": \"Il tuo voto è dentro!\",\n    \"ensSet\": \"ENS text record was successfully set\",\n    \"transactionSent\": \"Transaction sent\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Creare strategia\",\n    \"createSkin\": \"Creare skin\",\n    \"addNetwork\": \"Aggiungi rete\",\n    \"createPlugin\": \"Crea Plugin\",\n    \"strategies\": \"strategie\",\n    \"skins\": \"skin(s)\",\n    \"networks\": \"rete(e)\",\n    \"plugins\": \"plugin(s)\",\n    \"results\": \"risultato/i\",\n    \"category\": \"Category\",\n    \"categories\": {\n      \"all\": \"All\",\n      \"protocol\": \"Protocol\",\n      \"social\": \"Social\",\n      \"investment\": \"Investment\",\n      \"grant\": \"Grant\",\n      \"service\": \"Service\",\n      \"media\": \"Media\",\n      \"creator\": \"Creator\",\n      \"collector\": \"Collector\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Seleziona sistema di voto\",\n    \"single-choice\": \"Voto a scelta unica\",\n    \"approval\": \"Voto di approvazione\",\n    \"quadratic\": \"Votazione quadratica\",\n    \"ranked-choice\": \"Voto di scelta classificato\",\n    \"weighted\": \"Votazioni ponderate\",\n    \"basic\": \"Votazione di base\",\n    \"description\": {\n      \"single-choice\": \"Ogni elettore può selezionare una sola opzione.\",\n      \"approval\": \"Ogni elettore può selezionare un numero qualsiasi di scelte.\",\n      \"quadratic\": \"Ogni elettore può distribuire il potere di voto su un numero qualsiasi di scelte. I risultati sono calcolati in termini quadratici.\",\n      \"ranked-choice\": \"Ogni elettore può selezionare e classificare un numero qualsiasi di scelte. I risultati sono calcolati con il metodo di conteggio del runoff istantaneo.\",\n      \"weighted\": \"Ogni elettore può distribuire il potere di voto su qualsiasi numero di scelte.\",\n      \"basic\": \"Voto a scelta singola con tre scelte: Per, Contro o Astenuto\\n\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Privacy\",\n    \"title\": \"Select voting privacy\",\n    \"information\": \"The type of privacy used on proposals. (Enforced on all future proposals)\",\n    \"any\": \"Any\",\n    \"none\": \"None\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Choices are encrypted and only visible once the voting period is over\",\n      \"tooltip\": \"This proposal has Shutter privacy enabled. All votes will be encrypted until the voting period has ended and the final score is calculated\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select voting validation\",\n    \"information\": \"The type of validation used to determine if a user can vote. (Enforced on all future proposals)\",\n    \"any\": {\n      \"label\": \"Anyone can vote\",\n      \"description\": \"Anyone with voting power can cast a vote.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement to vote on this proposal. \"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your proposals from spam and vote manipulation by requiring users to have a Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold} and {operator} of the following stamps to vote on this proposal: {stamps}. \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Risultato attuale\",\n    \"currentBond\": \"Titolo attuale\",\n    \"finalizedIn\": \"Finalizzata {0}\",\n    \"executableIn\": \"Eseguibile {0}\",\n    \"finalOutcome\": \"Esito\",\n    \"nextBond\": \"Legame da impostare\",\n    \"setOutcomeTo\": \"Imposta il risultato a\",\n    \"claimBond\": \"Rivendica obbligazione\",\n    \"addBatch\": \"Aggiungere una linea di transazione\",\n    \"batch\": \"Gruppo di transazioni\",\n    \"transactions\": \"Transazioni\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"Indirizzo non valido\",\n    \"invalidAmount\": \"Importo non valido\",\n    \"invalidValue\": \"Valore errato\",\n    \"invalidAbi\": \"Abi Non Valido\",\n    \"invalidData\": \"Dati non validi\",\n    \"value\": \"Valore (wei)\",\n    \"data\": \"Dati\",\n    \"noCollectibles\": \"Ancora nessuna raccolta\",\n    \"asset\": \"Risorsa\",\n    \"amount\": \"Importo\",\n    \"type\": \"Tipo\",\n    \"transferFunds\": \"Trasferisci fondi\",\n    \"transferNFT\": \"Trasferisci NFT\",\n    \"contractInteraction\": \"Interazione contrattuale\",\n    \"rawTransaction\": \"Transazione\",\n    \"addTransaction\": \"Aggiungi transazione\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei a {address}\",\n      \"transferFunds\": \"Trasferisci {amount} {tokenSymbol} a {address}\",\n      \"transferNFT\": \"Invia {name} #{id} a {address}\",\n      \"raw\": \"Invia {name} #{id} a {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Richiesta esecuzione\",\n      \"setOutcome\": \"Imposta il risultato a\",\n      \"changeOutcome\": \"Cambiare risultato\",\n      \"executeTxs\": \"Esegui il batch di transazione {0} di {1}\",\n      \"executed\": \"Tutti gli ordini sono stati eseguiti\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"Qualcosa è andato storto\",\n      \"connectWallet\": \"Connetti il portafoglio per vedere i dettagli dell'esecuzione\",\n      \"switchChain\": \"Passa il tuo portafoglio a {0} per richiedere l'esecuzione\",\n      \"question\": \"Questa proposta è stata approvata e soddisfa i\",\n      \"criteria\": \"criteri di accettazione?\",\n      \"proposalPassed\": \"Did the proposal pass?\",\n      \"expired\": \"The proposal has expired\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to confirm the proposal passed\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"requestToolTip\": \"This will propose the transactions from this Snapshot vote on-chain. After a challenge window, if the proposal is valid, the transactions can be executed and your bond will be returned.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"Non è stato ancora stabilito un POAP per questa proposta: '(\",\n    \"no_voted_header\": \"Vota per ottenere questo POAP\",\n    \"unclaimed_header\": \"Mint your I voted POAP\",\n    \"claimed_header\": \"Congratulations! The POAP has been minted to your collection\",\n    \"loading_header\": \"The POAP is being minted to your collection\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"Sfoglia la nostra collezione\",\n    \"success_claim\": \"The POAP has been minted to your collection\",\n    \"error_claim\": \"Ci sono stati errori durante la votazione del commento.\"\n  },\n  \"progress\": {\n    \"progress\": \"Progress\",\n    \"inProgress\": \"In Progress\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"New Step\",\n    \"description\": \"Description\",\n    \"add\": \"Add\",\n    \"deleteStep\": \"Delete Step\",\n    \"deleteConfirm\": \"Are you sure you want to delete?\",\n    \"delete\": \"Delete\",\n    \"cancel\": \"Cancel\",\n    \"comeBack\": \"Come back after voting is complete to see how this proposal is progressing!\",\n    \"confirmSignature\": \"Signing this message will allow us to authorize your request to update the progress of your proposal.\",\n    \"wentWrong\": \"Oops something went wrong.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"Charts\",\n    \"noVotesYet\": \"There are no votes to visualize yet.\",\n    \"totalVotesPerDay\": \"Total votes per day\",\n    \"shareOfVotingPower\": \"Share of voting power\",\n    \"votingPowerPerDay\": \"Voting power per day\"\n  },\n  \"comment_box\": {\n    \"title\": \"Casella dei commenti\",\n    \"add\": \"Aggiungi i tuoi commenti qui\",\n    \"submit\": \"Invia\",\n    \"preview\": \"Anteprima\",\n    \"continue_editing\": \"Continua le modifiche\",\n    \"edit\": \"Scrivi la tua risposta qui\",\n    \"edit_button\": \"Modifica\",\n    \"dismiss\": \"Respingi\",\n    \"delete\": \"Cancella\",\n    \"add_reply\": \"Scrivi la tua risposta qui\",\n    \"edit_comment\": \"Modifica Commento\",\n    \"edit_modal\": \"Sei sicuro di voler uscire?\",\n    \"yes\": \"Si\",\n    \"no\": \"no\",\n    \"delete_comment\": \"Cancella commento.\",\n    \"delete_modal\": \"Sei sicuro di volerla eliminare?\",\n    \"error\": \"Oops, qualcosa è andato storto\",\n    \"replies\": \"Risposte\",\n    \"hide\": \"Nascondi\",\n    \"show\": \"Mostra\",\n    \"reply\": \"Rispondi\",\n    \"load_more\": \"Visualizza altri\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Create a space\",\n      \"timeline\": \"Timeline\",\n      \"notifications\": \"Notifications\",\n      \"explore\": \"Explore\",\n      \"playground\": \"Playground\",\n      \"space\": {\n        \"create\": \"Create {space} proposal\",\n        \"about\": \"About {space}\",\n        \"proposals\": \"{space} Proposals\",\n        \"proposal\": \"{space} proposal: {proposal}\",\n        \"settings\": \"{space} Settings\"\n      },\n      \"strategy\": \"{key} strategy\",\n      \"delegate\": \"Delegate\",\n      \"ranking\": \"Ranking\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Track proposals for {spaceName}\",\n    \"text\": \"Receive notifications every time a new proposal is created or ends\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 second | {n} seconds\",\n    \"minute\": \"1 minute | {n} minutes\",\n    \"hour\": \"1 hour | {n} hours\",\n    \"day\": \"1 day | {n} days\",\n    \"week\": \"1 week | {n} weeks\",\n    \"month\": \"1 month | {n} months\",\n    \"year\": \"1 year | {n} years\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Unsupported network\",\n    \"switchNetworkToNetwork\": \"To continue, you need to change the network in your wallet to {network}.\",\n    \"switchToNetwork\": \"Switch to {network}\",\n    \"goToDemoSite\": \"Go to demo website\"\n  },\n  \"treasury\": {\n    \"title\": \"Treasury\",\n    \"wallets\": {\n      \"title\": \"Wallets\",\n      \"empty\": \"This space doesn't have a treasury yet\",\n      \"addTreasury\": \"Add a treasury\"\n    },\n    \"assets\": {\n      \"title\": \"Assets\",\n      \"empty\": \"There are no assets in this contract\"\n    },\n    \"24hChange\": \"24h change\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Your email\",\n    \"title\": \"Get the latest Snapshot updates\"\n  },\n  \"joinCommunity\": \"Join Snapshot community\",\n  \"header\": {\n    \"title\": \"Where decisions get made\",\n    \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot is a decentralized governance platform that makes it easy to create and vote on proposals - all without spending a fortune on gas fees! Plus, our flexible system supports various voting types and strategies, so you can tailor the voting process to your needs.\",\n    \"subHeader\": \"Governance should be a snap\",\n    \"subDescription\": \"Web3 governance doesn't have to be complicated. Snapshot is the perfect solution for organizations looking for an easy and efficient way to govern their community or organization.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Resources\",\n    \"about\": \"About\",\n    \"blog\": \"Blog\",\n    \"jobs\": \"Jobs\",\n    \"discussions\": \"Discussions\",\n    \"github\": \"GitHub\",\n    \"docs\": \"Docs\",\n    \"support\": \"Support\",\n    \"hiring\": \"Join us!\"\n  }\n}\n"
  },
  {
    "path": "src/locales/ja-JP.json",
    "content": "{\n  \"searchPlaceholder\": \"検索\",\n  \"spaceCount\": \"{0} スペース\",\n  \"createSpace\": \"スペースを作成する\",\n  \"backToHome\": \"ホーム\",\n  \"actions\": \"アクション\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"投票結果\",\n  \"resultsError\": \"結果を計算できませんでした。戦略が誤って設定されていたり、または戦略に対してRPCノードが無反応であることが原因かもしれません。\",\n  \"resultsCalculating\": \"最終結果を計算中です。数分後にもこのメッセージが表示される場合は、スペースの管理者に連絡してください。\",\n  \"votingPowerFailedMessage\": \"投票力を計算できませんでした。これは、誤って設定された戦略や、戦略に関与しているRPCノードが応答しないことが原因の場合があります。\",\n  \"votingValidationFailedMessage\": \"当社側でエラーが発生し、あなたが投票に資格があるかどうかを検証できませんでした。これは、投票検証が誤って設定されたか、検証に関与しているAPIが応答しないことが原因の場合があります。\",\n  \"notValidVoterMessage\": \"おっと、あなたはこの提案に投票する資格がないようです。\",\n  \"getHelp\": \"ヘルプ\",\n  \"retry\": \"リトライ\",\n  \"currentResults\": \"現在の投票状況\",\n  \"reset\": \"リセット\",\n  \"close\": \"閉じる\",\n  \"save\": \"保存\",\n  \"author\": \"作成者\",\n  \"next\": \"次へ\",\n  \"choice\": \"選択\",\n  \"submit\": \"提出する\",\n  \"plugins\": \"プラグイン\",\n  \"information\": \"情報\",\n  \"confirm\": \"確定する\",\n  \"snapshot\": \"Snapshot\",\n  \"strategies\": \"ストラテジー\",\n  \"strategiesPage\": \"ストラテジー\",\n  \"space\": \"スペース\",\n  \"spaces\": \"スペース\",\n  \"verifiedSpace\": \"認証済みのスペース\",\n  \"warningSpace\": \"このスペースは潜在的な悪意があるとフラグが立てられています。注意して進めてください。\",\n  \"version\": \"バージョン\",\n  \"timeline\": \"タイムライン\",\n  \"ended\": \"が終了しました。\",\n  \"started\": \"が始まりました。\",\n  \"filters\": \"フィルター\",\n  \"allSpaces\": \"全てのスペース\",\n  \"submitOnchain\": \"パブリックチェーンへ送信する\",\n  \"inSpaces\": \"{0} 個のスペースが使用\",\n  \"votes\": \"投票数\",\n  \"seeMore\": \"もっと見る\",\n  \"seeAll\": \"すべて見る\",\n  \"network\": \"ネットワーク\",\n  \"networks\": \"ネットワーク\",\n  \"skins\": \"スキン\",\n  \"spaceMembers\": \"メンバー\",\n  \"members\": \"メンバーなし | {count} メンバー | {count} メンバー\",\n  \"editStrategy\": \"ストラテジーを編集\",\n  \"invalidProposals\": \"無効な提案\",\n  \"account\": \"アカウント\",\n  \"create3box\": \"3Box上にプロフィールを作成する\",\n  \"view3box\": \"3Box上のプロフィールを見る\",\n  \"edit3box\": \"3Box上のプロフィールを編集する\",\n  \"connectWallet\": \"ウォレットを接続する\",\n  \"toggleSkin\": \"スキンの切り替え\",\n  \"about\": \"Snapshotについて\",\n  \"license\": \"ライセンス\",\n  \"showMore\": \"もっと表示する\",\n  \"voted\": \"投票済み\",\n  \"reload\": \"リロード\",\n  \"ipfsServer\": \"IPFSサーバ\",\n  \"hub\": \"ハブ\",\n  \"cancel\": \"キャンセル\",\n  \"delete\": \"削除\",\n  \"demoSite\": \"これはデモサイトです。気軽にお試しください！\",\n  \"removeDelegation\": \"投票権の委任を削除する\",\n  \"confirmRemove\": \"以下のアドレスへの投票権の委任を削除してもよろしいですか？\",\n  \"removeSpace\": \"{0} スペースへの委任を削除しますか？\",\n  \"noVotingPower\": \"ブロック{blockNumber}には投票権がありません。\",\n  \"quorumReached\": \"クオーラムが達成されました\",\n  \"options\": \"オプション\",\n  \"votingPower\": \"あなたの投票力\",\n  \"comment\": {\n    \"placeholder\": \"理由を共有する（任意）\"\n  },\n  \"receipt\": \"レシート\",\n  \"relayer\": \"リレーヤー\",\n  \"verifyOnMycrypto\": \"MyCryptoでレシートを検証する\",\n  \"verifyOnSignatorio\": \"Signator.io で検証する\",\n  \"isCore\": \"コア\",\n  \"notificationsBlocked\": \"お使いのブラウザは通知を無効にしています\",\n  \"notificationsNotSupported\": \"お使いのブラウザは通知をサポートしていません\",\n  \"walletNotSupported\": \"ウォレットがサポートされていません\",\n  \"seeInExplorer\": \"エクスプローラーを参照する\",\n  \"learnMore\": \"もっと詳しく知る\",\n  \"logout\": \"ログアウト\",\n  \"continue\": \"続行する\",\n  \"add\": \"追加\",\n  \"edit\": \"編集\",\n  \"strategyParameters\": \"ストラテジーのパラメータ\",\n  \"addAction\": \"アクションを追加する\",\n  \"removeAction\": \"アクションを削除する\",\n  \"yourChoice\": \"選択肢 {0}\",\n  \"targetAddress\": \"ターゲットアドレス\",\n  \"value\": \"値\",\n  \"date\": \"日付\",\n  \"marketDetails\": \"マーケット詳細\",\n  \"addMarket\": \"マーケットを追加する\",\n  \"selectNetwork\": \"ネットワークを選択する\",\n  \"conditionId\": \"コンディションのID\",\n  \"basetokenAddress\": \"ベーストークンのアドレス\",\n  \"quoteAddress\": \"Quote currency address\",\n  \"removeMarket\": \"マーケットを削除する\",\n  \"back\": \"戻る\",\n  \"loading\": \"読み込み中...\",\n  \"predictedImpact\": \"予想される影響\",\n  \"marketSymbol\": \"{0} market\",\n  \"twoChoicesRequired\": \"このプラグインは２つ選択肢が必要です。\",\n  \"noResultsFound\": \"おっと、何もみつかりませんでした。\",\n  \"createFirstProposal\": \"最初の提案を作成しましょう\",\n  \"noSpacesJoined\": \"おっと、まだスペースに参加していません\",\n  \"addFavorites\": \"お気に入りに追加\",\n  \"createdBy\": \"{0} が作成\",\n  \"startIn\": \"{0} 後に開始\",\n  \"endIn\": \"{0} 後に終了\",\n  \"proposalTimeLeft\": \"残り{0}\",\n  \"endedAgo\": \"{0} に終了しました\",\n  \"proposalBy\": \"{0} が作成\",\n  \"endDate\": \"end {0}\",\n  \"defaultSkin\": \"デフォルトのスキン\",\n  \"select\": \"選択する\",\n  \"language\": \"言語\",\n  \"agree\": \"同意する\",\n  \"moderators\": \"モデレーター\",\n  \"playground\": \"プレイグラウンド\",\n  \"strategyParams\": \"ストラテジーのパラメータ\",\n  \"addresses\": \"アドレス\",\n  \"networkErrorPlayground\": \"ネットワークエラーが発生しました。 詳細については、ブラウザのコンソールを開いてください。\",\n  \"upload\": \"アップロード\",\n  \"join\": \"参加する\",\n  \"joined\": \"参加中\",\n  \"leave\": \"退出する\",\n  \"subspaces\": \"サブスペース\",\n  \"mainspace\": \"メインスペース\",\n  \"copyLink\": \"リンクをコピーする\",\n  \"duplicate\": \"複製する\",\n  \"joinedSpaces\": \"参加中のスペース\",\n  \"joinSpaces\": \"スペースに参加する\",\n  \"setDelegationToSpace\": \"委任を特定のスペースに限定する\",\n  \"theCurrentNetwork\": \"現在のネットワーク\",\n  \"optional\": \"（オプショナル）\",\n  \"homeLoadmore\": \"さらに読み込む\",\n  \"confirmAction\": \"アクションを確定する\",\n  \"or\": \"または\",\n  \"share\": \"共有する\",\n  \"shareOnTwitter\": \"Twitterで共有する\",\n  \"shareOnLenster\": \"Lensterで共有する\",\n  \"createButton\": \"作成する\",\n  \"discussion\": \"ディスカッション\",\n  \"changeWallet\": \"ウォレットを変更する\",\n  \"createASpace\": \"スペースを作成する\",\n  \"getStarted\": \"始める\",\n  \"skip\": \"スキップ\",\n  \"newSpaceNotice\": {\n    \"header\": \"スペースが 進行中です！\",\n    \"mainText\": \"あなたは、{settings}内のストラテジーを使用して投票力の計算方法を変更できます。設定の変更は、新しい提案にのみ影響します。既存の提案は変更できません。\",\n    \"learnMore\": \"より詳細については{documentation} をご覧ください。またはSnapshotの {discord} に参加してください。\",\n    \"gotIt\": \"了解\"\n  },\n  \"errors\": {\n    \"required\": \"フィールドが必要です\",\n    \"minLength\": \"最小文字数は {0} です\",\n    \"maxLength\": \"最大文字数は {0} です\",\n    \"pattern\": \"無効な文字\",\n    \"minItems\": \"最低 {0} 個のアイテムが必要\",\n    \"maxItems\": \"最大 {0} 個のアイテムが許可されます\",\n    \"minStrategy\": \"少なくとも１つストラテジーが必要です\",\n    \"website\": \"URLはhttps://www.example.com の形式でなければなりません\",\n    \"format\": \"無効な形式\",\n    \"type\": \"無効なタイプ\",\n    \"unsupportedImageType\": \"このファイル形式はサポートされていません。サポートされているファイル形式はjpeg、jpg、そしてpngです。\",\n    \"invalidAddress\": \"無効なアドレス\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"タイトル\",\n    \"discussion\": \"ディスカッション（任意）\",\n    \"categorie(s)\": \"最大2つのカテゴリを選択してください\",\n    \"proposalDescription\": \"概要 (任意)\",\n    \"preview\": \"プレビュー\",\n    \"choices\": \"選択肢\",\n    \"addChoice\": \"選択肢を追加する\",\n    \"startDate\": \"開始日を選択する\",\n    \"endDate\": \"終了日を選択する\",\n    \"startTime\": \"開始時刻を選択する\",\n    \"endTime\": \"終了時刻を選択する\",\n    \"publish\": \"公開する\",\n    \"untitled\": \"タイトル未設定\",\n    \"snapshotBlock\": \"Snapshotのブロック番号\",\n    \"voting\": \"投票\",\n    \"votingSystem\": \"投票システム\",\n    \"choice\": \"選択肢 {0}\",\n    \"period\": \"投票期間\",\n    \"start\": \"開始\",\n    \"end\": \"終了\",\n    \"days\": \"日間\",\n    \"hours\": \"時間\",\n    \"minutes\": \"分間\",\n    \"schedule\": \"提案を予定する\",\n    \"delayEnforced\": \"スペースは投票が開始されるまで遅延を強制する\",\n    \"periodEnforced\": \"スペースは投票期間を強制します\",\n    \"typeEnforced\": \"{type} はスペースによって強制されます\",\n    \"privacyEnforced\": \"{type} はスペースによって強制されます\",\n    \"edit\": \"編集\",\n    \"continue\": \"続行する\",\n    \"now\": \"今すぐ\",\n    \"votingPeriodExplainer\": \"これはユーザーが投票できる期間です。提案は投票期間が始まる前に表示され、投票が開始されるまで保留状態となります。\",\n    \"uploadImageExplainer\": \"ドラッグ＆ドロップ、選択、または貼り付けで画像を添付します。\",\n    \"uploading\": \"画像をアップロード中\",\n    \"markdown\": \"マークダウン形式がサポートされています\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"提案を提出するには、このスペースの作成者である必要があります。\",\n        \"minScore\": \"提案を提出するためには、少なくとも {0} {1} を保有する必要があります。\"\n      },\n      \"customValidation\": \"提案を提出するには、提案検証をクリアする必要があります。\",\n      \"executionError\": \"このスペースで提案を作成する資格を確認することができませんでした。これはおそらくストラテジーの誤設定によるものです。\"\n    },\n    \"errorGettingSnapshot\": \"投票権を計算するために必要なスナップショットブロック番号を取得する際にエラーが発生しました。後でもう一度お試しください。\"\n  },\n  \"delegate\": {\n    \"header\": \"委任\",\n    \"selectDelegate\": \"委任先を選択する\",\n    \"to\": \"宛先\",\n    \"addressPlaceholder\": \"アドレスまたはENSの名前\",\n    \"delegations\": \"あなたの委任者\",\n    \"allSpaces\": \"すべてのスペース用\",\n    \"delegated\": \"あなたへの委任\",\n    \"pendingTransaction\": \"保留中の取引がありません | 保留中の取引が1件あります | {count} 件の保留中の取引があります\",\n    \"topDelegates\": \"トップの委任先\",\n    \"noDelegatesFoundFor\": \"{0} の委任が見つかりません。\",\n    \"noValidEns\": \"ENSのアドレスが有効ではありません。\",\n    \"noValidAddress\": \"アドレスが有効ではありません\",\n    \"delegateToSelf\": \"自分自身に委任することはできません\",\n    \"delegateToSelfAddress\": \"自分のENSアドレスに委任することはできません\",\n    \"noValidSpaceId\": \"スペースIDが有効ではありません\",\n    \"noDelegationsAndDelegates\": \"委任と委任代理人が見つかりませんか？正しいネットワークに接続しているか確認してください。\",\n    \"delegateNotSupported\": \"現在、委任は{network} でサポートされていません。\"\n  },\n  \"proposal\": {\n    \"castVote\": \"投票する\",\n    \"vote\": \"投票する\",\n    \"startDate\": \"開始日\",\n    \"endDate\": \"終了日\",\n    \"votingSystem\": \"投票のシステム\",\n    \"privacy\": \"プライバシー\",\n    \"invalidChoice\": \"無効な選択\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"あなたの投票が完了しました！\",\n      \"gnosisSafeTitle\": \"あなたの投票は保留中です...\",\n      \"gnosisSafeDescription\": \" Safeを使用した投票には追加の署名者が必要で、取引が確認された後に表示されます\",\n      \"seeQueue\": \"キューに入っている取引を見る\",\n      \"tips\": {\n        \"1\": \"提案がアクティブの間は投票を変更することができます\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"提案\",\n    \"new\": \"新しい提案\",\n    \"noProposals\": \"まだ提案はありません\",\n    \"createProposal\": \"提案を作成する\",\n    \"showMore\": \"もっと見る\",\n    \"showLess\": \"表示を減らす\",\n    \"states\": {\n      \"all\": \"すべて\",\n      \"core\": \"コア\",\n      \"community\": \"コミュニティ\",\n      \"active\": \"アクティブ\",\n      \"pending\": \"保留中\",\n      \"closed\": \"終了\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"通知\",\n    \"noNotifications\": \"通知がありません。\",\n    \"proposalStarted\": \"提案が開始されました：\",\n    \"proposalEnded\": \"提案が終了しました：\",\n    \"markAllAsRead\": \"すべて既読にする\",\n    \"all\": \"すべて\",\n    \"unread\": \"未読\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"{action} このスペースをするには、{spaceName} 利用規約に同意する必要があります。\",\n    \"actionJoin\": \"参加する\",\n    \"actionCreate\": \"提案を作成する\",\n    \"actionVote\": \"投票する\"\n  },\n  \"settings\": {\n    \"header\": \"設定\",\n    \"editController\": \"コントローラの編集\",\n    \"connectWithSpaceOwner\": \"閲覧専用モードです。スペースの設定を変更するには、コントローラまたは管理者ウォレットと接続してください。\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Gnosis Safeが間違ったネットワーク上にあります。 {action} するには、{network} に接続してください。\",\n      \"settings\": \"スペースの設定を編集する\",\n      \"create\": \"提案を作成する\",\n      \"vote\": \"この提案に投票する\"\n    },\n    \"currentSpaceControllerIs\": \"現在のスペースコントローラは{address} です。\",\n    \"newController\": \"新しいコントローラ\",\n    \"noRecord\": \"{id} ドメインを {network} に登録していることを確認してください。その後、コントローラテキストレコードを編集して、スペース設定へのアクセス権を回復してください。\",\n    \"set\": \"設定\",\n    \"profile\": \"プロフィール\",\n    \"avatar\": \"アバター\",\n    \"name\": {\n      \"label\": \"名前\",\n      \"placeholder\": \"例：Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"概要\",\n      \"placeholder\": \"あなたの組織について何ですか？\"\n    },\n    \"categories\": {\n      \"label\": \"カテゴリー\",\n      \"select\": \"カテゴリーを選択\"\n    },\n    \"terms\": {\n      \"label\": \"利用規約\",\n      \"information\": \"ユーザーは、提案を作成したり投票したりする前に、これらの利用規約に1度だけ同意する必要があります\"\n    },\n    \"hideSpace\": \"ホームページからスペースを隠す\",\n    \"links\": \"ソーシャルアカウント\",\n    \"subspaces\": {\n      \"label\": \"サブスペース\",\n      \"information\": \"サブスペースは、メインスペースとサブスペースの両方で設定された場合にのみ表示されます。 {docs}\",\n      \"parent\": {\n        \"label\": \"メインスペース\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"このスペースがサブスペースであるメインスペースがスペースページに表示されます\"\n      },\n      \"children\": {\n        \"label\": \"サブスペース\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"ここにリストされた関連サブスペースは、スペースページに表示されます\"\n      }\n    },\n    \"website\": \"ウェブサイト\",\n    \"strategies\": {\n      \"label\": \"戦略（複数可）\",\n      \"information\": \"戦略は、投票権またはユーザーが提案を作成する資格があるかどうかを決定するために使用されます\"\n    },\n    \"network\": {\n      \"label\": \"ネットワーク\",\n      \"information\": \"このスペースに使用されるデフォルトのネットワーク。 ネットワークは個々の戦略でも指定できます\"\n    },\n    \"symbol\": {\n      \"label\": \"シンボル\",\n      \"information\": \"このスペースに使用されるデフォルトのシンボル、通常はトークンシンボルであるBAL（Balancer）など\"\n    },\n    \"strategiesList\": \"最大8つの戦略を選択してください\",\n    \"votingPowerIsCumulative\": \"投票権は累積的です\",\n    \"addStrategy\": \"ストラテジーを追加\",\n    \"testInPlayground\": \"プレイグラウンドでテスト\",\n    \"admins\": {\n      \"label\": \"管理者\",\n      \"information\": \"管理者はスペースの設定を変更し、スペースの提案を管理できます\"\n    },\n    \"authors\": {\n      \"label\": \"作者\",\n      \"information\": \"作者は常に提案を作成できます\"\n    },\n    \"proposalValidation\": \"提案の検証\",\n    \"validation\": \"タイプ\",\n    \"proposalThreshold\": {\n      \"label\": \"閾値\",\n      \"information\": \"提案を作成するために必要な最小の投票権\"\n    },\n    \"allowOnlyAuthors\": \"作成者のみが提案を提出できます\",\n    \"editValidation\": \"検証を編集\",\n    \"selectValidation\": \"検証を選択\",\n    \"validationParameters\": \"検証パラメータ\",\n    \"voting\": \"投票\",\n    \"votingDelay\": \"投票の遅延\",\n    \"votingPeriod\": \"投票期間\",\n    \"hours\": \"時間\",\n    \"days\": \"日\",\n    \"quorum\": {\n      \"label\": \"クオーラム\",\n      \"information\": \"提案が承認されるために必要な最小の投票権\"\n    },\n    \"type\": {\n      \"label\": \"タイプ\",\n      \"information\": \"このスペースで使用される投票システムのタイプ（将来の提案に強制されます）\"\n    },\n    \"anyType\": \"すべて\",\n    \"hideAbstain\": \"基本的な投票結果において棄権投票を無視する\",\n    \"customDomain\": \"カスタムドメイン\",\n    \"domain\": {\n      \"label\": \"ドメイン名\",\n      \"placeholder\": \"例：vote.balancer.fi\",\n      \"info\": \"カスタムドメインを設定するには、スペースを作成した後にGitHub上でプルリクエストを開く必要があります。 {docs}\"\n    },\n    \"skin\": \"スキン\",\n    \"treasuries\": {\n      \"label\": \"財務省\",\n      \"add\": \"財務省を追加\",\n      \"edit\": \"財務省を編集\",\n      \"information\": \"組織の財務省を追加して、スペース内に表示します\"\n    },\n    \"addPlugin\": \"プラグインを追加\",\n    \"editPlugin\": \"プラグインを編集\",\n    \"pluginParameters\": \"プラグインパラメータ\",\n    \"proposal\": {\n      \"title\": \"提案\",\n      \"guidelines\": {\n        \"title\": \"ガイドライン\",\n        \"information\": \"提案作成のガイドラインへのリンクを表示して、ユーザーが良好/有効な提案を構成するかどうかを理解するのに役立ちます\"\n      },\n      \"template\": {\n        \"title\": \"テンプレート\",\n        \"information\": \"テンプレートで始めて、ユーザーが必要な情報を理解するのに役立ちます\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"例 yam.eth\",\n    \"chooseExistingEns\": \"作成するスペースと紐付ける既存のENSドメインを選択してください。\",\n    \"useSingleExistingEns\": \"既存のENSドメインを使用してください:\",\n    \"orRegisterNewEns\": \"または新しいドメインを登録する：\",\n    \"demoTestnetEnsMessage\": \"{network}上でテストスペースを作成するには、ENSドメインが必要です。\",\n    \"toCreateASpace\": \"スペースを作成するためには、まずENSドメインが必要です。ドメインを入力し、ENS登録の指示に従ってください。\",\n    \"createASpace\": \"スペースを作成する\",\n    \"registerEnsButton\": \"登録する\",\n    \"supportedEnsTLDs\": \"サポートされているドメインの拡張子\",\n    \"helpDocsAndDiscordLinks\": \"スペースの設定方法がわからない場合は、{docs}を参照してください。Snapshot {discord}に参加してください。\",\n    \"setSpaceController\": \"スペースコントローラー\",\n    \"setSpaceControllerExists\": \"このドメインのsnapshotテキストレコードはすでに設定されています。変更する場合は編集を選択し、次のステップに進むことができます。\",\n    \"setSpaceControllerInfo\": \"スペースコントローラーは、スペースの設定を管理できるアカウントです。後で追加のスペースコントローラー（管理者）を追加できます。\",\n    \"setSpaceControllerInfoGnosisSafe\": \"Gnosis Safeを使用してスペースを作成する場合は、安全なアドレスをスペースコントローラーとして設定することをお勧めします。しない場合は、いくつかの追加ステップを実行する必要があります。 {link}\",\n    \"editSpaceController\": \"ENSのコントローラーを編集する\",\n    \"setController\": \"コントローラーを設定する\",\n    \"explainControllerAndEns\": \"{network}上で取引を行うことで、ENSドメインに「snapshot」TEXTレコードが追加されます。\",\n    \"confirmToSetAddress\": \"{address} をスペースのコントローラーとして設定してもよろしいですか？\",\n    \"controllerHasAuthority\": \"コントローラーはスペースの設定に対して完全な権限を持っています\",\n    \"controller\": \"コントローラー\",\n    \"selectEnsForSpace\": \"ENSアドレスを選択する\",\n    \"spaceOwnerAddressPlaceHolder\": \"例：{address}\",\n    \"controllerAddress\": \"コントローラーのアドレス\",\n    \"updateController\": \"コントローラーを更新する\",\n    \"seeOnEns\": \"ENS上で見る\",\n    \"goToSettings\": \"設定に移動する\",\n    \"setSpaceProfile\": \"スペースをカスタマイズする\",\n    \"waitForTransaction\": \"スペースを作成する前に取引の確定が必要です。{txUrl}\",\n    \"pleaseWaitMessage\": \"取引が確定されるまで少々お待ちください\",\n    \"notControllerAddress\": \"スペースを作成するには、コントローラーアドレス {wallet} に接続してください。\",\n    \"fillCurrentAccount\": \"現在ログインしているアカウントを使用する\",\n    \"domain\": {\n      \"title\": \"スペースドメインを設定する\",\n      \"ensMessage\": \"スペースを作成する前に必要なものの1つは、Ethereum mainnet上のENSドメインです。\",\n      \"ensMessageTestnet\": \"また、Goerliテストネット上で{link}することもできます。\",\n      \"tryDemo\": \"デモを試す\",\n      \"yourExistingSpaces\": \"既存のスペース\",\n      \"invalidEns\": \"このENS名は無効です。通常、登録時に無効な文字を使用したためです。\"\n    },\n    \"strategy\": {\n      \"title\": \"投票戦略をどのように設定しますか？\",\n      \"subtitle\": \"戦略の設定はいつでも変更できます。\",\n      \"blockTitle\": \"投票戦略を設定する\",\n      \"onePersonOneVote\": {\n        \"title\": \"1人1票\",\n        \"description\": \"投票できるホワイトリストを管理するか、単にすべてのアドレスが投票できるようにすることができます。すべての投票は等しく、トークンは必要ありません\",\n        \"whitelistInformation\": \"投票できるアカウントの数を指定する\",\n        \"ticketInformation\": \"すべてのアカウントが投票できます\",\n        \"votesEqualInfo\": \"各投票は等しく、トークンは必要ありません\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"トークン加重投票\",\n        \"description\": \"投票はトークンで重み付けされます。トークンはERC-20、ERC-721、またはERC-1155トークンスタンダードです\",\n        \"tokenNotFound\": \"トークンが見つかりません\",\n        \"seeOnEtherscan\": \"Etherscanで見る\"\n      },\n      \"advanced\": {\n        \"title\": \"カスタムセットアップ\",\n        \"description\": \"多くのオプションから最大8つの戦略を選択できます。あなたの用途に合った戦略が見つからない場合は、独自の戦略を作成することもできます\"\n      }\n    },\n    \"validationTitle\": \"誰がこのスペースを管理して提案を作成できますか？\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"プロフィールを編集\",\n    \"viewProfile\": \"プロフィールを表示\",\n    \"about\": {\n      \"header\": \"情報\",\n      \"joinedSpaces\": \"参加したスペース\",\n      \"createdSpaces\": \"作成したスペース\",\n      \"biography\": \"バイオグラフィー\",\n      \"notJoinSpacesYet\": \"まだスペースに参加していません\",\n      \"notCreatedSpacesYet\": \"まだスペースを作成していません\",\n      \"delegatorNetworkInfo\": \"ウォレット内のネットワークを切り替えて変更します\",\n      \"delegate\": \"委任\",\n      \"delegated\": \"委任された\",\n      \"delegateTo\": \"に委任する\",\n      \"delegateFor\": \"の委任者\",\n      \"noDelegatorsMessage\": \"{network}上に委任者はいません\",\n      \"notSupportedNetwork\": \"{network}では現在委任はサポートされていません\"\n    },\n    \"activity\": {\n      \"header\": \"活動\",\n      \"votedFor\": \"{choice}に投票しました\",\n      \"today\": \"今日\",\n      \"thisWeek\": \"今週\",\n      \"olderThanWeek\": \"1週間以上前\",\n      \"noActivity\": \"まだ活動はありません\"\n    },\n    \"settings\": {\n      \"header\": \"プロフィールを編集\",\n      \"name\": \"名前\",\n      \"biography\": \"自己紹介\",\n      \"namePlaceholder\": \"名前を入力\",\n      \"bioPlaceholder\": \"あなたの物語を話してください\",\n      \"change\": \"変更\",\n      \"remove\": \"削除\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"おつかれさまです！\",\n    \"copied\": \"コピーしました！\",\n    \"proposalDeleted\": \"提案が削除されました\",\n    \"somethingWentWrong\": \"おっと、何らかの問題が発生しました!\",\n    \"saved\": \"保存しました！\",\n    \"delegationSuccess\": \"委任されました\",\n    \"delegationRemoved\": \"委任が削除されました\",\n    \"proposalCreated\": \"提案が作成されました\",\n    \"voteSuccessful\": \"投票が完了しました！\",\n    \"ensSet\": \"ENSテキストレコードが正常に設定されました\",\n    \"transactionSent\": \"取引が送付されました\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"ストラテジーを作成する\",\n    \"createSkin\": \"スキンを作成する\",\n    \"addNetwork\": \"ネットワークを追加する\",\n    \"createPlugin\": \"プラグインを作成する\",\n    \"strategies\": \"ストラテジー\",\n    \"skins\": \"スキン\",\n    \"networks\": \"ネットワーク\",\n    \"plugins\": \"プラグイン\",\n    \"results\": \"結果\",\n    \"category\": \"カテゴリー\",\n    \"categories\": {\n      \"all\": \"全て\",\n      \"protocol\": \"プロトコル\",\n      \"social\": \"ソーシャル\",\n      \"investment\": \"投資\",\n      \"grant\": \"グラント\",\n      \"service\": \"サービス\",\n      \"media\": \"メディア\",\n      \"creator\": \"クリエイター\",\n      \"collector\": \"コレクター\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"投票システムを選択する\",\n    \"single-choice\": \"単一選択の投票\",\n    \"approval\": \"承認投票\",\n    \"quadratic\": \"二次投票\",\n    \"ranked-choice\": \"優先順位付投票\",\n    \"weighted\": \"加重投票\",\n    \"basic\": \"ベーシックな投票\",\n    \"description\": {\n      \"single-choice\": \"各投票者は一つの選択のみを選択することができます。\",\n      \"approval\": \"各投票者は任意の数の選択肢を選択することができます。\",\n      \"quadratic\": \"各投票者は、任意の数の選択肢に投票力を分散させることができます。結果は二次的に計算されます。\",\n      \"ranked-choice\": \"各投票者は、任意の数の選択肢を選択してランク付けすることができます。結果は、インスタント流出カウント法によって計算されます。\",\n      \"weighted\": \"各投票者は、任意の数の選択肢に投票力を分散させることができます。\",\n      \"basic\": \"3つの選択肢（賛成または 反対または棄権）の中から一つを選択する投票\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"プライバシー\",\n    \"title\": \"投票のプライバシーを選択\",\n    \"information\": \"提案に使用されるプライバシーの種類（今後のすべての提案で強制されます）\",\n    \"any\": \"すべて\",\n    \"none\": \"なし\",\n    \"shutter\": {\n      \"label\": \"シャッター\",\n      \"description\": \"選択肢は暗号化され、投票期間が終了して最終スコアが計算されるまで表示されません\",\n      \"tooltip\": \"この提案にはShutterプライバシーが有効になっています。すべての投票は、投票期間が終了して最終スコアが計算されるまで暗号化されます\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"検証\",\n    \"title\": \"投票検証を選択\",\n    \"information\": \"ユーザーが投票できるかどうかを判断するために使用される検証の種類（今後のすべての提案に強制されます）\",\n    \"any\": {\n      \"label\": \"誰でも投票可能\",\n      \"description\": \"投票権を持つ誰でも投票できます。\"\n    },\n    \"basic\": {\n      \"label\": \"基本\",\n      \"description\": \"ユーザーが投票できるかどうかを判断するためのあらゆる戦略を使用します。\",\n      \"invalidMessage\": \"この提案に投票するための最低限のバランス要件を満たしていません。\"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport ゲート付き\",\n      \"description\": \"Gitcoin Passportを必要とすることで、スパムや投票操作からあなたの提案を保護します。\",\n      \"invalidMessage\": \"この提案に投票するためには、{amount} 以下の切手を持つGitcoin Passportが必要です：{stamps}。\"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"現在の結果\",\n    \"currentBond\": \"現在のボンド\",\n    \"finalizedIn\": \"{0}に最終的に決定されました\",\n    \"executableIn\": \"{0}で実行可能\",\n    \"finalOutcome\": \"最終結果\",\n    \"nextBond\": \"次のボンドを設定する\",\n    \"setOutcomeTo\": \"アウトカムを設定する\",\n    \"claimBond\": \"ボンドを受け取る\",\n    \"addBatch\": \"トランザクションを追加する\",\n    \"batch\": \"バッチを追加する\",\n    \"transactions\": \"トランザクション\",\n    \"to\": \"宛先（アドレス）\",\n    \"invalidAddress\": \"無効なアドレス\",\n    \"invalidAmount\": \"無効な金額\",\n    \"invalidValue\": \"無効な値\",\n    \"invalidAbi\": \"無効なABI\",\n    \"invalidData\": \"無効なデータ\",\n    \"value\": \"値（wei）\",\n    \"data\": \"データ\",\n    \"noCollectibles\": \"コレクティブルがありません\",\n    \"asset\": \"資産\",\n    \"amount\": \"量\",\n    \"type\": \"タイプ\",\n    \"transferFunds\": \"送金する\",\n    \"transferNFT\": \"NFTを転送する\",\n    \"contractInteraction\": \"コントラクトとの対話\",\n    \"rawTransaction\": \"生トランザクション\",\n    \"addTransaction\": \"トランザクションを追加する\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount}weiを{address}に\",\n      \"transferFunds\": \"{amount}{tokenSymbol}を{address}に送金\",\n      \"transferNFT\": \"{name} #{id}を{address}に送信\",\n      \"raw\": \"{amount}weiを{address}に送信\"\n    },\n    \"labels\": {\n      \"request\": \"リクエストの実行\",\n      \"setOutcome\": \"結果を設定\",\n      \"changeOutcome\": \"結果を変更する\",\n      \"executeTxs\": \"{1}の{0}番目のトランザクションバッチを実行する\",\n      \"executed\": \"すべてのトランザクションが実行されました\",\n      \"noTransactions\": \"実行するトランザクションがありません\",\n      \"rejected\": \"提案が拒否されました\",\n      \"error\": \"何らかの問題が発生しました\",\n      \"connectWallet\": \"実行の詳細を表示するにはウォレットに接続してください\",\n      \"switchChain\": \"{0}に切り替えて実行をリクエストしてください\",\n      \"question\": \"この提案が承認され、条件を満たしていますか？\",\n      \"criteria\": \"受け入れ基準?\",\n      \"proposalPassed\": \"提案が承認されましたか？\",\n      \"expired\": \"提案の有効期限が切れました\",\n      \"approveBond\": \"保証金を承認する\",\n      \"confirmVoteResults\": \"提案が承認されたことを確認するにはクリックしてください\",\n      \"executeTxsUma\": \"トランザクションバッチを実行する\",\n      \"deleteDisputedProposal\": \"紛争中の提案を削除する\",\n      \"confirmVoteResultsToolTip\": \"この提案がこのスナップショット投票で承認されたことを確認してから、チェーン上で提案する前に。もしスナップショット投票で提案が拒否された場合、チェーン上の提案も拒否され、保証金を失うことになります。\",\n      \"approveBondToolTip\": \"チェーン上の提案には提案者からの保証金が必要です。これは、あなたのウォレットからトークンを承認することになります。無効な提案を行った場合、それは紛争となり、保証金を失うことになります。提案が有効な場合、トランザクションが実行されるときに保証金が返却されます。\",\n      \"requestToolTip\": \"これは、このスナップショット投票からのトランザクションをチェーン上で提案します。チャレンジウィンドウの後、提案が有効な場合、トランザクションを実行して保証金が返却されます。\",\n      \"executeToolTip\": \"これは、この提案からのトランザクションを実行し、提案者の保証金を返却します。\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"POAP はまだこの提案のためにセットアップされていません :(\",\n    \"no_voted_header\": \"投票してこのPOAPを獲得する\",\n    \"unclaimed_header\": \"投票の証にPOAPを発行する\",\n    \"claimed_header\": \"おめでとうございます！POAPがあなたのコレクションに追加されました。\",\n    \"loading_header\": \"POAPをあなたのコレクションに追加しています\",\n    \"button_claim\": \"発行する\",\n    \"button_show\": \"コレクションを閲覧する\",\n    \"success_claim\": \"POAP があなたのコレクションに追加されました\",\n    \"error_claim\": \"トークンを発行する際に問題が発生しました。\"\n  },\n  \"progress\": {\n    \"progress\": \"進捗\",\n    \"inProgress\": \"進行中\",\n    \"completed\": \"完了しました\",\n    \"complete\": \"完了\",\n    \"newStep\": \"新しいステップ\",\n    \"description\": \"説明\",\n    \"add\": \"追加\",\n    \"deleteStep\": \"ステップを削除する\",\n    \"deleteConfirm\": \"本当に削除しますか？\",\n    \"delete\": \"削除\",\n    \"cancel\": \"キャンセル\",\n    \"comeBack\": \"投票が完了したら、この提案の進捗状況を確認するために戻ってきてください！\",\n    \"confirmSignature\": \"このメッセージの署名を行うと、提案の進捗状況を更新するリクエストを承認できます。\",\n    \"wentWrong\": \"何かが間違っています。\",\n    \"voting\": \"投票\",\n    \"soon\": \"まもなく\"\n  },\n  \"charts\": {\n    \"charts\": \"チャート\",\n    \"noVotesYet\": \"表示する投票がまだありません。\",\n    \"totalVotesPerDay\": \"1日あたりの合計投票数\",\n    \"shareOfVotingPower\": \"Share of voting power\",\n    \"votingPowerPerDay\": \"1日あたりの投票力\"\n  },\n  \"comment_box\": {\n    \"title\": \"コメント欄\",\n    \"add\": \"コメントを入力\",\n    \"submit\": \"提出する\",\n    \"preview\": \"プレビュー\",\n    \"continue_editing\": \"編集を続ける\",\n    \"edit\": \"ここにあなたの返信を書いてください\",\n    \"edit_button\": \"編集\",\n    \"dismiss\": \"閉じる\",\n    \"delete\": \"削除\",\n    \"add_reply\": \"返信を入力\",\n    \"edit_comment\": \"コメントの編集\",\n    \"edit_modal\": \"編集してもよろしいですか？\",\n    \"yes\": \"はい\",\n    \"no\": \"いいえ\",\n    \"delete_comment\": \"コメントを削除\",\n    \"delete_modal\": \"削除してもよろしいですか？\",\n    \"error\": \"おっと、何らかの問題が発生しました\",\n    \"replies\": \"返信\",\n    \"hide\": \"非表示\",\n    \"show\": \"表示：\",\n    \"reply\": \"返信\",\n    \"load_more\": \"さらに表示\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"スペースを作成する\",\n      \"timeline\": \"タイムライン\",\n      \"notifications\": \"お知らせ\",\n      \"explore\": \"Explore\",\n      \"playground\": \"プレイグラウンド\",\n      \"space\": {\n        \"create\": \"{space} の提案を作成する\",\n        \"about\": \"{space} について\",\n        \"proposals\": \"{space} の提案\",\n        \"proposal\": \"{space} の提案: {proposal}\",\n        \"settings\": \"{space} の設定\"\n      },\n      \"strategy\": \"{key} のストラテジー\",\n      \"delegate\": \"委任\",\n      \"ranking\": \"Ranking\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"{spaceName} の提案を追跡する\",\n    \"text\": \"新しい提案が作成または終了するたびに通知を受け取る\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 秒 | {n} 秒\",\n    \"minute\": \"1 分 | {n} 分\",\n    \"hour\": \"1 時間 | {n} 時間\",\n    \"day\": \"1 日 | {n} 日\",\n    \"week\": \"1 週間 | {n} 週間\",\n    \"month\": \"1ヶ月 | {n} ヶ月\",\n    \"year\": \"1 年 | {n} 年\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"対応していないネットワーク\",\n    \"switchNetworkToNetwork\": \"続行するには、ウォレット内のネットワークを{network}に変更する必要があります。\",\n    \"switchToNetwork\": \"{network}に切り替える\",\n    \"goToDemoSite\": \"デモサイトに行く\"\n  },\n  \"treasury\": {\n    \"title\": \"財務\",\n    \"wallets\": {\n      \"title\": \"ウォレット\",\n      \"empty\": \"このスペースにはまだ財務がありません\",\n      \"addTreasury\": \"財務を追加する\"\n    },\n    \"assets\": {\n      \"title\": \"資産\",\n      \"empty\": \"この契約には資産がありません\"\n    },\n    \"24hChange\": \"24時間変化\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"あなたのメールアドレス\",\n    \"title\": \"Snapshotの最新情報を受け取る\"\n  },\n  \"joinCommunity\": \"コミュニティに参加\",\n  \"header\": {\n    \"title\": \"決定が行われる場所\",\n    \"description\": \"Snapshotは、コミュニティガバナンスのための無料でオープンソースのプラットフォームです。今すぐ自分のスペースを作成して決定を開始しましょう！\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshotは、投票を行うのにガス料金を大金を使わなくても簡単に作成し投票できる分散型ガバナンスプラットフォームです。さらに、柔軟なシステムは様々な投票タイプと戦略をサポートしているので、投票プロセスをニーズに合わせて調整できます。\",\n    \"subHeader\": \"ガバナンスはスナップで行う\",\n    \"subDescription\": \"Web3ガバナンスは複雑である必要はありません。Snapshotは、コミュニティや組織を管理するための簡単で効率的な方法を探している組織にとって完璧なソリューションです。\"\n  },\n  \"footerView\": {\n    \"resources\": \"リソース\",\n    \"about\": \"情報\",\n    \"blog\": \"ブログ\",\n    \"jobs\": \"求人\",\n    \"discussions\": \"ディスカッション\",\n    \"github\": \"GitHub\",\n    \"docs\": \"ドキュメント\",\n    \"support\": \"サポート\",\n    \"hiring\": \"私たちと一緒に！\"\n  }\n}\n"
  },
  {
    "path": "src/locales/ko-KR.json",
    "content": "{\n  \"searchPlaceholder\": \"검색\",\n  \"spaceCount\": \"{0} 공간(들)\",\n  \"createSpace\": \"공간 만들기\",\n  \"backToHome\": \"홈\",\n  \"actions\": \"액션\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"결과\",\n  \"resultsError\": \"결과를 계산할 수 없습니다. 이는 종종 잘못 구성된 전략이나 전략에 포함된 RPC 노드가 응답하지 않아서 일 수 있습니다.\",\n  \"resultsCalculating\": \"Final results are being calculated. If you still see this message after a few minutes contact the space admin.\",\n  \"votingPowerFailedMessage\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"votingValidationFailedMessage\": \"There was an error on our side and we could not verify if you are eligible to vote. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n  \"notValidVoterMessage\": \"Oops, you don't seem to be eligible to vote on this proposal.\",\n  \"getHelp\": \"도움 받기\",\n  \"retry\": \"다시하기\",\n  \"currentResults\": \"현재 결과\",\n  \"reset\": \"초기화\",\n  \"close\": \"Close\",\n  \"save\": \"저장\",\n  \"author\": \"저자\",\n  \"next\": \"다음\",\n  \"choice\": \"Choice\",\n  \"submit\": \"제출\",\n  \"plugins\": \"플러그인\",\n  \"information\": \"정보\",\n  \"confirm\": \"확인\",\n  \"snapshot\": \"스냅샷\",\n  \"strategies\": \"전략(들)\",\n  \"strategiesPage\": \"전략\",\n  \"space\": \"공간\",\n  \"spaces\": \"공간들\",\n  \"verifiedSpace\": \"검증된 공간\",\n  \"warningSpace\": \"This space has been flagged as potentially malicious. Proceed with caution.\",\n  \"version\": \"버전\",\n  \"timeline\": \"타임라인\",\n  \"ended\": \"ended\",\n  \"started\": \"started\",\n  \"filters\": \"필터\",\n  \"allSpaces\": \"모든 공간\",\n  \"submitOnchain\": \"온체인 제출\",\n  \"inSpaces\": \"{0} 공간(들)\",\n  \"votes\": \"투표\",\n  \"seeMore\": \"더보기\",\n  \"seeAll\": \"See all\",\n  \"network\": \"네트워크\",\n  \"networks\": \"네트워크들\",\n  \"skins\": \"스킨\",\n  \"spaceMembers\": \"Members\",\n  \"members\": \"회원 없음 | {count} 회원 | {count} 구성원\",\n  \"editStrategy\": \"전략 수정\",\n  \"invalidProposals\": \"유효하지 않는 제안\",\n  \"account\": \"계정\",\n  \"create3box\": \"3Box에서 프로필 만들기\",\n  \"view3box\": \"3Box에서 프로필 보기\",\n  \"edit3box\": \"3Box에서 프로필 수정\",\n  \"connectWallet\": \"지갑 연결\",\n  \"toggleSkin\": \"스킨 토글\",\n  \"about\": \"소개\",\n  \"license\": \"라이센스\",\n  \"showMore\": \"Show more\",\n  \"voted\": \"Voted\",\n  \"reload\": \"Reload\",\n  \"ipfsServer\": \"IPFS 서버\",\n  \"hub\": \"허브\",\n  \"cancel\": \"취소\",\n  \"delete\": \"Delete\",\n  \"demoSite\": \"데모 사이트입니다. 시도해 보세요!\",\n  \"removeDelegation\": \"위임 제거\",\n  \"confirmRemove\": \"위임을 제거하시겠습니까\",\n  \"removeSpace\": \"{0} 공간\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum reached\",\n  \"options\": \"옵션\",\n  \"votingPower\": \"당신의 투표권\",\n  \"comment\": {\n    \"placeholder\": \"Share your reason (optional)\"\n  },\n  \"receipt\": \"처리 결과\",\n  \"relayer\": \"중계자\",\n  \"verifyOnMycrypto\": \"MyCrypto에서 처리결과 확인\",\n  \"verifyOnSignatorio\": \"Signator.io에서 확인\",\n  \"isCore\": \"코어\",\n  \"notificationsBlocked\": \"브라우저가 알림을 차단하고 있습니다\",\n  \"notificationsNotSupported\": \"브라우저는 알림을 지원하지 않습니다\",\n  \"walletNotSupported\": \"지갑이 지원되지 않음\",\n  \"seeInExplorer\": \"See explorer\",\n  \"learnMore\": \"더 알아보기\",\n  \"logout\": \"로그아웃\",\n  \"continue\": \"계속\",\n  \"add\": \"추가\",\n  \"edit\": \"편집\",\n  \"strategyParameters\": \"전략 매개변수\",\n  \"addAction\": \"액션 추가\",\n  \"removeAction\": \"액션 제거\",\n  \"yourChoice\": \"{0} 선택\",\n  \"targetAddress\": \"대상 주소\",\n  \"value\": \"값\",\n  \"date\": \"데이터\",\n  \"marketDetails\": \"시장 세부 정보\",\n  \"addMarket\": \"시장 추가\",\n  \"selectNetwork\": \"네트워크 선택\",\n  \"conditionId\": \"조건 ID\",\n  \"basetokenAddress\": \"기본 토큰 주소\",\n  \"quoteAddress\": \"견적 통화 주소\",\n  \"removeMarket\": \"시장 제거\",\n  \"back\": \"뒤로\",\n  \"loading\": \"로딩 중\",\n  \"predictedImpact\": \"예상 영향\",\n  \"marketSymbol\": \"{0} 시장\",\n  \"twoChoicesRequired\": \"이 플러그인을 위해 두 가지 선택이 요구됩니다.\",\n  \"noResultsFound\": \"일치하는 결과가 없습니다.\",\n  \"createFirstProposal\": \"첫 번째 제안을 작성해 보세요.\",\n  \"noSpacesJoined\": \"아직 어떤 공간에도 가입하지 않았습니다.\",\n  \"addFavorites\": \"즐겨찾기 추가\",\n  \"createdBy\": \"{0} 에 의해\",\n  \"startIn\": \"시작 {0}\",\n  \"endIn\": \"종료 {0}\",\n  \"proposalTimeLeft\": \"{0} 남음\",\n  \"endedAgo\": \"{0} 에 종료됨\",\n  \"proposalBy\": \"{0} 에 의해\",\n  \"endDate\": \"말단 {0}\",\n  \"defaultSkin\": \"기본 스킨\",\n  \"select\": \"선택\",\n  \"language\": \"언어\",\n  \"agree\": \"동의함\",\n  \"moderators\": \"Moderators\",\n  \"playground\": \"플레이그라운드\",\n  \"strategyParams\": \"전략 매개변수\",\n  \"addresses\": \"주소\",\n  \"networkErrorPlayground\": \"네트워크 오류 - 자세한 내용을 보려면 브라우저 콘솔을 여십시오\",\n  \"upload\": \"업로드\",\n  \"join\": \"참여\",\n  \"joined\": \"참여함\",\n  \"leave\": \"나가기\",\n  \"subspaces\": \"Sub-spaces\",\n  \"mainspace\": \"Main space\",\n  \"copyLink\": \"링크 복사\",\n  \"duplicate\": \"Duplicate\",\n  \"joinedSpaces\": \"참여한 공간\",\n  \"joinSpaces\": \"공간 참여\",\n  \"setDelegationToSpace\": \"특정 공간으로 위임 제한\",\n  \"theCurrentNetwork\": \"현재 네트워크\",\n  \"optional\": \"(선택 사항)\",\n  \"homeLoadmore\": \"더 불러오기\",\n  \"confirmAction\": \"액션 확인\",\n  \"or\": \"또는\",\n  \"share\": \"Share\",\n  \"shareOnTwitter\": \"Share on Twitter\",\n  \"shareOnLenster\": \"Share on Lenster\",\n  \"createButton\": \"Create\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Change wallet\",\n  \"createASpace\": \"Create a space\",\n  \"getStarted\": \"Get started\",\n  \"skip\": \"Skip\",\n  \"newSpaceNotice\": {\n    \"header\": \"Your space is live!\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Learn more in the {documentation} or join Snapshot {discord} for help.\",\n    \"gotIt\": \"Got it!\"\n  },\n  \"errors\": {\n    \"required\": \"필드가 필요합니다\",\n    \"minLength\": \"Field is required\",\n    \"maxLength\": \"최대 길이: {0}\",\n    \"pattern\": \"잘못된 문자\",\n    \"minItems\": \"최소 {0} 개 항목 필요\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"minStrategy\": \"적어도 한 개의 전략이 필요합니다.\",\n    \"website\": \"URL should be in the format https://www.example.com\",\n    \"format\": \"잘못된 형식\",\n    \"type\": \"잘못된 유형\",\n    \"unsupportedImageType\": \"File type not supported, Supported formats are jpeg, jpg and png\",\n    \"invalidAddress\": \"Invalid address\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Title\",\n    \"discussion\": \"Discussion (optional)\",\n    \"categorie(s)\": \"최대 2개의 카테고리 선택\",\n    \"proposalDescription\": \"Description (optional)\",\n    \"preview\": \"미리보기\",\n    \"choices\": \"선택지\",\n    \"addChoice\": \"선택지 추가\",\n    \"startDate\": \"시작일 선택\",\n    \"endDate\": \"종료일 선택\",\n    \"startTime\": \"시작 시간 선택\",\n    \"endTime\": \"종료 시간 선택\",\n    \"publish\": \"게시\",\n    \"untitled\": \"제목 없음\",\n    \"snapshotBlock\": \"스냅샷 블록 번호\",\n    \"voting\": \"투표\",\n    \"votingSystem\": \"투표 유형\",\n    \"choice\": \"{0} 선택\",\n    \"period\": \"투표 기간\",\n    \"start\": \"시작\",\n    \"end\": \"종료\",\n    \"days\": \"일\",\n    \"hours\": \"시\",\n    \"minutes\": \"분\",\n    \"schedule\": \"일정 제안\",\n    \"delayEnforced\": \"투표가 시작될 때까지 공간이 지연됩니다.\",\n    \"periodEnforced\": \"공간은 투표 기간을 적용합니다.\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"편집\",\n    \"continue\": \"계속\",\n    \"now\": \"현재\",\n    \"votingPeriodExplainer\": \"사용자가 투표할 수 있는 기간입니다. 제안은 투표 기간 시작 전에 대기 중으로 보여질 것입니다.\",\n    \"uploadImageExplainer\": \"Attach images by dragging & dropping, selecting or pasting them.\",\n    \"uploading\": \"Uploading image\",\n    \"markdown\": \"Styling with Markdown is supported\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"제안서를 제출하려면 공간의 저자여야 합니다.\",\n        \"minScore\": \"당신은 제안서를 제출하기 위해 {0} {1} 의 최소가 필요합니다.\"\n      },\n      \"customValidation\": \"당신은 제안서를 제출하기 위해 제안의 유효성 검사를 통과해야합니다.\",\n      \"executionError\": \"제안 생성에 대한 자격이 있는지 확인하는 데 실패했습니다. 이는 잘못 구성된 전략 때문일 수 있습니다.\"\n    },\n    \"errorGettingSnapshot\": \"We encountered an error while fetching the snapshot block number which is needed to calculate your voting power. Please try again later.\"\n  },\n  \"delegate\": {\n    \"header\": \"대리자\",\n    \"selectDelegate\": \"대리인 선택\",\n    \"to\": \"누구에게\",\n    \"addressPlaceholder\": \"주소 또는 ENS 이름\",\n    \"delegations\": \"당신의 대리인\",\n    \"allSpaces\": \"모든 공간\",\n    \"delegated\": \"당신에게 위임됨\",\n    \"pendingTransaction\": \"보류 중인 트랜잭션 없음 | 1 보류 중인 트랜잭션 | {count} 보류 중인 트랜잭션\",\n    \"topDelegates\": \"상위 대리자\",\n    \"noDelegatesFoundFor\": \"{0} 에 대한 대리자를 찾을 수 없습니다.\",\n    \"noValidEns\": \"올바른 ENS 주소 아님\",\n    \"noValidAddress\": \"올바르지 않은 주소\",\n    \"delegateToSelf\": \"자기 자신에게는 위임할 수 없음\",\n    \"delegateToSelfAddress\": \"자기 자신의 ENS 주소로는 위임할 수 없슴\",\n    \"noValidSpaceId\": \"올바르지 않은 공간 ID\",\n    \"noDelegationsAndDelegates\": \"대리자를 찾을 수 없습니까? 올바른 네트워크에 연결되어 있는지 확인하십시오.\",\n    \"delegateNotSupported\": \"위임은 현재 {network} 에서 지원되지 않습니다.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"투표하기\",\n    \"vote\": \"투표\",\n    \"startDate\": \"시작일\",\n    \"endDate\": \"종료일\",\n    \"votingSystem\": \"투표 유형\",\n    \"privacy\": \"Privacy\",\n    \"invalidChoice\": \"Invalid choice\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Your vote is in!\",\n      \"gnosisSafeTitle\": \"Your vote is pending...\",\n      \"gnosisSafeDescription\": \" Votes with a Safe require additional signers and will be visible once the transaction is confirmed\",\n      \"seeQueue\": \"See queued transactions\",\n      \"tips\": {\n        \"1\": \"Votes can be changed while the proposal is active\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"제안\",\n    \"new\": \"새로운 제안 만들기\",\n    \"noProposals\": \"아직 새로운 제안이 없어요!\",\n    \"createProposal\": \"제안 만들기\",\n    \"showMore\": \"더 보기\",\n    \"showLess\": \"적게 보기\",\n    \"states\": {\n      \"all\": \"모두\",\n      \"core\": \"코어\",\n      \"community\": \"커뮤니티\",\n      \"active\": \"진행 중\",\n      \"pending\": \"보류 중\",\n      \"closed\": \"종료됨\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Notifications\",\n    \"noNotifications\": \"You have no notifications\",\n    \"proposalStarted\": \"proposal has started:\",\n    \"proposalEnded\": \"proposal has ended:\",\n    \"markAllAsRead\": \"Mark all as read\",\n    \"all\": \"All\",\n    \"unread\": \"Unread\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"join\",\n    \"actionCreate\": \"create a proposal in\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"설정\",\n    \"editController\": \"관리자 설정\",\n    \"connectWithSpaceOwner\": \"You are in view only mode, to modify space settings connect with a controller or admin wallet.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"edit the space settings\",\n      \"create\": \"create a proposal\",\n      \"vote\": \"vote on this proposal\"\n    },\n    \"currentSpaceControllerIs\": \"The current space controller is {address}\",\n    \"newController\": \"New controller\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Set\",\n    \"profile\": \"프로필\",\n    \"avatar\": \"아바타\",\n    \"name\": {\n      \"label\": \"Name\",\n      \"placeholder\": \"e.g. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"About\",\n      \"placeholder\": \"What is your organisation about?\"\n    },\n    \"categories\": {\n      \"label\": \"Categorie(s)\",\n      \"select\": \"Select categorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Terms of service\",\n      \"information\": \"Users will be required to accept these terms once before they can create a proposal or cast a vote\"\n    },\n    \"hideSpace\": \"홈페이지에서 공간 숨기기\",\n    \"links\": \"Social accounts\",\n    \"subspaces\": {\n      \"label\": \"Sub-spaces\",\n      \"information\": \"Sub-spaces will only be shown once configured on both the main space and the sub-space(s). {docs}\",\n      \"parent\": {\n        \"label\": \"Main space\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"The space that this space is a sub-space of will be displayed on the space page\"\n      },\n      \"children\": {\n        \"label\": \"Sub-spaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Related Sub-spaces listed here will be displayed on the space page\"\n      }\n    },\n    \"website\": \"Website\",\n    \"strategies\": {\n      \"label\": \"Strategie(s)\",\n      \"information\": \"Strategies are used to determine voting power or whether a user is eligible to create a proposal\"\n    },\n    \"network\": {\n      \"label\": \"Network\",\n      \"information\": \"The defaul network used for this space. Networks can also be specified in individual strategies\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbol\",\n      \"information\": \"The default symbol used for this space, usually the token symbol i.e. BAL for Balancer\"\n    },\n    \"strategiesList\": \"Select up to 8 strategies\",\n    \"votingPowerIsCumulative\": \"Voting power is cumulative\",\n    \"addStrategy\": \"전략 추가\",\n    \"testInPlayground\": \"Test in playground\",\n    \"admins\": {\n      \"label\": \"Admins\",\n      \"information\": \"Admins are able to modify the space settings and manage the space's proposals\"\n    },\n    \"authors\": {\n      \"label\": \"Authors\",\n      \"information\": \"Authors are always able to create proposals\"\n    },\n    \"proposalValidation\": \"제안 검증\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Threshold\",\n      \"information\": \"The minimum amount of voting power required to create a proposal\"\n    },\n    \"allowOnlyAuthors\": \"작성자만 제안서를 제출할 수 있도록 허용\",\n    \"editValidation\": \"유효성 검사 편집\",\n    \"selectValidation\": \"유효성 검사 선택\",\n    \"validationParameters\": \"검증 매개변수\",\n    \"voting\": \"투표\",\n    \"votingDelay\": \"투표 지연\",\n    \"votingPeriod\": \"투표 기간\",\n    \"hours\": \"Hours\",\n    \"days\": \"Days\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"The minimum amount of voting power required for the proposal to pass\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"The type of voting system used for this space. (Enforced on all future proposals)\"\n    },\n    \"anyType\": \"어떤\",\n    \"hideAbstain\": \"기본 투표 결과에서 기권 투표 무시\",\n    \"customDomain\": \"사용자 도메인\",\n    \"domain\": {\n      \"label\": \"Domain name\",\n      \"placeholder\": \"e.g. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"스킨\",\n    \"treasuries\": {\n      \"label\": \"Treasury\",\n      \"add\": \"Add treasury\",\n      \"edit\": \"Edit treasury\",\n      \"information\": \"Add treasuries of your organization to show them in your space\"\n    },\n    \"addPlugin\": \"플러그인 추가\",\n    \"editPlugin\": \"편집 플러그인\",\n    \"pluginParameters\": \"플러그인 매개변수\",\n    \"proposal\": {\n      \"title\": \"Proposal\",\n      \"guidelines\": {\n        \"title\": \"Guidelines\",\n        \"information\": \"Display a link to your guidelines on proposal creation to help users understand what constitutes a good/valid proposal\"\n      },\n      \"template\": {\n        \"title\": \"Template\",\n        \"information\": \"Start every proposal with a template to help users understand what information is required\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"예시 yam.eth\",\n    \"chooseExistingEns\": \"공간을 만드는데 사용할 ENS 도메인 선택:\",\n    \"useSingleExistingEns\": \"당신의 ENS 도메인을 사용하세요:\",\n    \"orRegisterNewEns\": \"또는 새로운 도메인을 등록하세요:\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"공간을 만들려면, 먼저 ENS 도메인이 필요합니다. 아래에 하나를 작성하고 ENS 등록 지침을 따라주세요.\",\n    \"createASpace\": \"공간 만들기\",\n    \"registerEnsButton\": \"등록\",\n    \"supportedEnsTLDs\": \"지원되는 도메인 끝자리\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or join Snapshot {discord}.\",\n    \"setSpaceController\": \"Space controller\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \" The space controller is the account that will be able to manage the space settings. Additional space controllers (admins) can be added later.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"When creating a space with a Gnosis Safe it's recommended to set the safe address as the space controller. If you don't, you need to follow some additional steps. {link}\",\n    \"editSpaceController\": \"ENS 관리자 수정\",\n    \"setController\": \"관리자 설정\",\n    \"explainControllerAndEns\": \"Setting the controller requires a transaction on the {network} which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"{address} 를 공간 관리자로 설정하겠습니까?\",\n    \"controllerHasAuthority\": \"관리자는 공간 설정에 대한 전체 권한을 갖습니다.\",\n    \"controller\": \"관리자\",\n    \"selectEnsForSpace\": \"ENS 주소 선택\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. {address}\",\n    \"controllerAddress\": \"관리자 주소\",\n    \"updateController\": \"관리자 업데이트\",\n    \"seeOnEns\": \"ENS 보기\",\n    \"goToSettings\": \"설정으로 이동\",\n    \"setSpaceProfile\": \"Customize your space\",\n    \"waitForTransaction\": \"The transaction need to confirm before you can create your space. {txUrl}\",\n    \"pleaseWaitMessage\": \"This can take a few minutes, please wait while the transaction is being confirmed\",\n    \"notControllerAddress\": \"Please connect with the controller address {wallet} to create the space.\",\n    \"fillCurrentAccount\": \"Use currently logged in account\",\n    \"domain\": {\n      \"title\": \"Setup your space domain\",\n      \"ensMessage\": \" One thing you need before you can create your own space, is an ENS domain on Ethereum mainnet.\",\n      \"ensMessageTestnet\": \"You can also {link} on the Goerli testnet and mess with things there first.\",\n      \"tryDemo\": \"try the demo\",\n      \"yourExistingSpaces\": \"Your existing spaces\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"How would you like to setup your voting strategy?\",\n      \"subtitle\": \"You can change your strategy settings any time.\",\n      \"blockTitle\": \"Setup voting strategy\",\n      \"onePersonOneVote\": {\n        \"title\": \"One person, one vote\",\n        \"description\": \"Manage a whitelist of people who can vote or simply allow any address to vote. Every vote is equal and no token is required\",\n        \"whitelistInformation\": \"Specify a number of accounts that can vote\",\n        \"ticketInformation\": \"Any account can vote\",\n        \"votesEqualInfo\": \"Each vote is equal and no token is required\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Token weighted voting\",\n        \"description\": \"Votes are weighted by a token. The token can be an ERC-20, ERC-721 or ERC-1155 token standard\",\n        \"tokenNotFound\": \"Token not found\",\n        \"seeOnEtherscan\": \"See on Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Custom setup\",\n        \"description\": \"Select up to 8 strategies with a wide range of options. If you can't find the right strategy for your use case, you can create your own\"\n      }\n    },\n    \"validationTitle\": \"Who can manage this space and create proposals?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Edit profile\",\n    \"viewProfile\": \"View profile\",\n    \"about\": {\n      \"header\": \"About\",\n      \"joinedSpaces\": \"Joined spaces\",\n      \"createdSpaces\": \"Created spaces\",\n      \"biography\": \"Bio\",\n      \"notJoinSpacesYet\": \"Hasn't joined any spaces yet\",\n      \"notCreatedSpacesYet\": \"Hasn't created any spaces yet\",\n      \"delegatorNetworkInfo\": \"Change by switching network in your wallet\",\n      \"delegate\": \"Delegate\",\n      \"delegated\": \"Delegated\",\n      \"delegateTo\": \"Delegate to\",\n      \"delegateFor\": \"Delegator for\",\n      \"noDelegatorsMessage\": \"No delegators on {network}\",\n      \"notSupportedNetwork\": \"Delegation currently isn't supported on {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activity\",\n      \"votedFor\": \"Voted {choice}\",\n      \"today\": \"Today\",\n      \"thisWeek\": \"This week\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"No activity yet\"\n    },\n    \"settings\": {\n      \"header\": \"Edit profile\",\n      \"name\": \"Name\",\n      \"biography\": \"Bio\",\n      \"namePlaceholder\": \"Enter name\",\n      \"bioPlaceholder\": \"Tell your story\",\n      \"change\": \"Change\",\n      \"remove\": \"Remove\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"해냈어요!\",\n    \"copied\": \"복사되었습니다!\",\n    \"proposalDeleted\": \"제안이 삭제됨\",\n    \"somethingWentWrong\": \"문제가 발생했습니다.\",\n    \"saved\": \"저장됨!\",\n    \"delegationSuccess\": \"위임 성공\",\n    \"delegationRemoved\": \"위임 제거됨\",\n    \"proposalCreated\": \"제안 생성됨\",\n    \"voteSuccessful\": \"투표가 완료되었습니다!\",\n    \"ensSet\": \"ENS 텍스트 레코드가 성공적으로 설정되었습니다.\",\n    \"transactionSent\": \"Transaction sent\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"전략 만들기\",\n    \"createSkin\": \"스킨 생성\",\n    \"addNetwork\": \"네트워크 추가\",\n    \"createPlugin\": \"플러그인 만들기\",\n    \"strategies\": \"멜라 (들)\",\n    \"skins\": \"스킨\",\n    \"networks\": \"네트워크\",\n    \"plugins\": \"플러그인\",\n    \"results\": \"결과(에스)\",\n    \"category\": \"Category\",\n    \"categories\": {\n      \"all\": \"모두\",\n      \"protocol\": \"프로토콜\",\n      \"social\": \"소셜\",\n      \"investment\": \"투자\",\n      \"grant\": \"그랜트\",\n      \"service\": \"서비스\",\n      \"media\": \"미디어\",\n      \"creator\": \"크리에이터\",\n      \"collector\": \"컬렉터\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"투표 유형 선택\",\n    \"single-choice\": \"단일 선택 투표\",\n    \"approval\": \"승인 투표\",\n    \"quadratic\": \"2차 투표\",\n    \"ranked-choice\": \"순위 선택 투표\",\n    \"weighted\": \"가중 투표\",\n    \"basic\": \"기본 투표\",\n    \"description\": {\n      \"single-choice\": \"각 유권자는 하나의 선택만 선택할 수 있습니다.\",\n      \"approval\": \"각 유권자는 선택의 수만큼 선택할 수 있습니다.\",\n      \"quadratic\": \"각 유권자는 선택의 수에 관계없이 투표권을 분산할 수 있습니다. 결과는 2차로 계산됩니다.\",\n      \"ranked-choice\": \"각 유권자는 원하는 수만큼 선택하고 순위를 매길 수 있습니다. 결과는 즉시 결선 투표 계수 방법으로 계산됩니다.\",\n      \"weighted\": \"각 유권자는 원하는 선택지들에 투표권을 행사 할 수있습니다.\",\n      \"basic\": \"세 가지 선택이 있는 단일 선택 투표: 찬성, 반대 또는 기권\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Privacy\",\n    \"title\": \"Select voting privacy\",\n    \"information\": \"The type of privacy used on proposals. (Enforced on all future proposals)\",\n    \"any\": \"Any\",\n    \"none\": \"None\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Choices are encrypted and only visible once the voting period is over\",\n      \"tooltip\": \"This proposal has Shutter privacy enabled. All votes will be encrypted until the voting period has ended and the final score is calculated\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select voting validation\",\n    \"information\": \"The type of validation used to determine if a user can vote. (Enforced on all future proposals)\",\n    \"any\": {\n      \"label\": \"Anyone can vote\",\n      \"description\": \"Anyone with voting power can cast a vote.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement to vote on this proposal. \"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your proposals from spam and vote manipulation by requiring users to have a Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold} and {operator} of the following stamps to vote on this proposal: {stamps}. \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"현재 결과\",\n    \"currentBond\": \"현재 채권\",\n    \"finalizedIn\": \"확정 {0}\",\n    \"executableIn\": \"{0} 안에 실행가능함\",\n    \"finalOutcome\": \"결과\",\n    \"nextBond\": \"결과를 설정하기 위한 채권\",\n    \"setOutcomeTo\": \"결과를 다음으로 설정\",\n    \"claimBond\": \"채권 청구\",\n    \"addBatch\": \"트랜잭션 일괄 처리 추가\",\n    \"batch\": \"트랜잭션 일괄처리\",\n    \"transactions\": \"트랜잭션\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"잘못된 주소\",\n    \"invalidAmount\": \"잘못된 금액\",\n    \"invalidValue\": \"잘못된 값\",\n    \"invalidAbi\": \"잘못된 ABI\",\n    \"invalidData\": \"잘못된 데이터\",\n    \"value\": \"값 (wei)\",\n    \"data\": \"데이터\",\n    \"noCollectibles\": \"컬렉션 없음\",\n    \"asset\": \"자산\",\n    \"amount\": \"수량\",\n    \"type\": \"유형\",\n    \"transferFunds\": \"자금 전송\",\n    \"transferNFT\": \"NFT 전송\",\n    \"contractInteraction\": \"컨트랙트 상호 작용\",\n    \"rawTransaction\": \"로우 트랜잭션\",\n    \"addTransaction\": \"트랜잭션 추가\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei에 {address}\",\n      \"transferFunds\": \"{amount}{tokenSymbol} 을 {address} 로 전송\",\n      \"transferNFT\": \"{name}#{id} 를 {address} 로 보내기\",\n      \"raw\": \"{amount} wei를 {address} 로 보내기\"\n    },\n    \"labels\": {\n      \"request\": \"요청 실행\",\n      \"setOutcome\": \"결과 설정\",\n      \"changeOutcome\": \"결과 변경\",\n      \"executeTxs\": \"{1} 의 트랜잭션 배치 {0} 실행\",\n      \"executed\": \"모든 트랜잭션이 실행되었습니다.\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"뭔가 잘못되었습니다.\",\n      \"connectWallet\": \"실행 세부 정보를 보려면 지갑을 연결합니다.\",\n      \"switchChain\": \"실행 요청을 위해 지갑을 {0} 로 바꾸세요.\",\n      \"question\": \"이 제안이 통과되었을 때 다음을 충족\",\n      \"criteria\": \"통과 기준?\",\n      \"proposalPassed\": \"제안이 통과되었습니까?\",\n      \"expired\": \"The proposal has expired\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to confirm the proposal passed\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"requestToolTip\": \"This will propose the transactions from this Snapshot vote on-chain. After a challenge window, if the proposal is valid, the transactions can be executed and your bond will be returned.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"이 제안에 대한 POAP이 아직 설정되지 않았습니다.\",\n    \"no_voted_header\": \"POAP를 얻으려면 투표하세요\",\n    \"unclaimed_header\": \"POAP에 투표했습니다.\",\n    \"claimed_header\": \"축하합니다! POAP이 당신의 컬렉션에 발행되었습니다.\",\n    \"loading_header\": \"POAP이 당신의 컬렉션에 발행되고 있습니다.\",\n    \"button_claim\": \"발행\",\n    \"button_show\": \"컬렉션 보기\",\n    \"success_claim\": \"POAP이 컬렉션에 발행되었습니다.\",\n    \"error_claim\": \"토큰 발행에 문제가 발생했습니다.\"\n  },\n  \"progress\": {\n    \"progress\": \"Progress\",\n    \"inProgress\": \"In Progress\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"New Step\",\n    \"description\": \"Description\",\n    \"add\": \"Add\",\n    \"deleteStep\": \"Delete Step\",\n    \"deleteConfirm\": \"Are you sure you want to delete?\",\n    \"delete\": \"Delete\",\n    \"cancel\": \"Cancel\",\n    \"comeBack\": \"Come back after voting is complete to see how this proposal is progressing!\",\n    \"confirmSignature\": \"Signing this message will allow us to authorize your request to update the progress of your proposal.\",\n    \"wentWrong\": \"Oops something went wrong.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"차트\",\n    \"noVotesYet\": \"아직 시각화할 투표가 없습니다.\",\n    \"totalVotesPerDay\": \"하루 총 투표수\",\n    \"shareOfVotingPower\": \"의결권 점유율\",\n    \"votingPowerPerDay\": \"하루 투표권\"\n  },\n  \"comment_box\": {\n    \"title\": \"주석 상자\",\n    \"add\": \"여기에 의견 추가\",\n    \"submit\": \"제출\",\n    \"preview\": \"미리보기\",\n    \"continue_editing\": \"편집 계속\",\n    \"edit\": \"댓글 편집\",\n    \"edit_button\": \"편집\",\n    \"dismiss\": \"취소\",\n    \"delete\": \"삭제\",\n    \"add_reply\": \"댓글 추가\",\n    \"edit_comment\": \"댓글 편집\",\n    \"edit_modal\": \"수정하시겠습니까?\",\n    \"yes\": \"예\",\n    \"no\": \"아니오\",\n    \"delete_comment\": \"댓글 삭제\",\n    \"delete_modal\": \"삭제 하시겠습니까?\",\n    \"error\": \"문제가 발생했습니다.\",\n    \"replies\": \"답글\",\n    \"hide\": \"숨김\",\n    \"show\": \"표시\",\n    \"reply\": \"답변\",\n    \"load_more\": \"더 불러오기\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"스냅샷\",\n      \"setup\": \"공간 만들기\",\n      \"timeline\": \"타임라인\",\n      \"notifications\": \"Notifications\",\n      \"explore\": \"탐색\",\n      \"playground\": \"플레이그라운드\",\n      \"space\": {\n        \"create\": \"{space} 제안 만들기\",\n        \"about\": \"{space} 정보\",\n        \"proposals\": \"{space} 제안\",\n        \"proposal\": \"{space} 제안: {proposal}\",\n        \"settings\": \"{space} 설정\"\n      },\n      \"strategy\": \"{key} 전략\",\n      \"delegate\": \"위임\",\n      \"ranking\": \"Ranking\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"{spaceName} 에서 제안 추적\",\n    \"text\": \"새 제안이 생성되거나 종료될 때마다 알림 수신\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 초 | {n} 초\",\n    \"minute\": \"1 분 | {n} 분\",\n    \"hour\": \"1 시간 | {n} 시간\",\n    \"day\": \"1 일 | {n} 일\",\n    \"week\": \"1 주 | {n} 주\",\n    \"month\": \"1 개월 | {n} 개월\",\n    \"year\": \"1 년 | {n} 년\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"지원되지 않는 네트워크\",\n    \"switchNetworkToNetwork\": \"To continue, you need to change the network in your wallet to {network}.\",\n    \"switchToNetwork\": \"Switch to {network}\",\n    \"goToDemoSite\": \"Go to demo website\"\n  },\n  \"treasury\": {\n    \"title\": \"Treasury\",\n    \"wallets\": {\n      \"title\": \"Wallets\",\n      \"empty\": \"This space doesn't have a treasury yet\",\n      \"addTreasury\": \"Add a treasury\"\n    },\n    \"assets\": {\n      \"title\": \"Assets\",\n      \"empty\": \"There are no assets in this contract\"\n    },\n    \"24hChange\": \"24h change\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Your email\",\n    \"title\": \"Get the latest Snapshot updates\"\n  },\n  \"joinCommunity\": \"Join Snapshot community\",\n  \"header\": {\n    \"title\": \"Where decisions get made\",\n    \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot is a decentralized governance platform that makes it easy to create and vote on proposals - all without spending a fortune on gas fees! Plus, our flexible system supports various voting types and strategies, so you can tailor the voting process to your needs.\",\n    \"subHeader\": \"Governance should be a snap\",\n    \"subDescription\": \"Web3 governance doesn't have to be complicated. Snapshot is the perfect solution for organizations looking for an easy and efficient way to govern their community or organization.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Resources\",\n    \"about\": \"About\",\n    \"blog\": \"Blog\",\n    \"jobs\": \"Jobs\",\n    \"discussions\": \"Discussions\",\n    \"github\": \"GitHub\",\n    \"docs\": \"Docs\",\n    \"support\": \"Support\",\n    \"hiring\": \"Join us!\"\n  }\n}\n"
  },
  {
    "path": "src/locales/languages.json",
    "content": "{\n  \"en-US\": {\n    \"name\": \"English\"\n  },\n  \"zh-CN\": {\n    \"name\": \"Chinese\",\n    \"nativeName\": \"简体中文\"\n  },\n  \"ja-JP\": {\n    \"name\": \"Japanese\",\n    \"nativeName\": \"日本語\"\n  },\n  \"ko-KR\": {\n    \"name\": \"Korean\",\n    \"nativeName\": \"한국어\"\n  },\n  \"fr-FR\": {\n    \"name\": \"French\",\n    \"nativeName\": \"Français\"\n  }\n}\n"
  },
  {
    "path": "src/locales/pt-PT.json",
    "content": "{\n  \"searchPlaceholder\": \"Pesquisar\",\n  \"spaceCount\": \"{0} comunidade(s)\",\n  \"createSpace\": \"Criar comunidade\",\n  \"backToHome\": \"Início\",\n  \"actions\": \"Ações\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"Resultados\",\n  \"resultsError\": \"Results could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"resultsCalculating\": \"Final results are being calculated. If you still see this message after a few minutes contact the space admin.\",\n  \"votingPowerFailedMessage\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"votingValidationFailedMessage\": \"There was an error on our side and we could not verify if you are eligible to vote. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n  \"notValidVoterMessage\": \"Oops, you don't seem to be eligible to vote on this proposal.\",\n  \"getHelp\": \"Get help\",\n  \"retry\": \"Retry\",\n  \"currentResults\": \"Resultados Atuais\",\n  \"reset\": \"Restaurar\",\n  \"close\": \"Close\",\n  \"save\": \"Guardar\",\n  \"author\": \"Autor\",\n  \"next\": \"Seguinte\",\n  \"choice\": \"Choice\",\n  \"submit\": \"Enviar\",\n  \"plugins\": \"Plugins\",\n  \"information\": \"Informações\",\n  \"confirm\": \"Confirmar\",\n  \"snapshot\": \"Snapshot\",\n  \"strategies\": \"Estratégia(s)\",\n  \"strategiesPage\": \"Estratégias\",\n  \"space\": \"Espaço\",\n  \"spaces\": \"Espaços\",\n  \"verifiedSpace\": \"Verified space\",\n  \"warningSpace\": \"This space has been flagged as potentially malicious. Proceed with caution.\",\n  \"version\": \"Versão\",\n  \"timeline\": \"Cronologia\",\n  \"ended\": \"ended\",\n  \"started\": \"started\",\n  \"filters\": \"Filtros\",\n  \"allSpaces\": \"Todos os espaços\",\n  \"submitOnchain\": \"Enviar na cadeia\",\n  \"inSpaces\": \"Em {0} comunidade(s)\",\n  \"votes\": \"Votos\",\n  \"seeMore\": \"Ver mais\",\n  \"seeAll\": \"See all\",\n  \"network\": \"Rede\",\n  \"networks\": \"Redes\",\n  \"skins\": \"Skins\",\n  \"spaceMembers\": \"Members\",\n  \"members\": \"Sem membros, {count} membro, {count} membros\",\n  \"editStrategy\": \"Editar estratégia\",\n  \"invalidProposals\": \"Propostas inválidas\",\n  \"account\": \"Conta\",\n  \"create3box\": \"Criar perfil no 3Box\",\n  \"view3box\": \"Ver perfil no 3Box\",\n  \"edit3box\": \"Editar perfil no 3Box\",\n  \"connectWallet\": \"Conectar carteira\",\n  \"toggleSkin\": \"Toggle skin\",\n  \"about\": \"Sobre nós\",\n  \"license\": \"Licença\",\n  \"showMore\": \"Show more\",\n  \"voted\": \"Voted\",\n  \"reload\": \"Reload\",\n  \"ipfsServer\": \"Protocolo IPFS\",\n  \"hub\": \"Hub\",\n  \"cancel\": \"Cancelar\",\n  \"delete\": \"Delete\",\n  \"demoSite\": \"Este é o site de demonstração, experimente!\",\n  \"removeDelegation\": \"Remover delegação\",\n  \"confirmRemove\": \"Tem certeza que deseja remover sua delegação a\",\n  \"removeSpace\": \"para a comunidade {0}\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum reached\",\n  \"options\": \"Opções\",\n  \"votingPower\": \"Seu poder de voto\",\n  \"comment\": {\n    \"placeholder\": \"Share your reason (optional)\"\n  },\n  \"receipt\": \"Comprovativo\",\n  \"relayer\": \"Retransmissor\",\n  \"verifyOnMycrypto\": \"Verificar comprovativo no MyCrypto\",\n  \"verifyOnSignatorio\": \"Verify on Signator.io\",\n  \"isCore\": \"Core\",\n  \"notificationsBlocked\": \"Your browser is blocking notifications\",\n  \"notificationsNotSupported\": \"Your browser does not support notifications\",\n  \"walletNotSupported\": \"Wallet is not supported\",\n  \"seeInExplorer\": \"See explorer\",\n  \"learnMore\": \"Saber mais\",\n  \"logout\": \"Terminar sessão\",\n  \"continue\": \"Continuar\",\n  \"add\": \"Adicionar\",\n  \"edit\": \"Editar\",\n  \"strategyParameters\": \"Parâmetros de estratégia\",\n  \"addAction\": \"Adicionar ação\",\n  \"removeAction\": \"Remover ação\",\n  \"yourChoice\": \"Opção {0}\",\n  \"targetAddress\": \"Endereço de destino\",\n  \"value\": \"Valor\",\n  \"date\": \"Dados\",\n  \"marketDetails\": \"Detalhes do mercado\",\n  \"addMarket\": \"Adicionar mercado\",\n  \"selectNetwork\": \"Selecionar rede\",\n  \"conditionId\": \"Condition ID\",\n  \"basetokenAddress\": \"Endereço base do token\",\n  \"quoteAddress\": \"Cotar endereço da moeda\",\n  \"removeMarket\": \"Excluir mercado\",\n  \"back\": \"Anterior\",\n  \"loading\": \"A carregar...\",\n  \"predictedImpact\": \"Impacto previsto\",\n  \"marketSymbol\": \"{0} mercado\",\n  \"twoChoicesRequired\": \"Duas opções são necessárias para este plugin.\",\n  \"noResultsFound\": \"Ops, não encontramos nenhum resultado\",\n  \"createFirstProposal\": \"Let's create your first proposal\",\n  \"noSpacesJoined\": \"Ops, você ainda não entrou em nenhuma comunidade\",\n  \"addFavorites\": \"Adicionar favoritos\",\n  \"createdBy\": \"Por {0}\",\n  \"startIn\": \"iniciar: {0}\",\n  \"endIn\": \"terminar {0}\",\n  \"proposalTimeLeft\": \"{0} esquerda\",\n  \"endedAgo\": \"finalizado {0}\",\n  \"proposalBy\": \"por {0}\",\n  \"endDate\": \"termina {0}\",\n  \"contentHash\": \"Hash do conteúdo\",\n  \"defaultSkin\": \"Skin padrão\",\n  \"select\": \"Selecionar\",\n  \"language\": \"Idioma\",\n  \"agree\": \"Eu concordo\",\n  \"moderators\": \"Moderators\",\n  \"playground\": \"Parque infantil\",\n  \"strategyParams\": \"Parâmetros de estratégia\",\n  \"addresses\": \"Moradas\",\n  \"networkErrorPlayground\": \"Erro de rede - por favor, abra o console do seu navegador para mais informações\",\n  \"upload\": \"Upload\",\n  \"join\": \"Junte-se\",\n  \"joined\": \"Juntou-se\",\n  \"leave\": \"Sair\",\n  \"subspaces\": \"Sub-spaces\",\n  \"mainspace\": \"Main space\",\n  \"copyLink\": \"Copiar link\",\n  \"duplicate\": \"Duplicate\",\n  \"joinedSpaces\": \"Juntou espaços\",\n  \"joinSpaces\": \"Juntar-se aos espaços\",\n  \"setDelegationToSpace\": \"Limit delegation to a specific space\",\n  \"theCurrentNetwork\": \"the current network\",\n  \"optional\": \"(optional)\",\n  \"homeLoadmore\": \"Load more\",\n  \"confirmAction\": \"Confirm action\",\n  \"or\": \"or\",\n  \"share\": \"Share\",\n  \"shareOnTwitter\": \"Share on Twitter\",\n  \"shareOnLenster\": \"Share on Lenster\",\n  \"createButton\": \"Create\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Change wallet\",\n  \"createASpace\": \"Create a space\",\n  \"getStarted\": \"Get started\",\n  \"skip\": \"Skip\",\n  \"newSpaceNotice\": {\n    \"header\": \"Your space is live!\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Learn more in the {documentation} or join Snapshot {discord} for help.\",\n    \"gotIt\": \"Got it!\"\n  },\n  \"errors\": {\n    \"required\": \"O campo é obrigatório\",\n    \"minLength\": \"Field is required\",\n    \"maxLength\": \"O comprimento máximo é {0}\",\n    \"pattern\": \"Caracteres Inválidos\",\n    \"minItems\": \"Mínimo {0} item(s) obrigatório(s)\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"minStrategy\": \"É necessária pelo menos uma estratégia.\",\n    \"website\": \"URL should be in the format https://www.example.com\",\n    \"format\": \"Formato inválido\",\n    \"type\": \"Contexto\",\n    \"unsupportedImageType\": \"File type not supported, Supported formats are jpeg, jpg and png\",\n    \"invalidAddress\": \"Invalid address\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Title\",\n    \"discussion\": \"Discussion (optional)\",\n    \"categorie(s)\": \"Select up to 2 categorie(s)\",\n    \"proposalDescription\": \"Description (optional)\",\n    \"preview\": \"Pré-visualizar\",\n    \"choices\": \"Opções\",\n    \"addChoice\": \"Adicionar opção\",\n    \"startDate\": \"Seleciona uma data de início\",\n    \"endDate\": \"Selecionar Data Final\",\n    \"startTime\": \"Selecionar horário de início\",\n    \"endTime\": \"Selecionar horário de término\",\n    \"publish\": \"Publicar\",\n    \"untitled\": \"Untitled\",\n    \"snapshotBlock\": \"Número de bloco Snapshot\",\n    \"voting\": \"Voting\",\n    \"votingSystem\": \"Voting system\",\n    \"choice\": \"Choice {0}\",\n    \"period\": \"Voting period\",\n    \"start\": \"Start\",\n    \"end\": \"End\",\n    \"days\": \"Days\",\n    \"hours\": \"Hours\",\n    \"minutes\": \"Minutes\",\n    \"schedule\": \"Schedule proposal\",\n    \"delayEnforced\": \"The space enforces a delay until voting can start\",\n    \"periodEnforced\": \"The space enforces the duration of the voting period\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"Edit\",\n    \"continue\": \"Continue\",\n    \"now\": \"Now\",\n    \"votingPeriodExplainer\": \"This is the time period in which users will be able to vote. The proposal will be visible and pending before the start of the voting period.\",\n    \"uploadImageExplainer\": \"Attach images by dragging & dropping, selecting or pasting them.\",\n    \"uploading\": \"Uploading image\",\n    \"markdown\": \"Styling with Markdown is supported\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"Você precisa ser um autor deste espaço para poder submeter uma proposta.\",\n        \"minScore\": \"Você precisa ter um mínimo de {0} {1} para submeter uma proposta.\"\n      },\n      \"customValidation\": \"Você precisa passar a validação da proposta para submeter uma proposta.\",\n      \"executionError\": \"Verifying your eligibility to create proposals in this space has failed. This is likely due to a misconfigured strategy.\"\n    },\n    \"errorGettingSnapshot\": \"We encountered an error while fetching the snapshot block number which is needed to calculate your voting power. Please try again later.\"\n  },\n  \"delegate\": {\n    \"header\": \"Delegar\",\n    \"selectDelegate\": \"Selecionar delegado\",\n    \"to\": \"Para\",\n    \"addressPlaceholder\": \"Nome do endereço ou ENS\",\n    \"delegations\": \"Sua(s) delegação(ões)\",\n    \"allSpaces\": \"Para todos os espaços\",\n    \"delegated\": \"Delegado a você\",\n    \"pendingTransaction\": \"sem transações pendentes | 1 transação pendente | {count} transações pendentes\",\n    \"topDelegates\": \"Principais delegados\",\n    \"noDelegatesFoundFor\": \"No delegates found for {0}\",\n    \"noValidEns\": \"Not a valid ENS address.\",\n    \"noValidAddress\": \"Not a valid address\",\n    \"delegateToSelf\": \"You cannot delegate to yourself\",\n    \"delegateToSelfAddress\": \"You cannot delegate to your own ENS address\",\n    \"noValidSpaceId\": \"Not a valid space ID\",\n    \"noDelegationsAndDelegates\": \"Can't find your delegations and delegates? Make sure you are connected to the correct network.\",\n    \"delegateNotSupported\": \"Delegation is currently not supported for {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Votar\",\n    \"vote\": \"Votar\",\n    \"startDate\": \"Data de início\",\n    \"endDate\": \"Data final\",\n    \"votingSystem\": \"Sistema de votação\",\n    \"privacy\": \"Privacy\",\n    \"invalidChoice\": \"Invalid choice\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Your vote is in!\",\n      \"gnosisSafeTitle\": \"Your vote is pending...\",\n      \"gnosisSafeDescription\": \" Votes with a Safe require additional signers and will be visible once the transaction is confirmed\",\n      \"seeQueue\": \"See queued transactions\",\n      \"tips\": {\n        \"1\": \"Votes can be changed while the proposal is active\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"Propostas\",\n    \"new\": \"Nova proposta\",\n    \"noProposals\": \"Ainda não há nenhuma proposta aqui!\",\n    \"createProposal\": \"Create proposal\",\n    \"showMore\": \"Show more\",\n    \"showLess\": \"Show less\",\n    \"states\": {\n      \"all\": \"Todas\",\n      \"core\": \"Core\",\n      \"community\": \"Comunidade\",\n      \"active\": \"Ativas\",\n      \"pending\": \"Pendente\",\n      \"closed\": \"Encerradas\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Notifications\",\n    \"noNotifications\": \"You have no notifications\",\n    \"proposalStarted\": \"proposal has started:\",\n    \"proposalEnded\": \"proposal has ended:\",\n    \"markAllAsRead\": \"Mark all as read\",\n    \"all\": \"All\",\n    \"unread\": \"Unread\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"join\",\n    \"actionCreate\": \"create a proposal in\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"Definições\",\n    \"editController\": \"Edit controller\",\n    \"connectWithSpaceOwner\": \"You are in view only mode, to modify space settings connect with a controller or admin wallet.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"edit the space settings\",\n      \"create\": \"create a proposal\",\n      \"vote\": \"vote on this proposal\"\n    },\n    \"currentSpaceControllerIs\": \"The current space controller is {address}\",\n    \"newController\": \"New controller\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Set\",\n    \"profile\": \"Perfil\",\n    \"avatar\": \"Imagem de Perfil\",\n    \"name\": {\n      \"label\": \"Name\",\n      \"placeholder\": \"e.g. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"About\",\n      \"placeholder\": \"What is your organisation about?\"\n    },\n    \"categories\": {\n      \"label\": \"Categorie(s)\",\n      \"select\": \"Select categorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Terms of service\",\n      \"information\": \"Users will be required to accept these terms once before they can create a proposal or cast a vote\"\n    },\n    \"hideSpace\": \"Ocultar comunidade da página inicial\",\n    \"links\": \"Social accounts\",\n    \"subspaces\": {\n      \"label\": \"Sub-spaces\",\n      \"information\": \"Sub-spaces will only be shown once configured on both the main space and the sub-space(s). {docs}\",\n      \"parent\": {\n        \"label\": \"Main space\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"The space that this space is a sub-space of will be displayed on the space page\"\n      },\n      \"children\": {\n        \"label\": \"Sub-spaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Related Sub-spaces listed here will be displayed on the space page\"\n      }\n    },\n    \"website\": \"Website\",\n    \"strategies\": {\n      \"label\": \"Strategie(s)\",\n      \"information\": \"Strategies are used to determine voting power or whether a user is eligible to create a proposal\"\n    },\n    \"network\": {\n      \"label\": \"Network\",\n      \"information\": \"The defaul network used for this space. Networks can also be specified in individual strategies\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbol\",\n      \"information\": \"The default symbol used for this space, usually the token symbol i.e. BAL for Balancer\"\n    },\n    \"strategiesList\": \"Select up to 8 strategies\",\n    \"votingPowerIsCumulative\": \"Voting power is cumulative\",\n    \"addStrategy\": \"Adicionar estratégia\",\n    \"testInPlayground\": \"Test in playground\",\n    \"admins\": {\n      \"label\": \"Admins\",\n      \"information\": \"Admins are able to modify the space settings and manage the space's proposals\"\n    },\n    \"authors\": {\n      \"label\": \"Authors\",\n      \"information\": \"Authors are always able to create proposals\"\n    },\n    \"proposalValidation\": \"Validação da proposta\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Threshold\",\n      \"information\": \"The minimum amount of voting power required to create a proposal\"\n    },\n    \"allowOnlyAuthors\": \"Permitir apenas autores para submeter a proposta\",\n    \"editValidation\": \"Editar validação\",\n    \"selectValidation\": \"Selecionar validação\",\n    \"validationParameters\": \"Parâmetros de validação\",\n    \"voting\": \"Votar\",\n    \"votingDelay\": \"Atraso na votação\",\n    \"votingPeriod\": \"Período de votação\",\n    \"hours\": \"Hours\",\n    \"days\": \"Days\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"The minimum amount of voting power required for the proposal to pass\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"The type of voting system used for this space. (Enforced on all future proposals)\"\n    },\n    \"anyType\": \"Qualquer\",\n    \"hideAbstain\": \"Ignore abstain votes in basic voting results\",\n    \"customDomain\": \"Domínio Personalizado\",\n    \"domain\": {\n      \"label\": \"Domain name\",\n      \"placeholder\": \"e.g. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"Skin\",\n    \"treasuries\": {\n      \"label\": \"Treasury\",\n      \"add\": \"Add treasury\",\n      \"edit\": \"Edit treasury\",\n      \"information\": \"Add treasuries of your organization to show them in your space\"\n    },\n    \"addPlugin\": \"Adicionar plugin\",\n    \"editPlugin\": \"Editar plugin\",\n    \"pluginParameters\": \"Parâmetros do plugin\",\n    \"proposal\": {\n      \"title\": \"Proposal\",\n      \"guidelines\": {\n        \"title\": \"Guidelines\",\n        \"information\": \"Display a link to your guidelines on proposal creation to help users understand what constitutes a good/valid proposal\"\n      },\n      \"template\": {\n        \"title\": \"Template\",\n        \"information\": \"Start every proposal with a template to help users understand what information is required\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"p. ex. yam.eth\",\n    \"chooseExistingEns\": \"Choose one of your existing ENS domains to create a space with:\",\n    \"useSingleExistingEns\": \"Use your existing ENS domain:\",\n    \"orRegisterNewEns\": \"Or register a new domain:\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"To create a space, you first need an ENS domain. Enter one below and follow the ENS registration instructions.\",\n    \"createASpace\": \"Criar comunidade\",\n    \"registerEnsButton\": \"Register\",\n    \"supportedEnsTLDs\": \"Supported domain endings\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or join Snapshot {discord}.\",\n    \"setSpaceController\": \"Space controller\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \" The space controller is the account that will be able to manage the space settings. Additional space controllers (admins) can be added later.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"When creating a space with a Gnosis Safe it's recommended to set the safe address as the space controller. If you don't, you need to follow some additional steps. {link}\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"Setting the controller requires a transaction on the {network} which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choose ENS address\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. {address}\",\n    \"controllerAddress\": \"Controller address\",\n    \"updateController\": \"Update controller\",\n    \"seeOnEns\": \"See on ENS\",\n    \"goToSettings\": \"Go to settings\",\n    \"setSpaceProfile\": \"Customize your space\",\n    \"waitForTransaction\": \"The transaction need to confirm before you can create your space. {txUrl}\",\n    \"pleaseWaitMessage\": \"This can take a few minutes, please wait while the transaction is being confirmed\",\n    \"notControllerAddress\": \"Please connect with the controller address {wallet} to create the space.\",\n    \"fillCurrentAccount\": \"Use currently logged in account\",\n    \"domain\": {\n      \"title\": \"Setup your space domain\",\n      \"ensMessage\": \" One thing you need before you can create your own space, is an ENS domain on Ethereum mainnet.\",\n      \"ensMessageTestnet\": \"You can also {link} on the Goerli testnet and mess with things there first.\",\n      \"tryDemo\": \"try the demo\",\n      \"yourExistingSpaces\": \"Your existing spaces\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"How would you like to setup your voting strategy?\",\n      \"subtitle\": \"You can change your strategy settings any time.\",\n      \"blockTitle\": \"Setup voting strategy\",\n      \"onePersonOneVote\": {\n        \"title\": \"One person, one vote\",\n        \"description\": \"Manage a whitelist of people who can vote or simply allow any address to vote. Every vote is equal and no token is required\",\n        \"whitelistInformation\": \"Specify a number of accounts that can vote\",\n        \"ticketInformation\": \"Any account can vote\",\n        \"votesEqualInfo\": \"Each vote is equal and no token is required\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Token weighted voting\",\n        \"description\": \"Votes are weighted by a token. The token can be an ERC-20, ERC-721 or ERC-1155 token standard\",\n        \"tokenNotFound\": \"Token not found\",\n        \"seeOnEtherscan\": \"See on Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Custom setup\",\n        \"description\": \"Select up to 8 strategies with a wide range of options. If you can't find the right strategy for your use case, you can create your own\"\n      }\n    },\n    \"validationTitle\": \"Who can manage this space and create proposals?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Edit profile\",\n    \"viewProfile\": \"View profile\",\n    \"about\": {\n      \"header\": \"About\",\n      \"joinedSpaces\": \"Joined spaces\",\n      \"createdSpaces\": \"Created spaces\",\n      \"biography\": \"Bio\",\n      \"notJoinSpacesYet\": \"Hasn't joined any spaces yet\",\n      \"notCreatedSpacesYet\": \"Hasn't created any spaces yet\",\n      \"delegatorNetworkInfo\": \"Change by switching network in your wallet\",\n      \"delegate\": \"Delegate\",\n      \"delegated\": \"Delegated\",\n      \"delegateTo\": \"Delegate to\",\n      \"delegateFor\": \"Delegator for\",\n      \"noDelegatorsMessage\": \"No delegators on {network}\",\n      \"notSupportedNetwork\": \"Delegation currently isn't supported on {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activity\",\n      \"votedFor\": \"Voted {choice}\",\n      \"today\": \"Today\",\n      \"thisWeek\": \"This week\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"No activity yet\"\n    },\n    \"settings\": {\n      \"header\": \"Edit profile\",\n      \"name\": \"Name\",\n      \"biography\": \"Bio\",\n      \"namePlaceholder\": \"Enter name\",\n      \"bioPlaceholder\": \"Tell your story\",\n      \"change\": \"Change\",\n      \"remove\": \"Remove\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"Conseguiu!\",\n    \"copied\": \"Copiado!\",\n    \"proposalDeleted\": \"Proposta excluída\",\n    \"somethingWentWrong\": \"Ops, algo correu mal!\",\n    \"saved\": \"Guardado!\",\n    \"delegationSuccess\": \"Delegação bem sucedida\",\n    \"delegationRemoved\": \"Delegação removida\",\n    \"proposalCreated\": \"Proposta criada\",\n    \"voteSuccessful\": \"Seu voto foi processado!\",\n    \"ensSet\": \"ENS text record was successfully set\",\n    \"transactionSent\": \"Transaction sent\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Criar estratégia\",\n    \"createSkin\": \"Criar skin\",\n    \"addNetwork\": \"Adicionar rede\",\n    \"createPlugin\": \"Criar plug-in\",\n    \"strategies\": \"estratégia(s)\",\n    \"skins\": \"skin(s)\",\n    \"networks\": \"rede(s)\",\n    \"plugins\": \"plugin(s)\",\n    \"results\": \"resultado(s)\",\n    \"category\": \"Category\",\n    \"categories\": {\n      \"all\": \"All\",\n      \"protocol\": \"Protocol\",\n      \"social\": \"Social\",\n      \"investment\": \"Investment\",\n      \"grant\": \"Grant\",\n      \"service\": \"Service\",\n      \"media\": \"Media\",\n      \"creator\": \"Creator\",\n      \"collector\": \"Collector\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Selecionar sistema de votação\",\n    \"single-choice\": \"Voto de escolha única\",\n    \"approval\": \"Votação de aprovação\",\n    \"quadratic\": \"Voto em Quadrático\",\n    \"ranked-choice\": \"Votação por escolha ranqueada\",\n    \"weighted\": \"Votação ponderada\",\n    \"basic\": \"Basic voting\",\n    \"description\": {\n      \"single-choice\": \"Cada eleitor pode selecionar apenas uma opção.\",\n      \"approval\": \"Cada eleitor pode selecionar qualquer número de opções.\",\n      \"quadratic\": \"Cada eleitor pode distribuir o poder de voto por qualquer número de escolhas. Os resultados são calculados quadraticamente.\",\n      \"ranked-choice\": \"Cada eleitor pode selecionar e classificar qualquer número de escolhas. Os resultados são calculados pelo método de contagem instantânea.\",\n      \"weighted\": \"Cada eleitor pode espalhar o poder de voto em qualquer número de escolhas.\",\n      \"basic\": \"Single choice voting with three choices: For, Against or Abstain\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Privacy\",\n    \"title\": \"Select voting privacy\",\n    \"information\": \"The type of privacy used on proposals. (Enforced on all future proposals)\",\n    \"any\": \"Any\",\n    \"none\": \"None\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Choices are encrypted and only visible once the voting period is over\",\n      \"tooltip\": \"This proposal has Shutter privacy enabled. All votes will be encrypted until the voting period has ended and the final score is calculated\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select voting validation\",\n    \"information\": \"The type of validation used to determine if a user can vote. (Enforced on all future proposals)\",\n    \"any\": {\n      \"label\": \"Anyone can vote\",\n      \"description\": \"Anyone with voting power can cast a vote.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement to vote on this proposal. \"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your proposals from spam and vote manipulation by requiring users to have a Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold} and {operator} of the following stamps to vote on this proposal: {stamps}. \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Resultado atual\",\n    \"currentBond\": \"Vínculo atual\",\n    \"finalizedIn\": \"{0} finalizado\",\n    \"executableIn\": \"Executável {0}\",\n    \"finalOutcome\": \"Resultado\",\n    \"nextBond\": \"Contexto\",\n    \"setOutcomeTo\": \"Definir resultado para\",\n    \"claimBond\": \"Alegar bond\",\n    \"addBatch\": \"Adicionar lote de transação\",\n    \"batch\": \"Lote de transação\",\n    \"transactions\": \"Transações\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"Adresso inválido\",\n    \"invalidAmount\": \"Quantia inválida\",\n    \"invalidValue\": \"Valor inválido\",\n    \"invalidAbi\": \"ABI inválido\",\n    \"invalidData\": \"Dados Inválidos\",\n    \"value\": \"Valor (wei)\",\n    \"data\": \"Dados\",\n    \"noCollectibles\": \"Não colecionáveis\",\n    \"asset\": \"Ativo\",\n    \"amount\": \"Contexto\",\n    \"type\": \"Tipo\",\n    \"transferFunds\": \"Transferir Fundos\",\n    \"transferNFT\": \"Transferir NFT\",\n    \"contractInteraction\": \"interação do contrato\",\n    \"rawTransaction\": \"Raw transaction\",\n    \"addTransaction\": \"Add transaction\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei to {address}\",\n      \"transferFunds\": \"Transfer {amount} {tokenSymbol} to {address}\",\n      \"transferNFT\": \"Send {name} #{id} to {address}\",\n      \"raw\": \"Send {amount} wei to {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Requisitar execução\",\n      \"setOutcome\": \"Definir resultado para\",\n      \"changeOutcome\": \"Alterar resultado\",\n      \"executeTxs\": \"Execute transaction batch {0} of {1}\",\n      \"executed\": \"All transactions have been executed\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"Something went wrong\",\n      \"connectWallet\": \"Connect wallet to see execution details\",\n      \"switchChain\": \"Switch your wallet to {0} to request execution\",\n      \"question\": \"Esta proposta foi aprovada e atende aos\",\n      \"criteria\": \"Critérios de Aceitação?\",\n      \"proposalPassed\": \"Did the proposal pass?\",\n      \"expired\": \"The proposal has expired\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to confirm the proposal passed\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"requestToolTip\": \"This will propose the transactions from this Snapshot vote on-chain. After a challenge window, if the proposal is valid, the transactions can be executed and your bond will be returned.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"Um POAP ainda não foi configurado para esta proposta :'(\",\n    \"no_voted_header\": \"Vote para obter este POAP\",\n    \"unclaimed_header\": \"Mint your I voted POAP\",\n    \"claimed_header\": \"Congratulations! The POAP has been minted to your collection\",\n    \"loading_header\": \"The POAP is being minted to your collection\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"Browse collection\",\n    \"success_claim\": \"The POAP has been minted to your collection\",\n    \"error_claim\": \"Ocorreu um problema ao cunhar o token\"\n  },\n  \"progress\": {\n    \"progress\": \"Progress\",\n    \"inProgress\": \"In Progress\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"New Step\",\n    \"description\": \"Description\",\n    \"add\": \"Add\",\n    \"deleteStep\": \"Delete Step\",\n    \"deleteConfirm\": \"Are you sure you want to delete?\",\n    \"delete\": \"Delete\",\n    \"cancel\": \"Cancel\",\n    \"comeBack\": \"Come back after voting is complete to see how this proposal is progressing!\",\n    \"confirmSignature\": \"Signing this message will allow us to authorize your request to update the progress of your proposal.\",\n    \"wentWrong\": \"Oops something went wrong.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"Charts\",\n    \"noVotesYet\": \"There are no votes to visualize yet.\",\n    \"totalVotesPerDay\": \"Total votes per day\",\n    \"shareOfVotingPower\": \"Share of voting power\",\n    \"votingPowerPerDay\": \"Voting power per day\"\n  },\n  \"comment_box\": {\n    \"title\": \"Comment box\",\n    \"add\": \"Add your comment here\",\n    \"submit\": \"Submit\",\n    \"preview\": \"Preview\",\n    \"continue_editing\": \"Continue editing\",\n    \"edit\": \"Edit your reply here\",\n    \"edit_button\": \"Edit\",\n    \"dismiss\": \"Dismiss\",\n    \"delete\": \"Delete\",\n    \"add_reply\": \"Add your reply here\",\n    \"edit_comment\": \"Edit comment\",\n    \"edit_modal\": \"Are you sure you want to edit?\",\n    \"yes\": \"Yes\",\n    \"no\": \"No\",\n    \"delete_comment\": \"Delete comment\",\n    \"delete_modal\": \"Are you sure you want to delete?\",\n    \"error\": \"Oops, something went wrong\",\n    \"replies\": \"replies\",\n    \"hide\": \"Hide\",\n    \"show\": \"Show\",\n    \"reply\": \"Reply\",\n    \"load_more\": \"Load more\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Create a space\",\n      \"timeline\": \"Timeline\",\n      \"notifications\": \"Notifications\",\n      \"explore\": \"Explore\",\n      \"playground\": \"Playground\",\n      \"space\": {\n        \"create\": \"Create {space} proposal\",\n        \"about\": \"About {space}\",\n        \"proposals\": \"{space} Proposals\",\n        \"proposal\": \"{space} proposal: {proposal}\",\n        \"settings\": \"{space} Settings\"\n      },\n      \"strategy\": \"{key} strategy\",\n      \"delegate\": \"Delegate\",\n      \"ranking\": \"Ranking\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Track proposals for {spaceName}\",\n    \"text\": \"Receive notifications every time a new proposal is created or ends\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 segundo | {n} segundos\",\n    \"minute\": \"1 minuto | {n} minutos\",\n    \"hour\": \"1 hour | {n} horas\",\n    \"day\": \"1 dia | {n} dias\",\n    \"week\": \"1 semana | {n} semanas\",\n    \"month\": \"1 mês | {n} meses\",\n    \"year\": \"1 ano | {n} anos\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Unsupported network\",\n    \"switchNetworkToNetwork\": \"To continue, you need to change the network in your wallet to {network}.\",\n    \"switchToNetwork\": \"Switch to {network}\",\n    \"goToDemoSite\": \"Go to demo website\"\n  },\n  \"treasury\": {\n    \"title\": \"Treasury\",\n    \"wallets\": {\n      \"title\": \"Wallets\",\n      \"empty\": \"This space doesn't have a treasury yet\",\n      \"addTreasury\": \"Add a treasury\"\n    },\n    \"assets\": {\n      \"title\": \"Assets\",\n      \"empty\": \"There are no assets in this contract\"\n    },\n    \"24hChange\": \"24h change\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Your email\",\n    \"title\": \"Get the latest Snapshot updates\"\n  },\n  \"joinCommunity\": \"Join Snapshot community\",\n  \"header\": {\n    \"title\": \"Where decisions get made\",\n    \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot is a decentralized governance platform that makes it easy to create and vote on proposals - all without spending a fortune on gas fees! Plus, our flexible system supports various voting types and strategies, so you can tailor the voting process to your needs.\",\n    \"subHeader\": \"Governance should be a snap\",\n    \"subDescription\": \"Web3 governance doesn't have to be complicated. Snapshot is the perfect solution for organizations looking for an easy and efficient way to govern their community or organization.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Resources\",\n    \"about\": \"About\",\n    \"blog\": \"Blog\",\n    \"jobs\": \"Jobs\",\n    \"discussions\": \"Discussions\",\n    \"github\": \"GitHub\",\n    \"docs\": \"Docs\",\n    \"support\": \"Support\",\n    \"hiring\": \"Join us!\"\n  }\n}\n"
  },
  {
    "path": "src/locales/ro-RO.json",
    "content": "{\n  \"searchPlaceholder\": \"Caută\",\n  \"spaceCount\": \"{0} spațiu(i)\",\n  \"createSpace\": \"Creează un spațiu\",\n  \"backToHome\": \"Prima Pagină\",\n  \"actions\": \"Acţiuni\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"Rezultate\",\n  \"resultsError\": \"Results could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"resultsCalculating\": \"Final results are being calculated. If you still see this message after a few minutes contact the space admin.\",\n  \"votingPowerFailedMessage\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"votingValidationFailedMessage\": \"There was an error on our side and we could not verify if you are eligible to vote. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n  \"notValidVoterMessage\": \"Oops, you don't seem to be eligible to vote on this proposal.\",\n  \"getHelp\": \"Solicită ajutor\",\n  \"retry\": \"Retry\",\n  \"currentResults\": \"Rezultatele actuale\",\n  \"reset\": \"Resetează\",\n  \"close\": \"Close\",\n  \"save\": \"Salvează\",\n  \"author\": \"Autor\",\n  \"next\": \"Continuare\",\n  \"choice\": \"Choice\",\n  \"submit\": \"Trimite\",\n  \"plugins\": \"Plugin-uri\",\n  \"information\": \"Informaţii\",\n  \"confirm\": \"Confirmă\",\n  \"snapshot\": \"Snapshot\",\n  \"strategies\": \"Strategie(i)\",\n  \"strategiesPage\": \"Strategii\",\n  \"space\": \"Spaţiu\",\n  \"spaces\": \"Spaţii\",\n  \"verifiedSpace\": \"Spațiu verificat\",\n  \"warningSpace\": \"This space has been flagged as potentially malicious. Proceed with caution.\",\n  \"version\": \"Versiune\",\n  \"timeline\": \"Cronologie\",\n  \"ended\": \"ended\",\n  \"started\": \"started\",\n  \"filters\": \"Filtre\",\n  \"allSpaces\": \"Toate spațiile\",\n  \"submitOnchain\": \"Trimite in lanț\",\n  \"inSpaces\": \"În {0} spațiu(i)\",\n  \"votes\": \"Voturi\",\n  \"seeMore\": \"Vezi mai multe\",\n  \"seeAll\": \"See all\",\n  \"network\": \"Rețea\",\n  \"networks\": \"Reţele\",\n  \"skins\": \"Skin-uri\",\n  \"spaceMembers\": \"Members\",\n  \"members\": \"Niciun membru | {count} membru |\\n {count} membri\",\n  \"editStrategy\": \"Modifică strategia\",\n  \"invalidProposals\": \"Propuneri invalide\",\n  \"account\": \"Cont\",\n  \"create3box\": \"Crează profil în 3Box\",\n  \"view3box\": \"Vezi profilul în 3Box\",\n  \"edit3box\": \"Modifică profilul în 3Box\",\n  \"connectWallet\": \"Conectează portofel\",\n  \"toggleSkin\": \"Comută skin\",\n  \"about\": \"Despre\",\n  \"license\": \"Licență\",\n  \"showMore\": \"Show more\",\n  \"voted\": \"Voted\",\n  \"reload\": \"Reload\",\n  \"ipfsServer\": \"Server IPFS\",\n  \"hub\": \"Hub\",\n  \"cancel\": \"Anulează\",\n  \"delete\": \"Delete\",\n  \"demoSite\": \"Acesta este site-ul demo, încercă-l!\",\n  \"removeDelegation\": \"Elimină delegația\",\n  \"confirmRemove\": \"Ești sigur că vrei să elimini delegația către\",\n  \"removeSpace\": \"pentru spațiul {0}\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum reached\",\n  \"options\": \"Opțiunea(i)\",\n  \"votingPower\": \"Puterea ta de vot\",\n  \"comment\": {\n    \"placeholder\": \"Share your reason (optional)\"\n  },\n  \"receipt\": \"Chitanță\",\n  \"relayer\": \"Relayer\",\n  \"verifyOnMycrypto\": \"Verifică chitanța pe MyCrypto\",\n  \"verifyOnSignatorio\": \"Verifică pe Signator.io\",\n  \"isCore\": \"Nucleu\",\n  \"notificationsBlocked\": \"Browserul tău blochează notificările\",\n  \"notificationsNotSupported\": \"Browserul tău nu acceptă notificări\",\n  \"walletNotSupported\": \"Portofelul nu este suportat\",\n  \"seeInExplorer\": \"See explorer\",\n  \"learnMore\": \"Află mai multe\",\n  \"logout\": \"Deconectează-te\",\n  \"continue\": \"Continuă\",\n  \"add\": \"Adaugă\",\n  \"edit\": \"Modifică\",\n  \"strategyParameters\": \"Parametrii strategiei\",\n  \"addAction\": \"Adaugă acţiune\",\n  \"removeAction\": \"Elimină acțiune\",\n  \"yourChoice\": \"Alege {0}\",\n  \"targetAddress\": \"Adresa destinației\",\n  \"value\": \"Valoare\",\n  \"date\": \"Date\",\n  \"marketDetails\": \"Detalii piață\",\n  \"addMarket\": \"Adaugă piață\",\n  \"selectNetwork\": \"Selectează rețea\",\n  \"conditionId\": \"Condiție ID\",\n  \"basetokenAddress\": \"Adresa token de bază\",\n  \"quoteAddress\": \"Adresa monedei de ofertă\",\n  \"removeMarket\": \"Elimină piață\",\n  \"back\": \"Înapoi\",\n  \"loading\": \"Se încarcă...\",\n  \"predictedImpact\": \"Impact estimat\",\n  \"marketSymbol\": \"{0} piață\",\n  \"twoChoicesRequired\": \"Pentru acest plugin sunt necesare două opţiuni.\",\n  \"noResultsFound\": \"Ups, nu găsim niciun rezultat\",\n  \"createFirstProposal\": \"Să creăm prima ta propunere\",\n  \"noSpacesJoined\": \"Ups, nu te-ai alăturat încă niciunui spațiu\",\n  \"addFavorites\": \"Adaugă la favorite\",\n  \"createdBy\": \"De către {0}\",\n  \"startIn\": \"pornește {0}\",\n  \"endIn\": \"sfârșit {0}\",\n  \"proposalTimeLeft\": \"{0} rămas\",\n  \"endedAgo\": \"încheiat {0}\",\n  \"proposalBy\": \"de către {0}\",\n  \"endDate\": \"sfârșit {0}\",\n  \"contentHash\": \"Hash conținut\",\n  \"defaultSkin\": \"Skin implicit\",\n  \"select\": \"Selectează\",\n  \"language\": \"Limbă\",\n  \"agree\": \"Sunt de acord\",\n  \"moderators\": \"Moderators\",\n  \"playground\": \"Teren de joacă\",\n  \"strategyParams\": \"Parametrii strategiei\",\n  \"addresses\": \"Adrese\",\n  \"networkErrorPlayground\": \"Eroare de rețea - te rugam sa deschizi consola browser-ului pentru mai multe informatii\",\n  \"upload\": \"Încarcă\",\n  \"join\": \"Alătură-te\",\n  \"joined\": \"Alăturat(ă)\",\n  \"leave\": \"Părăsește\",\n  \"subspaces\": \"Sub-spaces\",\n  \"mainspace\": \"Main space\",\n  \"copyLink\": \"Copiază link\",\n  \"duplicate\": \"Duplicate\",\n  \"joinedSpaces\": \"Spații alăturate\",\n  \"joinSpaces\": \"Alătura-te la spații\",\n  \"setDelegationToSpace\": \"Limitarea delegării către un anumit spațiu\",\n  \"theCurrentNetwork\": \"rețeaua actuală\",\n  \"optional\": \"(optional)\",\n  \"homeLoadmore\": \"Încarcă mai multe\",\n  \"confirmAction\": \"Confirm action\",\n  \"or\": \"or\",\n  \"share\": \"Share\",\n  \"shareOnTwitter\": \"Share on Twitter\",\n  \"shareOnLenster\": \"Share on Lenster\",\n  \"createButton\": \"Create\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Change wallet\",\n  \"createASpace\": \"Create a space\",\n  \"getStarted\": \"Get started\",\n  \"skip\": \"Skip\",\n  \"newSpaceNotice\": {\n    \"header\": \"Your space is live!\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Learn more in the {documentation} or join Snapshot {discord} for help.\",\n    \"gotIt\": \"Got it!\"\n  },\n  \"errors\": {\n    \"required\": \"Câmp obligatoriu\",\n    \"minLength\": \"Field is required\",\n    \"maxLength\": \"Lungime maximă de {0}\",\n    \"pattern\": \"Caracter invalid\",\n    \"minItems\": \"Minim {0} caracter(e) necesar(e)\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"minStrategy\": \"Este necesară cel puţin o strategie.\",\n    \"website\": \"URL should be in the format https://www.example.com\",\n    \"format\": \"Formatul nu este valid\",\n    \"type\": \"Tipul fişierului nu ese valid\",\n    \"unsupportedImageType\": \"File type not supported, Supported formats are jpeg, jpg and png\",\n    \"invalidAddress\": \"Invalid address\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Title\",\n    \"discussion\": \"Discussion (optional)\",\n    \"categorie(s)\": \"Selectează până la 2 categorii(e)\",\n    \"proposalDescription\": \"Description (optional)\",\n    \"preview\": \"Previzualizează\",\n    \"choices\": \"Alegeri\",\n    \"addChoice\": \"Adaugă alegere\",\n    \"startDate\": \"Selectează data de începere\",\n    \"endDate\": \"Selectează data de sfârșit\",\n    \"startTime\": \"Selectează ora de start\",\n    \"endTime\": \"Selectează ora de start\",\n    \"publish\": \"Publică\",\n    \"untitled\": \"Untitled\",\n    \"snapshotBlock\": \"Block-number la snapshot\",\n    \"voting\": \"Voting\",\n    \"votingSystem\": \"Voting system\",\n    \"choice\": \"Choice {0}\",\n    \"period\": \"Voting period\",\n    \"start\": \"Start\",\n    \"end\": \"End\",\n    \"days\": \"Days\",\n    \"hours\": \"Hours\",\n    \"minutes\": \"Minutes\",\n    \"schedule\": \"Schedule proposal\",\n    \"delayEnforced\": \"The space enforces a delay until voting can start\",\n    \"periodEnforced\": \"The space enforces the duration of the voting period\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"Edit\",\n    \"continue\": \"Continue\",\n    \"now\": \"Now\",\n    \"votingPeriodExplainer\": \"This is the time period in which users will be able to vote. The proposal will be visible and pending before the start of the voting period.\",\n    \"uploadImageExplainer\": \"Attach images by dragging & dropping, selecting or pasting them.\",\n    \"uploading\": \"Uploading image\",\n    \"markdown\": \"Styling with Markdown is supported\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"Trebuie sa fii autor al spațiu-lui pentru a înregistra o propunere.\",\n        \"minScore\": \"Trebuie sa ai un minim de {0} {1} pentru a înscrie o propunere.\"\n      },\n      \"customValidation\": \"Trebuie sa treci de validarea propunerii pentru a o înscrie.\",\n      \"executionError\": \"Verifying your eligibility to create proposals in this space has failed. This is likely due to a misconfigured strategy.\"\n    },\n    \"errorGettingSnapshot\": \"We encountered an error while fetching the snapshot block number which is needed to calculate your voting power. Please try again later.\"\n  },\n  \"delegate\": {\n    \"header\": \"Deleghează\",\n    \"selectDelegate\": \"Selectează delegat\",\n    \"to\": \"Către\",\n    \"addressPlaceholder\": \"Adresă sau nume ENS\",\n    \"delegations\": \"Delegațiile tale\",\n    \"allSpaces\": \"Pentru toate spațiile\",\n    \"delegated\": \"Delegat ție\",\n    \"pendingTransaction\": \"nicio tranzacție în așteptare | 1 tranzacție în așteptare | {count} tranzacții în așteptare\",\n    \"topDelegates\": \"Cei mai buni delegați\",\n    \"noDelegatesFoundFor\": \"Nici un delegat găsit pentru {0}\",\n    \"noValidEns\": \"Nu este o adresă ENS validă.\",\n    \"noValidAddress\": \"Nu este o adresă validă\",\n    \"delegateToSelf\": \"Nu poți delega către tine însuți\",\n    \"delegateToSelfAddress\": \"Nu poți delega la propria adresă ENS\",\n    \"noValidSpaceId\": \"Nu este un ID de spațiu valid\",\n    \"noDelegationsAndDelegates\": \"Nu iți găsești delegațiile și delegații? Asigură-te că ești conectat la rețeaua corectă.\",\n    \"delegateNotSupported\": \"Delegația nu este acceptată în prezent pentru {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Votează\",\n    \"vote\": \"Vot\",\n    \"startDate\": \"Data începerii\",\n    \"endDate\": \"Dată de sfârşit\",\n    \"votingSystem\": \"Sistem de votare\",\n    \"privacy\": \"Privacy\",\n    \"invalidChoice\": \"Invalid choice\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Your vote is in!\",\n      \"gnosisSafeTitle\": \"Your vote is pending...\",\n      \"gnosisSafeDescription\": \" Votes with a Safe require additional signers and will be visible once the transaction is confirmed\",\n      \"seeQueue\": \"See queued transactions\",\n      \"tips\": {\n        \"1\": \"Votes can be changed while the proposal is active\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"Propuneri\",\n    \"new\": \"Propunere nouă\",\n    \"noProposals\": \"Incă nu sunt propuneri!\",\n    \"createProposal\": \"Crează propunere\",\n    \"showMore\": \"Arată mai multe\",\n    \"showLess\": \"Arată mai puține\",\n    \"states\": {\n      \"all\": \"Toate\",\n      \"core\": \"Nucleu\",\n      \"community\": \"Comunitate\",\n      \"active\": \"Activ\",\n      \"pending\": \"În așteptare\",\n      \"closed\": \"Închis\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Notifications\",\n    \"noNotifications\": \"You have no notifications\",\n    \"proposalStarted\": \"proposal has started:\",\n    \"proposalEnded\": \"proposal has ended:\",\n    \"markAllAsRead\": \"Mark all as read\",\n    \"all\": \"All\",\n    \"unread\": \"Unread\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"join\",\n    \"actionCreate\": \"create a proposal in\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"Setări\",\n    \"editController\": \"Edit controller\",\n    \"connectWithSpaceOwner\": \"You are in view only mode, to modify space settings connect with a controller or admin wallet.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"edit the space settings\",\n      \"create\": \"create a proposal\",\n      \"vote\": \"vote on this proposal\"\n    },\n    \"currentSpaceControllerIs\": \"The current space controller is {address}\",\n    \"newController\": \"New controller\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Set\",\n    \"profile\": \"Profil\",\n    \"avatar\": \"Avatar\",\n    \"name\": {\n      \"label\": \"Name\",\n      \"placeholder\": \"e.g. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"About\",\n      \"placeholder\": \"What is your organisation about?\"\n    },\n    \"categories\": {\n      \"label\": \"Categorie(s)\",\n      \"select\": \"Select categorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Terms of service\",\n      \"information\": \"Users will be required to accept these terms once before they can create a proposal or cast a vote\"\n    },\n    \"hideSpace\": \"Ascunde spațiu de pe pagina principală\",\n    \"links\": \"Social accounts\",\n    \"subspaces\": {\n      \"label\": \"Sub-spaces\",\n      \"information\": \"Sub-spaces will only be shown once configured on both the main space and the sub-space(s). {docs}\",\n      \"parent\": {\n        \"label\": \"Main space\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"The space that this space is a sub-space of will be displayed on the space page\"\n      },\n      \"children\": {\n        \"label\": \"Sub-spaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Related Sub-spaces listed here will be displayed on the space page\"\n      }\n    },\n    \"website\": \"Website\",\n    \"strategies\": {\n      \"label\": \"Strategie(s)\",\n      \"information\": \"Strategies are used to determine voting power or whether a user is eligible to create a proposal\"\n    },\n    \"network\": {\n      \"label\": \"Network\",\n      \"information\": \"The defaul network used for this space. Networks can also be specified in individual strategies\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbol\",\n      \"information\": \"The default symbol used for this space, usually the token symbol i.e. BAL for Balancer\"\n    },\n    \"strategiesList\": \"Select up to 8 strategies\",\n    \"votingPowerIsCumulative\": \"Voting power is cumulative\",\n    \"addStrategy\": \"Adaugă strategie\",\n    \"testInPlayground\": \"Test in playground\",\n    \"admins\": {\n      \"label\": \"Admins\",\n      \"information\": \"Admins are able to modify the space settings and manage the space's proposals\"\n    },\n    \"authors\": {\n      \"label\": \"Authors\",\n      \"information\": \"Authors are always able to create proposals\"\n    },\n    \"proposalValidation\": \"Validare propunere\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Threshold\",\n      \"information\": \"The minimum amount of voting power required to create a proposal\"\n    },\n    \"allowOnlyAuthors\": \"Permite doar autorilor să înregistreze o propunere\",\n    \"editValidation\": \"Modifică validarea\",\n    \"selectValidation\": \"Selectează validarea\",\n    \"validationParameters\": \"Parametrii de validare\",\n    \"voting\": \"Votare\",\n    \"votingDelay\": \"Intârziere vot\",\n    \"votingPeriod\": \"Perioada de vot\",\n    \"hours\": \"Hours\",\n    \"days\": \"Days\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"The minimum amount of voting power required for the proposal to pass\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"The type of voting system used for this space. (Enforced on all future proposals)\"\n    },\n    \"anyType\": \"Oricare\",\n    \"hideAbstain\": \"Ignoră voturile de abținere în rezultatele votului de bază\",\n    \"customDomain\": \"Domeniu personalizat\",\n    \"domain\": {\n      \"label\": \"Domain name\",\n      \"placeholder\": \"e.g. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"Skin\",\n    \"treasuries\": {\n      \"label\": \"Treasury\",\n      \"add\": \"Add treasury\",\n      \"edit\": \"Edit treasury\",\n      \"information\": \"Add treasuries of your organization to show them in your space\"\n    },\n    \"addPlugin\": \"Adaugă extensie\",\n    \"editPlugin\": \"Modifică extensie\",\n    \"pluginParameters\": \"Parametrii extensie\",\n    \"proposal\": {\n      \"title\": \"Proposal\",\n      \"guidelines\": {\n        \"title\": \"Guidelines\",\n        \"information\": \"Display a link to your guidelines on proposal creation to help users understand what constitutes a good/valid proposal\"\n      },\n      \"template\": {\n        \"title\": \"Template\",\n        \"information\": \"Start every proposal with a template to help users understand what information is required\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"e.g. yam.eth\",\n    \"chooseExistingEns\": \"Alege unul dintre domeniile tale ENS existente pentru a crea un spațiu:\",\n    \"useSingleExistingEns\": \"Utilizează domeniul tău ENS existent:\",\n    \"orRegisterNewEns\": \"Sau înregistrează un nou domeniu:\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"Pentru a crea un spațiu, ai nevoie mai întâi de un domeniu ENS. Introdu unul mai jos și urmează instrucțiunile de înregistrare ENS.\",\n    \"createASpace\": \"Crează un spațiu\",\n    \"registerEnsButton\": \"Înregistrează-te\",\n    \"supportedEnsTLDs\": \"Sufix domenii suportate\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or join Snapshot {discord}.\",\n    \"setSpaceController\": \"Space controller\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \" The space controller is the account that will be able to manage the space settings. Additional space controllers (admins) can be added later.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"When creating a space with a Gnosis Safe it's recommended to set the safe address as the space controller. If you don't, you need to follow some additional steps. {link}\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"Setting the controller requires a transaction on the {network} which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choose ENS address\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. {address}\",\n    \"controllerAddress\": \"Controller address\",\n    \"updateController\": \"Update controller\",\n    \"seeOnEns\": \"See on ENS\",\n    \"goToSettings\": \"Go to settings\",\n    \"setSpaceProfile\": \"Customize your space\",\n    \"waitForTransaction\": \"The transaction need to confirm before you can create your space. {txUrl}\",\n    \"pleaseWaitMessage\": \"This can take a few minutes, please wait while the transaction is being confirmed\",\n    \"notControllerAddress\": \"Please connect with the controller address {wallet} to create the space.\",\n    \"fillCurrentAccount\": \"Use currently logged in account\",\n    \"domain\": {\n      \"title\": \"Setup your space domain\",\n      \"ensMessage\": \" One thing you need before you can create your own space, is an ENS domain on Ethereum mainnet.\",\n      \"ensMessageTestnet\": \"You can also {link} on the Goerli testnet and mess with things there first.\",\n      \"tryDemo\": \"try the demo\",\n      \"yourExistingSpaces\": \"Your existing spaces\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"How would you like to setup your voting strategy?\",\n      \"subtitle\": \"You can change your strategy settings any time.\",\n      \"blockTitle\": \"Setup voting strategy\",\n      \"onePersonOneVote\": {\n        \"title\": \"One person, one vote\",\n        \"description\": \"Manage a whitelist of people who can vote or simply allow any address to vote. Every vote is equal and no token is required\",\n        \"whitelistInformation\": \"Specify a number of accounts that can vote\",\n        \"ticketInformation\": \"Any account can vote\",\n        \"votesEqualInfo\": \"Each vote is equal and no token is required\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Token weighted voting\",\n        \"description\": \"Votes are weighted by a token. The token can be an ERC-20, ERC-721 or ERC-1155 token standard\",\n        \"tokenNotFound\": \"Token not found\",\n        \"seeOnEtherscan\": \"See on Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Custom setup\",\n        \"description\": \"Select up to 8 strategies with a wide range of options. If you can't find the right strategy for your use case, you can create your own\"\n      }\n    },\n    \"validationTitle\": \"Who can manage this space and create proposals?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Edit profile\",\n    \"viewProfile\": \"View profile\",\n    \"about\": {\n      \"header\": \"About\",\n      \"joinedSpaces\": \"Joined spaces\",\n      \"createdSpaces\": \"Created spaces\",\n      \"biography\": \"Bio\",\n      \"notJoinSpacesYet\": \"Hasn't joined any spaces yet\",\n      \"notCreatedSpacesYet\": \"Hasn't created any spaces yet\",\n      \"delegatorNetworkInfo\": \"Change by switching network in your wallet\",\n      \"delegate\": \"Delegate\",\n      \"delegated\": \"Delegated\",\n      \"delegateTo\": \"Delegate to\",\n      \"delegateFor\": \"Delegator for\",\n      \"noDelegatorsMessage\": \"No delegators on {network}\",\n      \"notSupportedNetwork\": \"Delegation currently isn't supported on {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activity\",\n      \"votedFor\": \"Voted {choice}\",\n      \"today\": \"Today\",\n      \"thisWeek\": \"This week\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"No activity yet\"\n    },\n    \"settings\": {\n      \"header\": \"Edit profile\",\n      \"name\": \"Name\",\n      \"biography\": \"Bio\",\n      \"namePlaceholder\": \"Enter name\",\n      \"bioPlaceholder\": \"Tell your story\",\n      \"change\": \"Change\",\n      \"remove\": \"Remove\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"Ai reușit!\",\n    \"copied\": \"Copiat!\",\n    \"proposalDeleted\": \"Propunere ştearsă\",\n    \"somethingWentWrong\": \"Ups, ceva nu a mers bine!\",\n    \"saved\": \"Salvează!\",\n    \"delegationSuccess\": \"Delegare reușită\",\n    \"delegationRemoved\": \"Delegare eliminată\",\n    \"proposalCreated\": \"Propunere creată\",\n    \"voteSuccessful\": \"Ai votat cu succes!\",\n    \"ensSet\": \"ENS text record was successfully set\",\n    \"transactionSent\": \"Transaction sent\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Crează strategie\",\n    \"createSkin\": \"Crează skin\",\n    \"addNetwork\": \"Adaugă rețea\",\n    \"createPlugin\": \"Crează plugin\",\n    \"strategies\": \"strategie(i)\",\n    \"skins\": \"skin(uri)\",\n    \"networks\": \"rețea(le)\",\n    \"plugins\": \"plugin(uri)\",\n    \"results\": \"rezultat(e)\",\n    \"category\": \"Category\",\n    \"categories\": {\n      \"all\": \"Toate\",\n      \"protocol\": \"Protocol\",\n      \"social\": \"Social\",\n      \"investment\": \"Investiţie\",\n      \"grant\": \"Subvenție\",\n      \"service\": \"Serviciu\",\n      \"media\": \"Media\",\n      \"creator\": \"Autorul\",\n      \"collector\": \"Colecționar\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Selectează sistemul de votare\",\n    \"single-choice\": \"Votare cu alegere unică\",\n    \"approval\": \"Votare cu aprobare\",\n    \"quadratic\": \"Votare cuadratica\",\n    \"ranked-choice\": \"Votare prin clasament\",\n    \"weighted\": \"Votare cu pondere\",\n    \"basic\": \"Votare de bază\",\n    \"description\": {\n      \"single-choice\": \"Fiecare votant poate selecta o singură alegere.\",\n      \"approval\": \"Fiecare alegător poate selecta orice număr de alegeri.\",\n      \"quadratic\": \"Fiecare alegător poate răspândi puterea de vot în orice număr de alegeri. Rezultatele sunt calculate cuadratic.\",\n      \"ranked-choice\": \"Fiecare alegător poate selecta şi clasifica orice număr de alegeri. Rezultatele sunt calculate prin metoda de numărare cu balotaj instantaneu.\",\n      \"weighted\": \"Fiecare alegator poate raspândi puterea de vot la oricâte alegeri.\",\n      \"basic\": \"Votul unic cu trei opțiuni: Pentru, Împotriva sau Abținere\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Privacy\",\n    \"title\": \"Select voting privacy\",\n    \"information\": \"The type of privacy used on proposals. (Enforced on all future proposals)\",\n    \"any\": \"Any\",\n    \"none\": \"None\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Choices are encrypted and only visible once the voting period is over\",\n      \"tooltip\": \"This proposal has Shutter privacy enabled. All votes will be encrypted until the voting period has ended and the final score is calculated\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select voting validation\",\n    \"information\": \"The type of validation used to determine if a user can vote. (Enforced on all future proposals)\",\n    \"any\": {\n      \"label\": \"Anyone can vote\",\n      \"description\": \"Anyone with voting power can cast a vote.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement to vote on this proposal. \"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your proposals from spam and vote manipulation by requiring users to have a Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold} and {operator} of the following stamps to vote on this proposal: {stamps}. \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Rezultat curent\",\n    \"currentBond\": \"Bond curent\",\n    \"finalizedIn\": \"Finalizat {0}\",\n    \"executableIn\": \"Executabil {0}\",\n    \"finalOutcome\": \"Rezultat\",\n    \"nextBond\": \"Bond pentru a seta rezultatul\",\n    \"setOutcomeTo\": \"Setează rezultatul la\",\n    \"claimBond\": \"Revendică bond\",\n    \"addBatch\": \"Adaugă manunchi de tranzactii\",\n    \"batch\": \"Manunchi de tranzacții\",\n    \"transactions\": \"Tranzacții\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"Adresă invalidă\",\n    \"invalidAmount\": \"Sumă invalidă\",\n    \"invalidValue\": \"Valoare invalidă\",\n    \"invalidAbi\": \"ABI invalid\",\n    \"invalidData\": \"Date necorespunzătoare\",\n    \"value\": \"Valoare (wei)\",\n    \"data\": \"Date\",\n    \"noCollectibles\": \"Nu sunt collectibles\",\n    \"asset\": \"Asset\",\n    \"amount\": \"Sumă\",\n    \"type\": \"Tip\",\n    \"transferFunds\": \"Transferă fonduri\",\n    \"transferNFT\": \"Transferă NFT\",\n    \"contractInteraction\": \"Interacțiune contract\",\n    \"rawTransaction\": \"Tranzacție brută\",\n    \"addTransaction\": \"Adaugă tranzacție\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei catre {address}\",\n      \"transferFunds\": \"Transferă {amount} {tokenSymbol} către {address}\",\n      \"transferNFT\": \"Trimite {name} #{id} către {address}\",\n      \"raw\": \"Trimite {amount} wei către {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Solicită executare\",\n      \"setOutcome\": \"Setează rezultat\",\n      \"changeOutcome\": \"Modifică rezultat\",\n      \"executeTxs\": \"Execută manunchiul de tranzacții {0} din {1}\",\n      \"executed\": \"Toate tranzacțiile au fost executate\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"Ceva nu a funcţionat\",\n      \"connectWallet\": \"Conectează portofelul pentru a vedea detaliile de execuție\",\n      \"switchChain\": \"Comută portofelul la {0} pentru a solicita execuția\",\n      \"question\": \"A trecut această propunere si îndeplinește\",\n      \"criteria\": \"criterii de acceptare?\",\n      \"proposalPassed\": \"Propunerea a fost aprobată?\",\n      \"expired\": \"The proposal has expired\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to confirm the proposal passed\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"requestToolTip\": \"This will propose the transactions from this Snapshot vote on-chain. After a challenge window, if the proposal is valid, the transactions can be executed and your bond will be returned.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"Un POAP nu a fost configurat pentru această propunere încă :'(\",\n    \"no_voted_header\": \"Votează pentru a obține acest POAP\",\n    \"unclaimed_header\": \"Revendică-ți POAP-ul Am votat\",\n    \"claimed_header\": \"Felicitări! POAP-ul a fost adăugat la colecția ta\",\n    \"loading_header\": \"POAP-ul este în curs de adăugare la colecția ta\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"Răsfoiește colecția\",\n    \"success_claim\": \"POAP-ul a fost adăugat la colecția ta\",\n    \"error_claim\": \"A apărut o problemă la mintarea acestui token\"\n  },\n  \"progress\": {\n    \"progress\": \"Progress\",\n    \"inProgress\": \"In Progress\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"New Step\",\n    \"description\": \"Description\",\n    \"add\": \"Add\",\n    \"deleteStep\": \"Delete Step\",\n    \"deleteConfirm\": \"Are you sure you want to delete?\",\n    \"delete\": \"Delete\",\n    \"cancel\": \"Cancel\",\n    \"comeBack\": \"Come back after voting is complete to see how this proposal is progressing!\",\n    \"confirmSignature\": \"Signing this message will allow us to authorize your request to update the progress of your proposal.\",\n    \"wentWrong\": \"Oops something went wrong.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"Grafice\",\n    \"noVotesYet\": \"Nu există voturi pentru vizualizare încă.\",\n    \"totalVotesPerDay\": \"Total voturi pe zi\",\n    \"shareOfVotingPower\": \"Proporția puterii de vot\",\n    \"votingPowerPerDay\": \"Putere de vot pe zi\"\n  },\n  \"comment_box\": {\n    \"title\": \"Căsuță de comentarii\",\n    \"add\": \"Adaugă comentariul tau aici\",\n    \"submit\": \"Trimite\",\n    \"preview\": \"Previzualizează\",\n    \"continue_editing\": \"Continuă editarea\",\n    \"edit\": \"Modifică-ți răspunsul aici\",\n    \"edit_button\": \"Editează\",\n    \"dismiss\": \"Renunță\",\n    \"delete\": \"Șterge\",\n    \"add_reply\": \"Adaugă răspunsul tău aici\",\n    \"edit_comment\": \"Editează comentariu\",\n    \"edit_modal\": \"Ești sigur că vrei să editezi?\",\n    \"yes\": \"Da\",\n    \"no\": \"Nu\",\n    \"delete_comment\": \"Șterge comentariu\",\n    \"delete_modal\": \"Eşti sigur că vrei să ştergi?\",\n    \"error\": \"Ups, ceva nu a mers bine\",\n    \"replies\": \"răspunsuri\",\n    \"hide\": \"Ascunde\",\n    \"show\": \"Afișează\",\n    \"reply\": \"Răspunde\",\n    \"load_more\": \"Încarcă mai mult\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Crează un spațiu\",\n      \"timeline\": \"Cronologie\",\n      \"notifications\": \"Notifications\",\n      \"explore\": \"Explorează\",\n      \"playground\": \"Teren de joacă\",\n      \"space\": {\n        \"create\": \"Crează {space} propunere\",\n        \"about\": \"Despre {space}\",\n        \"proposals\": \"{space} Propuneri\",\n        \"proposal\": \"Propunere {space}: {proposal}\",\n        \"settings\": \"Setări {space}\"\n      },\n      \"strategy\": \"Strategie {key}\",\n      \"delegate\": \"Deleghează\",\n      \"ranking\": \"Ranking\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Urmărește propuneri pentru {spaceName}\",\n    \"text\": \"Primește notificări de fiecare dată când o nouă propunere este creată sau se termină\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 secundă | {n} secunde\",\n    \"minute\": \"1 minut | {n} minute\",\n    \"hour\": \"1 oră | {n} ore\",\n    \"day\": \"1 zi | {n} zile\",\n    \"week\": \"1 săptămână | {n} săptămâni\",\n    \"month\": \"1 lună | {n} luni\",\n    \"year\": \"1 an | {n} ani\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Unsupported network\",\n    \"switchNetworkToNetwork\": \"To continue, you need to change the network in your wallet to {network}.\",\n    \"switchToNetwork\": \"Switch to {network}\",\n    \"goToDemoSite\": \"Go to demo website\"\n  },\n  \"treasury\": {\n    \"title\": \"Treasury\",\n    \"wallets\": {\n      \"title\": \"Wallets\",\n      \"empty\": \"This space doesn't have a treasury yet\",\n      \"addTreasury\": \"Add a treasury\"\n    },\n    \"assets\": {\n      \"title\": \"Assets\",\n      \"empty\": \"There are no assets in this contract\"\n    },\n    \"24hChange\": \"24h change\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Your email\",\n    \"title\": \"Get the latest Snapshot updates\"\n  },\n  \"joinCommunity\": \"Join Snapshot community\",\n  \"header\": {\n    \"title\": \"Where decisions get made\",\n    \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot is a decentralized governance platform that makes it easy to create and vote on proposals - all without spending a fortune on gas fees! Plus, our flexible system supports various voting types and strategies, so you can tailor the voting process to your needs.\",\n    \"subHeader\": \"Governance should be a snap\",\n    \"subDescription\": \"Web3 governance doesn't have to be complicated. Snapshot is the perfect solution for organizations looking for an easy and efficient way to govern their community or organization.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Resources\",\n    \"about\": \"About\",\n    \"blog\": \"Blog\",\n    \"jobs\": \"Jobs\",\n    \"discussions\": \"Discussions\",\n    \"github\": \"GitHub\",\n    \"docs\": \"Docs\",\n    \"support\": \"Support\",\n    \"hiring\": \"Join us!\"\n  }\n}\n"
  },
  {
    "path": "src/locales/ru-RU.json",
    "content": "{\n  \"searchPlaceholder\": \"Поиск\",\n  \"spaceCount\": \"{0} сообщество(сообществ)\",\n  \"createSpace\": \"Создать сообщество\",\n  \"backToHome\": \"Главная\",\n  \"actions\": \"Действия\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"Результаты\",\n  \"resultsError\": \"Не удалось подсчитать результаты. Часто это связано с неправильной конфигурацией стратегии или не отвечающим на запросы ноды RPC, задействованным в стратегии.\",\n  \"resultsCalculating\": \"Final results are being calculated. If you still see this message after a few minutes contact the space admin.\",\n  \"votingPowerFailedMessage\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"votingValidationFailedMessage\": \"There was an error on our side and we could not verify if you are eligible to vote. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n  \"notValidVoterMessage\": \"Oops, you don't seem to be eligible to vote on this proposal.\",\n  \"getHelp\": \"Получить помощь\",\n  \"retry\": \"Повторить\",\n  \"currentResults\": \"Текущий результат\",\n  \"reset\": \"Сброс\",\n  \"close\": \"Close\",\n  \"save\": \"Сохранить\",\n  \"author\": \"Автор\",\n  \"next\": \"Далее\",\n  \"choice\": \"Choice\",\n  \"submit\": \"Отправить\",\n  \"plugins\": \"Плагины\",\n  \"information\": \"Информация\",\n  \"confirm\": \"Подтвердить\",\n  \"snapshot\": \"Snapshot\",\n  \"strategies\": \"Стратегия(и)\",\n  \"strategiesPage\": \"Стратегии\",\n  \"space\": \"Сообщество\",\n  \"spaces\": \"Сообщества\",\n  \"verifiedSpace\": \"Проверенное сообщество\",\n  \"warningSpace\": \"This space has been flagged as potentially malicious. Proceed with caution.\",\n  \"version\": \"Версия\",\n  \"timeline\": \"Хронология\",\n  \"ended\": \"завершился\",\n  \"started\": \"начался\",\n  \"filters\": \"Фильтры\",\n  \"allSpaces\": \"Все сообщества\",\n  \"submitOnchain\": \"Отправить в блокчейн\",\n  \"inSpaces\": \"В {0} сообществе(ах)\",\n  \"votes\": \"Голоса\",\n  \"seeMore\": \"Подробнее\",\n  \"seeAll\": \"See all\",\n  \"network\": \"Сеть\",\n  \"networks\": \"Сети\",\n  \"skins\": \"Темы\",\n  \"spaceMembers\": \"Members\",\n  \"members\": \"Нет участников | {count} участник | {count} участников\",\n  \"editStrategy\": \"Изменить стратегию\",\n  \"invalidProposals\": \"Недействительные предложения\",\n  \"account\": \"Учетная запись\",\n  \"create3box\": \"Создать профиль на 3Box\",\n  \"view3box\": \"Просмотреть профиль на 3Box\",\n  \"edit3box\": \"Редактировать профиль на 3Box\",\n  \"connectWallet\": \"Подключить кошелек\",\n  \"toggleSkin\": \"Переключить тему\",\n  \"about\": \"О нас\",\n  \"license\": \"Лицензия\",\n  \"showMore\": \"Show more\",\n  \"voted\": \"Voted\",\n  \"reload\": \"Reload\",\n  \"ipfsServer\": \"IPFS сервер\",\n  \"hub\": \"Хаб\",\n  \"cancel\": \"Отменить\",\n  \"delete\": \"Delete\",\n  \"demoSite\": \"Это демо сайт, попробуйте!\",\n  \"removeDelegation\": \"Убрать делегирование\",\n  \"confirmRemove\": \"Вы уверены, что хотите отменить свое делегирование в\",\n  \"removeSpace\": \"для сообщества {0}\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum reached\",\n  \"options\": \"Вариант(ы)\",\n  \"votingPower\": \"Ваше право голоса\",\n  \"comment\": {\n    \"placeholder\": \"Share your reason (optional)\"\n  },\n  \"receipt\": \"Квитанция\",\n  \"relayer\": \"Передатчик\",\n  \"verifyOnMycrypto\": \"Подтвердите получение на MyCrypto\",\n  \"verifyOnSignatorio\": \"Верификация на Signator.io\",\n  \"isCore\": \"Основные\",\n  \"notificationsBlocked\": \"Ваш браузер блокирует уведомления\",\n  \"notificationsNotSupported\": \"Ваш браузер не поддерживает уведомления\",\n  \"walletNotSupported\": \"Кошелёк не поддерживается\",\n  \"seeInExplorer\": \"See explorer\",\n  \"learnMore\": \"Подробнее\",\n  \"logout\": \"Выйти\",\n  \"continue\": \"Продолжить\",\n  \"add\": \"Добавить\",\n  \"edit\": \"Изменить\",\n  \"strategyParameters\": \"Параметры стратегии\",\n  \"addAction\": \"Добавить действие\",\n  \"removeAction\": \"Удалить действие\",\n  \"yourChoice\": \"Вариант {0}\",\n  \"targetAddress\": \"Адрес назначения\",\n  \"value\": \"Значение\",\n  \"date\": \"Данные\",\n  \"marketDetails\": \"Информация о рынке\",\n  \"addMarket\": \"Добавить рынок\",\n  \"selectNetwork\": \"Выбрать сеть\",\n  \"conditionId\": \"Условия ID\",\n  \"basetokenAddress\": \"Адрес основного токена\",\n  \"quoteAddress\": \"Адрес котировки валюты\",\n  \"removeMarket\": \"Удалить рынок\",\n  \"back\": \"Назад\",\n  \"loading\": \"Загрузка...\",\n  \"predictedImpact\": \"Прогнозируемое влияние\",\n  \"marketSymbol\": \"{0} рынок\",\n  \"twoChoicesRequired\": \"Для этого плагина необходимо выбрать два варианта.\",\n  \"noResultsFound\": \"Упс, мы не можем найти результатов\",\n  \"createFirstProposal\": \"Давайте создадим ваше первое предложение\",\n  \"noSpacesJoined\": \"Упс, вы еще не присоединились ни к одному сообществу\",\n  \"addFavorites\": \"Добавить в избранное\",\n  \"createdBy\": \"От: {0}\",\n  \"startIn\": \"начать {0}\",\n  \"endIn\": \"по {0}\",\n  \"proposalTimeLeft\": \"{0} осталось\",\n  \"endedAgo\": \"завершился {0}\",\n  \"proposalBy\": \"от: {0}\",\n  \"endDate\": \"по {0}\",\n  \"contentHash\": \"Хэш содержимого\",\n  \"defaultSkin\": \"Тема по умолчанию\",\n  \"select\": \"Выбрать\",\n  \"language\": \"Язык\",\n  \"agree\": \"Я принимаю\",\n  \"moderators\": \"Moderators\",\n  \"playground\": \"Игровая площадка\",\n  \"strategyParams\": \"Параметры стратегии\",\n  \"addresses\": \"Адреса\",\n  \"networkErrorPlayground\": \"Ошибка сети — пожалуйста, откройте панель браузера для получения дополнительной информации\",\n  \"upload\": \"Загрузить\",\n  \"join\": \"Присоединиться\",\n  \"joined\": \"Присоединился\",\n  \"leave\": \"Покинуть\",\n  \"subspaces\": \"Sub-spaces\",\n  \"mainspace\": \"Main space\",\n  \"copyLink\": \"Копировать ссылку\",\n  \"duplicate\": \"Duplicate\",\n  \"joinedSpaces\": \"Вступившие сообщества\",\n  \"joinSpaces\": \"Вступить в сообщества\",\n  \"setDelegationToSpace\": \"Ограничить делегирование полномочий определенным сообществам\",\n  \"theCurrentNetwork\": \"текущая сеть\",\n  \"optional\": \"(необязательно)\",\n  \"homeLoadmore\": \"Загрузить больше\",\n  \"confirmAction\": \"Подтвердить действие\",\n  \"or\": \"или\",\n  \"share\": \"Поделиться\",\n  \"shareOnTwitter\": \"Share on Twitter\",\n  \"shareOnLenster\": \"Share on Lenster\",\n  \"createButton\": \"Создать\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Change wallet\",\n  \"createASpace\": \"Create a space\",\n  \"getStarted\": \"Get started\",\n  \"skip\": \"Skip\",\n  \"newSpaceNotice\": {\n    \"header\": \"Ваше сообщество запущено!\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Узнайте больше в {documentation} или присоединитесь к {discord} Snapshot для получения помощи.\",\n    \"gotIt\": \"Понятно!\"\n  },\n  \"errors\": {\n    \"required\": \"Обязательное поле\",\n    \"minLength\": \"Обязательное поле\",\n    \"maxLength\": \"Максимальная длина: {0}\",\n    \"pattern\": \"Неверный символ\",\n    \"minItems\": \"Требуется минимум {0} предмета(ов)\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"minStrategy\": \"Необходимо наличие хотя бы одной стратегии.\",\n    \"website\": \"Адрес должен быть в формате https://www.example.com\",\n    \"format\": \"Неверный формат\",\n    \"type\": \"Неверный тип\",\n    \"unsupportedImageType\": \"Тип файла не поддерживается, Поддерживаемые форматы: jpeg, jpg и png\",\n    \"invalidAddress\": \"Invalid address\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Название\",\n    \"discussion\": \"Discussion (optional)\",\n    \"categorie(s)\": \"Выберите до 2 категорий\",\n    \"proposalDescription\": \"Описание (необязательно)\",\n    \"preview\": \"Предпросмотр\",\n    \"choices\": \"Варианты ответов\",\n    \"addChoice\": \"Добавить вариант ответа\",\n    \"startDate\": \"Выбрать дату начала\",\n    \"endDate\": \"Выбрать дату окончания\",\n    \"startTime\": \"Выбрать время начала\",\n    \"endTime\": \"Выбрать время окончания\",\n    \"publish\": \"Опубликовать\",\n    \"untitled\": \"Без названия\",\n    \"snapshotBlock\": \"Номер блока snapshot\",\n    \"voting\": \"Голосование\",\n    \"votingSystem\": \"Система голосования\",\n    \"choice\": \"Выбор {0}\",\n    \"period\": \"Период голосования\",\n    \"start\": \"Начало\",\n    \"end\": \"Конец\",\n    \"days\": \"Дней\",\n    \"hours\": \"Часов\",\n    \"minutes\": \"Минут\",\n    \"schedule\": \"График предложения\",\n    \"delayEnforced\": \"Сообщество вводит отсрочку до начала голосования\",\n    \"periodEnforced\": \"Сообщество устанавливает продолжительность периода голосования\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"Изменить\",\n    \"continue\": \"Продолжить\",\n    \"now\": \"Сейчас\",\n    \"votingPeriodExplainer\": \"Период времени, в течение которого пользователи могут голосовать. Предложение будет видно и отложено до начала периода голосования.\",\n    \"uploadImageExplainer\": \"Attach images by dragging & dropping, selecting or pasting them.\",\n    \"uploading\": \"Uploading image\",\n    \"markdown\": \"Поддерживается обработка стилей с помощью Markdown\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"Чтобы внести предложение, необходимо быть автором сообщества.\",\n        \"minScore\": \"Для подачи заявки необходимо иметь минимум {0} {1}.\"\n      },\n      \"customValidation\": \"Чтобы подать предложение, необходимо пройти проверку предложения.\",\n      \"executionError\": \"Проверка вашего права на создание предложений в этом сообществе не удалась. Вероятно, это связано с неправильной конфигурацией стратегии.\"\n    },\n    \"errorGettingSnapshot\": \"We encountered an error while fetching the snapshot block number which is needed to calculate your voting power. Please try again later.\"\n  },\n  \"delegate\": {\n    \"header\": \"Делегировать\",\n    \"selectDelegate\": \"Выбрать делегата\",\n    \"to\": \"Кому\",\n    \"addressPlaceholder\": \"Адрес или название ENS\",\n    \"delegations\": \"Ваша(и) делегация (делегации)\",\n    \"allSpaces\": \"Для всех сообществ\",\n    \"delegated\": \"Делегировано вам\",\n    \"pendingTransaction\": \"нет незавершенных транзакций | 1 незавершенная транзакция | {count} незавершенных транзакций\",\n    \"topDelegates\": \"Лучшие делегаты\",\n    \"noDelegatesFoundFor\": \"Для {0} делегатов не найдено\",\n    \"noValidEns\": \"Не действительный адрес ENS.\",\n    \"noValidAddress\": \"Недействительный адрес\",\n    \"delegateToSelf\": \"Вы не можете делегировать сам себя\",\n    \"delegateToSelfAddress\": \"Вы не можете делегировать на свой собственный адрес ENS\",\n    \"noValidSpaceId\": \"Не действительное ID сообщества\",\n    \"noDelegationsAndDelegates\": \"Не можете найти свои делегации и делегатов? Убедитесь, что вы подключены к правильной сети.\",\n    \"delegateNotSupported\": \"Делегирование в настоящее время не поддерживается для {сети}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Проголосуйте\",\n    \"vote\": \"Голосовать\",\n    \"startDate\": \"Дата начала\",\n    \"endDate\": \"Дата окончания\",\n    \"votingSystem\": \"Система голосования\",\n    \"privacy\": \"Privacy\",\n    \"invalidChoice\": \"Invalid choice\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Your vote is in!\",\n      \"gnosisSafeTitle\": \"Your vote is pending...\",\n      \"gnosisSafeDescription\": \" Votes with a Safe require additional signers and will be visible once the transaction is confirmed\",\n      \"seeQueue\": \"See queued transactions\",\n      \"tips\": {\n        \"1\": \"Votes can be changed while the proposal is active\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"Предложения\",\n    \"new\": \"Новые предложения\",\n    \"noProposals\": \"Здесь еще нет ни одного предложения!\",\n    \"createProposal\": \"Создать предложение\",\n    \"showMore\": \"Показать больше\",\n    \"showLess\": \"Показывать меньше\",\n    \"states\": {\n      \"all\": \"Все\",\n      \"core\": \"Основные\",\n      \"community\": \"Сообщество\",\n      \"active\": \"Активные\",\n      \"pending\": \"В процессе\",\n      \"closed\": \"Закрытые\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Уведомление\",\n    \"noNotifications\": \"У вас нет уведомлений\",\n    \"proposalStarted\": \"предложение началось:\",\n    \"proposalEnded\": \"предложение закончилось:\",\n    \"markAllAsRead\": \"Отметить всё как прочитанное\",\n    \"all\": \"Все\",\n    \"unread\": \"Непрочитанные\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"join\",\n    \"actionCreate\": \"create a proposal in\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"Настройки\",\n    \"editController\": \"Изменить виджеты\",\n    \"connectWithSpaceOwner\": \"You are in view only mode, to modify space settings connect with a controller or admin wallet.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"edit the space settings\",\n      \"create\": \"create a proposal\",\n      \"vote\": \"vote on this proposal\"\n    },\n    \"currentSpaceControllerIs\": \"Текущий контроллер сообщества - {адрес}\",\n    \"newController\": \"Новый контроллер\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Установить\",\n    \"profile\": \"Профиль\",\n    \"avatar\": \"Аватар\",\n    \"name\": {\n      \"label\": \"Name\",\n      \"placeholder\": \"e.g. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"About\",\n      \"placeholder\": \"What is your organisation about?\"\n    },\n    \"categories\": {\n      \"label\": \"Categorie(s)\",\n      \"select\": \"Select categorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Terms of service\",\n      \"information\": \"Users will be required to accept these terms once before they can create a proposal or cast a vote\"\n    },\n    \"hideSpace\": \"Скрыть сообщество с главной страницы\",\n    \"links\": \"Social accounts\",\n    \"subspaces\": {\n      \"label\": \"Sub-spaces\",\n      \"information\": \"Sub-spaces will only be shown once configured on both the main space and the sub-space(s). {docs}\",\n      \"parent\": {\n        \"label\": \"Main space\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"The space that this space is a sub-space of will be displayed on the space page\"\n      },\n      \"children\": {\n        \"label\": \"Sub-spaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Related Sub-spaces listed here will be displayed on the space page\"\n      }\n    },\n    \"website\": \"Website\",\n    \"strategies\": {\n      \"label\": \"Strategie(s)\",\n      \"information\": \"Strategies are used to determine voting power or whether a user is eligible to create a proposal\"\n    },\n    \"network\": {\n      \"label\": \"Network\",\n      \"information\": \"The defaul network used for this space. Networks can also be specified in individual strategies\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbol\",\n      \"information\": \"The default symbol used for this space, usually the token symbol i.e. BAL for Balancer\"\n    },\n    \"strategiesList\": \"Select up to 8 strategies\",\n    \"votingPowerIsCumulative\": \"Voting power is cumulative\",\n    \"addStrategy\": \"Добавить стратегию\",\n    \"testInPlayground\": \"Test in playground\",\n    \"admins\": {\n      \"label\": \"Admins\",\n      \"information\": \"Admins are able to modify the space settings and manage the space's proposals\"\n    },\n    \"authors\": {\n      \"label\": \"Authors\",\n      \"information\": \"Authors are always able to create proposals\"\n    },\n    \"proposalValidation\": \"Проверка предложения\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Threshold\",\n      \"information\": \"The minimum amount of voting power required to create a proposal\"\n    },\n    \"allowOnlyAuthors\": \"Разрешить подавать предложения только авторам\",\n    \"editValidation\": \"Редактировать проверку\",\n    \"selectValidation\": \"Выбрать проверку\",\n    \"validationParameters\": \"Параметры проверки\",\n    \"voting\": \"Голосование\",\n    \"votingDelay\": \"Задержка голосования\",\n    \"votingPeriod\": \"Период голосования\",\n    \"hours\": \"Hours\",\n    \"days\": \"Days\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"The minimum amount of voting power required for the proposal to pass\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"The type of voting system used for this space. (Enforced on all future proposals)\"\n    },\n    \"anyType\": \"Любой\",\n    \"hideAbstain\": \"Игнорировать голоса воздержавшихся в основных результатах голосования\",\n    \"customDomain\": \"Пользовательский домен\",\n    \"domain\": {\n      \"label\": \"Domain name\",\n      \"placeholder\": \"e.g. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"Тема\",\n    \"treasuries\": {\n      \"label\": \"Treasury\",\n      \"add\": \"Add treasury\",\n      \"edit\": \"Edit treasury\",\n      \"information\": \"Add treasuries of your organization to show them in your space\"\n    },\n    \"addPlugin\": \"Добавить плагин\",\n    \"editPlugin\": \"Редактировать плагин\",\n    \"pluginParameters\": \"Настройки плагина\",\n    \"proposal\": {\n      \"title\": \"Proposal\",\n      \"guidelines\": {\n        \"title\": \"Guidelines\",\n        \"information\": \"Display a link to your guidelines on proposal creation to help users understand what constitutes a good/valid proposal\"\n      },\n      \"template\": {\n        \"title\": \"Template\",\n        \"information\": \"Start every proposal with a template to help users understand what information is required\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"например, yam.eth\",\n    \"chooseExistingEns\": \"Выберите один из существующих доменов ENS для создания сообщества:\",\n    \"useSingleExistingEns\": \"Используйте свой существующий домен ENS:\",\n    \"orRegisterNewEns\": \"Или зарегистрируйте новый домен:\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"Чтобы создать сообщество, вам сначала нужен домен ENS. Введите его ниже и следуйте инструкциям по регистрации ENS.\",\n    \"createASpace\": \"Создать сообщество\",\n    \"registerEnsButton\": \"Зарегистрироваться\",\n    \"supportedEnsTLDs\": \"Поддерживаемые доменные окончания\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or join Snapshot {discord}.\",\n    \"setSpaceController\": \"Space controller\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \" The space controller is the account that will be able to manage the space settings. Additional space controllers (admins) can be added later.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"When creating a space with a Gnosis Safe it's recommended to set the safe address as the space controller. If you don't, you need to follow some additional steps. {link}\",\n    \"editSpaceController\": \"Изменить контроллер на ENS\",\n    \"setController\": \"Настроить виджеты\",\n    \"explainControllerAndEns\": \"Setting the controller requires a transaction on the {network} which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Вы уверены, что хотите установить {address} в качестве управляющего сообществом?\",\n    \"controllerHasAuthority\": \"Контроллер имеет полное право управлять настройками сообщества\",\n    \"controller\": \"Контроллер\",\n    \"selectEnsForSpace\": \"Выберите ENS-адрес\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. {address}\",\n    \"controllerAddress\": \"Адрес контроллера\",\n    \"updateController\": \"Обновить контроллер\",\n    \"seeOnEns\": \"Смотреть на ENS\",\n    \"goToSettings\": \"Перейти в настройки\",\n    \"setSpaceProfile\": \"Настроить свое сообщество\",\n    \"waitForTransaction\": \"Подтвердите транзакцию, чтобы создать свое сообщество.{txUrl}\",\n    \"pleaseWaitMessage\": \"Это может занять несколько минут, пожалуйста, подождите, подтверждения трансакции\",\n    \"notControllerAddress\": \"Please connect with the controller address {wallet} to create the space.\",\n    \"fillCurrentAccount\": \"Use currently logged in account\",\n    \"domain\": {\n      \"title\": \"Setup your space domain\",\n      \"ensMessage\": \" One thing you need before you can create your own space, is an ENS domain on Ethereum mainnet.\",\n      \"ensMessageTestnet\": \"You can also {link} on the Goerli testnet and mess with things there first.\",\n      \"tryDemo\": \"try the demo\",\n      \"yourExistingSpaces\": \"Your existing spaces\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"How would you like to setup your voting strategy?\",\n      \"subtitle\": \"You can change your strategy settings any time.\",\n      \"blockTitle\": \"Setup voting strategy\",\n      \"onePersonOneVote\": {\n        \"title\": \"One person, one vote\",\n        \"description\": \"Manage a whitelist of people who can vote or simply allow any address to vote. Every vote is equal and no token is required\",\n        \"whitelistInformation\": \"Specify a number of accounts that can vote\",\n        \"ticketInformation\": \"Any account can vote\",\n        \"votesEqualInfo\": \"Each vote is equal and no token is required\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Token weighted voting\",\n        \"description\": \"Votes are weighted by a token. The token can be an ERC-20, ERC-721 or ERC-1155 token standard\",\n        \"tokenNotFound\": \"Token not found\",\n        \"seeOnEtherscan\": \"See on Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Custom setup\",\n        \"description\": \"Select up to 8 strategies with a wide range of options. If you can't find the right strategy for your use case, you can create your own\"\n      }\n    },\n    \"validationTitle\": \"Who can manage this space and create proposals?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Edit profile\",\n    \"viewProfile\": \"View profile\",\n    \"about\": {\n      \"header\": \"About\",\n      \"joinedSpaces\": \"Joined spaces\",\n      \"createdSpaces\": \"Created spaces\",\n      \"biography\": \"Bio\",\n      \"notJoinSpacesYet\": \"Hasn't joined any spaces yet\",\n      \"notCreatedSpacesYet\": \"Hasn't created any spaces yet\",\n      \"delegatorNetworkInfo\": \"Change by switching network in your wallet\",\n      \"delegate\": \"Delegate\",\n      \"delegated\": \"Delegated\",\n      \"delegateTo\": \"Delegate to\",\n      \"delegateFor\": \"Delegator for\",\n      \"noDelegatorsMessage\": \"No delegators on {network}\",\n      \"notSupportedNetwork\": \"Delegation currently isn't supported on {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activity\",\n      \"votedFor\": \"Voted {choice}\",\n      \"today\": \"Today\",\n      \"thisWeek\": \"This week\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"No activity yet\"\n    },\n    \"settings\": {\n      \"header\": \"Edit profile\",\n      \"name\": \"Name\",\n      \"biography\": \"Bio\",\n      \"namePlaceholder\": \"Enter name\",\n      \"bioPlaceholder\": \"Tell your story\",\n      \"change\": \"Change\",\n      \"remove\": \"Remove\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"У вас получилось!\",\n    \"copied\": \"Скопировано!\",\n    \"proposalDeleted\": \"Предложение удалено\",\n    \"somethingWentWrong\": \"Ой, что-то пошло не так!\",\n    \"saved\": \"Сохранено!\",\n    \"delegationSuccess\": \"Делегирование прошло успешно\",\n    \"delegationRemoved\": \"Делегирование удалено\",\n    \"proposalCreated\": \"Предложение создано\",\n    \"voteSuccessful\": \"Ваш голос принят!\",\n    \"ensSet\": \"Текстовая запись ENS успешно установлена\",\n    \"transactionSent\": \"Транзакция отправлена\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Создать стратегию\",\n    \"createSkin\": \"Создать тему\",\n    \"addNetwork\": \"Добавить сеть\",\n    \"createPlugin\": \"Создать плагин\",\n    \"strategies\": \"стратегия(и)\",\n    \"skins\": \"тема(ы)\",\n    \"networks\": \"сеть(и)\",\n    \"plugins\": \"плагин(ы)\",\n    \"results\": \"результат(ы)\",\n    \"category\": \"Category\",\n    \"categories\": {\n      \"all\": \"Все\",\n      \"protocol\": \"Протоколы\",\n      \"social\": \"Социальные\",\n      \"investment\": \"Инвестиционные\",\n      \"grant\": \"Гранты\",\n      \"service\": \"Услуги\",\n      \"media\": \"Медиа\",\n      \"creator\": \"Создатели\",\n      \"collector\": \"Коллекционеры\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Выберите систему голосования\",\n    \"single-choice\": \"Голосование с одним вариантом ответа\",\n    \"approval\": \"Утверждающее голосование\",\n    \"quadratic\": \"Квадратичное голосование\",\n    \"ranked-choice\": \"Голосование по рейтингу\",\n    \"weighted\": \"Взвешенное голосование\",\n    \"basic\": \"Основное голосование\",\n    \"description\": {\n      \"single-choice\": \"Каждый участник голосования может выбрать только один вариант.\",\n      \"approval\": \"Каждый избиратель может выбрать любое количество вариантов.\",\n      \"quadratic\": \"Каждый участник может распределить право голоса между любым количеством вариантов. Результаты подсчитываются квадратично.\",\n      \"ranked-choice\": \"Каждый участник может выбрать и расставить любое количество вариантов. Результаты вычисляются методом мгновенного подсчета голосов.\",\n      \"weighted\": \"Каждый участник голосования может распределить право голоса между любым количеством вариантов.\",\n      \"basic\": \"Голосование с одним вариантом выбора с тремя вариантами: За, Против или Воздержался\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Privacy\",\n    \"title\": \"Select voting privacy\",\n    \"information\": \"The type of privacy used on proposals. (Enforced on all future proposals)\",\n    \"any\": \"Any\",\n    \"none\": \"None\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Choices are encrypted and only visible once the voting period is over\",\n      \"tooltip\": \"This proposal has Shutter privacy enabled. All votes will be encrypted until the voting period has ended and the final score is calculated\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select voting validation\",\n    \"information\": \"The type of validation used to determine if a user can vote. (Enforced on all future proposals)\",\n    \"any\": {\n      \"label\": \"Anyone can vote\",\n      \"description\": \"Anyone with voting power can cast a vote.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement to vote on this proposal. \"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your proposals from spam and vote manipulation by requiring users to have a Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold} and {operator} of the following stamps to vote on this proposal: {stamps}. \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Текущий исход\",\n    \"currentBond\": \"Текущая связь\",\n    \"finalizedIn\": \"Завершено {0}\",\n    \"executableIn\": \"Исполняемый {0}\",\n    \"finalOutcome\": \"Итог\",\n    \"nextBond\": \"Связать для установления результата\",\n    \"setOutcomeTo\": \"Установить результат в\",\n    \"claimBond\": \"Получить облигацию\",\n    \"addBatch\": \"Добавить пакет транзакций\",\n    \"batch\": \"Пакет транзакций\",\n    \"transactions\": \"Транзакции\",\n    \"to\": \"На (адрес)\",\n    \"invalidAddress\": \"Неверный адрес\",\n    \"invalidAmount\": \"Недопустимая сумма\",\n    \"invalidValue\": \"Недопустимое значение\",\n    \"invalidAbi\": \"Недопустимый ABI\",\n    \"invalidData\": \"Неверные данные\",\n    \"value\": \"Ценность (wei)\",\n    \"data\": \"Данные\",\n    \"noCollectibles\": \"Нет Коллекций\",\n    \"asset\": \"Актив\",\n    \"amount\": \"Сумма\",\n    \"type\": \"Тип\",\n    \"transferFunds\": \"Перевести средства\",\n    \"transferNFT\": \"Передать NFT\",\n    \"contractInteraction\": \"Взаимодействие с контрактом\",\n    \"rawTransaction\": \"Транзакция в исходном виде\",\n    \"addTransaction\": \"Добавить транзакцию\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei to {address}\",\n      \"transferFunds\": \"Перевести {amount} {tokenSymbol} на {address}\",\n      \"transferNFT\": \"Отправить {name} #{id} на {address}\",\n      \"raw\": \"Отправить {amount} wei на {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Выполнение запроса\",\n      \"setOutcome\": \"Задать результат\",\n      \"changeOutcome\": \"Изменить результат\",\n      \"executeTxs\": \"Выполнить пакет транзакций {0} из {1}\",\n      \"executed\": \"Все транзакции выполнены\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"Что-то пошло не так\",\n      \"connectWallet\": \"Подключите кошелек, чтобы увидеть детали выполнения\",\n      \"switchChain\": \"Переключите ваш кошелек на {0} для выполнения запроса\",\n      \"question\": \"Принято ли это предложение и соответствует ли оно\",\n      \"criteria\": \"Критерии одобрения?\",\n      \"proposalPassed\": \"Did the proposal pass?\",\n      \"expired\": \"The proposal has expired\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to confirm the proposal passed\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"requestToolTip\": \"This will propose the transactions from this Snapshot vote on-chain. After a challenge window, if the proposal is valid, the transactions can be executed and your bond will be returned.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"POAP еще не был настроен для этого предложения:'(\",\n    \"no_voted_header\": \"Проголосуйте, чтобы получить этот POAP\",\n    \"unclaimed_header\": \"Я проголосовал, забрать свой POAP\",\n    \"claimed_header\": \"Поздравляем! POAP добавлен в вашу коллекцию\",\n    \"loading_header\": \"POAP добавляется в вашу коллекцию\",\n    \"button_claim\": \"Минт\",\n    \"button_show\": \"Посмотреть коллекцию\",\n    \"success_claim\": \"POAP добавлен в вашу коллекцию\",\n    \"error_claim\": \"При создании токена возникла проблема\"\n  },\n  \"progress\": {\n    \"progress\": \"Progress\",\n    \"inProgress\": \"In Progress\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"New Step\",\n    \"description\": \"Description\",\n    \"add\": \"Add\",\n    \"deleteStep\": \"Delete Step\",\n    \"deleteConfirm\": \"Are you sure you want to delete?\",\n    \"delete\": \"Delete\",\n    \"cancel\": \"Cancel\",\n    \"comeBack\": \"Come back after voting is complete to see how this proposal is progressing!\",\n    \"confirmSignature\": \"Signing this message will allow us to authorize your request to update the progress of your proposal.\",\n    \"wentWrong\": \"Oops something went wrong.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"Графики\",\n    \"noVotesYet\": \"Голосов за визуализацию пока нет.\",\n    \"totalVotesPerDay\": \"Общее количество голосов за день\",\n    \"shareOfVotingPower\": \"Поделиться правом голоса\",\n    \"votingPowerPerDay\": \"Доля голосов в день\"\n  },\n  \"comment_box\": {\n    \"title\": \"Comment box\",\n    \"add\": \"Add your comment here\",\n    \"submit\": \"Submit\",\n    \"preview\": \"Preview\",\n    \"continue_editing\": \"Continue editing\",\n    \"edit\": \"Edit your reply here\",\n    \"edit_button\": \"Edit\",\n    \"dismiss\": \"Dismiss\",\n    \"delete\": \"Delete\",\n    \"add_reply\": \"Add your reply here\",\n    \"edit_comment\": \"Edit comment\",\n    \"edit_modal\": \"Are you sure you want to edit?\",\n    \"yes\": \"Yes\",\n    \"no\": \"No\",\n    \"delete_comment\": \"Delete comment\",\n    \"delete_modal\": \"Are you sure you want to delete?\",\n    \"error\": \"Oops, something went wrong\",\n    \"replies\": \"replies\",\n    \"hide\": \"Hide\",\n    \"show\": \"Show\",\n    \"reply\": \"Reply\",\n    \"load_more\": \"Load more\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Создать сообщество\",\n      \"timeline\": \"Хронологическая лента\",\n      \"notifications\": \"Уведомления\",\n      \"explore\": \"Изучить\",\n      \"playground\": \"Рабочая площадь\",\n      \"space\": {\n        \"create\": \"Создайте предложение {space}\",\n        \"about\": \"О {space}\",\n        \"proposals\": \"Предложения {space}\",\n        \"proposal\": \"предложение {space}: {proposal}\",\n        \"settings\": \"Настройки {space}\"\n      },\n      \"strategy\": \"{key} стратегия\",\n      \"delegate\": \"Делегировать\",\n      \"ranking\": \"Ranking\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Отслеживать предложения для {spaceName}\",\n    \"text\": \"Получать уведомления каждый раз, когда создается или завершается новое предложение\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 секунда | {n} секунд(-ы)\",\n    \"minute\": \"1 минута | {n} минут(-ы)\",\n    \"hour\": \"1 час | {n} часов(а)\",\n    \"day\": \"1 день | {n} дней(я)\",\n    \"week\": \"1 неделя | {n} недель(и)\",\n    \"month\": \"1 месяц | {n} месяцев(а)\",\n    \"year\": \"1 год | {n} лет(года)\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Неподдерживаемая сеть\",\n    \"switchNetworkToNetwork\": \"To continue, you need to change the network in your wallet to {network}.\",\n    \"switchToNetwork\": \"Switch to {network}\",\n    \"goToDemoSite\": \"Go to demo website\"\n  },\n  \"treasury\": {\n    \"title\": \"Treasury\",\n    \"wallets\": {\n      \"title\": \"Wallets\",\n      \"empty\": \"This space doesn't have a treasury yet\",\n      \"addTreasury\": \"Add a treasury\"\n    },\n    \"assets\": {\n      \"title\": \"Assets\",\n      \"empty\": \"There are no assets in this contract\"\n    },\n    \"24hChange\": \"24h change\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Your email\",\n    \"title\": \"Get the latest Snapshot updates\"\n  },\n  \"joinCommunity\": \"Join Snapshot community\",\n  \"header\": {\n    \"title\": \"Where decisions get made\",\n    \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot is a decentralized governance platform that makes it easy to create and vote on proposals - all without spending a fortune on gas fees! Plus, our flexible system supports various voting types and strategies, so you can tailor the voting process to your needs.\",\n    \"subHeader\": \"Governance should be a snap\",\n    \"subDescription\": \"Web3 governance doesn't have to be complicated. Snapshot is the perfect solution for organizations looking for an easy and efficient way to govern their community or organization.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Resources\",\n    \"about\": \"About\",\n    \"blog\": \"Blog\",\n    \"jobs\": \"Jobs\",\n    \"discussions\": \"Discussions\",\n    \"github\": \"GitHub\",\n    \"docs\": \"Docs\",\n    \"support\": \"Support\",\n    \"hiring\": \"Join us!\"\n  }\n}\n"
  },
  {
    "path": "src/locales/tr-TR.json",
    "content": "{\n  \"searchPlaceholder\": \"Ara\",\n  \"spaceCount\": \"adet\",\n  \"createSpace\": \"Alan oluştur\",\n  \"backToHome\": \"Anasayfa\",\n  \"actions\": \"İşlemler\",\n  \"poweredBy\": \"Powered by\",\n  \"results\": \"Sonuçlar\",\n  \"resultsError\": \"Results could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"resultsCalculating\": \"Final results are being calculated. If you still see this message after a few minutes contact the space admin.\",\n  \"votingPowerFailedMessage\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"votingValidationFailedMessage\": \"There was an error on our side and we could not verify if you are eligible to vote. This is often due to a misconfigured voting validation or an unresponsive API involved in the validation.\",\n  \"notValidVoterMessage\": \"Oops, you don't seem to be eligible to vote on this proposal.\",\n  \"getHelp\": \"Get help\",\n  \"retry\": \"Retry\",\n  \"currentResults\": \"Güncel Sonuçlar\",\n  \"reset\": \"Sıfırla\",\n  \"close\": \"Close\",\n  \"save\": \"Kaydet\",\n  \"author\": \"Yazar\",\n  \"next\": \"Sonraki\",\n  \"choice\": \"Choice\",\n  \"submit\": \"Gönder\",\n  \"plugins\": \"Eklentiler\",\n  \"information\": \"Bilgi\",\n  \"confirm\": \"Onayla\",\n  \"snapshot\": \"Anlık Görüntü\",\n  \"strategies\": \"Strateji\",\n  \"strategiesPage\": \"Stratejiler\",\n  \"space\": \"Boşluk\",\n  \"spaces\": \"Aralık\",\n  \"verifiedSpace\": \"Verified space\",\n  \"warningSpace\": \"This space has been flagged as potentially malicious. Proceed with caution.\",\n  \"version\": \"Sürüm\",\n  \"timeline\": \"Zaman çizelgesi\",\n  \"ended\": \"ended\",\n  \"started\": \"started\",\n  \"filters\": \"Filtreler\",\n  \"allSpaces\": \"Tümü\",\n  \"submitOnchain\": \"Zincirde onayla\",\n  \"inSpaces\": \"{0} Adet alan içinde\",\n  \"votes\": \"Oylar\",\n  \"seeMore\": \"Daha fazla göster\",\n  \"seeAll\": \"See all\",\n  \"network\": \"Ağ\",\n  \"networks\": \"Ağlar\",\n  \"skins\": \"Ciltler\",\n  \"spaceMembers\": \"Members\",\n  \"members\": \"Üye yok | {count} Üye | {count} Üyeler\",\n  \"editStrategy\": \"Stratejiyi Düzenle\",\n  \"invalidProposals\": \"Geçersiz teklifler\",\n  \"account\": \"Hesap\",\n  \"create3box\": \"Profili 3Box'da oluştur\",\n  \"view3box\": \"Profili 3Box'da görüntüle\",\n  \"edit3box\": \"Profili 3Box'da düzenle\",\n  \"connectWallet\": \"Cüzdanı bağlayın\",\n  \"toggleSkin\": \"Toggle skin\",\n  \"about\": \"Hakkında\",\n  \"license\": \"Lisans\",\n  \"showMore\": \"Show more\",\n  \"voted\": \"Voted\",\n  \"reload\": \"Reload\",\n  \"ipfsServer\": \"IPFS protokolü\",\n  \"hub\": \"Hub\",\n  \"cancel\": \"İptal\",\n  \"delete\": \"Delete\",\n  \"demoSite\": \"Bu site şuan sadece demo, bize şans ver!\",\n  \"removeDelegation\": \"Delegasyonları Kaldır\",\n  \"confirmRemove\": \"Yetkilendirmenizi kaldırmak istediğinizden emin misiniz\",\n  \"removeSpace\": \"{0} alanı için\",\n  \"noVotingPower\": \"Oops, it seems you don't have any voting power at block {blockNumber}.\",\n  \"quorumReached\": \"quorum reached\",\n  \"options\": \"Seçenekler\",\n  \"votingPower\": \"Oy Verme Gücünüz\",\n  \"comment\": {\n    \"placeholder\": \"Share your reason (optional)\"\n  },\n  \"receipt\": \"Dekont\",\n  \"relayer\": \"Aktaran\",\n  \"verifyOnMycrypto\": \"MyCrypto'da dekontu doğrulayın\",\n  \"verifyOnSignatorio\": \"Signator.io'da doğrula\",\n  \"isCore\": \"Genel\",\n  \"notificationsBlocked\": \"Tarayıcın bildirimleri engelliyor\",\n  \"notificationsNotSupported\": \"Your browser does not support notifications\",\n  \"walletNotSupported\": \"Wallet is not supported\",\n  \"seeInExplorer\": \"See explorer\",\n  \"learnMore\": \"Daha fazla bilgi edinin\",\n  \"logout\": \"Çıkış\",\n  \"continue\": \"Devam et\",\n  \"add\": \"Ekle\",\n  \"edit\": \"Düzenle\",\n  \"strategyParameters\": \"Strateji parametreleri\",\n  \"addAction\": \"İşlem Ekle\",\n  \"removeAction\": \"İşlemi kaldırın\",\n  \"yourChoice\": \"Seçenek {0}\",\n  \"targetAddress\": \"Hedef adresi\",\n  \"value\": \"Fiyat\",\n  \"date\": \"Veri\",\n  \"marketDetails\": \"Market detayları\",\n  \"addMarket\": \"Market ekle\",\n  \"selectNetwork\": \"Ağ seçin\",\n  \"conditionId\": \"Durum ID'si\",\n  \"basetokenAddress\": \"Ana token adresi\",\n  \"quoteAddress\": \"Karşıt cüzdan adresi\",\n  \"removeMarket\": \"Marketi kaldır\",\n  \"back\": \"Geri\",\n  \"loading\": \"Yükleniyor...\",\n  \"predictedImpact\": \"Öngörülen etki\",\n  \"marketSymbol\": \"{0} market\",\n  \"twoChoicesRequired\": \"Bu eklenti için iki seçenek gereklidir.\",\n  \"noResultsFound\": \"Hata, herhangi bir sonuç bulamıyoruz\",\n  \"createFirstProposal\": \"İlk teklifinizi oluşturalım\",\n  \"noSpacesJoined\": \"Oops, henüz herhangi bir alana katılmadın\",\n  \"addFavorites\": \"Favorileri ekleyin\",\n  \"createdBy\": \"{0} Tarafından\",\n  \"startIn\": \"başlangıç {0}\",\n  \"endIn\": \"bitiş {0}\",\n  \"proposalTimeLeft\": \"{0} left\",\n  \"endedAgo\": \"ended {0}\",\n  \"proposalBy\": \"{0} tarafından\",\n  \"endDate\": \"bitiş {0}\",\n  \"contentHash\": \"İçerik sağlama\",\n  \"defaultSkin\": \"Varsayılan seçim\",\n  \"select\": \"Seçin\",\n  \"language\": \"Dil\",\n  \"agree\": \"Kabul Ediyorum\",\n  \"moderators\": \"Moderators\",\n  \"playground\": \"Oyun Alanı\",\n  \"strategyParams\": \"Strateji parametreleri\",\n  \"addresses\": \"Adresler\",\n  \"networkErrorPlayground\": \"Ağ hatası - daha fazla bilgi için lütfen tarayıcı konsolunuzu açın\",\n  \"upload\": \"Yükle\",\n  \"join\": \"Katıl\",\n  \"joined\": \"Katıldın\",\n  \"leave\": \"Ayrıl\",\n  \"subspaces\": \"Sub-spaces\",\n  \"mainspace\": \"Main space\",\n  \"copyLink\": \"Linki kopyala\",\n  \"duplicate\": \"Duplicate\",\n  \"joinedSpaces\": \"Alana katıldın\",\n  \"joinSpaces\": \"Alanlara katıl\",\n  \"setDelegationToSpace\": \"Limit delegation to a specific space\",\n  \"theCurrentNetwork\": \"the current network\",\n  \"optional\": \"(optional)\",\n  \"homeLoadmore\": \"Load more\",\n  \"confirmAction\": \"Confirm action\",\n  \"or\": \"or\",\n  \"share\": \"Share\",\n  \"shareOnTwitter\": \"Share on Twitter\",\n  \"shareOnLenster\": \"Share on Lenster\",\n  \"createButton\": \"Create\",\n  \"discussion\": \"Discussion\",\n  \"changeWallet\": \"Change wallet\",\n  \"createASpace\": \"Create a space\",\n  \"getStarted\": \"Get started\",\n  \"skip\": \"Skip\",\n  \"newSpaceNotice\": {\n    \"header\": \"Your space is live!\",\n    \"mainText\": \"You can change how voting power is calculated via strategies in your {settings}. Changes to your settings will only affect new proposals, existing proposals can not be changed.\",\n    \"learnMore\": \"Learn more in the {documentation} or join Snapshot {discord} for help.\",\n    \"gotIt\": \"Got it!\"\n  },\n  \"errors\": {\n    \"required\": \"Bu alan zorunludur\",\n    \"minLength\": \"Field is required\",\n    \"maxLength\": \"Maksimum Uzunluk: {0}\",\n    \"pattern\": \"Geçersiz karakter\",\n    \"minItems\": \"Minimum {0} öğe gerekli\",\n    \"maxItems\": \"Maximum {0} item(s) allowed\",\n    \"minStrategy\": \"En az bir strateji gerekiyor.\",\n    \"website\": \"URL should be in the format https://www.example.com\",\n    \"format\": \"Geçersiz format\",\n    \"type\": \"Geçersiz tür\",\n    \"unsupportedImageType\": \"File type not supported, Supported formats are jpeg, jpg and png\",\n    \"invalidAddress\": \"Invalid address\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"Title\",\n    \"discussion\": \"Discussion (optional)\",\n    \"categorie(s)\": \"Select up to 2 categorie(s)\",\n    \"proposalDescription\": \"Description (optional)\",\n    \"preview\": \"Önizleme\",\n    \"choices\": \"Seçenekler\",\n    \"addChoice\": \"Seçim ekle\",\n    \"startDate\": \"Başlangıç zamanını seçin\",\n    \"endDate\": \"Bitiş zamanını seçin\",\n    \"startTime\": \"Başlangıç zamanını seçin\",\n    \"endTime\": \"Bitiş zamanını seçin\",\n    \"publish\": \"Yayımla\",\n    \"untitled\": \"Untitled\",\n    \"snapshotBlock\": \"Ekran görüntüsü alınma blok numarası\",\n    \"voting\": \"Voting\",\n    \"votingSystem\": \"Voting system\",\n    \"choice\": \"Choice {0}\",\n    \"period\": \"Voting period\",\n    \"start\": \"Start\",\n    \"end\": \"End\",\n    \"days\": \"Days\",\n    \"hours\": \"Hours\",\n    \"minutes\": \"Minutes\",\n    \"schedule\": \"Schedule proposal\",\n    \"delayEnforced\": \"The space enforces a delay until voting can start\",\n    \"periodEnforced\": \"The space enforces the duration of the voting period\",\n    \"typeEnforced\": \"{type} is enforced by the space\",\n    \"privacyEnforced\": \"{type} is enforced by the space\",\n    \"edit\": \"Edit\",\n    \"continue\": \"Continue\",\n    \"now\": \"Now\",\n    \"votingPeriodExplainer\": \"This is the time period in which users will be able to vote. The proposal will be visible and pending before the start of the voting period.\",\n    \"uploadImageExplainer\": \"Attach images by dragging & dropping, selecting or pasting them.\",\n    \"uploading\": \"Uploading image\",\n    \"markdown\": \"Styling with Markdown is supported\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"Bir teklif göndermek için alanın yazarı olman gerekir.\",\n        \"minScore\": \"Bir teklif gönderebilmek için minimum {0} {1} sahip olunması gerekir.\"\n      },\n      \"customValidation\": \"Bir teklif göndermek için teklif doğrulamasını geçmeniz gerekir.\",\n      \"executionError\": \"Verifying your eligibility to create proposals in this space has failed. This is likely due to a misconfigured strategy.\"\n    },\n    \"errorGettingSnapshot\": \"We encountered an error while fetching the snapshot block number which is needed to calculate your voting power. Please try again later.\"\n  },\n  \"delegate\": {\n    \"header\": \"Temsilci\",\n    \"selectDelegate\": \"Temsilci seç\",\n    \"to\": \"Alıcı\",\n    \"addressPlaceholder\": \"Adres veya ENS adı\",\n    \"delegations\": \"Temsilcilik(ler)iniz\",\n    \"allSpaces\": \"Tüm alanlar için\",\n    \"delegated\": \"Görevlendir\",\n    \"pendingTransaction\": \"bekleyen işlem yok | 1 bekleyen işlem | {count} bekleyen işlem\",\n    \"topDelegates\": \"Top delegates\",\n    \"noDelegatesFoundFor\": \"No delegates found for {0}\",\n    \"noValidEns\": \"Not a valid ENS address.\",\n    \"noValidAddress\": \"Not a valid address\",\n    \"delegateToSelf\": \"You cannot delegate to yourself\",\n    \"delegateToSelfAddress\": \"You cannot delegate to your own ENS address\",\n    \"noValidSpaceId\": \"Not a valid space ID\",\n    \"noDelegationsAndDelegates\": \"Can't find your delegations and delegates? Make sure you are connected to the correct network.\",\n    \"delegateNotSupported\": \"Delegation is currently not supported for {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Oyunu kullan\",\n    \"vote\": \"Oyla\",\n    \"startDate\": \"Başlangıç tarihi\",\n    \"endDate\": \"Bitiş tarihi\",\n    \"votingSystem\": \"Oylama sistemi\",\n    \"privacy\": \"Privacy\",\n    \"invalidChoice\": \"Invalid choice\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"Your vote is in!\",\n      \"gnosisSafeTitle\": \"Your vote is pending...\",\n      \"gnosisSafeDescription\": \" Votes with a Safe require additional signers and will be visible once the transaction is confirmed\",\n      \"seeQueue\": \"See queued transactions\",\n      \"tips\": {\n        \"1\": \"Votes can be changed while the proposal is active\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"Teklifler\",\n    \"new\": \"Yeni teklif\",\n    \"noProposals\": \"Henüz yeni bir teklif yok!\",\n    \"createProposal\": \"Teklif oluştur\",\n    \"showMore\": \"Show more\",\n    \"showLess\": \"Show less\",\n    \"states\": {\n      \"all\": \"Tümü\",\n      \"core\": \"Genel\",\n      \"community\": \"Topluluk\",\n      \"active\": \"Aktif\",\n      \"pending\": \"Beklemede\",\n      \"closed\": \"Kapandı\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"Notifications\",\n    \"noNotifications\": \"You have no notifications\",\n    \"proposalStarted\": \"proposal has started:\",\n    \"proposalEnded\": \"proposal has ended:\",\n    \"markAllAsRead\": \"Mark all as read\",\n    \"all\": \"All\",\n    \"unread\": \"Unread\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"To {action} this space, you must agree to the {spaceName} terms of service.\",\n    \"actionJoin\": \"join\",\n    \"actionCreate\": \"create a proposal in\",\n    \"actionVote\": \"vote in\"\n  },\n  \"settings\": {\n    \"header\": \"Ayarlar\",\n    \"editController\": \"Edit controller\",\n    \"connectWithSpaceOwner\": \"You are in view only mode, to modify space settings connect with a controller or admin wallet.\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"Your Gnosis Safe is on the wrong network. Please connect to {network} to {action}.\",\n      \"settings\": \"edit the space settings\",\n      \"create\": \"create a proposal\",\n      \"vote\": \"vote on this proposal\"\n    },\n    \"currentSpaceControllerIs\": \"The current space controller is {address}\",\n    \"newController\": \"New controller\",\n    \"noRecord\": \"No text-record found. Make sure you have registered {id} domain on {network}, then edit the controller text-record to regain access to the space settings.\",\n    \"set\": \"Set\",\n    \"profile\": \"Profil\",\n    \"avatar\": \"Profil Resmi\",\n    \"name\": {\n      \"label\": \"Name\",\n      \"placeholder\": \"e.g. Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"About\",\n      \"placeholder\": \"What is your organisation about?\"\n    },\n    \"categories\": {\n      \"label\": \"Categorie(s)\",\n      \"select\": \"Select categorie(s)\"\n    },\n    \"terms\": {\n      \"label\": \"Terms of service\",\n      \"information\": \"Users will be required to accept these terms once before they can create a proposal or cast a vote\"\n    },\n    \"hideSpace\": \"Ana sayfadan alanı gizle\",\n    \"links\": \"Social accounts\",\n    \"subspaces\": {\n      \"label\": \"Sub-spaces\",\n      \"information\": \"Sub-spaces will only be shown once configured on both the main space and the sub-space(s). {docs}\",\n      \"parent\": {\n        \"label\": \"Main space\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"The space that this space is a sub-space of will be displayed on the space page\"\n      },\n      \"children\": {\n        \"label\": \"Sub-spaces\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"Related Sub-spaces listed here will be displayed on the space page\"\n      }\n    },\n    \"website\": \"Website\",\n    \"strategies\": {\n      \"label\": \"Strategie(s)\",\n      \"information\": \"Strategies are used to determine voting power or whether a user is eligible to create a proposal\"\n    },\n    \"network\": {\n      \"label\": \"Network\",\n      \"information\": \"The defaul network used for this space. Networks can also be specified in individual strategies\"\n    },\n    \"symbol\": {\n      \"label\": \"Symbol\",\n      \"information\": \"The default symbol used for this space, usually the token symbol i.e. BAL for Balancer\"\n    },\n    \"strategiesList\": \"Select up to 8 strategies\",\n    \"votingPowerIsCumulative\": \"Voting power is cumulative\",\n    \"addStrategy\": \"Strateji ekle\",\n    \"testInPlayground\": \"Test in playground\",\n    \"admins\": {\n      \"label\": \"Admins\",\n      \"information\": \"Admins are able to modify the space settings and manage the space's proposals\"\n    },\n    \"authors\": {\n      \"label\": \"Authors\",\n      \"information\": \"Authors are always able to create proposals\"\n    },\n    \"proposalValidation\": \"Teklif doğrulama\",\n    \"validation\": \"Type\",\n    \"proposalThreshold\": {\n      \"label\": \"Threshold\",\n      \"information\": \"The minimum amount of voting power required to create a proposal\"\n    },\n    \"allowOnlyAuthors\": \"Yalnızca yazarların teklif göndermesine izin ver\",\n    \"editValidation\": \"Doğrulamaları düzenle\",\n    \"selectValidation\": \"Doğrulamayı seçin\",\n    \"validationParameters\": \"Doğrulama parametreleri\",\n    \"voting\": \"Oylama\",\n    \"votingDelay\": \"Oylama gecikmesi\",\n    \"votingPeriod\": \"Oylama periyodu\",\n    \"hours\": \"Hours\",\n    \"days\": \"Days\",\n    \"quorum\": {\n      \"label\": \"Quorum\",\n      \"information\": \"The minimum amount of voting power required for the proposal to pass\"\n    },\n    \"type\": {\n      \"label\": \"Type\",\n      \"information\": \"The type of voting system used for this space. (Enforced on all future proposals)\"\n    },\n    \"anyType\": \"Hiç\",\n    \"hideAbstain\": \"Ignore abstain votes in basic voting results\",\n    \"customDomain\": \"Özel alanadı\",\n    \"domain\": {\n      \"label\": \"Domain name\",\n      \"placeholder\": \"e.g. vote.balancer.fi\",\n      \"info\": \"To setup a custom domain you additionally need to open a pull request on github after you have created the space. {docs}\"\n    },\n    \"skin\": \"Birim\",\n    \"treasuries\": {\n      \"label\": \"Treasury\",\n      \"add\": \"Add treasury\",\n      \"edit\": \"Edit treasury\",\n      \"information\": \"Add treasuries of your organization to show them in your space\"\n    },\n    \"addPlugin\": \"Eklenti ekle\",\n    \"editPlugin\": \"Eklentiyi düzenle\",\n    \"pluginParameters\": \"Eklenti Parametreleri\",\n    \"proposal\": {\n      \"title\": \"Proposal\",\n      \"guidelines\": {\n        \"title\": \"Guidelines\",\n        \"information\": \"Display a link to your guidelines on proposal creation to help users understand what constitutes a good/valid proposal\"\n      },\n      \"template\": {\n        \"title\": \"Template\",\n        \"information\": \"Start every proposal with a template to help users understand what information is required\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"örneğin. yam.eth\",\n    \"chooseExistingEns\": \"Choose one of your existing ENS domains to create a space with:\",\n    \"useSingleExistingEns\": \"Use your existing ENS domain:\",\n    \"orRegisterNewEns\": \"Or register a new domain:\",\n    \"demoTestnetEnsMessage\": \"To create a test space you need an ENS domain on {network}.\",\n    \"toCreateASpace\": \"To create a space, you first need an ENS domain. Enter one below and follow the ENS registration instructions.\",\n    \"createASpace\": \"Oluştur\",\n    \"registerEnsButton\": \"Kaydol\",\n    \"supportedEnsTLDs\": \"Supported domain endings\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to setup your space? Learn more in the {docs} or join Snapshot {discord}.\",\n    \"setSpaceController\": \"Space controller\",\n    \"setSpaceControllerExists\": \"The snapshot text-record for this domain has already been set. Choose edit to change it, otherwise you can skip to the next step.\",\n    \"setSpaceControllerInfo\": \" The space controller is the account that will be able to manage the space settings. Additional space controllers (admins) can be added later.\",\n    \"setSpaceControllerInfoGnosisSafe\": \"When creating a space with a Gnosis Safe it's recommended to set the safe address as the space controller. If you don't, you need to follow some additional steps. {link}\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"Setting the controller requires a transaction on the {network} which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choose ENS address\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. {address}\",\n    \"controllerAddress\": \"Controller address\",\n    \"updateController\": \"Update controller\",\n    \"seeOnEns\": \"See on ENS\",\n    \"goToSettings\": \"Go to settings\",\n    \"setSpaceProfile\": \"Customize your space\",\n    \"waitForTransaction\": \"The transaction need to confirm before you can create your space. {txUrl}\",\n    \"pleaseWaitMessage\": \"This can take a few minutes, please wait while the transaction is being confirmed\",\n    \"notControllerAddress\": \"Please connect with the controller address {wallet} to create the space.\",\n    \"fillCurrentAccount\": \"Use currently logged in account\",\n    \"domain\": {\n      \"title\": \"Setup your space domain\",\n      \"ensMessage\": \" One thing you need before you can create your own space, is an ENS domain on Ethereum mainnet.\",\n      \"ensMessageTestnet\": \"You can also {link} on the Goerli testnet and mess with things there first.\",\n      \"tryDemo\": \"try the demo\",\n      \"yourExistingSpaces\": \"Your existing spaces\",\n      \"invalidEns\": \"This ENS name is invalid. Usually this is due the use of invalid characters during registration.\"\n    },\n    \"strategy\": {\n      \"title\": \"How would you like to setup your voting strategy?\",\n      \"subtitle\": \"You can change your strategy settings any time.\",\n      \"blockTitle\": \"Setup voting strategy\",\n      \"onePersonOneVote\": {\n        \"title\": \"One person, one vote\",\n        \"description\": \"Manage a whitelist of people who can vote or simply allow any address to vote. Every vote is equal and no token is required\",\n        \"whitelistInformation\": \"Specify a number of accounts that can vote\",\n        \"ticketInformation\": \"Any account can vote\",\n        \"votesEqualInfo\": \"Each vote is equal and no token is required\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"Token weighted voting\",\n        \"description\": \"Votes are weighted by a token. The token can be an ERC-20, ERC-721 or ERC-1155 token standard\",\n        \"tokenNotFound\": \"Token not found\",\n        \"seeOnEtherscan\": \"See on Etherscan\"\n      },\n      \"advanced\": {\n        \"title\": \"Custom setup\",\n        \"description\": \"Select up to 8 strategies with a wide range of options. If you can't find the right strategy for your use case, you can create your own\"\n      }\n    },\n    \"validationTitle\": \"Who can manage this space and create proposals?\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"Edit profile\",\n    \"viewProfile\": \"View profile\",\n    \"about\": {\n      \"header\": \"About\",\n      \"joinedSpaces\": \"Joined spaces\",\n      \"createdSpaces\": \"Created spaces\",\n      \"biography\": \"Bio\",\n      \"notJoinSpacesYet\": \"Hasn't joined any spaces yet\",\n      \"notCreatedSpacesYet\": \"Hasn't created any spaces yet\",\n      \"delegatorNetworkInfo\": \"Change by switching network in your wallet\",\n      \"delegate\": \"Delegate\",\n      \"delegated\": \"Delegated\",\n      \"delegateTo\": \"Delegate to\",\n      \"delegateFor\": \"Delegator for\",\n      \"noDelegatorsMessage\": \"No delegators on {network}\",\n      \"notSupportedNetwork\": \"Delegation currently isn't supported on {network} \"\n    },\n    \"activity\": {\n      \"header\": \"Activity\",\n      \"votedFor\": \"Voted {choice}\",\n      \"today\": \"Today\",\n      \"thisWeek\": \"This week\",\n      \"olderThanWeek\": \"Older than a week\",\n      \"noActivity\": \"No activity yet\"\n    },\n    \"settings\": {\n      \"header\": \"Edit profile\",\n      \"name\": \"Name\",\n      \"biography\": \"Bio\",\n      \"namePlaceholder\": \"Enter name\",\n      \"bioPlaceholder\": \"Tell your story\",\n      \"change\": \"Change\",\n      \"remove\": \"Remove\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"Başardınız!\",\n    \"copied\": \"Kopyalandı!\",\n    \"proposalDeleted\": \"Teklif silindi\",\n    \"somethingWentWrong\": \"Hoop! Birşeyler yanlış gitti!\",\n    \"saved\": \"Kaydedildi!\",\n    \"delegationSuccess\": \"Delegasyon Başarılı\",\n    \"delegationRemoved\": \"Delegation removed\",\n    \"proposalCreated\": \"Proposal created\",\n    \"voteSuccessful\": \"Your vote is in!\",\n    \"ensSet\": \"ENS text record was successfully set\",\n    \"transactionSent\": \"Transaction sent\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Strateji oluştur\",\n    \"createSkin\": \"Yeni bir tema oluştur\",\n    \"addNetwork\": \"Ağ ekle\",\n    \"createPlugin\": \"Eklenti oluştur\",\n    \"strategies\": \"strateji(ler)\",\n    \"skins\": \"birim(ler)\",\n    \"networks\": \"ağ(lar)\",\n    \"plugins\": \"eklenti(ler)\",\n    \"results\": \"sonuç(lar)\",\n    \"category\": \"Category\",\n    \"categories\": {\n      \"all\": \"All\",\n      \"protocol\": \"Protocol\",\n      \"social\": \"Social\",\n      \"investment\": \"Investment\",\n      \"grant\": \"Grant\",\n      \"service\": \"Service\",\n      \"media\": \"Media\",\n      \"creator\": \"Creator\",\n      \"collector\": \"Collector\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Oylama sistemini seçiniz\",\n    \"single-choice\": \"Tek seçenekli oylama\",\n    \"approval\": \"Oy onayı\",\n    \"quadratic\": \"İkinci dereceden(quadratic) oylama\",\n    \"ranked-choice\": \"Dereceli seçim oylaması\",\n    \"weighted\": \"Ağırlıklı oylama\",\n    \"basic\": \"Basic voting\",\n    \"description\": {\n      \"single-choice\": \"Her seçmen sadece bir seçim yapabilir.\",\n      \"approval\": \"Her seçmen istediği sayıda seçim yapabilir.\",\n      \"quadratic\": \"Her seçmen, oy verme gücünü herhangi bir sayıda seçeneğe yayabilir. Sonuçlar kuadratik olarak hesaplanır.\",\n      \"ranked-choice\": \"Her seçmen istediği sayıda seçeneği seçebilir ve sıralayabilir. Sonuçlar anlık akış sayma yöntemiyle hesaplanır.\",\n      \"weighted\": \"Her seçmen, oy verme gücünü herhangi bir sayıda seçeneğe yayabilir.\",\n      \"basic\": \"Single choice voting with three choices: For, Against or Abstain\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"Privacy\",\n    \"title\": \"Select voting privacy\",\n    \"information\": \"The type of privacy used on proposals. (Enforced on all future proposals)\",\n    \"any\": \"Any\",\n    \"none\": \"None\",\n    \"shutter\": {\n      \"label\": \"Shutter\",\n      \"description\": \"Choices are encrypted and only visible once the voting period is over\",\n      \"tooltip\": \"This proposal has Shutter privacy enabled. All votes will be encrypted until the voting period has ended and the final score is calculated\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"Validation\",\n    \"title\": \"Select voting validation\",\n    \"information\": \"The type of validation used to determine if a user can vote. (Enforced on all future proposals)\",\n    \"any\": {\n      \"label\": \"Anyone can vote\",\n      \"description\": \"Anyone with voting power can cast a vote.\"\n    },\n    \"basic\": {\n      \"label\": \"Basic\",\n      \"description\": \"Use any strategy to determine if a user can vote.\",\n      \"invalidMessage\": \"You do not meet the minimum balance requirement to vote on this proposal. \"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport gated\",\n      \"description\": \"Protect your proposals from spam and vote manipulation by requiring users to have a Gitcoin Passport.\",\n      \"invalidMessage\": \"You need a Gitcoin Passport with score above {scoreThreshold} and {operator} of the following stamps to vote on this proposal: {stamps}. \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Current outcome\",\n    \"currentBond\": \"Current bond\",\n    \"finalizedIn\": \"Sonuçlandı {0}\",\n    \"executableIn\": \"Çalıştırılabilir {0}\",\n    \"finalOutcome\": \"Outcome\",\n    \"nextBond\": \"Bond to set outcome\",\n    \"setOutcomeTo\": \"Set outcome to\",\n    \"claimBond\": \"Teminat talebi\",\n    \"addBatch\": \"Add transaction batch\",\n    \"batch\": \"Transaction batch\",\n    \"transactions\": \"Transactions\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"Invalid address\",\n    \"invalidAmount\": \"Invalid amount\",\n    \"invalidValue\": \"Invalid value\",\n    \"invalidAbi\": \"Invalid ABI\",\n    \"invalidData\": \"Invalid data\",\n    \"value\": \"Value (wei)\",\n    \"data\": \"Data\",\n    \"noCollectibles\": \"No collectibles\",\n    \"asset\": \"Asset\",\n    \"amount\": \"Amount\",\n    \"type\": \"Type\",\n    \"transferFunds\": \"Transfer funds\",\n    \"transferNFT\": \"Transfer NFT\",\n    \"contractInteraction\": \"Contract interaction\",\n    \"rawTransaction\": \"Raw transaction\",\n    \"addTransaction\": \"Add transaction\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei to {address}\",\n      \"transferFunds\": \"Transfer {amount} {tokenSymbol} to {address}\",\n      \"transferNFT\": \"Send {name} #{id} to {address}\",\n      \"raw\": \"Send {amount} wei to {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Yürütme isteğinde bulun\",\n      \"setOutcome\": \"Sonucu ayarla\",\n      \"changeOutcome\": \"Sonucu değiştir\",\n      \"executeTxs\": \"Execute transaction batch {0} of {1}\",\n      \"executed\": \"All transactions have been executed\",\n      \"noTransactions\": \"There are no transactions to execute\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"Something went wrong\",\n      \"connectWallet\": \"Connect wallet to see execution details\",\n      \"switchChain\": \"Switch your wallet to {0} to request execution\",\n      \"question\": \"Bu teklif kabul edildi mi ve beklentileri karşılıyor mu\",\n      \"criteria\": \"kabul şartları?\",\n      \"proposalPassed\": \"Did the proposal pass?\",\n      \"expired\": \"The proposal has expired\",\n      \"approveBond\": \"Approve bond\",\n      \"confirmVoteResults\": \"Click to confirm the proposal passed\",\n      \"executeTxsUma\": \"Execute transaction batch\",\n      \"deleteDisputedProposal\": \"Delete disputed proposal\",\n      \"confirmVoteResultsToolTip\": \"Make sure this proposal was approved by this Snapshot vote before proposing on-chain. If the Snapshot vote rejected the proposal, the on-chain proposal will be rejected as well and you will lose your bond.\",\n      \"approveBondToolTip\": \"On-chain proposals require a bond from the proposer. This will approve tokens from your wallet to be posted as a bond. If you make an invalid proposal, it will be disputed and you will lose your bond. If the proposal is valid, your bond will be returned when the transactions are executed.\",\n      \"requestToolTip\": \"This will propose the transactions from this Snapshot vote on-chain. After a challenge window, if the proposal is valid, the transactions can be executed and your bond will be returned.\",\n      \"executeToolTip\": \"This will execute the transactions from this proposal and return the proposer's bond.\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"Bu teklif için henüz bir POAP kurulmadı :'(\",\n    \"no_voted_header\": \"Bu POAP'ı almak için oy verin\",\n    \"unclaimed_header\": \"Mint your I voted POAP\",\n    \"claimed_header\": \"Congratulations! The POAP has been minted to your collection\",\n    \"loading_header\": \"The POAP is being minted to your collection\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"Browse collection\",\n    \"success_claim\": \"The POAP has been minted to your collection\",\n    \"error_claim\": \"Jeton basılırken bir hata oluştu\"\n  },\n  \"progress\": {\n    \"progress\": \"Progress\",\n    \"inProgress\": \"In Progress\",\n    \"completed\": \"Completed\",\n    \"complete\": \"Complete\",\n    \"newStep\": \"New Step\",\n    \"description\": \"Description\",\n    \"add\": \"Add\",\n    \"deleteStep\": \"Delete Step\",\n    \"deleteConfirm\": \"Are you sure you want to delete?\",\n    \"delete\": \"Delete\",\n    \"cancel\": \"Cancel\",\n    \"comeBack\": \"Come back after voting is complete to see how this proposal is progressing!\",\n    \"confirmSignature\": \"Signing this message will allow us to authorize your request to update the progress of your proposal.\",\n    \"wentWrong\": \"Oops something went wrong.\",\n    \"voting\": \"Voting\",\n    \"soon\": \"Soon\"\n  },\n  \"charts\": {\n    \"charts\": \"Charts\",\n    \"noVotesYet\": \"There are no votes to visualize yet.\",\n    \"totalVotesPerDay\": \"Total votes per day\",\n    \"shareOfVotingPower\": \"Share of voting power\",\n    \"votingPowerPerDay\": \"Voting power per day\"\n  },\n  \"comment_box\": {\n    \"title\": \"Comment box\",\n    \"add\": \"Add your comment here\",\n    \"submit\": \"Submit\",\n    \"preview\": \"Preview\",\n    \"continue_editing\": \"Continue editing\",\n    \"edit\": \"Edit your reply here\",\n    \"edit_button\": \"Edit\",\n    \"dismiss\": \"Dismiss\",\n    \"delete\": \"Delete\",\n    \"add_reply\": \"Add your reply here\",\n    \"edit_comment\": \"Edit comment\",\n    \"edit_modal\": \"Are you sure you want to edit?\",\n    \"yes\": \"Yes\",\n    \"no\": \"No\",\n    \"delete_comment\": \"Delete comment\",\n    \"delete_modal\": \"Are you sure you want to delete?\",\n    \"error\": \"Oops, something went wrong\",\n    \"replies\": \"replies\",\n    \"hide\": \"Hide\",\n    \"show\": \"Show\",\n    \"reply\": \"Reply\",\n    \"load_more\": \"Load more\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Create a space\",\n      \"timeline\": \"Timeline\",\n      \"notifications\": \"Notifications\",\n      \"explore\": \"Explore\",\n      \"playground\": \"Playground\",\n      \"space\": {\n        \"create\": \"Create {space} proposal\",\n        \"about\": \"About {space}\",\n        \"proposals\": \"{space} Proposals\",\n        \"proposal\": \"{space} proposal: {proposal}\",\n        \"settings\": \"{space} Settings\"\n      },\n      \"strategy\": \"{key} strategy\",\n      \"delegate\": \"Delegate\",\n      \"ranking\": \"Ranking\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Track proposals for {spaceName}\",\n    \"text\": \"Receive notifications every time a new proposal is created or ends\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 second | {n} seconds\",\n    \"minute\": \"1 minute | {n} minutes\",\n    \"hour\": \"1 hour | {n} hours\",\n    \"day\": \"1 day | {n} days\",\n    \"week\": \"1 week | {n} weeks\",\n    \"month\": \"1 month | {n} months\",\n    \"year\": \"1 year | {n} years\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Unsupported network\",\n    \"switchNetworkToNetwork\": \"To continue, you need to change the network in your wallet to {network}.\",\n    \"switchToNetwork\": \"Switch to {network}\",\n    \"goToDemoSite\": \"Go to demo website\"\n  },\n  \"treasury\": {\n    \"title\": \"Treasury\",\n    \"wallets\": {\n      \"title\": \"Wallets\",\n      \"empty\": \"This space doesn't have a treasury yet\",\n      \"addTreasury\": \"Add a treasury\"\n    },\n    \"assets\": {\n      \"title\": \"Assets\",\n      \"empty\": \"There are no assets in this contract\"\n    },\n    \"24hChange\": \"24h change\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"Your email\",\n    \"title\": \"Get the latest Snapshot updates\"\n  },\n  \"joinCommunity\": \"Join Snapshot community\",\n  \"header\": {\n    \"title\": \"Where decisions get made\",\n    \"description\": \"Snapshot is a free, open-source platform for community governance. Create your own space now and start making decisions!\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot is a decentralized governance platform that makes it easy to create and vote on proposals - all without spending a fortune on gas fees! Plus, our flexible system supports various voting types and strategies, so you can tailor the voting process to your needs.\",\n    \"subHeader\": \"Governance should be a snap\",\n    \"subDescription\": \"Web3 governance doesn't have to be complicated. Snapshot is the perfect solution for organizations looking for an easy and efficient way to govern their community or organization.\"\n  },\n  \"footerView\": {\n    \"resources\": \"Resources\",\n    \"about\": \"About\",\n    \"blog\": \"Blog\",\n    \"jobs\": \"Jobs\",\n    \"discussions\": \"Discussions\",\n    \"github\": \"GitHub\",\n    \"docs\": \"Docs\",\n    \"support\": \"Support\",\n    \"hiring\": \"Join us!\"\n  }\n}\n"
  },
  {
    "path": "src/locales/uk-UA.json",
    "content": "{\n  \"searchPlaceholder\": \"Пошук\",\n  \"spaceCount\": \"{0} розділ(и)\",\n  \"createSpace\": \"Створити розділ\",\n  \"backToHome\": \"Головна\",\n  \"actions\": \"Дії\",\n  \"results\": \"Результат\",\n  \"resultsError\": \"Results could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"vpError\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"getHelp\": \"Get help\",\n  \"retry\": \"Retry\",\n  \"currentResults\": \"Поточні результати\",\n  \"reset\": \"Повернути налаштування\",\n  \"save\": \"Зберегти\",\n  \"author\": \"Автор\",\n  \"next\": \"Далі\",\n  \"submit\": \"Надіслати\",\n  \"plugins\": \"Модулі\",\n  \"information\": \"Інформація\",\n  \"confirm\": \"Підтвердити\",\n  \"snapshot\": \"Snapshot\",\n  \"strategies\": \"Стратегія(ї)\",\n  \"strategiesPage\": \"Стратегії\",\n  \"space\": \"Сховище\",\n  \"spaces\": \"Сховища\",\n  \"verifiedSpace\": \"Verified space\",\n  \"version\": \"Версія \",\n  \"timeline\": \"Часова шкала\",\n  \"filters\": \"Фільтри\",\n  \"allSpaces\": \"Всі області\",\n  \"submitOnchain\": \"Надіслати в ланцюжок\",\n  \"inSpaces\": \"У {0} сховищах\",\n  \"votes\": \"Голоси\",\n  \"seeMore\": \"Показати більше\",\n  \"network\": \"Мережа\",\n  \"networks\": \"Мережі\",\n  \"skins\": \"Теми оформлення\",\n  \"members\": \"No members | {count} member | {count} members\",\n  \"editStrategy\": \"Редагувати стратегію\",\n  \"invalidProposals\": \"Недійсні пропозиції\",\n  \"account\": \"Мій профіль\",\n  \"create3box\": \"Створити профіль у 3Box\",\n  \"view3box\": \"Переглянути профіль у 3Box\",\n  \"edit3box\": \"Редагувати профіль у 3Box\",\n  \"connectWallet\": \"Підключити гаманець\",\n  \"toggleSkin\": \"Toggle skin\",\n  \"about\": \"Про\",\n  \"license\": \"Ліцензія\",\n  \"ipfsServer\": \"IPFS сервер\",\n  \"hub\": \"Хаб\",\n  \"cancel\": \"Скасувати\",\n  \"deleteProposal\": \"Видалити пропозицію\",\n  \"demoSite\": \"Це демо-сайт, спробуйте!\",\n  \"removeDelegation\": \"Видалити делегацію\",\n  \"confirmRemove\": \"Ви впевнені, що хочете видалити делегування?\",\n  \"removeSpace\": \"для простору {0}\",\n  \"confirmVote\": \"Підтвердити свій голос\",\n  \"sureToVote\": \"Are you sure you want to cast this vote?\",\n  \"cannotBeUndone\": \"Цю дію не можна скасувати.\",\n  \"options\": \"Параметри\",\n  \"votingPower\": \"Ваша сила голосу\",\n  \"receipt\": \"Квитанція\",\n  \"relayer\": \"Ретранслятор\",\n  \"verifyOnMycrypto\": \"Перевірити чек на MyCrypto\",\n  \"verifyOnSignatorio\": \"Verify on Signator.io\",\n  \"isCore\": \"Ядро\",\n  \"notificationsBlocked\": \"Your browser is blocking notifications\",\n  \"notificationsNotSupported\": \"Your browser does not support notifications\",\n  \"walletNotSupported\": \"Wallet is not supported\",\n  \"seeInExplorer\": \"Дивитися в провіднику\",\n  \"learnMore\": \"Дізнатися більше\",\n  \"logout\": \"Вийти\",\n  \"continue\": \"Продовжити\",\n  \"add\": \"Додати\",\n  \"edit\": \"Редагувати\",\n  \"strategyParameters\": \"Параметри стратегії\",\n  \"addAction\": \"Додати дію\",\n  \"removeAction\": \"Видалити дію\",\n  \"yourChoice\": \"Вибір {0}\",\n  \"targetAddress\": \"Адреса призначення\",\n  \"value\": \"Значення\",\n  \"date\": \"Дані\",\n  \"marketDetails\": \"Деталі ринку\",\n  \"addMarket\": \"Додати ринок\",\n  \"selectNetwork\": \"Обрати мережу\",\n  \"conditionId\": \"Стан ID:\",\n  \"basetokenAddress\": \"Основна адреса токена\",\n  \"quoteAddress\": \"Укажіть адресу валюти\",\n  \"removeMarket\": \"Видалити ринок\",\n  \"back\": \"Назад\",\n  \"loading\": \"Завантаження...\",\n  \"predictedImpact\": \"Передбачуваний вплив\",\n  \"marketSymbol\": \"{0} ринок\",\n  \"twoChoicesRequired\": \"Потрібно два варіанти для цього плагіна.\",\n  \"noResultsFound\": \"Ой! Нічого не знайдено\",\n  \"createFirstProposal\": \"Let's create your first proposal\",\n  \"noSpacesJoined\": \"Oops, you haven't joined any spaces yet\",\n  \"addFavorites\": \"Додати улюблені\",\n  \"createdBy\": \"Від {0}\",\n  \"startIn\": \"початок {0}\",\n  \"endIn\": \"кінець {0}\",\n  \"proposalTimeLeft\": \"{0} left\",\n  \"endedAgo\": \"ended {0}\",\n  \"proposalBy\": \"від {0}\",\n  \"endDate\": \"кінець {0}\",\n  \"contentHash\": \"Хеш контенту\",\n  \"defaultSkin\": \"Тема оформлення за замовчуванням\",\n  \"select\": \"Вибрати\",\n  \"language\": \"Мова\",\n  \"agree\": \"Я згоден\",\n  \"mustAgreeToTerms\": \"Перш ніж ви зможете завершити цю дію, ви повинні погодитися з {0} умовами використання:\",\n  \"playground\": \"Ігровий майданчик\",\n  \"strategyParams\": \"Параметри стратегії\",\n  \"addresses\": \"Адреси\",\n  \"networkErrorPlayground\": \"Помилка мережі - будь ласка, відкрийте ваш браузер для отримання додаткової інформації\",\n  \"upload\": \"Завантажити\",\n  \"join\": \"Join\",\n  \"joined\": \"Joined\",\n  \"leave\": \"Leave\",\n  \"copyLink\": \"Скопіювати посилання\",\n  \"duplicateProposal\": \"Дублювати пропозицію\",\n  \"joinedSpaces\": \"Joined spaces\",\n  \"joinSpaces\": \"Join spaces\",\n  \"setDelegationToSpace\": \"Limit delegation to a specific space\",\n  \"theCurrentNetwork\": \"the current network\",\n  \"optional\": \"(optional)\",\n  \"homeLoadmore\": \"Load more\",\n  \"confirmAction\": \"Confirm action\",\n  \"or\": \"or\",\n  \"share\": \"Share\",\n  \"errors\": {\n    \"required\": \"Обов'язкове поле\",\n    \"minLength\": \"Мінімальна довжина: {0}\",\n    \"maxLength\": \"Максимальна довжина: {0}\",\n    \"pattern\": \"Неприпустимий символ:\",\n    \"minItems\": \"Потрібно мінімум {0} елементів\",\n    \"minStrategy\": \"Потрібна принаймні одна стратегія.\",\n    \"format\": \"Невірний формат\",\n    \"type\": \"Invalid type\"\n  },\n  \"create\": {\n    \"question\": \"Ask a question...\",\n    \"categorie(s)\": \"Select up to 2 categorie(s)\",\n    \"content\": \"Tell us more about your proposal (optional)\",\n    \"preview\": \"Попередній перегляд\",\n    \"choices\": \"Вибір\",\n    \"addChoice\": \"Додати вибір\",\n    \"startDate\": \"Виберіть дату початку\",\n    \"endDate\": \"Оберіть дату\",\n    \"startTime\": \"Оберіть час початку\",\n    \"endTime\": \"Оберіть час закінчення\",\n    \"publish\": \"Публікувати\",\n    \"untitled\": \"Untitled\",\n    \"snapshotBlock\": \"Номер блоку знімка\",\n    \"voting\": \"Voting\",\n    \"votingSystem\": \"Voting system\",\n    \"choice\": \"Choice {0}\",\n    \"period\": \"Voting period\",\n    \"start\": \"Start\",\n    \"end\": \"End\",\n    \"days\": \"Days\",\n    \"hours\": \"Hours\",\n    \"minutes\": \"Minutes\",\n    \"schedule\": \"Schedule proposal\",\n    \"delayEnforced\": \"The space enforces a delay until voting can start\",\n    \"periodEnforced\": \"The space enforces the duration of the voting period\",\n    \"edit\": \"Edit\",\n    \"continue\": \"Continue\",\n    \"now\": \"Now\",\n    \"votingPeriodExplainer\": \"This is the time period in which users will be able to vote. The proposal will be visible and pending before the start of the voting period.\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"You need to be an author of the space in order to submit a proposal.\",\n        \"minScore\": \"Вам потрібно мати мінімум {0} {1} для того, щоб зробити пропозицію.\"\n      },\n      \"customValidation\": \"Вам необхідно пройти перевірку пропозиції, щоб запропонувати пропозицію.\",\n      \"executionError\": \"Verifying your eligibility to create proposals in this space has failed. This is likely due to a misconfigured strategy.\"\n    }\n  },\n  \"delegate\": {\n    \"header\": \"Делегувати\",\n    \"selectDelegate\": \"Вибрати делегата\",\n    \"to\": \"Для\",\n    \"addressPlaceholder\": \"Адреса або ім'я ENS\",\n    \"delegations\": \"Ваша делегація(ї)\",\n    \"allSpaces\": \"Для всіх сховищ\",\n    \"delegated\": \"Делеговано вам\",\n    \"pendingTransaction\": \"no pending transaction | 1 pending transaction | {count} pending transactions\",\n    \"topDelegates\": \"Top delegates\",\n    \"noDelegatesFoundFor\": \"No delegates found for {0}\",\n    \"noValidEns\": \"Not a valid ENS address.\",\n    \"noValidAddress\": \"Not a valid address\",\n    \"delegateToSelf\": \"You cannot delegate to yourself\",\n    \"delegateToSelfAddress\": \"You cannot delegate to your own ENS address\",\n    \"noValidSpaceId\": \"Not a valid space ID\",\n    \"noDelegationsAndDelegates\": \"Can't find your delegations and delegates? Make sure you are connected to the correct network.\",\n    \"delegateNotSupported\": \"Delegation is currently not supported for {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Віддайте свій голос\",\n    \"vote\": \"Голосувати\",\n    \"startDate\": \"Дата початку\",\n    \"endDate\": \"Дата завершення\",\n    \"votingSystem\": \"Система голосування\"\n  },\n  \"proposals\": {\n    \"header\": \"Пропозиції\",\n    \"new\": \"Нова пропозиція\",\n    \"noProposals\": \"Тут поки що немає пропозицій!\",\n    \"createProposal\": \"Create proposal\",\n    \"showMore\": \"Show more\",\n    \"showLess\": \"Show less\",\n    \"states\": {\n      \"all\": \"Всі\",\n      \"core\": \"Ядро\",\n      \"community\": \"Спільнота\",\n      \"active\": \"Активний\",\n      \"pending\": \"Очікує підтвердження\",\n      \"closed\": \"Закрито\"\n    }\n  },\n  \"settings\": {\n    \"header\": \"Налаштування\",\n    \"setEnsTextRecord\": \"Set ENS text record\",\n    \"editController\": \"Edit controller\",\n    \"needToSetEnsText\": \"Your space settings will be stored in a file on IPFS. Before you can edit them, you need to set an ENS text record pointing to that file.\",\n    \"profile\": \"Профіль\",\n    \"avatar\": \"Зображення користувача\",\n    \"name\": \"Ім’я\",\n    \"about\": \"Про\",\n    \"terms\": \"Умови\",\n    \"network\": \"Мережа\",\n    \"symbol\": \"Символ\",\n    \"skin\": \"Тема оформлення\",\n    \"customDomain\": \"Custom domain\",\n    \"domain\": \"Ім'я домену\",\n    \"strategies\": \"Стратегія(ї)\",\n    \"categories\": \"Categorie(s)\",\n    \"selectCategories\": \"Select categories\",\n    \"hideSpace\": \"Сховати простір з головної сторінки\",\n    \"addStrategy\": \"Додати стратегію\",\n    \"authors\": \"Authors\",\n    \"admins\": \"Адмінстратори\",\n    \"defaultTab\": \"Закладка за замовчуванням\",\n    \"proposalThreshold\": \"Поріг пропозиції\",\n    \"allowOnlyAuthors\": \"Allow only authors to submit a proposal\",\n    \"editPlugin\": \"Редагувати плагін\",\n    \"addPlugin\": \"Додати плагін\",\n    \"pluginParameters\": \"Параметри плагіну\",\n    \"proposalValidation\": \"Перевірка пропозиції\",\n    \"validation\": \"Перевірка\",\n    \"editValidation\": \"Редагувати перевірку\",\n    \"selectValidation\": \"Виберіть перевірку\",\n    \"validationParameters\": \"Параметри перевірки\",\n    \"warningTextRecord\": \"Ви повинні встановити текстовий запис з домену ENS.\",\n    \"votingDelay\": \"Voting delay\",\n    \"votingPeriod\": \"Voting period\",\n    \"voting\": \"Voting\",\n    \"quorum\": \"Quorum\",\n    \"type\": \"Type\",\n    \"anyType\": \"Any\",\n    \"hideAbstain\": \"Ignore abstain votes in basic voting results\",\n    \"connectWithSpaceOwner\": \"To modify space settings, connect with a controller or admin wallet.\"\n  },\n  \"setup\": {\n    \"example\": \"наприклад yam.eth\",\n    \"chooseExistingEns\": \"Choose one of your existing ENS domains to create a space with:\",\n    \"useSingleExistingEns\": \"Use your existing ENS domain:\",\n    \"orRegisterNewEns\": \"Or register a new domain:\",\n    \"toCreateASpace\": \"To create a space, you first need an ENS domain. Enter one below and follow the ENS registration instructions.\",\n    \"createASpace\": \"Створити області\",\n    \"registerEnsButton\": \"Register\",\n    \"supportedEnsTLDs\": \"Supported domain endings\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to create a space? Learn more in the <a target='_blank' href='https://docs.snapshot.org/spaces/create'> documentation </a> or join Snapshot <a target='_blank' href='https://discord.snapshot.org/'>Discord</a>.\",\n    \"setSpaceController\": \"Set space controller address on ENS\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"This action requires a transaction on the Ethereum Mainnet which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choose ENS address\",\n    \"textRecordExists\": \"Currently the space controller is {address}. You can update the space controller by using the form below.\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. 0xF78108c9BBaF466dd96BE41be728Fe3220b37119\",\n    \"controllerAddress\": \"Controller address\",\n    \"updateController\": \"Update controller\",\n    \"connectWithEnsController\": \"To modify the space controller you need to connect with the wallet that owns {ens}\",\n    \"seeOnEns\": \"See on ENS\",\n    \"goToSettings\": \"Go to settings\"\n  },\n  \"notify\": {\n    \"youDidIt\": \"Ви це зробили!\",\n    \"copied\": \"Скопійовано!\",\n    \"proposalDeleted\": \"Пропозицію видалено\",\n    \"somethingWentWrong\": \"Ой, щось пішло не так!\",\n    \"saved\": \"Saved!\",\n    \"delegationSuccess\": \"Delegation successful\",\n    \"delegationRemoved\": \"Delegation removed\",\n    \"proposalCreated\": \"Proposal created\",\n    \"voteSuccessful\": \"Your vote is in!\",\n    \"ensSet\": \"ENS text record was successfully set\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Створити стратегію\",\n    \"createSkin\": \"Створити тему\",\n    \"addNetwork\": \"Додати мережу\",\n    \"createPlugin\": \"Створити плагін\",\n    \"strategies\": \"стратегія(ї)\",\n    \"skins\": \"тема(и) оформлення\",\n    \"networks\": \"мережа(і)\",\n    \"plugins\": \"плагін(и)\",\n    \"results\": \"результат(и)\",\n    \"categories\": {\n      \"all\": \"All\",\n      \"protocol\": \"Protocol\",\n      \"social\": \"Social\",\n      \"investment\": \"Investment\",\n      \"grant\": \"Grant\",\n      \"service\": \"Service\",\n      \"media\": \"Media\",\n      \"creator\": \"Creator\",\n      \"collector\": \"Collector\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Виберіть систему голосування\",\n    \"single-choice\": \"Голосування з одним вибором\",\n    \"approval\": \"Затвердження голосування\",\n    \"quadratic\": \"Квадратичне голосування\",\n    \"ranked-choice\": \"Голосування за ранговий вибір\",\n    \"weighted\": \"Взважене голосування\",\n    \"basic\": \"Basic voting\",\n    \"description\": {\n      \"single-choice\": \"Кожен учасник голосування може зробити лише один вибір.\",\n      \"approval\": \"Кожен учасник може вибрати будь-яку кількість варіантів.\",\n      \"quadratic\": \"Кожен учасник голосування може поширювати свій голос на будь -яку кількість варіантів. Результати розраховуються квадратично.\",\n      \"ranked-choice\": \"Кожен учасник голосування може обрати та оцінити будь -яку кількість варіантів. Результати розраховуються миттєво\",\n      \"weighted\": \"Кожен учасник голосування може розподіляти своє право голосу на будь -яку кількість варіантів.\",\n      \"basic\": \"Single choice voting with three choices: For, Against or Abstain\"\n    },\n    \"choices\": {\n      \"for\": \"For\",\n      \"against\": \"Against\",\n      \"abstain\": \"Abstain\"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Current outcome\",\n    \"currentBond\": \"Current bond\",\n    \"finalizedIn\": \"Завершено {0}\",\n    \"executableIn\": \"Виконано {0}\",\n    \"finalOutcome\": \"Outcome\",\n    \"nextBond\": \"Bond to set outcome\",\n    \"setOutcomeTo\": \"Set outcome to\",\n    \"claimBond\": \"Отримати облігації\",\n    \"addBatch\": \"Add transaction batch\",\n    \"batch\": \"Transaction batch\",\n    \"transactions\": \"Transactions\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"Invalid address\",\n    \"invalidAmount\": \"Invalid amount\",\n    \"invalidValue\": \"Invalid value\",\n    \"invalidAbi\": \"Invalid ABI\",\n    \"invalidData\": \"Invalid data\",\n    \"value\": \"Value (wei)\",\n    \"data\": \"Data\",\n    \"noCollectibles\": \"No collectibles\",\n    \"asset\": \"Asset\",\n    \"amount\": \"Amount\",\n    \"type\": \"Type\",\n    \"transferFunds\": \"Transfer funds\",\n    \"transferNFT\": \"Transfer NFT\",\n    \"contractInteraction\": \"Contract interaction\",\n    \"rawTransaction\": \"Raw transaction\",\n    \"addTransaction\": \"Add transaction\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei to {address}\",\n      \"transferFunds\": \"Transfer {amount} {tokenSymbol} to {address}\",\n      \"transferNFT\": \"Send {name} #{id} to {address}\",\n      \"raw\": \"Send {amount} wei to {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Виконання запиту\",\n      \"setOutcome\": \"Встановіть результат\",\n      \"changeOutcome\": \"Змінити результат\",\n      \"executeTxs\": \"Execute transaction batch {0} of {1}\",\n      \"executed\": \"All transactions have been executed\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"Something went wrong\",\n      \"connectWallet\": \"Connect wallet to see execution details\",\n      \"switchChain\": \"Switch your wallet to {0} to request execution\",\n      \"question\": \"Чи пройшла ця пропозиція і чи відповідає вона\",\n      \"criteria\": \"критерії приймання?\",\n      \"proposalPassed\": \"Did the proposal pass?\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"POAP ще не налаштований для цієї пропозиції :'(\",\n    \"no_voted_header\": \"Проголосуйте, щоб отримати POAP\",\n    \"unclaimed_header\": \"Mint your I voted POAP\",\n    \"claimed_header\": \"Congratulations! The POAP has been minted to your collection\",\n    \"loading_header\": \"The POAP is being minted to your collection\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"Browse collection\",\n    \"success_claim\": \"The POAP has been minted to your collection\",\n    \"error_claim\": \"Під створюванні токену виникла проблема\"\n  },\n  \"charts\": {\n    \"charts\": \"Charts\",\n    \"noVotesYet\": \"There are no votes to visualize yet.\",\n    \"totalVotesPerDay\": \"Total votes per day\",\n    \"shareOfVotingPower\": \"Share of voting power\",\n    \"votingPowerPerDay\": \"Voting power per day\"\n  },\n  \"comment_box\": {\n    \"title\": \"Comment box\",\n    \"add\": \"Add your comment here\",\n    \"submit\": \"Submit\",\n    \"preview\": \"Preview\",\n    \"continue_editing\": \"Continue editing\",\n    \"edit\": \"Edit your reply here\",\n    \"edit_button\": \"Edit\",\n    \"dismiss\": \"Dismiss\",\n    \"delete\": \"Delete\",\n    \"add_reply\": \"Add your reply here\",\n    \"edit_comment\": \"Edit comment\",\n    \"edit_modal\": \"Are you sure you want to edit?\",\n    \"yes\": \"Yes\",\n    \"no\": \"No\",\n    \"delete_comment\": \"Delete comment\",\n    \"delete_modal\": \"Are you sure you want to delete?\",\n    \"error\": \"Oops, something went wrong\",\n    \"replies\": \"replies\",\n    \"hide\": \"Hide\",\n    \"show\": \"Show\",\n    \"reply\": \"Reply\",\n    \"load_more\": \"Load more\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Create a space\",\n      \"timeline\": \"Timeline\",\n      \"explore\": \"Explore\",\n      \"playground\": \"Playground\",\n      \"space\": {\n        \"create\": \"Create {space} proposal\",\n        \"about\": \"About {space}\",\n        \"proposals\": \"{space} Proposals\",\n        \"proposal\": \"{space} proposal: {proposal}\",\n        \"settings\": \"{space} Settings\"\n      },\n      \"strategy\": \"{key} strategy\",\n      \"delegate\": \"Delegate\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Track proposals for {spaceName}\",\n    \"text\": \"Receive notifications every time a new proposal is created or ends\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 second | {n} seconds\",\n    \"minute\": \"1 minute | {n} minutes\",\n    \"hour\": \"1 hour | {n} hours\",\n    \"day\": \"1 day | {n} days\",\n    \"week\": \"1 week | {n} weeks\",\n    \"month\": \"1 month | {n} months\",\n    \"year\": \"1 year | {n} years\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Unsupported network\",\n    \"ensOnlyMainnet\": \"Snapshot only supports ENS on the ethereum mainnet. Once the space is created, you can use it across multiple chains.\",\n    \"switchNetworkToMainnet\": \"To continue, you need to change the network in your wallet to Ethereum Mainnet.\",\n    \"switchToMainnet\": \"Switch to Mainnet\"\n  }\n}\n"
  },
  {
    "path": "src/locales/vi-VN.json",
    "content": "{\n  \"searchPlaceholder\": \"Tìm kiếm\",\n  \"spaceCount\": \"{0} khoảng trống(s)\",\n  \"createSpace\": \"Tạo khoảng trống\",\n  \"backToHome\": \"Nhà\",\n  \"actions\": \"Hành động\",\n  \"results\": \"Kết quả\",\n  \"resultsError\": \"Results could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"vpError\": \"Your voting power could not be calculated. This is often due to a misconfigured strategy or an unresponsive RPC node involved in the strategy.\",\n  \"getHelp\": \"Get help\",\n  \"retry\": \"Retry\",\n  \"currentResults\": \"Kết quả hiện tại\",\n  \"reset\": \"Cài lại\",\n  \"save\": \"Lưu lại\",\n  \"author\": \"Tác giả\",\n  \"next\": \"Tiếp theo\",\n  \"submit\": \"Nộp\",\n  \"plugins\": \"Bổ sung\",\n  \"information\": \"Thông tin\",\n  \"confirm\": \"Xác nhận\",\n  \"snapshot\": \"Chụp ảnh\",\n  \"strategies\": \"Chiến lược(s)\",\n  \"strategiesPage\": \"Chiến lược\",\n  \"space\": \"Khoảng trống\",\n  \"spaces\": \"Những khoảng trống\",\n  \"verifiedSpace\": \"Verified space\",\n  \"version\": \"Phiên bản\",\n  \"timeline\": \"Mốc thời gian\",\n  \"filters\": \"Bộ lọc\",\n  \"allSpaces\": \"Tất cả các khoảng trống\",\n  \"submitOnchain\": \"Nộp on chain\",\n  \"inSpaces\": \"Trong {0} khoảng trống(s)\",\n  \"votes\": \"Bầu\",\n  \"seeMore\": \"Xem nhiều hơn\",\n  \"network\": \"Mạng\",\n  \"networks\": \"Mạng\",\n  \"skins\": \"Giao diện\",\n  \"members\": \"Chưa có thành viên | {count} thành viên} | {count} thành viên\",\n  \"editStrategy\": \"Chỉnh sửa chiến lược\",\n  \"invalidProposals\": \"Đề xuất không hợp lệ\",\n  \"account\": \"Tài khoản\",\n  \"create3box\": \"Tạo hồ sơ trên 3Box\",\n  \"view3box\": \"Xem hồ sơ trên 3Box\",\n  \"edit3box\": \"Chỉnh sửa hồ sơ trên 3Box\",\n  \"connectWallet\": \"Kết nối ví\",\n  \"toggleSkin\": \"Toggle skin\",\n  \"about\": \"Về\",\n  \"license\": \"Giấy phép\",\n  \"ipfsServer\": \"Máy chủ IPFS\",\n  \"hub\": \"Trung tâm\",\n  \"cancel\": \"Hủy bỏ\",\n  \"deleteProposal\": \"Xóa đề xuất\",\n  \"demoSite\": \"Đây là trang demo, hãy thử!\",\n  \"removeDelegation\": \"Gỡ bỏ ủy quyền\",\n  \"confirmRemove\": \"Bạn chắc chắn muốn xóa ủy quyền cho\",\n  \"removeSpace\": \"cho khoảng trống {0}\",\n  \"confirmVote\": \"Xác nhận phiếu bầu\",\n  \"sureToVote\": \"Are you sure you want to cast this vote?\",\n  \"cannotBeUndone\": \"Hành động này không thể hoàn tác.\",\n  \"options\": \"Các lựa chọn(s)\",\n  \"votingPower\": \"Quyền biểu quyết của bạn\",\n  \"receipt\": \"Nhận\",\n  \"relayer\": \"Lặp lại\",\n  \"verifyOnMycrypto\": \"Xác minh biên nhận trên MyCrypto\",\n  \"verifyOnSignatorio\": \"Verify on Signator.io\",\n  \"isCore\": \"Cốt lõi\",\n  \"notificationsBlocked\": \"Your browser is blocking notifications\",\n  \"notificationsNotSupported\": \"Your browser does not support notifications\",\n  \"walletNotSupported\": \"Wallet is not supported\",\n  \"seeInExplorer\": \"Xem trên trình duyệt\",\n  \"learnMore\": \"Tìm hiểu thêm\",\n  \"logout\": \"Đăng xuất\",\n  \"continue\": \"Tiếp tục\",\n  \"add\": \"Thêm vào\",\n  \"edit\": \"Chỉnh sửa\",\n  \"strategyParameters\": \"Thông số chiến lược\",\n  \"addAction\": \"Thêm hành động\",\n  \"removeAction\": \"Xóa hành động\",\n  \"yourChoice\": \"Lựa chọn {0}\",\n  \"targetAddress\": \"Địa chỉ mục tiêu\",\n  \"value\": \"Giá trị\",\n  \"date\": \"Dữ liệu\",\n  \"marketDetails\": \"Chi tiết thị trường\",\n  \"addMarket\": \"Thêm thị trường\",\n  \"selectNetwork\": \"Chọn mạng\",\n  \"conditionId\": \"Điều kiện ID\",\n  \"basetokenAddress\": \"Địa chỉ mã thông báo cơ sở\",\n  \"quoteAddress\": \"Trích dẫn địa chỉ tiền tệ\",\n  \"removeMarket\": \"Xóa thị trường\",\n  \"back\": \"Trở lại\",\n  \"loading\": \"Tải...\",\n  \"predictedImpact\": \"Tác động dự đoán\",\n  \"marketSymbol\": \"{0} thị trường\",\n  \"twoChoicesRequired\": \"Cần có hai lựa chọn cho plugin này.\",\n  \"noResultsFound\": \"Rất tiếc, chúng tôi không thể tìm thấy bất kỳ kết quả nào\",\n  \"createFirstProposal\": \"Let's create your first proposal\",\n  \"noSpacesJoined\": \"Bạn chưa tham gia một nhóm nào cả\",\n  \"addFavorites\": \"Thêm yêu thích\",\n  \"createdBy\": \"Bởi {0}\",\n  \"startIn\": \"bắt đầu {0}\",\n  \"endIn\": \"kết thúc {0}\",\n  \"proposalTimeLeft\": \"{0} left\",\n  \"endedAgo\": \"ended {0}\",\n  \"proposalBy\": \"bởi {0}\",\n  \"endDate\": \"kết thúc {0}\",\n  \"contentHash\": \"Nội dung băm\",\n  \"defaultSkin\": \"Skin mặc định\",\n  \"select\": \"Lựa chọn\",\n  \"language\": \"Ngôn ngữ\",\n  \"agree\": \"Tôi đồng ý\",\n  \"mustAgreeToTerms\": \"Trước khi có thể hoàn tất hành động này, bạn phải đồng ý với {0} điều khoản dịch vụ tại đây:\",\n  \"playground\": \"Sân chơi\",\n  \"strategyParams\": \"Thông số chiến lược\",\n  \"addresses\": \"Địa chỉ\",\n  \"networkErrorPlayground\": \"Lỗi mạng - vui lòng mở bảng điều khiển trình duyệt của bạn để biết thêm thông tin\",\n  \"upload\": \"Tải lên\",\n  \"join\": \"Tham gia\",\n  \"joined\": \"Đã tham gia\",\n  \"leave\": \"Rời nhóm\",\n  \"copyLink\": \"Sao chép đường dẫn\",\n  \"duplicateProposal\": \"Sao chép đề xuất\",\n  \"joinedSpaces\": \"Các nhóm đã tham gia\",\n  \"joinSpaces\": \"Tham gia nhóm\",\n  \"setDelegationToSpace\": \"Limit delegation to a specific space\",\n  \"theCurrentNetwork\": \"the current network\",\n  \"optional\": \"(optional)\",\n  \"homeLoadmore\": \"Load more\",\n  \"confirmAction\": \"Confirm action\",\n  \"or\": \"or\",\n  \"share\": \"Share\",\n  \"errors\": {\n    \"required\": \"Trường này là bắt buộc\",\n    \"minLength\": \"Độ dài tối thiểu là {0}\",\n    \"maxLength\": \"Độ dài tối đa là {0}\",\n    \"pattern\": \"Ký tự không hợp lệ\",\n    \"minItems\": \"Yêu cầu tối thiểu {0} mục(s)\",\n    \"minStrategy\": \"Ít nhất một chiến lược là bắt buộc.\",\n    \"format\": \"Định dạng không hợp lệ\",\n    \"type\": \"Kiểu dữ liệu không hợp lệ\"\n  },\n  \"create\": {\n    \"question\": \"Ask a question...\",\n    \"categorie(s)\": \"Select up to 2 categorie(s)\",\n    \"content\": \"Tell us more about your proposal (optional)\",\n    \"preview\": \"Xem trước\",\n    \"choices\": \"Lựa chọn\",\n    \"addChoice\": \"Thêm lựa chọn\",\n    \"startDate\": \"Chọn ngày bắt đầu\",\n    \"endDate\": \"Chọn ngày kết thúc\",\n    \"startTime\": \"Chọn thời gian bắt đầu\",\n    \"endTime\": \"Chọn thời gian kết thúc\",\n    \"publish\": \"Xuất bản\",\n    \"untitled\": \"Untitled\",\n    \"snapshotBlock\": \"Số khối chụp ảnh nhanh\",\n    \"voting\": \"Voting\",\n    \"votingSystem\": \"Voting system\",\n    \"choice\": \"Choice {0}\",\n    \"period\": \"Voting period\",\n    \"start\": \"Start\",\n    \"end\": \"End\",\n    \"days\": \"Days\",\n    \"hours\": \"Hours\",\n    \"minutes\": \"Minutes\",\n    \"schedule\": \"Schedule proposal\",\n    \"delayEnforced\": \"The space enforces a delay until voting can start\",\n    \"periodEnforced\": \"The space enforces the duration of the voting period\",\n    \"edit\": \"Edit\",\n    \"continue\": \"Continue\",\n    \"now\": \"Now\",\n    \"votingPeriodExplainer\": \"This is the time period in which users will be able to vote. The proposal will be visible and pending before the start of the voting period.\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"You need to be an author of the space in order to submit a proposal.\",\n        \"minScore\": \"Bạn cần có ít nhất {0} {1} để khởi tạo một đề xuất.\"\n      },\n      \"customValidation\": \"You need to pass the proposal validation in order to submit a proposal.\",\n      \"executionError\": \"Verifying your eligibility to create proposals in this space has failed. This is likely due to a misconfigured strategy.\"\n    }\n  },\n  \"delegate\": {\n    \"header\": \"Ủy thác\",\n    \"selectDelegate\": \"Select delegate\",\n    \"to\": \"Đến\",\n    \"addressPlaceholder\": \"Địa chỉ hay ENS\",\n    \"delegations\": \"Ủy quyền của bạn(s)\",\n    \"allSpaces\": \"Cho tất cả không gian\",\n    \"delegated\": \"Ủy quyền cho bạn\",\n    \"pendingTransaction\": \"no pending transaction | 1 pending transaction | {count} pending transactions\",\n    \"topDelegates\": \"Top delegates\",\n    \"noDelegatesFoundFor\": \"No delegates found for {0}\",\n    \"noValidEns\": \"Not a valid ENS address.\",\n    \"noValidAddress\": \"Not a valid address\",\n    \"delegateToSelf\": \"You cannot delegate to yourself\",\n    \"delegateToSelfAddress\": \"You cannot delegate to your own ENS address\",\n    \"noValidSpaceId\": \"Not a valid space ID\",\n    \"noDelegationsAndDelegates\": \"Can't find your delegations and delegates? Make sure you are connected to the correct network.\",\n    \"delegateNotSupported\": \"Delegation is currently not supported for {network}.\"\n  },\n  \"proposal\": {\n    \"castVote\": \"Hãy bình chọn\",\n    \"vote\": \"Bầu\",\n    \"startDate\": \"Ngày bắt đầu\",\n    \"endDate\": \"Ngày kết thúc\",\n    \"votingSystem\": \"Hệ thống bầu cử\"\n  },\n  \"proposals\": {\n    \"header\": \"Đề xuất\",\n    \"new\": \"Đề xuất mới\",\n    \"noProposals\": \"Không có bất kỳ đề xuất nào ở đây!\",\n    \"createProposal\": \"Create proposal\",\n    \"showMore\": \"Show more\",\n    \"showLess\": \"Show less\",\n    \"states\": {\n      \"all\": \"Tất cả\",\n      \"core\": \"Cốt lõi\",\n      \"community\": \"Cộng đồng\",\n      \"active\": \"Hoạt động\",\n      \"pending\": \"Đang chờ xử lý\",\n      \"closed\": \"Đã đóng\"\n    }\n  },\n  \"settings\": {\n    \"header\": \"Cài đặt\",\n    \"setEnsTextRecord\": \"Set ENS text record\",\n    \"editController\": \"Edit controller\",\n    \"needToSetEnsText\": \"Your space settings will be stored in a file on IPFS. Before you can edit them, you need to set an ENS text record pointing to that file.\",\n    \"profile\": \"Hồ sơ\",\n    \"avatar\": \"Hình đại diện\",\n    \"name\": \"Tên\",\n    \"about\": \"Về\",\n    \"terms\": \"Kỳ hạn\",\n    \"network\": \"Mạng\",\n    \"symbol\": \"Biểu tượng\",\n    \"skin\": \"Skin\",\n    \"customDomain\": \"Custom domain\",\n    \"domain\": \"Tên miền\",\n    \"strategies\": \"Chiến lược(s)\",\n    \"categories\": \"Categorie(s)\",\n    \"selectCategories\": \"Select categories\",\n    \"hideSpace\": \"Ẩn không gian khỏi trang chủ\",\n    \"addStrategy\": \"Thêm chiến lược\",\n    \"authors\": \"Authors\",\n    \"admins\": \"Admins\",\n    \"defaultTab\": \"Tab mặc định\",\n    \"proposalThreshold\": \"Ngưỡng đề xuất\",\n    \"allowOnlyAuthors\": \"Allow only authors to submit a proposal\",\n    \"editPlugin\": \"Chỉnh sửa plugin\",\n    \"addPlugin\": \"Thêm plugin\",\n    \"pluginParameters\": \"Thông số plugin\",\n    \"proposalValidation\": \"Proposal validation\",\n    \"validation\": \"Validation\",\n    \"editValidation\": \"Edit validation\",\n    \"selectValidation\": \"Select validation\",\n    \"validationParameters\": \"Validation parameters\",\n    \"warningTextRecord\": \"You must set a text record on the ENS domain.\",\n    \"votingDelay\": \"Voting delay\",\n    \"votingPeriod\": \"Voting period\",\n    \"voting\": \"Voting\",\n    \"quorum\": \"Quorum\",\n    \"type\": \"Type\",\n    \"anyType\": \"Any\",\n    \"hideAbstain\": \"Ignore abstain votes in basic voting results\",\n    \"connectWithSpaceOwner\": \"To modify space settings, connect with a controller or admin wallet.\"\n  },\n  \"setup\": {\n    \"example\": \"e.g.yam.eth\",\n    \"chooseExistingEns\": \"Choose one of your existing ENS domains to create a space with:\",\n    \"useSingleExistingEns\": \"Use your existing ENS domain:\",\n    \"orRegisterNewEns\": \"Or register a new domain:\",\n    \"toCreateASpace\": \"To create a space, you first need an ENS domain. Enter one below and follow the ENS registration instructions.\",\n    \"createASpace\": \"Tạo một không gian\",\n    \"registerEnsButton\": \"Register\",\n    \"supportedEnsTLDs\": \"Supported domain endings\",\n    \"helpDocsAndDiscordLinks\": \"Not sure how to create a space? Learn more in the <a target='_blank' href='https://docs.snapshot.org/spaces/create'> documentation </a> or join Snapshot <a target='_blank' href='https://discord.snapshot.org/'>Discord</a>.\",\n    \"setSpaceController\": \"Set space controller address on ENS\",\n    \"editSpaceController\": \"Edit controller on ENS\",\n    \"setController\": \"Set controller\",\n    \"explainControllerAndEns\": \"This action requires a transaction on the Ethereum Mainnet which will add a \\\"snapshot\\\" TEXT record to your ENS domain.\",\n    \"confirmToSetAddress\": \"Are you sure you want to set {address} as the controller of the space?\",\n    \"controllerHasAuthority\": \"The controller has full authority over the space settings\",\n    \"controller\": \"Controller\",\n    \"selectEnsForSpace\": \"Choose ENS address\",\n    \"textRecordExists\": \"Currently the space controller is {address}. You can update the space controller by using the form below.\",\n    \"spaceOwnerAddressPlaceHolder\": \"e.g. 0xF78108c9BBaF466dd96BE41be728Fe3220b37119\",\n    \"controllerAddress\": \"Controller address\",\n    \"updateController\": \"Update controller\",\n    \"connectWithEnsController\": \"To modify the space controller you need to connect with the wallet that owns {ens}\",\n    \"seeOnEns\": \"See on ENS\",\n    \"goToSettings\": \"Go to settings\"\n  },\n  \"notify\": {\n    \"youDidIt\": \"Bạn đã thực hiện nó!\",\n    \"copied\": \"Đã sao chép!\",\n    \"proposalDeleted\": \"Đề xuất bị xóa\",\n    \"somethingWentWrong\": \"Rất tiếc, có gì đó không ổn!\",\n    \"saved\": \"Đã lưu!\",\n    \"delegationSuccess\": \"Delegation successful\",\n    \"delegationRemoved\": \"Delegation removed\",\n    \"proposalCreated\": \"Đề xuất đã được khởi tạo\",\n    \"voteSuccessful\": \"Phiếu bầu của bạn đã được lưu lại!\",\n    \"ensSet\": \"ENS text record was successfully set\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"Tạo chiến lược\",\n    \"createSkin\": \"Tạo skin\",\n    \"addNetwork\": \"Thêm mạng\",\n    \"createPlugin\": \"Tạo plugin\",\n    \"strategies\": \"chiến lược(s)\",\n    \"skins\": \"skin(s)\",\n    \"networks\": \"mạng(s)\",\n    \"plugins\": \"plugin(s)\",\n    \"results\": \"kết quả(s)\",\n    \"categories\": {\n      \"all\": \"All\",\n      \"protocol\": \"Protocol\",\n      \"social\": \"Social\",\n      \"investment\": \"Investment\",\n      \"grant\": \"Grant\",\n      \"service\": \"Service\",\n      \"media\": \"Media\",\n      \"creator\": \"Creator\",\n      \"collector\": \"Collector\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"Chọn hệ thống bầu cử\",\n    \"single-choice\": \"Biểu quyết một lựa chọn\",\n    \"approval\": \"Phê duyệt bầu cử\",\n    \"quadratic\": \"Biểu quyết bậc hai\",\n    \"ranked-choice\": \"Ranked choice voting\",\n    \"weighted\": \"Weighted voting\",\n    \"basic\": \"Basic voting\",\n    \"description\": {\n      \"single-choice\": \"Mỗi cử tri chỉ được chọn một sự lựa chọn.\",\n      \"approval\": \"Mỗi cử tri có thể chọn bất kỳ số lượng lựa chọn nào.\",\n      \"quadratic\": \"Each voter may spread voting power across any number of choices. Results are calculated quadratically.\",\n      \"ranked-choice\": \"Each voter may select and rank any number of choices. Results are calculated by instant-runoff counting method.\",\n      \"weighted\": \"Each voter may spread voting power across any number of choices.\",\n      \"basic\": \"Single choice voting with three choices: For, Against or Abstain\"\n    },\n    \"choices\": {\n      \"for\": \"For\",\n      \"against\": \"Against\",\n      \"abstain\": \"Abstain\"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"Current outcome\",\n    \"currentBond\": \"Current bond\",\n    \"finalizedIn\": \"Đã hoàn thành {0}\",\n    \"executableIn\": \"Thực thi {0}\",\n    \"finalOutcome\": \"Outcome\",\n    \"nextBond\": \"Bond to set outcome\",\n    \"setOutcomeTo\": \"Set outcome to\",\n    \"claimBond\": \"Yêu cầu đặt cược\",\n    \"addBatch\": \"Add transaction batch\",\n    \"batch\": \"Transaction batch\",\n    \"transactions\": \"Các giao dịch\",\n    \"to\": \"To (address)\",\n    \"invalidAddress\": \"Địa chỉ không hợp lệ\",\n    \"invalidAmount\": \"Invalid amount\",\n    \"invalidValue\": \"Invalid value\",\n    \"invalidAbi\": \"Invalid ABI\",\n    \"invalidData\": \"Invalid data\",\n    \"value\": \"Value (wei)\",\n    \"data\": \"Data\",\n    \"noCollectibles\": \"No collectibles\",\n    \"asset\": \"Asset\",\n    \"amount\": \"Amount\",\n    \"type\": \"Type\",\n    \"transferFunds\": \"Transfer funds\",\n    \"transferNFT\": \"Transfer NFT\",\n    \"contractInteraction\": \"Contract interaction\",\n    \"rawTransaction\": \"Raw transaction\",\n    \"addTransaction\": \"Add transaction\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei to {address}\",\n      \"transferFunds\": \"Gửi {amount} {tokenSymbol} tới địa chỉ {address}\",\n      \"transferNFT\": \"Gửi {name} #{id} tới địa chỉ {address}\",\n      \"raw\": \"Gửi {amount} wei tới địa chỉ {address}\"\n    },\n    \"labels\": {\n      \"request\": \"Yêu cầu thực hiện\",\n      \"setOutcome\": \"Đặt kết quả\",\n      \"changeOutcome\": \"Thay đổi kết quả\",\n      \"executeTxs\": \"Thực hiện đa giao dịch {0} của {1}\",\n      \"executed\": \"Giao dịch thành công\",\n      \"rejected\": \"Proposal rejected\",\n      \"error\": \"Đã xảy ra lỗi\",\n      \"connectWallet\": \"Kết nối ví\",\n      \"switchChain\": \"Vui lòng chọn mạng lưới {0} để thực hiện giao dịch\",\n      \"question\": \"Đề xuất này có được thông qua không và nó có đáp ứng được\",\n      \"criteria\": \"tiêu chí chấp nhận?\",\n      \"proposalPassed\": \"Did the proposal pass?\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"POAP chưa được thiết lập cho đề xuất này: '(\",\n    \"no_voted_header\": \"Bỏ phiếu để nhận POAP này\",\n    \"unclaimed_header\": \"Mint your I voted POAP\",\n    \"claimed_header\": \"Congratulations! The POAP has been minted to your collection\",\n    \"loading_header\": \"The POAP is being minted to your collection\",\n    \"button_claim\": \"Mint\",\n    \"button_show\": \"Bộ sưu tập\",\n    \"success_claim\": \"The POAP has been minted to your collection\",\n    \"error_claim\": \"Đã xảy ra sự cố khi in mã thông báo\"\n  },\n  \"charts\": {\n    \"charts\": \"Charts\",\n    \"noVotesYet\": \"There are no votes to visualize yet.\",\n    \"totalVotesPerDay\": \"Total votes per day\",\n    \"shareOfVotingPower\": \"Share of voting power\",\n    \"votingPowerPerDay\": \"Voting power per day\"\n  },\n  \"comment_box\": {\n    \"title\": \"Khung bình luận\",\n    \"add\": \"Nhập bình luận\",\n    \"submit\": \"Gửi\",\n    \"preview\": \"Xem trước\",\n    \"continue_editing\": \"Tiếp tục chỉnh sửa\",\n    \"edit\": \"Sửa bình luận\",\n    \"edit_button\": \"Chỉnh sửa\",\n    \"dismiss\": \"Hủy\",\n    \"delete\": \"Xóa\",\n    \"add_reply\": \"Thêm bình luận\",\n    \"edit_comment\": \"Sửa bình luận\",\n    \"edit_modal\": \"Bạn có muốn chỉnh sửa bình luận không?\",\n    \"yes\": \"Đồng ý\",\n    \"no\": \"Không\",\n    \"delete_comment\": \"Xóa bình luận\",\n    \"delete_modal\": \"Xác nhận xóa bình luận?\",\n    \"error\": \"Đã xảy ra lỗi\",\n    \"replies\": \"các bình luận\",\n    \"hide\": \"Ẩn\",\n    \"show\": \"Hiển thị\",\n    \"reply\": \"Bình luận\",\n    \"load_more\": \"Hiển thị thêm\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"Create a space\",\n      \"timeline\": \"Timeline\",\n      \"explore\": \"Explore\",\n      \"playground\": \"Playground\",\n      \"space\": {\n        \"create\": \"Create {space} proposal\",\n        \"about\": \"About {space}\",\n        \"proposals\": \"{space} Proposals\",\n        \"proposal\": \"{space} proposal: {proposal}\",\n        \"settings\": \"{space} Settings\"\n      },\n      \"strategy\": \"{key} strategy\",\n      \"delegate\": \"Delegate\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"Track proposals for {spaceName}\",\n    \"text\": \"Receive notifications every time a new proposal is created or ends\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 second | {n} seconds\",\n    \"minute\": \"1 minute | {n} minutes\",\n    \"hour\": \"1 hour | {n} hours\",\n    \"day\": \"1 day | {n} days\",\n    \"week\": \"1 week | {n} weeks\",\n    \"month\": \"1 month | {n} months\",\n    \"year\": \"1 year | {n} years\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"Unsupported network\",\n    \"ensOnlyMainnet\": \"Snapshot only supports ENS on the ethereum mainnet. Once the space is created, you can use it across multiple chains.\",\n    \"switchNetworkToMainnet\": \"To continue, you need to change the network in your wallet to Ethereum Mainnet.\",\n    \"switchToMainnet\": \"Switch to Mainnet\"\n  }\n}\n"
  },
  {
    "path": "src/locales/zh-CN.json",
    "content": "{\n  \"searchPlaceholder\": \"搜索\",\n  \"spaceCount\": \"{0} 个空间\",\n  \"createSpace\": \"创建空间\",\n  \"backToHome\": \"主页\",\n  \"actions\": \"操作\",\n  \"poweredBy\": \"技术支持\",\n  \"results\": \"结果\",\n  \"resultsError\": \"结果无法计算。这通常是由于策略配置错误或者策略中涉及到了无反应的RPC节点。\",\n  \"resultsCalculating\": \"正在计算最终结果。如果您在几分钟后仍然看到这个消息，请联系空间管理员。\",\n  \"votingPowerFailedMessage\": \"您的投票权无法计算。这通常是由于策略配置错误或者策略中涉及到了无反应的RPC节点。\",\n  \"votingValidationFailedMessage\": \"我们一方出现了一个错误，我们无法核实你是否有资格投票。 这常常是由于错误配置的投票验证或在验证中包含一个不适应的 API。\",\n  \"notValidVoterMessage\": \"很抱歉，您似乎没有资格对此提案进行投票。\",\n  \"getHelp\": \"获取帮助\",\n  \"retry\": \"重试\",\n  \"currentResults\": \"当前结果\",\n  \"reset\": \"重置\",\n  \"close\": \"关闭\",\n  \"save\": \"保存\",\n  \"author\": \"作者\",\n  \"next\": \"下一页\",\n  \"choice\": \"选项\",\n  \"submit\": \"提交\",\n  \"plugins\": \"插件\",\n  \"information\": \"信息\",\n  \"confirm\": \"确认\",\n  \"snapshot\": \"Snapshot\",\n  \"strategies\": \"策略\",\n  \"strategiesPage\": \"策略\",\n  \"space\": \"空间\",\n  \"spaces\": \"空间\",\n  \"verifiedSpace\": \"已认证的空间\",\n  \"warningSpace\": \"这个空间已被标记为潜在的恶意空间，请谨慎行事。\",\n  \"version\": \"版本\",\n  \"timeline\": \"时间线\",\n  \"ended\": \"已结束\",\n  \"started\": \"已开始\",\n  \"filters\": \"过滤器\",\n  \"allSpaces\": \"所有空间\",\n  \"submitOnchain\": \"链上提交\",\n  \"inSpaces\": \"在 {0} 空间中\",\n  \"votes\": \"投票数\",\n  \"seeMore\": \"查看更多\",\n  \"seeAll\": \"查看所有\",\n  \"network\": \"网络\",\n  \"networks\": \"网络\",\n  \"skins\": \"皮肤\",\n  \"spaceMembers\": \"成员\",\n  \"members\": \"没有成员 | {count} 个成员 | {count} 个成员\",\n  \"editStrategy\": \"编辑策略\",\n  \"invalidProposals\": \"无效提案\",\n  \"account\": \"账户\",\n  \"create3box\": \"在 3Box 上创建个人资料\",\n  \"view3box\": \"在 3Box 上查看个人资料\",\n  \"edit3box\": \"在 3Box 上编辑个人资料\",\n  \"connectWallet\": \"连接钱包\",\n  \"toggleSkin\": \"切换皮肤\",\n  \"about\": \"关于\",\n  \"license\": \"许可证\",\n  \"showMore\": \"显示更多\",\n  \"voted\": \"已投票\",\n  \"reload\": \"刷新\",\n  \"ipfsServer\": \"IPFS 服务器\",\n  \"hub\": \"中心\",\n  \"cancel\": \"取消\",\n  \"delete\": \"删除\",\n  \"demoSite\": \"这是一个演示站点，请试试看！\",\n  \"removeDelegation\": \"移除委托\",\n  \"confirmRemove\": \"您确定要移除对的委托吗？\",\n  \"removeSpace\": \"用于空间 {0}\",\n  \"noVotingPower\": \"欧，看起来你在区块 blockNumber 没有任何投票权。\",\n  \"quorumReached\": \"已达到既定人数\",\n  \"options\": \"选项\",\n  \"votingPower\": \"您的投票权\",\n  \"comment\": {\n    \"placeholder\": \"分享您的理由(可选)\"\n  },\n  \"receipt\": \"凭证\",\n  \"relayer\": \"中继器\",\n  \"verifyOnMycrypto\": \"在 MyCrypto 上验证凭证\",\n  \"verifyOnSignatorio\": \"在 Signator.io 上验证\",\n  \"isCore\": \"核心\",\n  \"notificationsBlocked\": \"你的浏览器正在屏蔽通知\",\n  \"notificationsNotSupported\": \"你的浏览器不支持接收通知\",\n  \"walletNotSupported\": \"不支持该钱包\",\n  \"seeInExplorer\": \"查看浏览器\",\n  \"learnMore\": \"了解更多\",\n  \"logout\": \"登出\",\n  \"continue\": \"继续\",\n  \"add\": \"添加\",\n  \"edit\": \"编辑\",\n  \"strategyParameters\": \"策略参数\",\n  \"addAction\": \"添加操作\",\n  \"removeAction\": \"移除操作\",\n  \"yourChoice\": \"选择 {0}\",\n  \"targetAddress\": \"目标地址\",\n  \"value\": \"数值\",\n  \"date\": \"数据\",\n  \"marketDetails\": \"市场信息\",\n  \"addMarket\": \"添加市场\",\n  \"selectNetwork\": \"选择网络\",\n  \"conditionId\": \"状况 ID\",\n  \"basetokenAddress\": \"基础代币地址\",\n  \"quoteAddress\": \"引用货币地址\",\n  \"removeMarket\": \"移除市场\",\n  \"back\": \"返回\",\n  \"loading\": \"正在加载…\",\n  \"predictedImpact\": \"预计影响\",\n  \"marketSymbol\": \"{0} 市场\",\n  \"twoChoicesRequired\": \"此插件需要两个选项。\",\n  \"noResultsFound\": \"哎呀，我们找不到任何结果\",\n  \"createFirstProposal\": \"开始创建您的第一个提案\",\n  \"noSpacesJoined\": \"哎呀，您还没有加入任何空间\",\n  \"addFavorites\": \"添加收藏\",\n  \"createdBy\": \"由 {0}\",\n  \"startIn\": \"从 {0} 开始\",\n  \"endIn\": \"结束 {0}\",\n  \"proposalTimeLeft\": \"还剩 {0}\",\n  \"endedAgo\": \"结束 {0}\",\n  \"proposalBy\": \"由 {0}\",\n  \"endDate\": \"结束 {0}\",\n  \"contentHash\": \"内容哈希\",\n  \"defaultSkin\": \"默认皮肤\",\n  \"select\": \"选择\",\n  \"language\": \"语言\",\n  \"agree\": \"我同意\",\n  \"moderators\": \"版主\",\n  \"playground\": \"练习场\",\n  \"strategyParams\": \"策略参数\",\n  \"addresses\": \"地址\",\n  \"networkErrorPlayground\": \"网络错误 - 请打开您的浏览器控制台获取更多信息\",\n  \"upload\": \"上传\",\n  \"join\": \"加入\",\n  \"joined\": \"已加入\",\n  \"leave\": \"离开\",\n  \"subspaces\": \"子空间\",\n  \"mainspace\": \"主空间\",\n  \"copyLink\": \"复制链接\",\n  \"duplicate\": \"复制\",\n  \"joinedSpaces\": \"已加入的空间\",\n  \"joinSpaces\": \"加入空间\",\n  \"setDelegationToSpace\": \"将委托限制在一个指定空间内\",\n  \"theCurrentNetwork\": \"当前网络\",\n  \"optional\": \"(选填)\",\n  \"homeLoadmore\": \"加载更多\",\n  \"confirmAction\": \"确认操作\",\n  \"or\": \"或者\",\n  \"share\": \"分享\",\n  \"shareOnTwitter\": \"分享到 Twitter\",\n  \"shareOnLenster\": \"分享到 Lenster\",\n  \"createButton\": \"创建\",\n  \"discussion\": \"讨论\",\n  \"changeWallet\": \"更换钱包\",\n  \"createASpace\": \"创建空间\",\n  \"getStarted\": \"开始\",\n  \"skip\": \"略过\",\n  \"newSpaceNotice\": {\n    \"header\": \"您的空间已经上线了！\",\n    \"mainText\": \"您可以通过 {settings} 中的策略更改投票权的计算方式。 更改您的设置只会影响新的提案，不会更改现有的提案。\",\n    \"learnMore\": \"在 {documentation} 获得更多信息，或加入Snapshot {discord} 寻求帮助。\",\n    \"gotIt\": \"了解！\"\n  },\n  \"errors\": {\n    \"required\": \"此项为必填项\",\n    \"minLength\": \"此项为必填项\",\n    \"maxLength\": \"最大长度为 {0}\",\n    \"pattern\": \"无效字符\",\n    \"minItems\": \"最少需要 {0} 个项目\",\n    \"maxItems\": \"最多允许 0个项目 \",\n    \"minStrategy\": \"至少需要一个策略\",\n    \"website\": \"URL 格式应为 https://www.example.com\",\n    \"format\": \"格式无效\",\n    \"type\": \"无效的类型\",\n    \"unsupportedImageType\": \"不支持该文件类型，支持的格式是 jpeg、 jpg 和 png\",\n    \"invalidAddress\": \"无效地址\"\n  },\n  \"create\": {\n    \"proposalTitle\": \"标题\",\n    \"discussion\": \"讨论 (可选)\",\n    \"categorie(s)\": \"最多选择2个类别\",\n    \"proposalDescription\": \"描述（可选）\",\n    \"preview\": \"预览\",\n    \"choices\": \"选项\",\n    \"addChoice\": \"添加选项\",\n    \"startDate\": \"选择开始日期\",\n    \"endDate\": \"选择结束日期\",\n    \"startTime\": \"选择开始时间\",\n    \"endTime\": \"选择结束时间\",\n    \"publish\": \"发布\",\n    \"untitled\": \"未命名\",\n    \"snapshotBlock\": \"快照区块号\",\n    \"voting\": \"投票\",\n    \"votingSystem\": \"投票制度\",\n    \"choice\": \"选项 {0}\",\n    \"period\": \"投票时长\",\n    \"start\": \"开始\",\n    \"end\": \"结束\",\n    \"days\": \"天\",\n    \"hours\": \"小时\",\n    \"minutes\": \"分\",\n    \"schedule\": \"计划提案\",\n    \"delayEnforced\": \"空间强制执行直到投票可以开始\",\n    \"periodEnforced\": \"空间强制执行投票期\",\n    \"typeEnforced\": \"{type}由空间强制执行\",\n    \"privacyEnforced\": \"{type} 由空间强制执行\",\n    \"edit\": \"编辑\",\n    \"continue\": \"继续\",\n    \"now\": \"当前\",\n    \"votingPeriodExplainer\": \"这是用户可以投票的时间段。 该提案将在投票期开始之前可见并处于待定状态。\",\n    \"uploadImageExplainer\": \"通过拖放、选择或粘贴来附加文件。\",\n    \"uploading\": \"正在上传图片\",\n    \"markdown\": \"支持Markdown样式\",\n    \"validationWarning\": {\n      \"basic\": {\n        \"member\": \"您需要成为空间的作者才能提交提案。\",\n        \"minScore\": \"您至少需要有 {0} {1} 才能提交提案。\"\n      },\n      \"customValidation\": \"您需要通过提案验证后才能提交提案。\",\n      \"executionError\": \"您在此空间中创建提案的资格验证失败。这可能是由于配置了错误的策略。\"\n    },\n    \"errorGettingSnapshot\": \"我们在获取计算您投票能力所需的快照区块时遇到了一个错误。请稍后再试。\"\n  },\n  \"delegate\": {\n    \"header\": \"委托\",\n    \"selectDelegate\": \"选择委托\",\n    \"to\": \"给\",\n    \"addressPlaceholder\": \"地址或ENS域名\",\n    \"delegations\": \"您的授权\",\n    \"allSpaces\": \"用于所有空间\",\n    \"delegated\": \"已委托给您\",\n    \"pendingTransaction\": \"没有待处理交易 | 1 个待处理交易 | {count} 个待处理交易\",\n    \"topDelegates\": \"顶级委托\",\n    \"noDelegatesFoundFor\": \"没有找到 {0} 的委托\",\n    \"noValidEns\": \"此ENS地址无效\",\n    \"noValidAddress\": \"此地址无效\",\n    \"delegateToSelf\": \"不能将自己选为委托人\",\n    \"delegateToSelfAddress\": \"您不能委托给您自己的ENS地址\",\n    \"noValidSpaceId\": \"此空间ID无效\",\n    \"noDelegationsAndDelegates\": \"找不到您的委托人和代委托？请确保您已经连接到正确的网络。\",\n    \"delegateNotSupported\": \"目前 {network} 不支持委托。\"\n  },\n  \"proposal\": {\n    \"castVote\": \"投出您的票\",\n    \"vote\": \"投票\",\n    \"startDate\": \"开始日期\",\n    \"endDate\": \"结束日期\",\n    \"votingSystem\": \"投票制度\",\n    \"privacy\": \"隐私\",\n    \"invalidChoice\": \"无效的选择\",\n    \"postVoteModal\": {\n      \"defaultTitle\": \"投票成功！\",\n      \"gnosisSafeTitle\": \"您的投票正在进行中...\",\n      \"gnosisSafeDescription\": \" 使用Safe进行的投票需要额外的签名者，并在交易确认后可见\",\n      \"seeQueue\": \"查看队列中的交易\",\n      \"tips\": {\n        \"1\": \"投票可以在提案处于活动状态时更改\"\n      }\n    }\n  },\n  \"proposals\": {\n    \"header\": \"提案\",\n    \"new\": \"新提案\",\n    \"noProposals\": \"这里还没有任何提案！\",\n    \"createProposal\": \"创建提案\",\n    \"showMore\": \"显示更多\",\n    \"showLess\": \"收起\",\n    \"states\": {\n      \"all\": \"所有\",\n      \"core\": \"核心\",\n      \"community\": \"社区\",\n      \"active\": \"活跃\",\n      \"pending\": \"待开始\",\n      \"closed\": \"已关闭\"\n    }\n  },\n  \"notifications\": {\n    \"header\": \"通知\",\n    \"noNotifications\": \"您没有通知\",\n    \"proposalStarted\": \"提案已开始：\",\n    \"proposalEnded\": \"提案已结束：\",\n    \"markAllAsRead\": \"全部标记为已读\",\n    \"all\": \"全部\",\n    \"unread\": \"未读\"\n  },\n  \"modalTerms\": {\n    \"mustAgreeTo\": \"要{action} 此空间，您必须同意{spaceName} 的服务条款。\",\n    \"actionJoin\": \"加入\",\n    \"actionCreate\": \"创建提案\",\n    \"actionVote\": \"投票\"\n  },\n  \"settings\": {\n    \"header\": \"设置\",\n    \"editController\": \"编辑控制人\",\n    \"connectWithSpaceOwner\": \"您处于只读模式，要修改空间设置，请连接控制人或管理员的钱包。\",\n    \"gnosisWrongNetwork\": {\n      \"base\": \"您的Gnosis Safe 在错误的网络上。请链接到网络来action。\",\n      \"settings\": \"编辑空间设置\",\n      \"create\": \"创建提案\",\n      \"vote\": \"对此提案进行投票\"\n    },\n    \"currentSpaceControllerIs\": \"当前空间控制人是 {address}\",\n    \"newController\": \"新的控制人\",\n    \"noRecord\": \"未找到文本记录。请确保 id 已经在 network注册了域名，然后编辑控制人的文本记录来重新设置空间。\",\n    \"set\": \"设置\",\n    \"profile\": \"用户信息\",\n    \"avatar\": \"头像\",\n    \"name\": {\n      \"label\": \"名称\",\n      \"placeholder\": \"例如 Yam Network\"\n    },\n    \"about\": {\n      \"label\": \"关于\",\n      \"placeholder\": \"介绍您的组织\"\n    },\n    \"categories\": {\n      \"label\": \"类别\",\n      \"select\": \"选择类别\"\n    },\n    \"terms\": {\n      \"label\": \"服务条款\",\n      \"information\": \"用户在创建提案或投票前需要接受一次这些条款\"\n    },\n    \"hideSpace\": \"从主页隐藏空间\",\n    \"links\": \"社交账户\",\n    \"subspaces\": {\n      \"label\": \"子空间\",\n      \"information\": \"子空间仅在主空间和子空间都配置后才会显示。 \",\n      \"parent\": {\n        \"label\": \"主空间\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"此空间作为子空间的空间将显示在空间页面上\"\n      },\n      \"children\": {\n        \"label\": \"子空间\",\n        \"placeholder\": \"pistachiodao.eth\",\n        \"information\": \"此处列出的相关子空间将显示在空间页面上\"\n      }\n    },\n    \"website\": \"网站\",\n    \"strategies\": {\n      \"label\": \"策略\",\n      \"information\": \"策略用于确定投票权或用户是否有资格创建提案。\"\n    },\n    \"network\": {\n      \"label\": \"网络\",\n      \"information\": \"用于此空间的默认网络。网络也可以在单独的策略中指定。\"\n    },\n    \"symbol\": {\n      \"label\": \"符号\",\n      \"information\": \"此空间使用的默认符号，通常是代币的符号。例如BAL代表Balancer\"\n    },\n    \"strategiesList\": \"最多选择 8 个策略\",\n    \"votingPowerIsCumulative\": \"投票权是累积的\",\n    \"addStrategy\": \"添加策略\",\n    \"testInPlayground\": \"在playground中测试\",\n    \"admins\": {\n      \"label\": \"管理员\",\n      \"information\": \"管理员能够修改空间设置和管理空间提案\"\n    },\n    \"authors\": {\n      \"label\": \"作者\",\n      \"information\": \"创建者能够创建提案\"\n    },\n    \"proposalValidation\": \"提案验证\",\n    \"validation\": \"类型\",\n    \"proposalThreshold\": {\n      \"label\": \"阈值\",\n      \"information\": \"创建提案所需的最低投票权\"\n    },\n    \"allowOnlyAuthors\": \"仅允许作者提交提案\",\n    \"editValidation\": \"编辑验证\",\n    \"selectValidation\": \"选择验证\",\n    \"validationParameters\": \"验证参数\",\n    \"voting\": \"投票\",\n    \"votingDelay\": \"投票延迟开始\",\n    \"votingPeriod\": \"投票时长\",\n    \"hours\": \"小时\",\n    \"days\": \"天\",\n    \"quorum\": {\n      \"label\": \"提案人数\",\n      \"information\": \"提案通过所需的最低投票权\"\n    },\n    \"type\": {\n      \"label\": \"类型\",\n      \"information\": \"这个空间所使用的投票系统(未来的所有提案也照此执行)\"\n    },\n    \"anyType\": \"任意\",\n    \"hideAbstain\": \"忽略基本投票结果中的弃权票\",\n    \"customDomain\": \"自定义域名\",\n    \"domain\": {\n      \"label\": \"域名\",\n      \"placeholder\": \"例如 vote.balancer.fi\",\n      \"info\": \"要设置一个自定义域名，您另外需要在github上开启一个pull request {docs}。\"\n    },\n    \"skin\": \"皮肤\",\n    \"treasuries\": {\n      \"label\": \"金库\",\n      \"add\": \"添加金库\",\n      \"edit\": \"编辑金库\",\n      \"information\": \"添加您的组织的金库以在您的空间中展示它们\"\n    },\n    \"addPlugin\": \"添加插件\",\n    \"editPlugin\": \"编辑插件\",\n    \"pluginParameters\": \"插件参数\",\n    \"proposal\": {\n      \"title\": \"提案\",\n      \"guidelines\": {\n        \"title\": \"指南\",\n        \"information\": \"在提案创建指南放置一个链接，以帮助用户了解什么是一个好的/有效的提案\"\n      },\n      \"template\": {\n        \"title\": \"模板\",\n        \"information\": \"以模板启动每个提案，以帮助用户了解需要的信息\"\n      }\n    }\n  },\n  \"setup\": {\n    \"example\": \"例如 yam.eth\",\n    \"chooseExistingEns\": \"选择您现有的一个ENS域名来创建空间：\",\n    \"useSingleExistingEns\": \"使用您现有的ENS域名：\",\n    \"orRegisterNewEns\": \"或者注册一个新的域名：\",\n    \"demoTestnetEnsMessage\": \"若要创建测试空间，您需要在 {network} 上的 ENS 域名。\",\n    \"toCreateASpace\": \"要创建空间，您首先需要一个ENS域名。在下面输入一个，并按照ENS的注册说明进行操作。\",\n    \"createASpace\": \"创建空间\",\n    \"registerEnsButton\": \"注册\",\n    \"supportedEnsTLDs\": \"支持的域名结尾\",\n    \"helpDocsAndDiscordLinks\": \"不知道如何创建空间？在 docs了解更多信息或加入Snapshot的Discord。\",\n    \"setSpaceController\": \"空间控制人\",\n    \"setSpaceControllerExists\": \"此域名的snapshot文本记录已被设置。您可以选择编辑更改它，您也可以跳到下一步。\",\n    \"setSpaceControllerInfo\": \" 空间控制人是能够管理空间设置的帐户。 稍后可以添加其他空间控制人（管理员）。\",\n    \"setSpaceControllerInfoGnosisSafe\": \"使用Gnosis Safe创建空间时，建议将安全地址设置为空间控制人。 如果您不这样做，则需要遵循一些额外的步骤。{link}\",\n    \"editSpaceController\": \"在 ENS 上编辑控制人\",\n    \"setController\": \"设置控制人\",\n    \"explainControllerAndEns\": \"设置控制人需要在 {network} 上进行交易，该交易会将“snapshot”文本记录添加到您的 ENS 域。\",\n    \"confirmToSetAddress\": \"您确定要设置 {address} 作为空间的控制人吗？\",\n    \"controllerHasAuthority\": \"管理员对空间设置拥有所有权限\",\n    \"controller\": \"控制人\",\n    \"selectEnsForSpace\": \"选择ENS地址\",\n    \"spaceOwnerAddressPlaceHolder\": \"例如： {address}\",\n    \"controllerAddress\": \"控制人地址\",\n    \"updateController\": \"更新控制人\",\n    \"seeOnEns\": \"在 ENS 上查看\",\n    \"goToSettings\": \"前往设置\",\n    \"setSpaceProfile\": \"自定义您的空间\",\n    \"waitForTransaction\": \"您需要先确认交易才能创建您的空间。 {txUrl}\",\n    \"pleaseWaitMessage\": \"这可能需要几分钟，请等待交易确认\",\n    \"notControllerAddress\": \"请连接控制人地址 {wallet} 以创建空间。\",\n    \"fillCurrentAccount\": \"使用当前登录的帐户\",\n    \"domain\": {\n      \"title\": \"设置空间域名\",\n      \"ensMessage\": \" 在创建自己的空间之前，您需要设置以太坊主网上的 ENS 域名。\",\n      \"ensMessageTestnet\": \"你也可以先在Goerli测试网 {link} 测试并处理问题。\",\n      \"tryDemo\": \"尝试这个 demo\",\n      \"yourExistingSpaces\": \"您已经存在的空间\",\n      \"invalidEns\": \"此ENS名称无效。 通常这是由于在注册时使用了无效字符。\"\n    },\n    \"strategy\": {\n      \"title\": \"您想如何设置您的投票策略？\",\n      \"subtitle\": \"您可以随时改变您的策略设置。\",\n      \"blockTitle\": \"设置投票策略\",\n      \"onePersonOneVote\": {\n        \"title\": \"一人，一票\",\n        \"description\": \"管理可以投票或只允许任何地址投票的白名单。每个投票都是平等的，不需要代币。\",\n        \"whitelistInformation\": \"指定一些可以投票的账户\",\n        \"ticketInformation\": \"任何帐户都可以投票\",\n        \"votesEqualInfo\": \"每次投票都是平等的，无代币限制\"\n      },\n      \"tokenVoting\": {\n        \"title\": \"代币加权投票\",\n        \"description\": \"投票用代币作为参考的权重。代币可以是 ERC-20、 ERC-721 或 ERC-115 的代币标准\",\n        \"tokenNotFound\": \"未找到代币\",\n        \"seeOnEtherscan\": \"在Etherscan上查看\"\n      },\n      \"advanced\": {\n        \"title\": \"自定义设置\",\n        \"description\": \"选择最多8个具有多种选项的策略。 如果你找不到正确的策略，你可以创建自己的策略\"\n      }\n    },\n    \"validationTitle\": \"谁可以管理这个空间并创建提案？\"\n  },\n  \"profile\": {\n    \"buttonEdit\": \"编辑个人资料\",\n    \"viewProfile\": \"查看个人资料\",\n    \"about\": {\n      \"header\": \"关于我们\",\n      \"joinedSpaces\": \"已加入的空间\",\n      \"createdSpaces\": \"创建空间\",\n      \"biography\": \"个人介绍\",\n      \"notJoinSpacesYet\": \"尚未加入任何空间\",\n      \"notCreatedSpacesYet\": \"尚未创建任何空间\",\n      \"delegatorNetworkInfo\": \"通过切换你钱包中的网络进行更改\",\n      \"delegate\": \"委托\",\n      \"delegated\": \"已委托\",\n      \"delegateTo\": \"委托给\",\n      \"delegateFor\": \"代表\",\n      \"noDelegatorsMessage\": \"{network} 上没有代表\",\n      \"notSupportedNetwork\": \"目前在 {network} 上不支持委托 \"\n    },\n    \"activity\": {\n      \"header\": \"活动\",\n      \"votedFor\": \"投了 {choice}\",\n      \"today\": \"今天\",\n      \"thisWeek\": \"本周\",\n      \"olderThanWeek\": \"早于一周前\",\n      \"noActivity\": \"暂无动态\"\n    },\n    \"settings\": {\n      \"header\": \"编辑个人资料\",\n      \"name\": \"姓名\",\n      \"biography\": \"个人介绍\",\n      \"namePlaceholder\": \"输入名字\",\n      \"bioPlaceholder\": \"讲述您的故事\",\n      \"change\": \"修改\",\n      \"remove\": \"删除\"\n    }\n  },\n  \"notify\": {\n    \"youDidIt\": \"您成功了!\",\n    \"copied\": \"已复制！\",\n    \"proposalDeleted\": \"提案被删除\",\n    \"somethingWentWrong\": \"哎呀，似乎出了点问题！\",\n    \"saved\": \"已保存！\",\n    \"delegationSuccess\": \"委托成功\",\n    \"delegationRemoved\": \"委托已移除\",\n    \"proposalCreated\": \"提案已创建\",\n    \"voteSuccessful\": \"投票成功！\",\n    \"ensSet\": \"ENS 文本记录已成功设置\",\n    \"transactionSent\": \"交易已发送\"\n  },\n  \"explore\": {\n    \"createStrategy\": \"创建策略\",\n    \"createSkin\": \"创建新皮肤\",\n    \"addNetwork\": \"添加网络\",\n    \"createPlugin\": \"创建插件\",\n    \"strategies\": \"策略\",\n    \"skins\": \"皮肤\",\n    \"networks\": \"网络\",\n    \"plugins\": \"插件\",\n    \"results\": \"结果\",\n    \"category\": \"分类\",\n    \"categories\": {\n      \"all\": \"所有\",\n      \"protocol\": \"协议\",\n      \"social\": \"社群媒体\",\n      \"investment\": \"投资\",\n      \"grant\": \"拨款项目\",\n      \"service\": \"服务\",\n      \"media\": \"文化传媒\",\n      \"creator\": \"创作者\",\n      \"collector\": \"收藏家\"\n    }\n  },\n  \"voting\": {\n    \"selectVoting\": \"选择投票系统\",\n    \"single-choice\": \"单选投票\",\n    \"approval\": \"赞成投票\",\n    \"quadratic\": \"二次投票\",\n    \"ranked-choice\": \"排序选择投票\",\n    \"weighted\": \"加权投票\",\n    \"basic\": \"基本投票\",\n    \"description\": {\n      \"single-choice\": \"每个投票人只能选择一个选项。\",\n      \"approval\": \"每个投票人可以选择任意数量的选项。\",\n      \"quadratic\": \"每个选民都可以在任何数量的选择中分配投票权。结果按四舍五入计算。\",\n      \"ranked-choice\": \"每个选民都可以选择和排列任何数目的选择。结果是通过即时计算的。\",\n      \"weighted\": \"每个选民都可以在任何选择中分散投票权。\",\n      \"basic\": \"有三种投票单选项：支持，反对，和弃权\"\n    }\n  },\n  \"privacy\": {\n    \"label\": \"隐私\",\n    \"title\": \"选择投票隐私\",\n    \"information\": \"提案中使用的隐私类型。(在所有未来的提案中强制执行)\",\n    \"any\": \"任何类型\",\n    \"none\": \"未设置\",\n    \"shutter\": {\n      \"label\": \"快门\",\n      \"description\": \"选择是加密的，只有在投票期结束后才能看到\",\n      \"tooltip\": \"该提案已启用快门隐私。所有的投票都将被加密，直到投票期结束并计算出最终的分数\",\n      \"url\": \"https://blog.shutter.network/shielded-voting/\"\n    }\n  },\n  \"validation\": {\n    \"label\": \"验证\",\n    \"title\": \"选择投票验证\",\n    \"information\": \"验证类型用来确定用户是否可以投票。(在所有未来的提案中强制执行)\",\n    \"any\": {\n      \"label\": \"任何人都可以投票\",\n      \"description\": \"任何拥有投票权的人都可以投票。\"\n    },\n    \"basic\": {\n      \"label\": \"基础\",\n      \"description\": \"使用任何策略来确定用户是否可以投票。\",\n      \"invalidMessage\": \"您不符合就该提案进行投票的最低余额要求。\"\n    },\n    \"passport-gated\": {\n      \"label\": \"Gitcoin Passport门槛\",\n      \"description\": \"通过要求用户拥有 Gitcoin Passport 来保护您的提案免受垃圾邮件和投票操纵。\",\n      \"invalidMessage\": \"你需要一本带有 amount个如下邮票的Gitcoin Passport才能对这个提案投票: stamps \"\n    }\n  },\n  \"safeSnap\": {\n    \"currentOutcome\": \"当前结果\",\n    \"currentBond\": \"当前的保证金\",\n    \"finalizedIn\": \"已完成 {0}\",\n    \"executableIn\": \"可执行 {0}\",\n    \"finalOutcome\": \"结果\",\n    \"nextBond\": \"设置结果的绑定\",\n    \"setOutcomeTo\": \"设置结果为\",\n    \"claimBond\": \"认领保证金\",\n    \"addBatch\": \"添加交易批次\",\n    \"batch\": \"交易批次\",\n    \"transactions\": \"交易次数\",\n    \"to\": \"至 (地址)\",\n    \"invalidAddress\": \"无效地址\",\n    \"invalidAmount\": \"无效金额\",\n    \"invalidValue\": \"无效值\",\n    \"invalidAbi\": \"无效的ABI\",\n    \"invalidData\": \"无效数据\",\n    \"value\": \"值 (wei)\",\n    \"data\": \"数据\",\n    \"noCollectibles\": \"暂无收藏\",\n    \"asset\": \"资产\",\n    \"amount\": \"数额\",\n    \"type\": \"类型\",\n    \"transferFunds\": \"发送资金\",\n    \"transferNFT\": \"发送NFT\",\n    \"contractInteraction\": \"智能合约交互\",\n    \"rawTransaction\": \"原始交易记录\",\n    \"addTransaction\": \"添加交易\",\n    \"transactionLabels\": {\n      \"contractInteraction\": \"{functionName}() - {amount} wei 到 {address}\",\n      \"transferFunds\": \"将 {amount} {tokenSymbol} 发送到 {address}\",\n      \"transferNFT\": \"发送 {name} #{id} 至 {address}\",\n      \"raw\": \"发送 {amount} 到 {address}\"\n    },\n    \"labels\": {\n      \"request\": \"请求执行\",\n      \"setOutcome\": \"添加结果\",\n      \"changeOutcome\": \"修改结果\",\n      \"executeTxs\": \"执行交易批处理 {0} / {1}\",\n      \"executed\": \"所有交易已执行\",\n      \"noTransactions\": \"没有需要执行的交易。\",\n      \"rejected\": \"提案被否决\",\n      \"error\": \"出了些问题\",\n      \"connectWallet\": \"连接钱包以查看执行详情\",\n      \"switchChain\": \"将你的钱包切换到 {0} 以请求执行\",\n      \"question\": \"该提案是否通过并且是否符合\",\n      \"criteria\": \"接收标准？\",\n      \"proposalPassed\": \"该提案通过了吗？\",\n      \"expired\": \"提案已过期\",\n      \"approveBond\": \"批准保证金\",\n      \"confirmVoteResults\": \"点击确认已通过的提案\",\n      \"executeTxsUma\": \"执行交易批次\",\n      \"deleteDisputedProposal\": \"删除有争议的提案\",\n      \"confirmVoteResultsToolTip\": \"请确保这项提案在提出链上提案之前经快照投票批准。 如果快照投票否决了这项提案，链上的提案也将被否决，你将失去你的保证金。\",\n      \"approveBondToolTip\": \"链上提案需要提案人提供保证金。这将批准你的钱包中的代币作为保证金。如果你做了一个无效的提案，它将受到争议，你将失去你的保证金。如果提案是有效的，你的保证金将在交易执行时被退回。\",\n      \"requestToolTip\": \"这将在链上提议该快照投票的交易。在一个挑战窗口后，如果提案有效，交易可以被执行，你的保证金将被退回。\",\n      \"executeToolTip\": \"这将执行此提案中的交易并退还提议者的保证金。\"\n    }\n  },\n  \"poap\": {\n    \"no_poap_header\": \"尚未为此提议设置POAP :'(\",\n    \"no_voted_header\": \"投票以获取此POAP\",\n    \"unclaimed_header\": \"铸造您投票本提案的专属POAP\",\n    \"claimed_header\": \"恭喜！POAP 已被铸造到您的收藏中\",\n    \"loading_header\": \"POAP正在被铸造到您的收藏中\",\n    \"button_claim\": \"铸造\",\n    \"button_show\": \"浏览收藏\",\n    \"success_claim\": \"POAP已被铸造到您的收藏中\",\n    \"error_claim\": \"创建令牌时出错\"\n  },\n  \"progress\": {\n    \"progress\": \"进度\",\n    \"inProgress\": \"进行中\",\n    \"completed\": \"已完成\",\n    \"complete\": \"完成\",\n    \"newStep\": \"新步骤\",\n    \"description\": \"说明\",\n    \"add\": \"添加\",\n    \"deleteStep\": \"删除步骤\",\n    \"deleteConfirm\": \"您确定要删除吗？\",\n    \"delete\": \"删除\",\n    \"cancel\": \"取消\",\n    \"comeBack\": \"投票结束后再来看看这个提案的进展情况吧！\",\n    \"confirmSignature\": \"签名此消息将使我们能够授权您的请求来更新您的提案的进度。\",\n    \"wentWrong\": \"哎呀，出了点问题\",\n    \"voting\": \"投票中\",\n    \"soon\": \"即将到来\"\n  },\n  \"charts\": {\n    \"charts\": \"图表\",\n    \"noVotesYet\": \"目前还没有投票可呈现\",\n    \"totalVotesPerDay\": \"每日总票数\",\n    \"shareOfVotingPower\": \"投票权重比例\",\n    \"votingPowerPerDay\": \"每日投票权\"\n  },\n  \"comment_box\": {\n    \"title\": \"评论框\",\n    \"add\": \"在这里添加您的评论\",\n    \"submit\": \"提交\",\n    \"preview\": \"预览\",\n    \"continue_editing\": \"继续编辑\",\n    \"edit\": \"请在此编辑您的回复\",\n    \"edit_button\": \"编辑\",\n    \"dismiss\": \"取消\",\n    \"delete\": \"删除\",\n    \"add_reply\": \"在此添加您的回复\",\n    \"edit_comment\": \"编辑评论\",\n    \"edit_modal\": \"您确定要编辑吗？\",\n    \"yes\": \"是\",\n    \"no\": \"否\",\n    \"delete_comment\": \"删除评论\",\n    \"delete_modal\": \"您确定要删除吗？\",\n    \"error\": \"哎呀，出了些问题\",\n    \"replies\": \"回复\",\n    \"hide\": \"隐藏\",\n    \"show\": \"显示\",\n    \"reply\": \"回复\",\n    \"load_more\": \"显示更多\"\n  },\n  \"page\": {\n    \"title\": {\n      \"home\": \"Snapshot\",\n      \"setup\": \"创建空间\",\n      \"timeline\": \"时间线\",\n      \"notifications\": \"通知\",\n      \"explore\": \"浏览\",\n      \"playground\": \"练习场\",\n      \"space\": {\n        \"create\": \"创建 {space} 提案\",\n        \"about\": \"关于 {space}\",\n        \"proposals\": \"{space} 提案\",\n        \"proposal\": \"{space} 提案： {proposal}\",\n        \"settings\": \"{space} 设置\"\n      },\n      \"strategy\": \"{key} 策略\",\n      \"delegate\": \"委托\",\n      \"ranking\": \"排名\"\n    }\n  },\n  \"hal\": {\n    \"title\": \"追踪 {spaceName} 的提案\",\n    \"text\": \"每次创建新提案或提案结束时都接收通知\"\n  },\n  \"timeUnits\": {\n    \"second\": \"1 秒 | {n} 秒\",\n    \"minute\": \"1 分钟 | {n} 分钟\",\n    \"hour\": \"1 小时 | {n} 小时\",\n    \"day\": \"1 天 | {n} 天\",\n    \"week\": \"1周 | {n} 周\",\n    \"month\": \"1 个月 | {n} 个月\",\n    \"year\": \"1 年 | {n} 年\"\n  },\n  \"unsupportedNetwork\": {\n    \"unsupportedNetwork\": \"不支持的网络\",\n    \"switchNetworkToNetwork\": \"若要继续，您需要将钱包中的网络更改为 {network}。\",\n    \"switchToNetwork\": \"切换到 {network}\",\n    \"goToDemoSite\": \"前往演示网站\"\n  },\n  \"treasury\": {\n    \"title\": \"金库\",\n    \"wallets\": {\n      \"title\": \"钱包\",\n      \"empty\": \"此空间还没有金库\",\n      \"addTreasury\": \"添加一个金库\"\n    },\n    \"assets\": {\n      \"title\": \"资产\",\n      \"empty\": \"该合约中没有资产\"\n    },\n    \"24hChange\": \"24小时变化\"\n  },\n  \"newsletter\": {\n    \"yourEmail\": \"您的电子邮件地址\",\n    \"title\": \"获取最近的Snapshot更新\"\n  },\n  \"joinCommunity\": \"加入Snapshot社区\",\n  \"header\": {\n    \"title\": \"在哪里作出决定\",\n    \"description\": \"Snapshot是一个用于社区治理的免费开放源码平台。现在您可以创建自己的空间并开始决策！\"\n  },\n  \"aboutPage\": {\n    \"description\": \"Snapshot是一个去中心化的治理平台，这使得用户很容易创建提案并就其进行表决，所有这些提案都不需要花费gas！ 此外，我们的灵活系统支持各种投票类型和策略，以便您可以根据您的需要调整投票过程。\",\n    \"subHeader\": \"治理应该是一种快照\",\n    \"subDescription\": \"Web3管理不一定要复杂。 Snapshot是各组织寻求一种简单有效的方法来管理其社区或组织的完美解决办法。\"\n  },\n  \"footerView\": {\n    \"resources\": \"资源\",\n    \"about\": \"关于\",\n    \"blog\": \"博客\",\n    \"jobs\": \"工作机会\",\n    \"discussions\": \"讨论\",\n    \"github\": \"GitHub\",\n    \"docs\": \"文档\",\n    \"support\": \"支持\",\n    \"hiring\": \"加入我们！\"\n  }\n}\n"
  },
  {
    "path": "src/main.ts",
    "content": "import { Buffer } from 'buffer';\n(window as any).global = window;\n(window as any).Buffer = Buffer;\n\nimport { LockPlugin } from '@snapshot-labs/lock/plugins/vue3';\nimport { DefaultApolloClient } from '@vue/apollo-composable';\nimport { createHead } from '@vueuse/head';\nimport options from '@/helpers/auth';\nimport VueTippy from 'vue-tippy';\nimport VueViewer from 'v-viewer';\nimport { apolloClient } from '@/helpers/apollo';\n// import { initSentry } from '@/sentry';\nimport { KNOWN_DOMAINS, KNOWN_HOSTS } from '@/helpers/constants';\nimport i18n from '@/helpers/i18n';\nimport router from '@/router';\nimport '@/assets/css/main.scss';\nimport App from '@/App.vue';\n\nconst parentUrl =\n  window.location != window.parent.location\n    ? document.referrer ||\n      document.location.ancestorOrigins[\n        document.location.ancestorOrigins.length - 1\n      ]\n    : document.location.href;\nconst parentHost = new URL(parentUrl).host;\nif (\n  window !== window.parent &&\n  !KNOWN_HOSTS.includes(parentHost) &&\n  !KNOWN_DOMAINS.includes(parentHost.split('.').slice(-2).join('.'))\n) {\n  document.documentElement.style.display = 'none';\n  throw new Error(`Unknown host: ${parentHost}`);\n}\n\nconst head = createHead();\n\nconst app = createApp({\n  setup() {\n    provide(DefaultApolloClient, apolloClient);\n  },\n  render: () => h(App)\n});\n\n// initSentry(app, router);\n\napp\n  .use(head)\n  .use(i18n)\n  .use(router)\n  .use(LockPlugin, options)\n  .use(VueTippy, {\n    defaultProps: { delay: [400, null] },\n    directive: 'tippy' // => v-tippy\n  })\n  .use(VueViewer, { defaultOptions: { navbar: true, toolbar: false } });\n\napp.mount('#app');\n\nexport default app;\n"
  },
  {
    "path": "src/plugins/README.md",
    "content": "Discover how to develop a plugin by following our comprehensive documentation at https://docs.snapshot.org/developer-guides/create\n"
  },
  {
    "path": "src/plugins/domino/ProposalSidebar.vue",
    "content": "<script setup>\nimport DominoCustomBlock from './components/CustomBlock.vue';\n\ndefineProps({\n  space: Object\n});\n</script>\n\n<template>\n  <DominoCustomBlock v-if=\"space.plugins.domino\" :space=\"space\" />\n</template>\n"
  },
  {
    "path": "src/plugins/domino/components/CustomBlock.vue",
    "content": "<script setup>\nconst props = defineProps({\n  space: Object\n});\n\nconst timezone = Intl.DateTimeFormat()?.resolvedOptions()?.timeZone ?? 'UTC';\n\nconst automationTemplates = computed(() => [\n  {\n    name: 'Send Discord Alert 48h Before Proposal End',\n    imageUrl: 'https://domino-static.s3.eu-west-3.amazonaws.com/discord-logo.svg',\n    url: `https://domino.run/explore/templates/32?spaceId=${encodeURIComponent(props.space.id)}`\n  },\n  {\n    name: 'Send Telegram Alert 24h Before Proposal End If Quorum Not Reached',\n    imageUrl: 'https://telegram.org/img/apple-touch-icon.png',\n    url: `https://domino.run/explore/templates/36?spaceId=${encodeURIComponent(props.space.id)}`\n  },\n  {\n    name: 'Send New Proposals to Discord',\n    imageUrl: 'https://domino-static.s3.eu-west-3.amazonaws.com/discord-logo.svg',\n    url: `https://domino.run/explore/templates/28?snapshotSpaceID=${encodeURIComponent(props.space.id)}&timezone=${encodeURIComponent(timezone)}`\n  },\n  {\n    name: 'Send New Proposals to Telegram',\n    imageUrl: 'https://telegram.org/img/apple-touch-icon.png',\n    url: `https://domino.run/explore/templates/29?snapshotSpaceID=${encodeURIComponent(props.space.id)}&timezone=${encodeURIComponent(timezone)}`\n  },\n  {\n    name: 'Send New Proposals to Slack',\n    imageUrl: 'https://domino-static.s3.eu-west-3.amazonaws.com/slack-logo.png',\n    url: `https://domino.run/explore/templates/30?snapshotSpaceID=${encodeURIComponent(props.space.id)}&timezone=${encodeURIComponent(timezone)}`\n  },\n]);\n\nconst customWorkflowUrl = computed(() => {\n  const triggerConfig = {\n    conditions: [\n      {\n        \"operator\": \"and\",\n        \"conditions\": [\n          {\n            \"key\": \"space\",\n            \"type\": \"string\",\n            \"value\": {\n              \"comparator\": \"eq\",\n              \"value\": props.space.id\n            }\n          }\n        ]\n      }\n    ]\n  };\n\n  return `https://domino.run/automations/create?triggerUuid=${encodeURIComponent('snapshot-tmkg6ni3l3r@1.0.0/proposal-event')}&triggerConfig=${encodeURIComponent(JSON.stringify(triggerConfig))}`;\n});\n\n</script>\n\n<template>\n  <TuneBlock>\n    <template #header>\n      <TuneBlockHeader :title=\"$t('domino.title')\" :information=\"$t('domino.information')\"/>\n    </template>\n\n    <div>\n      <ul class=\"space-y-2\">\n        <li v-for=\"(item, index) in automationTemplates\" :key=\"index\" class=\"flex items-center justify-between gap-4 group\">\n          <a :href=\"item.url\" target=\"_blank\" class=\"flex items-center gap-2\">\n            <img\n              class=\"mx-auto rounded-xl border border-skin-border opacity-95 group-hover:opacity-100\"\n              :src=\"item.imageUrl\"\n              :alt=\"item.name\"\n              width=\"36\"\n              height=\"36\"\n            />\n\n            <div class=\"text-sm leading-tight\">{{ item.name }}</div>\n          </a>\n        </li>\n        \n        <li class=\"text-center\">\n          <a href=\"https://domino.run/explore/apps/snapshot-tmkg6ni3l3r\" target=\"_blank\">\n\n            <div class=\"text-sm leading-tight text-skin-link\">\n              {{ $t('domino.viewMore') }}\n            </div>\n          </a>\n        </li>\n      </ul>\n\n      <div class=\"py-3 text-center\">\n        <a :href=\"customWorkflowUrl\" target=\"_blank\">\n          <TuneButton>{{ $t('domino.createCustomWorkflow') }}</TuneButton>\n        </a>\n      </div>\n    </div>\n\n    <div class=\"flex items-center justify-center pt-2\">\n      <a href=\"https://domino.run/\" target=\"_blank\">\n        <img class=\"block flex-0 h-4\" src=\"https://domino-static.s3.eu-west-3.amazonaws.com/domino-logo-full.png\" alt=\"Domino\"  />\n      </a>\n    </div>\n  </TuneBlock>\n</template>\n"
  },
  {
    "path": "src/plugins/domino/plugin.json",
    "content": "{\n  \"name\": \"Domino\",\n  \"version\": \"1.0.0\",\n  \"author\": \"domino.run\",\n  \"website\": \"https://github.com/snapshot-labs/snapshot/tree/master/src/plugins/domino\",\n  \"icon\": \"ipfs://QmePXkJYHhkSMXi7qpvShtNg8Vhd5kVDYQNcPjAcZv3QdG\"\n}\n\n"
  },
  {
    "path": "src/plugins/gnosis/Create.vue",
    "content": "<script setup>\nimport GnosisConfig from './components/Config.vue';\n\nconst { pluginIndex } = usePlugins();\n\ndefineProps({\n  space: Object,\n  proposal: Object,\n  modelValue: Object\n});\n\nconst emit = defineEmits(['update']);\nconst update = form => {\n  emit('update', { key: 'gnosis', form });\n};\n</script>\n\n<template>\n  <BaseBlock :title=\"pluginIndex.gnosis.name\">\n    <GnosisConfig\n      v-if=\"space.plugins.gnosis\"\n      :proposal=\"proposal\"\n      :network=\"space.network\"\n      :model-value=\"\n        modelValue?.gnosis || {\n          network: '1',\n          conditionId: '',\n          baseTokenAddress: '',\n          quoteCurrencyAddress: ''\n        }\n      \"\n      @update:model-value=\"update\"\n    />\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/plugins/gnosis/ProposalSidebar.vue",
    "content": "<script setup>\nimport GnosisCustomBlock from './components/CustomBlock.vue';\n\nconst { pluginIndex } = usePlugins();\n\ndefineProps({ proposal: Object });\n</script>\n\n<template>\n  <BaseBlock\n    v-if=\"proposal.plugins?.gnosis?.baseTokenAddress\"\n    :title=\"pluginIndex.gnosis.name\"\n  >\n    <GnosisCustomBlock\n      :proposal-config=\"proposal.plugins.gnosis\"\n      :choices=\"proposal.choices\"\n    />\n  </BaseBlock>\n</template>\n"
  },
  {
    "path": "src/plugins/gnosis/components/Config.vue",
    "content": "<script setup>\nimport GnosisCustomBlock from './CustomBlock.vue';\n\nconst props = defineProps(['modelValue', 'proposal', 'network']);\nconst emit = defineEmits(['update:modelValue']);\n\nconst input = reactive(props.modelValue);\nconst preview = ref(false);\n\nconst isValid = computed(() => {\n  return (\n    (input.conditionId &&\n      input.baseTokenAddress &&\n      input.quoteCurrencyAddress) ||\n    input === {}\n  );\n});\n\nwatch(input, () => {\n  emit('update:modelValue', input);\n});\n\nconst getChoices = () => {\n  return props.proposal.choices.map(choice => choice.text);\n};\n</script>\n\n<template>\n  <div class=\"mb-2 text-center\">\n    <h4 class=\"mb-3\">{{ $t('marketDetails') }}</h4>\n    <div v-if=\"!preview\" class=\"space-y-2\">\n      <TuneButton class=\"w-full\">\n        <select\n          v-model=\"input.network\"\n          class=\"input w-full text-center\"\n          :placeholder=\"$t('selectNetwork')\"\n          required\n        >\n          <option value=\"1\" selected>Mainnet</option>\n          <option value=\"100\">xDai</option>\n        </select>\n      </TuneButton>\n      <TuneButton class=\"w-full\">\n        <input\n          v-model=\"input.conditionId\"\n          class=\"input w-full text-center\"\n          :placeholder=\"$t('conditionId')\"\n          required\n        />\n      </TuneButton>\n      <TuneButton class=\"w-full\">\n        <input\n          v-model=\"input.baseTokenAddress\"\n          class=\"input w-full text-center\"\n          :placeholder=\"$t('basetokenAddress')\"\n          required\n        />\n      </TuneButton>\n      <TuneButton class=\"w-full\">\n        <input\n          v-model=\"input.quoteCurrencyAddress\"\n          class=\"input w-full text-center\"\n          :placeholder=\"$t('quoteAddress')\"\n          required\n        />\n      </TuneButton>\n    </div>\n  </div>\n  <div v-if=\"preview\">\n    <GnosisCustomBlock :proposal-config=\"input\" :choices=\"getChoices()\" />\n  </div>\n  <TuneButton\n    v-if=\"!preview\"\n    :disabled=\"!isValid\"\n    class=\"my-2 w-full\"\n    primary\n    @click=\"preview = true\"\n  >\n    {{ $t('create.preview') }}\n  </TuneButton>\n  <TuneButton\n    v-if=\"preview\"\n    class=\"mb-2 w-full\"\n    primary\n    @click=\"preview = false\"\n  >\n    {{ $t('back') }}\n  </TuneButton>\n</template>\n"
  },
  {
    "path": "src/plugins/gnosis/components/CustomBlock.vue",
    "content": "<script>\nimport Plugin from '../index';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\nimport { shorten } from '@/helpers/utils';\n\nexport default {\n  props: ['proposalConfig', 'choices'],\n  setup() {\n    return { shorten };\n  },\n  data() {\n    return {\n      loading: false,\n      plugin: new Plugin(),\n      baseToken: {},\n      baseTokenUrl: '',\n      quoteToken: {},\n      baseProductMarketMaker: {},\n      quoteProductMarketMaker: {},\n      quoteCurrencyPrice: 0.0,\n      priceFirstOption: 0.0,\n      priceSecondOption: 0.0,\n      predictPriceImpact: 0.0\n    };\n  },\n  async created() {\n    this.loading = true;\n    const network = this.proposalConfig.network || '1';\n    const broviderUrl = import.meta.env.VITE_BROVIDER_URL;\n    const provider = getProvider(network, { broviderUrl });\n    this.baseToken = await this.plugin.getTokenInfo(\n      provider,\n      this.proposalConfig.baseTokenAddress\n    );\n    this.baseTokenUrl = this.getLogoUrl(this.baseToken.checksumAddress);\n    this.quoteToken = await this.plugin.getTokenInfo(\n      provider,\n      this.proposalConfig.quoteCurrencyAddress\n    );\n    const conditionQuery = await this.plugin.getOmenCondition(\n      network,\n      this.proposalConfig.conditionId\n    );\n    this.baseProductMarketMaker =\n      conditionQuery.condition.fixedProductMarketMakers.find(\n        market =>\n          market.collateralToken === this.proposalConfig.baseTokenAddress\n      );\n    this.quoteProductMarketMaker =\n      conditionQuery.condition.fixedProductMarketMakers.find(\n        market => market.collateralToken === this.quoteToken.address\n      );\n\n    const tokenPairQuery = await this.plugin.getUniswapPair(\n      network,\n      this.proposalConfig.quoteCurrencyAddress,\n      this.proposalConfig.baseTokenAddress\n    );\n    if (\n      tokenPairQuery !== undefined &&\n      tokenPairQuery.token0Price !== undefined\n    ) {\n      this.quoteCurrencyPrice = parseFloat(tokenPairQuery.token0Price);\n    }\n    this.priceFirstOption = this.getTokenPrice(0);\n    this.priceSecondOption = this.getTokenPrice(1);\n    this.predictPriceImpact =\n      ((this.priceFirstOption - this.priceSecondOption) /\n        this.priceSecondOption) *\n      100;\n    this.loading = false;\n  },\n  methods: {\n    getLogoUrl(checksumAddress) {\n      return `https://gnosis-safe-token-logos.s3.amazonaws.com/${checksumAddress}.png`;\n    },\n    getMarketUrl(marketIndex) {\n      const network = this.proposalConfig.network || '1';\n      if (network === '100') {\n        return `https://xdai.omen.eth.link/#/${marketIndex.id}`;\n      }\n      return `https://omen.eth.link/#/${marketIndex.id}`;\n    },\n    getTokenPrice(outcomeIndex) {\n      return (\n        this.quoteCurrencyPrice *\n        (parseFloat(\n          this.baseProductMarketMaker.outcomeTokenMarginalPrices[outcomeIndex]\n        ) /\n          parseFloat(\n            this.quoteProductMarketMaker.outcomeTokenMarginalPrices[\n              outcomeIndex\n            ]\n          ))\n      );\n    }\n  }\n};\n</script>\n\n<template>\n  <div v-if=\"choices.length > 1\">\n    <div v-if=\"loading\" class=\"loading\">\n      {{ $t('loading') }}\n    </div>\n    <div class=\"mb-1\">\n      <b>\n        {{ $t('predictedImpact') }}\n      </b>\n      <div class=\"float-right\">\n        <span :aria-label=\"baseToken.name\" class=\"tooltipped tooltipped-n\">\n          <img\n            class=\"circle inline-block border !align-middle leading-none\"\n            :src=\"baseTokenUrl\"\n            :alt=\"baseToken.name\"\n            width=\"22\"\n            height=\"22\"\n          />\n        </span>\n        {{ predictPriceImpact.toFixed(2) }}%\n      </div>\n    </div>\n    <div class=\"mb-1\" :title=\"choices[0]\">\n      <b>{{ shorten(choices[0], 'name') }}</b>\n      <span class=\"float-right\">\n        1\n        {{ baseToken.symbol }}\n        =\n        {{ priceFirstOption.toFixed(2) }}\n        {{ quoteToken.symbol }}\n      </span>\n    </div>\n    <div\n      :title=\"choices[1]\"\n      class=\"mb-1 rounded-t-none border-b bg-skin-header-bg md:rounded-t-md\"\n      style=\"padding-bottom: 12px\"\n    >\n      <b>{{ shorten(choices[1], 'name') }}</b>\n      <span class=\"float-right\">\n        1\n        {{ baseToken.symbol }}\n        =\n        {{ priceSecondOption.toFixed(2) }}\n        {{ quoteToken.symbol }}\n      </span>\n    </div>\n    <div class=\"mb-1\" style=\"padding-top: 12px\">\n      <b>{{ $tc('marketSymbol', [baseToken.symbol]) }}</b>\n      <a\n        :href=\"getMarketUrl(baseProductMarketMaker)\"\n        target=\"_blank\"\n        class=\"float-right\"\n      >\n        <BaseIcon name=\"external-link\" class=\"ml-1\" />\n      </a>\n    </div>\n    <div>\n      <div class=\"mb-1\">\n        <b>{{ $tc('marketSymbol', [baseToken.symbol]) }}</b>\n        <a\n          :href=\"getMarketUrl(quoteProductMarketMaker)\"\n          target=\"_blank\"\n          class=\"float-right\"\n        >\n          <BaseIcon name=\"external-link\" class=\"ml-1\" />\n        </a>\n      </div>\n    </div>\n  </div>\n  <div v-else>\n    {{ $t('twoChoicesRequired') }}\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/gnosis/index.ts",
    "content": "import { getAddress } from '@ethersproject/address';\nimport snapshot from '@snapshot-labs/snapshot.js';\n\nconst UNISWAP_V2_SUBGRAPH_URL = {\n  '1': 'https://subgrapher.snapshot.org/subgraph/arbitrum/EYCKATKGBKLWvSfwvBjzfCBmGwYNdVkduYXVivCsLRFu',\n  '100':\n    'https://subgrapher.snapshot.org/subgraph/arbitrum/F5u74NSaLF92s1qUSacQU4fmWizmK9yqDhXAq6RPtgky'\n};\n\nconst OMEN_SUBGRAPH_URL = {\n  '1': 'https://subgrapher.snapshot.org/subgraph/arbitrum/7JbjEfSTme3LioqJ7SJZ9Y1GhSD55X98Btd8fg7iYUPT',\n  '100':\n    'https://subgrapher.snapshot.org/subgraph/arbitrum/9fUVQpFwzpdWS9bq5WkAnmKbNNcoBwatMR4yZq81pbbz'\n};\n\nconst WETH_ADDRESS = {\n  '1': '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',\n  '4': '0xc778417e063141139fce010982780140aa0cd5ab',\n  '100': '0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1'\n};\n\nconst OMEN_GQL_QUERY = {\n  condition: {\n    __args: {\n      id: undefined\n    },\n    id: true,\n    fixedProductMarketMakers: {\n      id: true,\n      collateralToken: true,\n      outcomeTokenAmounts: true,\n      outcomeTokenMarginalPrices: true\n    }\n  }\n};\n\nconst UNISWAP_V2_GQL_QUERY = {\n  pairsTokens: {\n    __aliasFor: 'pairs',\n    __args: {\n      where: {\n        token0: true,\n        token1: true\n      }\n    },\n    token0Price: true\n  },\n  pairsTokensInverted: {\n    __aliasFor: 'pairs',\n    __args: {\n      where: {\n        token0: true,\n        token1: true\n      }\n    },\n    token1Price: true\n  },\n  pairsTokens0: {\n    __aliasFor: 'pairs',\n    __args: {\n      where: {\n        token0: true,\n        token1: true\n      }\n    },\n    token0Price: true\n  },\n  pairsTokens1: {\n    __aliasFor: 'pairs',\n    __args: {\n      where: {\n        token0: true,\n        token1: true\n      }\n    },\n    token0Price: true\n  }\n};\n\nconst erc20Abi = [\n  {\n    constant: true,\n    inputs: [],\n    name: 'name',\n    outputs: [{ name: '', type: 'string' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [{ name: '_upgradedAddress', type: 'address' }],\n    name: 'deprecate',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [\n      { name: '_spender', type: 'address' },\n      { name: '_value', type: 'uint256' }\n    ],\n    name: 'approve',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [],\n    name: 'deprecated',\n    outputs: [{ name: '', type: 'bool' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [{ name: '_evilUser', type: 'address' }],\n    name: 'addBlackList',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [],\n    name: 'totalSupply',\n    outputs: [{ name: '', type: 'uint256' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [\n      { name: '_from', type: 'address' },\n      { name: '_to', type: 'address' },\n      { name: '_value', type: 'uint256' }\n    ],\n    name: 'transferFrom',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [],\n    name: 'upgradedAddress',\n    outputs: [{ name: '', type: 'address' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [{ name: '', type: 'address' }],\n    name: 'balances',\n    outputs: [{ name: '', type: 'uint256' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [],\n    name: 'decimals',\n    outputs: [{ name: '', type: 'uint256' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [],\n    name: 'maximumFee',\n    outputs: [{ name: '', type: 'uint256' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [],\n    name: '_totalSupply',\n    outputs: [{ name: '', type: 'uint256' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [],\n    name: 'unpause',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [{ name: '_maker', type: 'address' }],\n    name: 'getBlackListStatus',\n    outputs: [{ name: '', type: 'bool' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [\n      { name: '', type: 'address' },\n      { name: '', type: 'address' }\n    ],\n    name: 'allowed',\n    outputs: [{ name: '', type: 'uint256' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [],\n    name: 'paused',\n    outputs: [{ name: '', type: 'bool' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [{ name: 'who', type: 'address' }],\n    name: 'balanceOf',\n    outputs: [{ name: '', type: 'uint256' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [],\n    name: 'pause',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [],\n    name: 'getOwner',\n    outputs: [{ name: '', type: 'address' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [],\n    name: 'owner',\n    outputs: [{ name: '', type: 'address' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [],\n    name: 'symbol',\n    outputs: [{ name: '', type: 'string' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [\n      { name: '_to', type: 'address' },\n      { name: '_value', type: 'uint256' }\n    ],\n    name: 'transfer',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [\n      { name: 'newBasisPoints', type: 'uint256' },\n      { name: 'newMaxFee', type: 'uint256' }\n    ],\n    name: 'setParams',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [{ name: 'amount', type: 'uint256' }],\n    name: 'issue',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [{ name: 'amount', type: 'uint256' }],\n    name: 'redeem',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [\n      { name: '_owner', type: 'address' },\n      { name: '_spender', type: 'address' }\n    ],\n    name: 'allowance',\n    outputs: [{ name: 'remaining', type: 'uint256' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [],\n    name: 'basisPointsRate',\n    outputs: [{ name: '', type: 'uint256' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [{ name: '', type: 'address' }],\n    name: 'isBlackListed',\n    outputs: [{ name: '', type: 'bool' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [{ name: '_clearedUser', type: 'address' }],\n    name: 'removeBlackList',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    constant: true,\n    inputs: [],\n    name: 'MAX_UINT',\n    outputs: [{ name: '', type: 'uint256' }],\n    payable: false,\n    stateMutability: 'view',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [{ name: 'newOwner', type: 'address' }],\n    name: 'transferOwnership',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    constant: false,\n    inputs: [{ name: '_blackListedUser', type: 'address' }],\n    name: 'destroyBlackFunds',\n    outputs: [],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'function'\n  },\n  {\n    inputs: [\n      { name: '_initialSupply', type: 'uint256' },\n      { name: '_name', type: 'string' },\n      { name: '_symbol', type: 'string' },\n      { name: '_decimals', type: 'uint256' }\n    ],\n    payable: false,\n    stateMutability: 'nonpayable',\n    type: 'constructor'\n  },\n  {\n    anonymous: false,\n    inputs: [{ indexed: false, name: 'amount', type: 'uint256' }],\n    name: 'Issue',\n    type: 'event'\n  },\n  {\n    anonymous: false,\n    inputs: [{ indexed: false, name: 'amount', type: 'uint256' }],\n    name: 'Redeem',\n    type: 'event'\n  },\n  {\n    anonymous: false,\n    inputs: [{ indexed: false, name: 'newAddress', type: 'address' }],\n    name: 'Deprecate',\n    type: 'event'\n  },\n  {\n    anonymous: false,\n    inputs: [\n      { indexed: false, name: 'feeBasisPoints', type: 'uint256' },\n      { indexed: false, name: 'maxFee', type: 'uint256' }\n    ],\n    name: 'Params',\n    type: 'event'\n  },\n  {\n    anonymous: false,\n    inputs: [\n      { indexed: false, name: '_blackListedUser', type: 'address' },\n      { indexed: false, name: '_balance', type: 'uint256' }\n    ],\n    name: 'DestroyedBlackFunds',\n    type: 'event'\n  },\n  {\n    anonymous: false,\n    inputs: [{ indexed: false, name: '_user', type: 'address' }],\n    name: 'AddedBlackList',\n    type: 'event'\n  },\n  {\n    anonymous: false,\n    inputs: [{ indexed: false, name: '_user', type: 'address' }],\n    name: 'RemovedBlackList',\n    type: 'event'\n  },\n  {\n    anonymous: false,\n    inputs: [\n      { indexed: true, name: 'owner', type: 'address' },\n      { indexed: true, name: 'spender', type: 'address' },\n      { indexed: false, name: 'value', type: 'uint256' }\n    ],\n    name: 'Approval',\n    type: 'event'\n  },\n  {\n    anonymous: false,\n    inputs: [\n      { indexed: true, name: 'from', type: 'address' },\n      { indexed: true, name: 'to', type: 'address' },\n      { indexed: false, name: 'value', type: 'uint256' }\n    ],\n    name: 'Transfer',\n    type: 'event'\n  },\n  { anonymous: false, inputs: [], name: 'Pause', type: 'event' },\n  { anonymous: false, inputs: [], name: 'Unpause', type: 'event' }\n];\n\n/**\n * Returns the token `name` and `symbol` from a given ERC-20 contract address\n * @param web3\n * @param tokenAddress\n * @param method\n */\nconst getTokenInfo = async (web3, tokenAddress) => {\n  return await snapshot.utils.multicall(\n    web3._network.chainId.toString(),\n    web3,\n    erc20Abi,\n    [\n      [tokenAddress, 'name'],\n      [tokenAddress, 'symbol']\n    ]\n  );\n};\n\nexport default class Plugin {\n  public author = 'davidalbela';\n  public version = '0.0.1';\n  public name = 'Gnosis Impact';\n  public website = 'https://gnosis.io';\n  public options: any;\n\n  async getTokenInfo(web3: any, tokenAddress: string) {\n    try {\n      const tokenInfo = await getTokenInfo(web3, tokenAddress);\n      return {\n        address: tokenAddress,\n        checksumAddress: getAddress(tokenAddress),\n        name: tokenInfo[0][0],\n        symbol: tokenInfo[1][0]\n      };\n    } catch (e: any) {\n      throw new Error(e);\n    }\n  }\n\n  async getOmenCondition(network: string, conditionId: any) {\n    try {\n      const query = OMEN_GQL_QUERY;\n      query.condition.__args.id = conditionId;\n      return await snapshot.utils.subgraphRequest(\n        OMEN_SUBGRAPH_URL[network],\n        query\n      );\n    } catch (e) {\n      console.error(e);\n    }\n  }\n\n  async getUniswapPair(network: string, token0: any, token1: any) {\n    try {\n      const query = UNISWAP_V2_GQL_QUERY;\n      query.pairsTokens.__args.where = {\n        token0: token0.toLowerCase(),\n        token1: token1.toLowerCase()\n      };\n      query.pairsTokensInverted.__args.where = {\n        token0: token1.toLowerCase(),\n        token1: token0.toLowerCase()\n      };\n      query.pairsTokens0.__args.where = {\n        token0: token0.toLowerCase(),\n        token1: WETH_ADDRESS[network]\n      };\n      query.pairsTokens1.__args.where = {\n        token0: token1.toLowerCase(),\n        token1: WETH_ADDRESS[network]\n      };\n      const result = await snapshot.utils.subgraphRequest(\n        UNISWAP_V2_SUBGRAPH_URL[network],\n        query\n      );\n\n      if (result.pairsTokens.length > 0) {\n        return result.pairsTokens[0];\n      } else if (result.pairsTokensInverted.length > 0) {\n        return {\n          token0Price: result.pairsTokensInverted[0].token1Price\n        };\n      } else if (\n        result.pairsTokens0.length > 0 &&\n        result.pairsTokens1.length > 0\n      ) {\n        return {\n          token0Price: (\n            parseFloat(result.pairsTokens0[0].token0Price) /\n            parseFloat(result.pairsTokens1[0].token0Price)\n          ).toString()\n        };\n      }\n      throw new Error(\n        `Does not exist market pairs for ${token0} and ${token1}.`\n      );\n    } catch (e) {\n      console.error(e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/plugins/gnosis/plugin.json",
    "content": "{\n  \"name\": \"Gnosis Impact\",\n  \"version\": \"0.0.1\",\n  \"author\": \"davidalbela\",\n  \"website\": \"https://...\",\n  \"icon\": \"ipfs://QmPhmL1jPjaYKeKeEzyetAueNHCsGgrUVqSJasiTrcPWvx\",\n  \"defaults\": {\n    \"space\": {},\n    \"proposal\": {}\n  }\n}\n"
  },
  {
    "path": "src/plugins/oSnap/Create.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport { cloneDeep } from 'lodash';\nimport {\n  GnosisSafe,\n  Network,\n  OsnapPluginData,\n  Transaction,\n  nonNullable\n} from './types';\nimport { getIsOsnapEnabled, getModuleAddressForTreasury } from './utils';\nimport CreateSafe from './CreateSafe.vue';\nimport { toChecksumAddress } from '@/helpers/utils';\nimport OsnapMarketingWidget from './components/OsnapMarketingWidget.vue';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n}>();\n\n// we need to disable using the new plugin if the old one is still installed\nconst hasLegacyPluginInstalled = 'safeSnap' in props.space.plugins;\n\nconst isLoading = ref(false);\n\n// emits an event with the shape expected by the parent of the plugin component\nconst emit = defineEmits<{\n  update: [value: { key: 'oSnap'; form: OsnapPluginData }];\n}>();\n\nconst newPluginData = ref<OsnapPluginData>({\n  safes: null\n});\n\nconst updateSafes = (safes: GnosisSafe[]) => {\n  newPluginData.value = { safes };\n  emit('update', { key: 'oSnap', form: newPluginData.value });\n};\n\nconst allSafes = ref<GnosisSafe[]>([]);\nconst configuredSafes = computed(() => newPluginData.value.safes ?? []);\n\nconst unconfiguredSafes = computed<GnosisSafe[]>(() => {\n  if (newPluginData.value?.safes?.length && configuredSafes.value.length) {\n    return allSafes.value.filter(safe =>\n      !!findSafe(safe, configuredSafes.value) ? false : true\n    );\n  }\n  return allSafes.value;\n});\n\nfunction safeEqual(safe1: GnosisSafe, safe2: GnosisSafe): boolean {\n  return (\n    safe1.safeAddress === safe2.safeAddress && safe1.network === safe1.network\n  );\n}\n\nfunction findSafe(safeToFind: GnosisSafe, from: GnosisSafe[]) {\n  return from.length\n    ? from.find(safe => safeEqual(safe, safeToFind))\n    : undefined;\n}\n\n// maps over the treasuries and creates a safe for each one\n// only returns safes that have oSnap enabled\nasync function createOsnapEnabledSafes() {\n  const treasuryPromises = await Promise.allSettled(\n    props.space.treasuries.map(async treasury => {\n      const isOsnapEnabled = await getIsOsnapEnabled(\n        treasury.network as Network,\n        treasury.address\n      );\n      return isOsnapEnabled ? treasury : null;\n    })\n  );\n\n  const treasuriesWithOsnapEnabled = treasuryPromises\n    .map(res => (res.status === 'fulfilled' ? res.value : null))\n    .filter(nonNullable);\n\n  const safePromises = await Promise.allSettled(\n    treasuriesWithOsnapEnabled.map(async treasury => {\n      const moduleAddress = await getModuleAddressForTreasury(\n        treasury.network as Network,\n        treasury.address\n      );\n      return moduleAddress\n        ? {\n            safeName: treasury.name,\n            safeAddress: toChecksumAddress(treasury.address),\n            network: treasury.network as Network,\n            transactions: [] as Transaction[],\n            moduleAddress\n          }\n        : null;\n    })\n  );\n\n  const safes = safePromises\n    .map(res => (res.status === 'fulfilled' ? res.value : null))\n    .filter(nonNullable);\n\n  return safes;\n}\n\nfunction addSafeToConfigure(safe: GnosisSafe) {\n  updateSafes([...(newPluginData.value.safes ?? []), safe]);\n}\n\nfunction addNewSafe() {\n  addSafeToConfigure(unconfiguredSafes.value[0]);\n}\n\nfunction removeSafe(safeIndex: number) {\n  const copy = [...configuredSafes.value];\n  copy.splice(safeIndex, 1);\n  updateSafes(copy);\n}\n\nfunction updateSafe(safe: GnosisSafe, safeIndex: number) {\n  const copy = [...configuredSafes.value];\n  copy[safeIndex] = safe;\n  updateSafes(copy);\n}\n\nonMounted(async () => {\n  isLoading.value = true;\n  allSafes.value = await createOsnapEnabledSafes();\n  const initialSafe = cloneDeep(allSafes.value[0]);\n  updateSafes([initialSafe]);\n  isLoading.value = false;\n});\n</script>\n\n<template>\n  <div class=\"rounded-2xl border border-skin-border relative\">\n    <div v-if=\"!space.treasuries.length\" class=\"rounded-2xl border p-4 text-md\">\n      <h2>Warning: no treasuries</h2>\n      <p>\n        You have installed the oSnap plugin, but you don't have any treasuries.\n      </p>\n      <p>\n        Please add a Safe as a treasury and enable oSnap on it to use the oSnap\n        plugin.\n      </p>\n    </div>\n    <div\n      v-else-if=\"hasLegacyPluginInstalled\"\n      class=\"rounded-2xl border p-4 text-md\"\n    >\n      <h2 class=\"mb-2\">Warning: Multiple oSnap enabled plugins detected</h2>\n      <p class=\"mb-2\">\n        For best experience using oSnap, please remove the SafeSnap plugin from\n        your space.\n      </p>\n    </div>\n\n    <div v-else-if=\"isLoading\" class=\"grid min-h-[180px] place-items-center\">\n      <h2 class=\"text-center\">\n        Loading oSnap Safes <LoadingSpinner class=\"ml-2 inline\" big />\n      </h2>\n    </div>\n    <div v-else-if=\"!allSafes.length\">\n      <h2>Warning: no oSnap safes found</h2>\n      <p>\n        You have installed the oSnap plugin, but you don't have any oSnap safes.\n      </p>\n      <p>\n        Please add a Safe as a treasury and enable oSnap on it to use the oSnap\n        plugin.\n      </p>\n    </div>\n    <div\n      v-else-if=\"!newPluginData.safes?.length\"\n      class=\"rounded-2xl border p-4 text-md\"\n    >\n      <h4>Add treasuries to start building</h4>\n    </div>\n    <template v-else>\n      <CreateSafe\n        v-for=\"(safe, i) in newPluginData.safes\"\n        :class=\"{ 'border-t border-skin-border': i !== 0 }\"\n        :key=\"`${safe.network}:${safe.safeAddress}`\"\n        :safe=\"safe\"\n        :all-safes=\"allSafes\"\n        :unconfigured-safes=\"unconfiguredSafes\"\n        @remove-safe=\"() => removeSafe(i)\"\n        @update-safe=\"safe => updateSafe(safe, i)\"\n      />\n    </template>\n    <OsnapMarketingWidget class=\"absolute z-2 top-[-16px] right-[16px]\" />\n  </div>\n  <TuneButton\n    v-if=\"unconfiguredSafes.length\"\n    class=\"mt-4 w-full\"\n    @click=\"addNewSafe\"\n    >Add treasury +</TuneButton\n  >\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/CreateSafe.vue",
    "content": "<script setup lang=\"ts\">\nimport { GnosisSafe, NFT, Token, Transaction } from './types';\nimport { fetchBalances, fetchCollectibles } from './utils';\nimport { cloneDeep } from 'lodash';\nimport SelectSafe from './components/Input/SelectSafe.vue';\nimport TransactionBuilder from './components/TransactionBuilder/TransactionBuilder.vue';\nimport BotSupportWarning from './components/BotSupportWarning.vue';\n\n// PROPS\nconst props = defineProps<{\n  allSafes: GnosisSafe[];\n  unconfiguredSafes: GnosisSafe[];\n  safe: GnosisSafe;\n}>();\n\n// VARS\nconst configuredSafe = ref<GnosisSafe>();\nconst tokens = ref<Token[]>([]);\nconst collectibles = ref<NFT[]>([]);\nconst isLoading = ref(false);\n\n// EMITS\n\n// emits an event with the shape expected by the parent of the plugin component\nconst emit = defineEmits<{\n  updateSafe: [value: GnosisSafe];\n  addSafe: [value: GnosisSafe];\n  removeSafe: [];\n}>();\n\n// METHODS\nfunction update(newlyConfiguredSafe: GnosisSafe) {\n  emit('updateSafe', newlyConfiguredSafe);\n}\n\n// METHODS\nfunction removeSafe() {\n  if (configuredSafe.value) {\n    emit('removeSafe');\n  }\n}\n\n// when changing safes, we create a whole new object and replace it\nfunction replaceSafe(safe: GnosisSafe | null) {\n  if (!safe) return;\n  configuredSafe.value = cloneDeep(safe);\n  update(configuredSafe.value);\n}\n\nfunction addTransaction(transaction: Transaction) {\n  if (!configuredSafe.value) return;\n  configuredSafe.value.transactions.push(transaction);\n}\n\nfunction removeTransaction(transactionIndex: number) {\n  if (!configuredSafe.value) return;\n  configuredSafe.value.transactions.splice(transactionIndex, 1);\n}\n\nfunction updateTransaction(transaction: Transaction, transactionIndex: number) {\n  if (!configuredSafe.value) return;\n  configuredSafe.value.transactions[transactionIndex] = transaction;\n}\n\n// fetch tokens and nfts for selected safe\nasync function loadBalancesAndCollectibles() {\n  if (!configuredSafe.value?.safeAddress) return;\n  isLoading.value = true;\n  tokens.value = await fetchBalances(\n    configuredSafe.value.network,\n    configuredSafe.value.safeAddress\n  );\n  collectibles.value = await fetchCollectibles(\n    configuredSafe.value.network,\n    configuredSafe.value.safeAddress\n  );\n  isLoading.value = false;\n}\n\nasync function loadBalancesAndUpdate() {\n  isLoading.value = true;\n  // fetch tokens and nfts\n  await loadBalancesAndCollectibles();\n  isLoading.value = false;\n\n  if (configuredSafe.value) {\n    update(configuredSafe.value);\n  }\n}\n\nwatch(\n  () => [configuredSafe.value?.safeAddress, configuredSafe.value?.network],\n  loadBalancesAndUpdate\n);\n\nonMounted(async () => {\n  // take the first safe available\n  configuredSafe.value = cloneDeep(props.safe);\n  loadBalancesAndUpdate();\n});\n</script>\n\n<template>\n  <div class=\"flex p-4 flex-col gap-0 relative pb-4\">\n    <button class=\"text-red ml-auto\" @click=\"removeSafe\">\n      Remove treasury\n    </button>\n    <h2 class=\"text-md\">Add oSnap transactions</h2>\n    <h3 class=\"text-base\">Pick a safe</h3>\n    <SelectSafe\n      :safes=\"[...unconfiguredSafes, configuredSafe ?? props.safe]\"\n      :selectedSafe=\"configuredSafe ?? props.safe\"\n      @updateSafe=\"replaceSafe($event)\"\n    />\n    <BotSupportWarning\n      v-if=\"configuredSafe\"\n      :safe-address=\"configuredSafe.safeAddress\"\n      :chain-id=\"configuredSafe.network\"\n    />\n    <div class=\"mt-4 border-b last:border-b-0\">\n      <TransactionBuilder\n        v-if=\"!!configuredSafe\"\n        :safe=\"configuredSafe\"\n        :tokens=\"tokens\"\n        :collectibles=\"collectibles\"\n        @add-transaction=\"addTransaction\"\n        @remove-transaction=\"removeTransaction\"\n        @update-transaction=\"updateTransaction\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/Proposal.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace, Proposal, Results } from '@/helpers/interfaces';\nimport { isBigNumberish } from '@ethersproject/bignumber/lib/bignumber';\nimport { formatEther, formatUnits } from '@ethersproject/units';\nimport HandleOutcome from './components/HandleOutcome/HandleOutcome.vue';\nimport ReadOnly from './components/Input/ReadOnly.vue';\nimport SafeLinkWithAvatar from './components/SafeLinkWithAvatar.vue';\nimport { GnosisSafe, Transaction, isLegacySingleSafe } from './types';\nimport OsnapMarketingWidget from './components/OsnapMarketingWidget.vue';\nimport TenderlySimulation from './components/TransactionBuilder/TenderlySimulation.vue';\nimport BotSupportWarning from './components/BotSupportWarning.vue';\n\nconst keyOrder = [\n  'to',\n  'recipient',\n  'amount',\n  'value',\n  'token symbol',\n  'token address'\n];\n\nconst objectFromEntriesSorted = (obj: Object) => {\n  // set as array first to the correct preserve order\n  let entries = Object.entries(obj);\n  let sorted: Array<[string, any]> = [];\n\n  keyOrder.forEach(key => {\n    if (obj.hasOwnProperty(key)) {\n      sorted.push([key, obj[key]]);\n      entries = entries.filter(item => item[0] !== key);\n    }\n  });\n  // ensure we don't filter out any items we didn't explicitly sort\n  return [...sorted, ...entries];\n};\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n  results: Results;\n}>();\n\nconst safes = computed<GnosisSafe[]>(() => {\n  if (isLegacySingleSafe(props.proposal.plugins.oSnap)) {\n    return [props.proposal.plugins.oSnap.safe] as GnosisSafe[];\n  }\n  return props.proposal.plugins.oSnap.safes as GnosisSafe[];\n});\n\nfunction enrichTransactionsForDisplay(transactions: Transaction[]) {\n  // handle here\n  return transactions.map(enrichTransactionForDisplay);\n}\n\nfunction enrichTransactionForDisplay(transaction: Transaction) {\n  const { to, value, data } = transaction;\n  const commonProperties = { to, value: formatEther(value), data };\n  if (transaction.type === 'raw') {\n    return { ...commonProperties, type: 'Raw' };\n  }\n  if (transaction.type === 'contractInteraction') {\n    const { method, parameters } = transaction;\n    return {\n      ...commonProperties,\n      type: 'Contract interaction',\n      'method name': method.name,\n      ...Object.fromEntries(\n        method.inputs.map((input, i) => {\n          return [`${input.name} (param ${i + 1}): `, parameters[i]];\n        })\n      )\n    };\n  }\n  if (transaction.type === 'transferFunds') {\n    const { token, amount: unformattedAmount } = transaction;\n    const amount =\n      isBigNumberish(unformattedAmount) && !!token?.decimals\n        ? formatUnits(unformattedAmount, token.decimals)\n        : unformattedAmount;\n    return {\n      ...commonProperties,\n      type: 'Transfer funds',\n      'token address':\n        token?.address === 'main' ? 'native token' : token?.address,\n      'token symbol': token?.symbol,\n      recipient: transaction.recipient,\n      amount\n    };\n  }\n  if (transaction.type === 'transferNFT') {\n    const { recipient, collectable } = transaction;\n    return {\n      ...commonProperties,\n      type: 'Transfer NFT',\n      recipient,\n      collectable: `${collectable?.tokenName} #${collectable?.id}`,\n      'collectible address': collectable?.address\n    };\n  }\n  return { ...commonProperties, type: 'Raw' };\n}\n</script>\n\n<template>\n  <div v-if=\"safes.length > 0\" class=\"flex flex-col gap-4\">\n    <template v-for=\"safe in safes\">\n      <div\n        class=\"flex w-full flex-col gap-4 rounded-2xl border border-skin-border p-3 relative\"\n      >\n        <OsnapMarketingWidget class=\"absolute top-[-16px] right-[16px]\" />\n        <h2 class=\"text-lg\">oSnap Transactions</h2>\n        <div class=\"flex flex-col items-center gap-3 md:flex-row\">\n          <SafeLinkWithAvatar :safe=\"safe\" />\n        </div>\n\n        <BotSupportWarning\n          :chain-id=\"safe.network\"\n          :safe-address=\"safe.safeAddress\"\n        />\n\n        <div class=\"divider mx-auto h-[1px] w-full bg-skin-border\" />\n        <div\n          v-for=\"({ type, ...details }, index) in enrichTransactionsForDisplay(\n            safe.transactions\n          )\"\n          class=\"flex flex-col gap-2\"\n        >\n          <h4 class=\"mb-2\">Transaction #{{ index + 1 }} — {{ type }}</h4>\n\n          <ReadOnly v-for=\"[key, value] in objectFromEntriesSorted(details)\">\n            <strong\n              class=\"mr-2 inline-block whitespace-nowrap first-letter:capitalize\"\n              >{{ key }}</strong\n            >\n            <span class=\"break-all\">{{ value }}</span>\n          </ReadOnly>\n        </div>\n\n        <TenderlySimulation\n          :transactions=\"safe.transactions\"\n          :safe=\"safe\"\n          :network=\"safe.network\"\n        />\n\n        <HandleOutcome\n          v-if=\"!!results\"\n          :space=\"space\"\n          :proposal=\"proposal\"\n          :transactions=\"safe.transactions\"\n          :results=\"results\"\n          :module-address=\"safe.moduleAddress\"\n          :network=\"safe.network\"\n        />\n      </div>\n    </template>\n  </div>\n  <template v-else>\n    <p>There are no transactions associated with this proposal.</p>\n  </template>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/README.md",
    "content": "# oSnap Snapshot Plugin\n\nThis is a Snapshot plugin that facilitates using the Optimistic Governor to execute a set of transactions.\n\nSee https://docs.snapshot.org/user-guides/plugins for general info about Snapshot plugin development.\n\n## Terms\n\nThere are some terms that can be confusing in this plugin, because they are used to mean different things in different contexts.\n\n* Proposal — in the context of a normal Snapshot vote, regardless of plugins, \"Proposal\" refers to the set of questions that gets submitted to Snapshot and presented to voters. In the context of the Optimistic Governor, \"Proposal\" refers to the set of transactions that are submitted to the Optimistic Governor contract to be executed. This can be confusing because a Snapshot \"Proposal\" that uses the oSnap plugin will itself have an Optimistic Governor \"Proposal\" for the transactions that it aims to execute. As far as possible we have prefixed Optimistic Governor proposals with \"OG\" in the code to avoid confusion.\n\n* Assertion — Optimistic Oracle V3 calls a piece of information that is asserted as true an \"assertion\". In the context of the Optimistic Governor, an assertion is made on the Optimistic Oracle which states that the specified set of transactions is valid and should be executed. Some key information about the Optimistic Governor proposal can only be found in the context of assertions, such as the assertion transaction hash and log index which are used to generate links to the Optimistic Oracle UI.\n\n* SafeSnap and oSnap — oSnap was originally part of the SafeSnap plugin, hence the similar names. SafeSnap uses the Reality oracle, while oSnap provides the option to use the Optimistic Oracle instead. Eventually it was decided that oSnap deserves to be its own plugin, and so it was split off from SafeSnap. However, for legacy support reasons, the oSnap functionality in the SafeSnap plugin is still available.\n\n* Votes — both Snapshot and the Optimistic Oracle use votes for their function. The _Snapshot_ vote takes place first in the context of oSnap. When creating a Snapshot Proposal with oSnap, the Snapshot vote takes place first. Only if the Snapshot vote passes can transactions be proposed to the Optimistic Governor. In fact, if the assertion for an Optimistic Governor proposal is disputed, the Optimistic Governor proposal is immediately deleted by the contract. This means that the Optimistic Oracle vote that proceeds from the dispute has no bearing on the Optimistic Governor proposal. The Optimistic Oracle vote is only used to determine whether the disputer or the proposer loses their bond."
  },
  {
    "path": "src/plugins/oSnap/components/BotSupportWarning.vue",
    "content": "<script setup lang=\"ts\">\nimport { Network, SpaceConfigResponse } from '../types';\nimport { onMounted } from 'vue';\nimport { isConfigCompliant } from '../utils';\nimport BasePopoverHover from '../../../components/BasePopoverHover.vue';\n\nconst props = defineProps<{\n  safeAddress: string;\n  chainId: Network;\n}>();\n\nconst configStatus = ref<SpaceConfigResponse>();\n\nasync function getConfigStatus() {\n  try {\n    const res = await isConfigCompliant(props.safeAddress, props.chainId);\n    configStatus.value = res;\n  } catch (error) {\n    console.error(error);\n  }\n}\n\nonMounted(() => getConfigStatus());\nwatch([props.chainId, props.safeAddress], getConfigStatus);\n</script>\n\n<template>\n  <div v-if=\"configStatus\">\n    <BasePopoverHover v-if=\"configStatus.automaticExecution\">\n      <template v-slot:button>\n        <span class=\"text-green\">\n          <BaseIcon name=\"check\" :size=\"'14'\" class=\"mr-1\" /> Automatic\n          Execution\n        </span>\n      </template>\n      <template v-slot:content>\n        <div class=\"p-3 text-green text-sm\">\n          <p>\n            You are using the default settings. If your proposal passes, your\n            transaction will be\n            <strong>automatically executed</strong> and verified by the UMA\n            Optimistic Oracle.\n          </p>\n        </div>\n      </template>\n    </BasePopoverHover>\n\n    <BasePopoverHover v-else>\n      <template v-slot:button>\n        <span class=\"text-red\">\n          <BaseIcon name=\"warning\" :size=\"'14'\" class=\"mr-1\" /> Automatic\n          Execution\n        </span>\n      </template>\n      <template v-slot:content>\n        <div class=\"p-3 text-red text-sm\">\n          <p>\n            You are <strong>not</strong> using the default settings. If your\n            proposal passes, you will be required to <strong>manually</strong>\n            request transaction execution and post a bond to the UMA Optimistic\n            Oracle for verification.\n          </p>\n          <p class=\"mt-2\">Reasons:</p>\n          <ul class=\"pl-4 [&>li]:list-disc\">\n            <li v-if=\"!configStatus.bondAmount\">Bond Amount (Should be 2)</li>\n            <li v-if=\"!configStatus.bondToken\">Bond Token (Should be WETH)</li>\n            <li v-if=\"!configStatus.rules\">\n              Space URL (only snapshot.org <strong>production</strong> spaces\n              are supported with automated execution. There is no bot support\n              for testnets.)\n            </li>\n          </ul>\n        </div>\n      </template>\n    </BasePopoverHover>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/ExternalLink.vue",
    "content": "<script setup lang=\"ts\">\nimport { sanitizeUrl } from '@braintree/sanitize-url';\n\ntype Link = Record<string, any> | string;\n\ndefineProps<{\n  link: Link;\n  hideIcon?: boolean;\n  disabled?: boolean;\n}>();\n</script>\n\n<template>\n  <a\n    v-if=\"typeof link === 'string'\"\n    :href=\"sanitizeUrl(link)\"\n    target=\"_blank\"\n    :class=\"[\n      'inline-flex items-center gap-2 whitespace-nowrap rounded-3xl border border-skin-border bg-skin-bg px-4 font-semibold first-letter:capitalize leading-[42px] hover:border-skin-text',\n      { 'pointer-events-none': disabled }\n    ]\"\n    rel=\"noopener noreferrer\"\n  >\n    <slot />\n    <i-ho-external-link v-if=\"!hideIcon\" class=\"ml-auto text-sm\" />\n  </a>\n  <router-link\n    v-else\n    :to=\"link\"\n    :class=\"['whitespace-nowrap', { 'pointer-events-none': disabled }]\"\n  >\n    <slot />\n    <i-ho-external-link v-if=\"!hideIcon\" class=\"ml-auto text-sm\" />\n  </router-link>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/HandleOutcome/HandleOutcome.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace, Proposal, Results } from '@/helpers/interfaces';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { StaticJsonRpcProvider } from '@ethersproject/providers';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { sleep } from '@snapshot-labs/snapshot.js/src/utils';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\nimport {\n  CollateralDetails,\n  Network,\n  OGModuleDetails,\n  OGProposalState,\n  Transaction\n} from '../../types';\nimport {\n  approveBond,\n  executeProposal,\n  getCollateralDetailsForProposal,\n  getOGModuleDetails,\n  getOGProposalState,\n  getUserCollateralAllowance,\n  getUserCollateralBalance,\n  submitProposal\n} from '../../utils';\nimport CanRequestTxExecution from './steps/CanRequestTxExecution.vue';\nimport InOOChallengePeriod from './steps/InOOChallengePeriod.vue';\nimport CanProposeToOG from './steps/CanProposeToOG.vue';\nimport RejectedBySnapshotVote from './steps/RejectedBySnapshotVote.vue';\nimport TallyingSnapshotVotes from './steps/TallyingSnapshotVotes.vue';\nimport TransactionsExecuted from './steps/TransactionsExecuted.vue';\n\ndeclare global {\n  interface Window {\n    ethereum: any;\n  }\n}\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n  results: Results;\n  transactions: Transaction[];\n  network: Network;\n  moduleAddress: string;\n}>();\n\nconst { web3 } = useWeb3();\nconst isLoading = ref(true);\nconst hasConnectedWallet = computed(() => !!web3.value.account);\nconst provider: StaticJsonRpcProvider = getProvider(props.network);\nconst ogModuleDetails = ref<OGModuleDetails>();\nconst oGProposalState = ref<OGProposalState>();\nconst collateralDetails = ref<CollateralDetails>();\nconst userCollateralBalance = ref<BigNumber>();\nconst userCollateralAllowance = ref<BigNumber>();\n\nconst hasSufficientAllowance = computed(() => {\n  if (\n    isLoading.value ||\n    !hasConnectedWallet.value ||\n    collateralDetails.value === undefined ||\n    ogModuleDetails.value === undefined ||\n    userCollateralAllowance.value === undefined\n  )\n    return undefined;\n  return userCollateralAllowance.value.gte(ogModuleDetails.value.minimumBond);\n});\nconst hasSufficientBalance = computed(() => {\n  if (\n    isLoading.value ||\n    !hasConnectedWallet.value ||\n    collateralDetails.value === undefined ||\n    ogModuleDetails.value === undefined ||\n    userCollateralBalance.value === undefined\n  )\n    return undefined;\n  return userCollateralBalance.value.gte(ogModuleDetails.value.minimumBond);\n});\nconst { createPendingTransaction, removePendingTransaction } = useTxStatus();\nconst { notify } = useFlashNotification();\nconst { quorum } = useQuorum(props);\n\ntype TransactionExecutionState =\n  | { status: 'tallying-snapshot-votes' }\n  | { status: 'rejected-by-snapshot-vote' }\n  | OGProposalState;\n\nconst transactionExecutionState = computed<\n  TransactionExecutionState | undefined\n>(() => {\n  const proposalFinalized = wasProposalFinalized(props.proposal);\n\n  if (!proposalFinalized) return { status: 'tallying-snapshot-votes' };\n\n  const proposalPassed = didProposalPass(props.proposal);\n\n  if (!proposalPassed) return { status: 'rejected-by-snapshot-vote' };\n\n  return oGProposalState.value;\n});\n\nfunction getFormattedTransactions() {\n  return props.transactions.map(transaction => {\n    return transaction.formatted;\n  });\n}\n\nasync function updateOgProposalState() {\n  isLoading.value = true;\n  try {\n    if (ogModuleDetails.value) {\n      oGProposalState.value = await getOGProposalState({\n        moduleDetails: ogModuleDetails.value,\n        network: props.network,\n        explanation: props.proposal.ipfs,\n        transactions: getFormattedTransactions()\n      });\n    }\n  } catch (e) {\n    console.error('Error loading uma execution details', e);\n  } finally {\n    isLoading.value = false;\n  }\n}\n\nasync function onApproveBond() {\n  if (!collateralDetails.value || !ogModuleDetails.value) return;\n  let txPendingId: string | undefined;\n  try {\n    await ensureRightNetwork(props.network);\n    const approvingBond = approveBond(\n      getInstance().web3,\n      props.moduleAddress,\n      collateralDetails.value?.address,\n      ogModuleDetails.value?.minimumBond\n    );\n    const step = await approvingBond.next();\n    if (step.value) {\n      txPendingId = createPendingTransaction(step.value.hash);\n      await approvingBond.next();\n      await sleep(3e3);\n      notify('Successfully approved bond');\n      // update our knowledge of users approval\n      userCollateralAllowance.value = await getUserCollateralAllowance(\n        collateralDetails.value.erc20Contract,\n        web3.value.account,\n        props.moduleAddress\n      );\n      await updateOgProposalState();\n    }\n  } catch (e) {\n    console.error(e);\n    notify(['red', 'Failed to approve bond']);\n  } finally {\n    if (txPendingId) {\n      removePendingTransaction(txPendingId);\n    }\n  }\n}\n\nasync function onSubmitProposal() {\n  if (!getInstance().isAuthenticated.value) return;\n  let txPendingId: string | undefined;\n  try {\n    await ensureRightNetwork(props.network);\n    const proposalSubmission = submitProposal(\n      getInstance().web3,\n      props.moduleAddress,\n      props.proposal.ipfs,\n      getFormattedTransactions()\n    );\n    const step = await proposalSubmission.next();\n    if (step.value) {\n      txPendingId = createPendingTransaction(step.value.hash);\n      await proposalSubmission.next();\n      notify('Proposal submitted successfully');\n      await sleep(3e3);\n      await updateOgProposalState();\n    }\n  } catch (e) {\n    notify(['red', 'Failed to submit proposal']);\n    console.error(e);\n  } finally {\n    if (txPendingId) {\n      removePendingTransaction(txPendingId);\n    }\n  }\n}\n\nasync function onExecuteProposal() {\n  if (!getInstance().isAuthenticated.value) return;\n  let txPendingId: string | undefined;\n  try {\n    await ensureRightNetwork(props.network);\n  } catch (e) {\n    console.error(e);\n  }\n\n  try {\n    const executingProposal = executeProposal(\n      getInstance().web3,\n      props.moduleAddress,\n      getFormattedTransactions()\n    );\n    const step = await executingProposal.next();\n    if (step.value) {\n      txPendingId = createPendingTransaction(step.value.hash);\n      await executingProposal.next();\n      notify('Proposal executed successfully');\n      await sleep(3e3);\n      await updateOgProposalState();\n    }\n  } catch (err) {\n    notify(['red', 'Failed to execute proposal']);\n  } finally {\n    if (txPendingId) {\n      removePendingTransaction(txPendingId);\n    }\n  }\n}\n\nconst connectedToRightChain = computed(() => {\n  return Number(props.network) === Number(web3.value.network.chainId);\n});\n\nconst networkName = computed(() => {\n  return networks[props.network].name;\n});\n\nfunction didProposalPass(proposal: Proposal) {\n  // ensure the vote has ended\n  if (proposal.state !== 'closed') return false;\n  // ensure total votes are more than quorum\n  if (proposal.scores_total && proposal.scores_total < proposal.quorum)\n    return false;\n  const votes = Object.fromEntries(\n    proposal.choices.map((choice, i) => {\n      return [choice.toLowerCase(), proposal.scores[i] ?? 0];\n    })\n  );\n  // ensure there are more \"for\" votes than \"against\" votes\n  // abstain votes should not count against the proposal\n  return votes['for'] > (votes['against'] ?? 0);\n}\n\nfunction wasProposalFinalized(proposal: Proposal) {\n  return proposal.scores_state === 'final';\n}\n\nasync function ensureRightNetwork(chainId: Network) {\n  const chainIdInt = parseInt(chainId);\n  const connectedToChainId = getInstance().provider.value?.chainId;\n  if (connectedToChainId === chainIdInt) return; // already on right chain\n\n  if (!window.ethereum || !getInstance().provider.value?.isMetaMask) {\n    // we cannot switch automatically\n    throw new Error(\n      `Connected to wrong chain #${connectedToChainId}, required: #${chainId}`\n    );\n  }\n\n  const network = networks[chainId];\n  const chainIdHex = `0x${chainIdInt.toString(16)}`;\n\n  try {\n    // check if the chain to connect to is installed\n    await window.ethereum.request({\n      method: 'wallet_switchEthereumChain',\n      params: [{ chainId: chainIdHex }] // chainId must be in hexadecimal numbers\n    });\n  } catch (error) {\n    // This error code indicates that the chain has not been added to MetaMask. Let's add it.\n    // @ts-expect-error non-standard error type\n    if (error.code === 4902) {\n      try {\n        await window.ethereum.request({\n          method: 'wallet_addEthereumChain',\n          params: [\n            {\n              chainId: chainIdHex,\n              chainName: network.name,\n              rpcUrls: network.rpc,\n              blockExplorerUrls: [network.explorer.url]\n            }\n          ]\n        });\n      } catch (addError) {\n        console.error(addError);\n      }\n    }\n    console.error(error);\n  }\n\n  await sleep(1e3); // somehow the switch does not take immediate effect :/\n  if (window.ethereum.chainId !== chainIdHex) {\n    throw new Error(\n      `Could not switch to the right chain on MetaMask (required: ${chainIdHex}, active: ${window.ethereum.chainId})`\n    );\n  }\n}\n\nasync function loadActions() {\n  collateralDetails.value = await getCollateralDetailsForProposal(\n    provider,\n    props.moduleAddress\n  );\n  ogModuleDetails.value = await getOGModuleDetails({\n    provider,\n    network: props.network,\n    moduleAddress: props.moduleAddress,\n    transactions: getFormattedTransactions()\n  });\n  userCollateralBalance.value = await getUserCollateralBalance(\n    collateralDetails.value.erc20Contract,\n    web3.value.account\n  );\n  userCollateralAllowance.value = await getUserCollateralAllowance(\n    collateralDetails.value.erc20Contract,\n    web3.value.account,\n    props.moduleAddress\n  );\n  await updateOgProposalState();\n}\n\nonMounted(async () => {\n  loadActions();\n});\n</script>\n\n<template>\n  <div v-if=\"!hasConnectedWallet\" class=\"my-4\">\n    Connect your wallet to see execution details\n  </div>\n  <div v-if=\"!connectedToRightChain\" class=\"my-4\">\n    Switch your wallet to {{ networkName }} to request execution\n  </div>\n  <template v-if=\"hasConnectedWallet && connectedToRightChain\">\n    <div v-if=\"isLoading\" class=\"grid place-items-center\">\n      <LoadingSpinner />\n    </div>\n    <template\n      v-if=\"\n        !isLoading &&\n        transactionExecutionState !== undefined &&\n        ogModuleDetails !== undefined\n      \"\n    >\n      <div>\n        <h3>Request transaction execution</h3>\n        <div class=\"flex flex-col gap-2\">\n          <TallyingSnapshotVotes\n            v-if=\"\n              transactionExecutionState.status === 'tallying-snapshot-votes'\n            \"\n          />\n          <RejectedBySnapshotVote\n            v-if=\"\n              transactionExecutionState.status === 'rejected-by-snapshot-vote'\n            \"\n          />\n          <CanProposeToOG\n            v-if=\"\n              transactionExecutionState.status === 'can-propose-to-og' &&\n              !!collateralDetails &&\n              !!ogModuleDetails &&\n              !!userCollateralBalance &&\n              hasSufficientBalance !== undefined &&\n              hasSufficientAllowance !== undefined\n            \"\n            :has-sufficient-balance=\"hasSufficientBalance\"\n            :has-sufficient-allowance=\"hasSufficientAllowance\"\n            :minimum-bond=\"ogModuleDetails.minimumBond\"\n            :user-balance=\"userCollateralBalance\"\n            :decimals=\"collateralDetails.decimals\"\n            :symbol=\"collateralDetails.symbol\"\n            :challenge-period=\"\n              Number(ogModuleDetails.challengePeriod.toString())\n            \"\n            :quorum=\"quorum\"\n            :scores-total=\"proposal.scores_total\"\n            :is-disputed=\"transactionExecutionState.isDisputed\"\n            @submit-proposal=\"onSubmitProposal\"\n            @approve-bond=\"onApproveBond\"\n          />\n          <InOOChallengePeriod\n            v-if=\"transactionExecutionState.status === 'in-oo-challenge-period'\"\n            :network=\"network\"\n            :expiration-time=\"transactionExecutionState.expirationTime\"\n            :assertion-hash=\"transactionExecutionState.assertionHash\"\n            :assertion-log-index=\"transactionExecutionState.assertionLogIndex\"\n          />\n          <CanRequestTxExecution\n            v-if=\"\n              transactionExecutionState.status === 'can-request-tx-execution'\n            \"\n            :transaction-count=\"transactions.length\"\n            @execute-proposal=\"onExecuteProposal\"\n          />\n          <TransactionsExecuted\n            v-if=\"transactionExecutionState.status === 'transactions-executed'\"\n          />\n        </div>\n      </div>\n    </template>\n  </template>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/HandleOutcome/steps/CanProposeToOG.vue",
    "content": "<script setup lang=\"ts\">\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { formatUnits } from '@ethersproject/units';\nconst { formatDuration } = useIntl();\n\nconst props = defineProps<{\n  hasSufficientAllowance: boolean;\n  hasSufficientBalance: boolean;\n  isDisputed: boolean;\n  minimumBond: BigNumber;\n  userBalance: BigNumber;\n  decimals: BigNumber;\n  symbol: string;\n  challengePeriod: number;\n  quorum: number;\n  scoresTotal: number;\n}>();\n\nconst emit = defineEmits<{\n  approveBond: [event: void];\n  submitProposal: [event: void];\n}>();\n\nconst hasReachedQuorum = computed(() => props.scoresTotal >= props.quorum);\n</script>\n\n<template>\n  <div class=\"rounded-lg border p-3\">\n    <div>\n      <strong class=\"pr-3\">Required bond:</strong>\n      <span class=\"float-right text-skin-link\">\n        {{ formatUnits(minimumBond, decimals) }} {{ symbol }}\n      </span>\n    </div>\n    <div>\n      <strong class=\"pr-3\">Challenge period:</strong>\n      <span class=\"float-right text-skin-link\">\n        {{ formatDuration(challengePeriod) }}\n      </span>\n    </div>\n  </div>\n  <div>\n    <BaseMessage v-if=\"!hasReachedQuorum\" level=\"warning-red\">\n      Quorum did not reach space requirement\n    </BaseMessage>\n    <BaseMessage v-if=\"!hasSufficientBalance\" level=\"warning-red\">\n      Your balance is less than the required bond\n    </BaseMessage>\n  </div>\n  <div v-if=\"!hasSufficientAllowance\">\n    <p>\n      On-chain proposals require a bond from the proposer. This will approve\n      tokens from your wallet to be posted as a bond. If you make an invalid\n      proposal, it will be disputed and you will lose your bond. If the proposal\n      is valid, your bond will be returned when the transactions are executed.\n    </p>\n    <TuneButton\n      :disabled=\"!hasReachedQuorum || !hasSufficientBalance\"\n      @click=\"emit('approveBond')\"\n      class=\"mr-2 mt-4 w-full\"\n    >\n      Approve bond\n    </TuneButton>\n  </div>\n\n  <div v-else>\n    <p v-if=\"isDisputed\" class=\"mb-2\">\n      Warning: This proposal was disputed on-chain. Exercise caution when\n      proposing, because your proposal may be disputed too.\n    </p>\n    <TuneButton\n      @click=\"emit('submitProposal')\"\n      class=\"my-1 w-full\"\n      :disabled=\"!hasReachedQuorum || !hasSufficientBalance\"\n    >\n      Make assertion on Oracle\n    </TuneButton>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/HandleOutcome/steps/CanRequestTxExecution.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps<{\n  transactionCount: number;\n}>();\nconst emit = defineEmits<{\n  executeProposal: [event: void];\n}>();\n</script>\n\n<template>\n  <div>\n    <p>\n      This will execute the transactions from this proposal and return the\n      asserter's bond.\n    </p>\n    <TuneButton @click=\"emit('executeProposal')\" class=\"w-full\">\n      Execute {{ transactionCount }} transaction{{\n        transactionCount > 1 ? 's' : ''\n      }}\n    </TuneButton>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/HandleOutcome/steps/InOOChallengePeriod.vue",
    "content": "<script setup lang=\"ts\">\nimport { getOracleUiLink } from '@/plugins/oSnap/utils';\nimport { type Network } from '../../../types';\nconst props = defineProps<{\n  network: Network;\n  expirationTime: number;\n  assertionHash: string;\n  assertionLogIndex: string;\n}>();\n\nconst oracleUiLink = getOracleUiLink(\n  props.network,\n  props.assertionHash,\n  Number(props.assertionLogIndex)\n);\n\nconst expirationDateLocaleString = new Date(\n  props.expirationTime * 1000\n).toLocaleString();\n</script>\n\n<template>\n  <span>\n    Transactions can be executed at\n    <strong>{{ expirationDateLocaleString }}</strong>\n  </span>\n\n  <div>\n    <a :href=\"oracleUiLink\" rel=\"noreferrer noopener\" target=\"_blank\">\n      UMA Oracle URL to dispute\n      <span class=\"iconfont iconexternal-link\" />\n    </a>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/HandleOutcome/steps/RejectedBySnapshotVote.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div>\n    <p>\n      Transactions cannot be executed because the Snapshot vote resolved as\n      \"against\".\n    </p>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/HandleOutcome/steps/TallyingSnapshotVotes.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div>\n    <p>Waiting for Snapshot vote counting to conclude.</p>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/HandleOutcome/steps/TransactionsExecuted.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div>\n    <p>All transactions have been executed</p>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/Input/Address.vue",
    "content": "<script setup lang=\"ts\">\nimport { mustBeEthereumAddress } from '../../utils';\n\nconst props = defineProps<{\n  modelValue: string;\n  label: string;\n  error?: string;\n  disabled?: boolean;\n}>();\n\nconst emit = defineEmits<{\n  'update:modelValue': [value: string];\n}>();\n\nconst input = ref('');\nconst dirty = ref(false);\nconst error = ref('');\n\nconst validate = () => {\n  if (!dirty.value) {\n    error.value = '';\n    return;\n  }\n  if (input.value === '') {\n    error.value = 'Address is required';\n    return;\n  }\n\n  if (!mustBeEthereumAddress(input.value)) {\n    error.value = 'Invalid address';\n    return;\n  }\n  error.value = '';\n};\n\nwatch(input, validate);\n\nwatch(\n  () => props.modelValue,\n  value => {\n    input.value = value;\n  }\n);\n\nonMounted(() => {\n  if (props.modelValue) {\n    input.value = props.modelValue;\n  }\n});\n\nconst handleInput = () => {\n  emit('update:modelValue', input.value);\n};\n\nconst handleBlur = () => {\n  dirty.value = true;\n  validate();\n};\n</script>\n\n<template>\n  <UiInput\n    v-model=\"input\"\n    :disabled=\"disabled\"\n    placeholder=\"0x123...abc\"\n    :error=\"props.error ?? (error || '')\"\n    @input=\"handleInput()\"\n    @blur=\"handleBlur\"\n  >\n    <template v-if=\"label\" #label>{{ label }}</template>\n  </UiInput>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/Input/Amount.vue",
    "content": "<script setup lang=\"ts\">\nimport { formatUnits, parseUnits } from '@ethersproject/units';\nimport { amountPositive } from '../../utils';\n\nconst props = defineProps<{\n  modelValue: string;\n  label: string;\n  decimals: number | undefined;\n  enforcePositiveValue?: boolean;\n}>();\n\nconst emit = defineEmits<{\n  'update:modelValue': [value: string];\n}>();\n\nconst input = ref('0');\nconst isValid = ref(true);\nconst dirty = ref(false);\n\nconst format = (amount: string) => {\n  try {\n    // empty string throws\n    const parsed = parseUnits(amount, props.decimals).toString();\n    return parsed;\n  } catch {\n    return '0';\n  }\n};\n\nconst handleInput = () => {\n  dirty.value = true;\n  const value = format(input.value);\n  // empty string value will throw error being converted to BigNumber\n  emit('update:modelValue', value ?? '0');\n};\n\nonMounted(() => {\n  if (props.modelValue) {\n    input.value = formatUnits(props.modelValue, props.decimals);\n  }\n});\n\nwatch(input, () => {\n  const value = format(input.value);\n  const valid = !!value;\n  isValid.value = valid;\n  if (props.enforcePositiveValue) {\n    const isPositive = amountPositive(input.value, props.decimals);\n    isValid.value = isPositive;\n  }\n});\n</script>\n\n<template>\n  <UiInput\n    v-model=\"input\"\n    :error=\"dirty && !isValid && $t('safeSnap.invalidAmount')\"\n    @input=\"handleInput()\"\n  >\n    <template v-if=\"label\" #label>{{ label }}</template>\n  </UiInput>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/Input/FileInput/FileInput.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref, defineProps, watch } from 'vue';\nimport { getFilesFromEvent, isFileOfType } from './utils';\n\ntype InputState = 'IDLE' | 'INVALID_TYPE' | 'VALID' | 'INVALID_QUANTITY';\n\nconst props = defineProps<{\n  fileType: File['type'];\n  error?: string | undefined;\n  multiple?: boolean;\n  defaultLabel?: string;\n}>();\n\nconst emit = defineEmits<{\n  (event: 'update:file', file: File | null): void;\n  (event: 'update:fileInputState', state: InputState): void;\n}>();\nconst inputRef = ref<HTMLInputElement>();\nconst file = ref<File | null>();\nconst fileInputState = ref<InputState>('IDLE');\nconst isDropping = ref(false);\n\nconst isError = computed(() => {\n  return (\n    !!props.error ||\n    fileInputState.value === 'INVALID_TYPE' ||\n    fileInputState.value === 'INVALID_QUANTITY'\n  );\n});\n\nconst isAccepted = computed(() => {\n  return fileInputState.value === 'VALID' && !props?.error;\n});\n\nconst handleFileChange = (event: Event | DragEvent) => {\n  isDropping.value = false;\n  const _files = getFilesFromEvent(event);\n  if (!_files?.length) return;\n\n  // enforce single drop based on props\n  if (!props.multiple) {\n    if (_files?.length && _files?.length > 1) {\n      fileInputState.value = 'INVALID_QUANTITY';\n      file.value = null;\n      clearInputValue();\n      return;\n    }\n  }\n  const _file = _files[0];\n  if (!isFileOfType(_file, props.fileType)) {\n    fileInputState.value = 'INVALID_TYPE';\n    file.value = null;\n  } else {\n    file.value = _file;\n    fileInputState.value = 'VALID';\n  }\n  clearInputValue();\n};\n\nfunction clearInputValue() {\n  if (inputRef?.value) {\n    inputRef.value.value = '';\n  }\n}\n\nfunction toggleDropping() {\n  isDropping.value = !isDropping.value;\n}\n\nwatch(file, newFile => {\n  if (newFile) {\n    emit('update:file', newFile);\n  }\n});\n\nwatch(fileInputState, newState => {\n  emit('update:fileInputState', newState);\n});\n</script>\n\n<template>\n  <label\n    for=\"file_input\"\n    @dragenter.prevent=\"toggleDropping\"\n    @dragleave.prevent=\"toggleDropping\"\n    @dragover.prevent\n    @drop.prevent=\"handleFileChange($event)\"\n    class=\"my-2 w-full group hover:bg-transparent hover:border-skin-text hover:text-skin-link hover:cursor-pointer inline-block border-2 border-dashed py-2 px-4 rounded-xl\"\n    :class=\"{\n      'border-solid border-skin-text text-skin-link bg-transparent': isDropping,\n      'bg-red/10 border-red/50 text-red/80': isError,\n      'bg-green/10 border-green/50 text-green/80': isAccepted\n    }\"\n  >\n    <div\n      class=\"flex line-clamp-2 flex-col gap-1 items-center text-center justify-center\"\n    >\n      <i-ho-upload />\n      <span class=\"line-clamp-2\">\n        <template v-if=\"props.error\">{{ props.error }}</template>\n        <template v-else-if=\"fileInputState === 'INVALID_TYPE'\"\n          >File type must be <strong>{{ props.fileType }}</strong\n          >. Please choose another.</template\n        >\n        <template v-else-if=\"fileInputState === 'INVALID_QUANTITY'\"\n          >Drop only <strong class=\"underline\">one</strong> file at a time\n        </template>\n        <template v-else-if=\"fileInputState === 'VALID' && file\">{{\n          file.name\n        }}</template>\n        <template v-else=\"fileInputState === 'IDLE'\">{{\n          props.defaultLabel ?? 'Click to select file, or drag n drop'\n        }}</template>\n      </span>\n    </div>\n\n    <input\n      ref=\"inputRef\"\n      id=\"file_input\"\n      class=\"hidden\"\n      :accept=\"props.fileType\"\n      type=\"file\"\n      @change=\"handleFileChange\"\n    />\n  </label>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/Input/FileInput/utils.ts",
    "content": "export function isFileOfType(file: File, type: File['type']) {\n  return file.type === type;\n}\n\nexport function getFilesFromEvent(event: DragEvent | Event) {\n  let _files: FileList | undefined | null;\n\n  if (event instanceof DragEvent) {\n    _files = event.dataTransfer?.files;\n  }\n\n  if (event.target && event.target instanceof HTMLInputElement) {\n    _files = (event?.currentTarget as HTMLInputElement)?.files;\n  }\n  if (!_files) return;\n  return _files;\n}\n"
  },
  {
    "path": "src/plugins/oSnap/components/Input/MethodParameter/MethodParameter.vue",
    "content": "<script setup lang=\"ts\">\nimport { ParamType } from '@ethersproject/abi';\nimport { hexZeroPad } from '@ethersproject/bytes';\nimport { ref, computed, watch, onMounted } from 'vue';\nimport { extractTypes, getParamLabel, getParamPlaceholder } from './utils';\nimport { isBytesLikeSafe, validateSingleOrArray } from '@/plugins/oSnap/utils';\n\nconst props = defineProps<{\n  parameter: ParamType;\n  value: string;\n  validateOnMount?: boolean;\n}>();\n\nconst emit = defineEmits<{\n  updateParameterValue: [value: string];\n}>();\n\nconst isDirty = ref(false);\n\nconst inputType = computed(() => extractTypes(props.parameter));\n\nconst isBooleanInput = computed(\n  () => inputType.value.input === 'single' && inputType.value.type === 'bool'\n);\nconst isStringInput = computed(\n  () => inputType.value.input === 'single' && inputType.value.type === 'string'\n);\nconst isAddressInput = computed(\n  () => inputType.value.input === 'single' && inputType.value.type === 'address'\n);\nconst isNumberInput = computed(\n  () =>\n    inputType.value.input === 'single' && inputType.value.type.includes('int')\n);\nconst isBytesInput = computed(\n  () => inputType.value.input === 'single' && inputType.value.type === 'bytes'\n);\nconst isBytes32Input = computed(\n  () => inputType.value.input === 'single' && inputType.value.type === 'bytes32'\n);\nconst isArrayInput = computed(() => inputType.value.input !== 'single');\n\nconst label = computed(() => getParamLabel(props.parameter));\nconst placeholder = computed(() => getParamPlaceholder(props.parameter));\n\nconst newValue = ref(props.value);\n\nconst validationState = ref(true);\nconst isInputValid = computed(() => validationState.value);\n\nconst validationErrorMessage = ref<string>();\n\nconst errorMessageForDisplay = computed(() => {\n  if (!isInputValid.value) {\n    return validationErrorMessage.value\n      ? validationErrorMessage.value\n      : `Invalid ${props.parameter.baseType}`;\n  }\n});\n\nconst allowQuickFixForBytes32 = computed(() => {\n  if (errorMessageForDisplay?.value?.includes('short')) {\n    return true;\n  }\n  return false;\n});\n\nfunction validate() {\n  if (!isDirty.value) return true;\n  return validateSingleOrArray(newValue.value, inputType.value.type);\n}\n\nwatch(props.parameter, () => {\n  newValue.value = '';\n  isDirty.value = false;\n});\n\nwatch(newValue, () => {\n  const valid = validate();\n  if (valid) {\n    validationErrorMessage.value = undefined;\n  }\n  validationState.value = valid;\n  emit('updateParameterValue', newValue.value);\n});\n\n// provide better feedback/validation messages for bytes32 inputs\nwatch(newValue, value => {\n  if (isBytes32Input.value && !isArrayInput.value) {\n    const data = value?.slice(2) || '';\n\n    if (data.length < 64) {\n      const padded = hexZeroPad(value, 32);\n      if (isBytesLikeSafe(padded)) {\n        validationErrorMessage.value = 'bytes32 too short';\n        return;\n      }\n    }\n\n    if (data.length > 64) {\n      validationErrorMessage.value = 'Value too long';\n      return false;\n    }\n\n    validationErrorMessage.value = undefined;\n  }\n});\n\nfunction onChange(value: string) {\n  newValue.value = value;\n  isDirty.value = true;\n}\n\nfunction formatBytes32() {\n  if (isBytes32Input) {\n    newValue.value = hexZeroPad(newValue.value, 32);\n  }\n}\n\nonMounted(() => {\n  if (props.validateOnMount) {\n    isDirty.value = true;\n  }\n  validationState.value = validate();\n});\n</script>\n\n<template>\n  <UiInput\n    v-if=\"isArrayInput\"\n    :placeholder=\"placeholder\"\n    :error=\"errorMessageForDisplay\"\n    :model-value=\"value\"\n    @update:model-value=\"onChange($event)\"\n  >\n    <template #label>{{ label }}</template>\n  </UiInput>\n  <UiSelect\n    v-if=\"isBooleanInput\"\n    :model-value=\"value\"\n    @update:model-value=\"onChange($event)\"\n  >\n    <template #label>{{ label }}</template>\n    <option :value=\"true\">true</option>\n    <option :value=\"false\">false</option>\n  </UiSelect>\n\n  <AddressInput\n    v-if=\"isAddressInput\"\n    :label=\"label\"\n    :model-value=\"value\"\n    @update:model-value=\"onChange($event)\"\n  />\n\n  <UiInput\n    v-if=\"isNumberInput\"\n    :placeholder=\"placeholder\"\n    :error=\"errorMessageForDisplay\"\n    :model-value=\"value\"\n    @update:model-value=\"onChange($event)\"\n  >\n    <template #label>{{ label }}</template>\n  </UiInput>\n  <UiInput\n    v-if=\"isBytesInput\"\n    :placeholder=\"placeholder\"\n    :error=\"errorMessageForDisplay\"\n    :model-value=\"value\"\n    @update:model-value=\"onChange($event)\"\n  >\n    <template #label>{{ label }}</template>\n  </UiInput>\n  <UiInput\n    v-if=\"isBytes32Input\"\n    :placeholder=\"placeholder\"\n    :error=\"errorMessageForDisplay\"\n    :model-value=\"value\"\n    :quick-fix=\"allowQuickFixForBytes32 ? formatBytes32 : undefined\"\n    @blur=\"formatBytes32\"\n    @update:model-value=\"onChange($event)\"\n  >\n    <template #label>{{ label }}</template>\n  </UiInput>\n  <UiInput\n    v-if=\"isStringInput\"\n    :placeholder=\"placeholder\"\n    :model-value=\"value\"\n    @update:model-value=\"onChange($event)\"\n  >\n    <template #label>{{ label }}</template>\n  </UiInput>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/Input/MethodParameter/utils.ts",
    "content": "import { InputTypes } from '@/plugins/oSnap/types';\nimport { MaybeNestedArrays } from '@/plugins/oSnap/utils';\nimport { ParamType } from '@ethersproject/abi';\n\nexport function getTypes(param: ParamType): MaybeNestedArrays<InputTypes> {\n  if (param.baseType === 'array') {\n    if (param.components) {\n      return param.components.map(getTypes);\n    }\n    if (param.arrayChildren) {\n      return getTypes(param.arrayChildren);\n    }\n  }\n\n  if (param.baseType === 'tuple') {\n    return param.components.map(getTypes);\n  }\n\n  return param.baseType as InputTypes;\n}\n\nexport function extractTypes(param: ParamType) {\n  if (param.baseType === 'tuple') {\n    return {\n      input: 'tuple',\n      type: getTypes(param)\n    } as const;\n  }\n  if (param.baseType === 'array') {\n    return {\n      input: 'array',\n      type: getTypes(param)\n    } as const;\n  }\n  return {\n    input: 'single',\n    type: getTypes(param) as InputTypes\n  } as const;\n}\n\nconst placeholders = {\n  string: 'a string of text',\n  address: '0x123...abc',\n  int: '123',\n  bytes: '0x123abc',\n  bytes32: '0x123abc',\n  bool: 'true'\n} as const;\n\nfunction reduceInt(value: string) {\n  return value.includes('int') ? 'int' : value;\n}\n\nexport function getParamPlaceholder(param: ParamType) {\n  if (param.baseType === 'array') {\n    if (param.arrayChildren) {\n      return `[${placeholders[reduceInt(param.arrayChildren.baseType)]}]`;\n    }\n    return '[...]';\n  }\n\n  if (param.baseType === 'tuple') {\n    return '[...]';\n  }\n\n  return placeholders[reduceInt(param.baseType)];\n}\n\nexport function getParamLabel(param: ParamType) {\n  const name = param.name?.length ? param.name + ' ' : '';\n  if (param.baseType === 'array') {\n    if (param.arrayChildren) {\n      return `${name} (${param.arrayChildren.baseType}[])`;\n    }\n    return `${name} (tuple[])`;\n  }\n\n  if (param.baseType === 'tuple') {\n    return `${name} (tuple)`;\n  }\n\n  return `${name} (${param.baseType})`;\n}\n\nfunction convertToStrings(value: unknown) {\n  if (Array.isArray(value)) {\n    return value.map(item => convertToStrings(item)); // Recursive processing for nested arrays\n  } else {\n    return String(value); // Convert other types (number, boolean, hex strings) to string\n  }\n}\n\nexport function parseInputArray(\n  input: string\n): MaybeNestedArrays<string> | undefined {\n  try {\n    // Step 1: Preprocess the input to ensure it's in a valid JSON format\n    const validJsonString = preprocessInputToJson(input);\n    // Step 2: Safely parse the string into an array\n    const result = JSON.parse(validJsonString);\n    const toStrings = convertToStrings(result);\n    return toStrings;\n  } catch (error) {\n    return `Error parsing input: ${error}`;\n  }\n}\n\n// This function aims to quote unquoted strings and hex values to make the input JSON-compliant\nfunction preprocessInputToJson(input: string): string {\n  // Regex to find hex values and unquoted strings\n  const hexRegex = /(\\b0x[0-9A-Fa-f]+\\b)/g;\n  return input\n    .replace(hexRegex, '\"$1\"')\n    .replace(/(['\"])?([a-z0-9A-Z_]+)(['\"])?:/g, '\"$2\": ')\n    .replace(/'/g, '\"');\n}\n"
  },
  {
    "path": "src/plugins/oSnap/components/Input/ReadOnly.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div\n    class=\"z-10 flex w-full rounded-3xl border border-skin-border bg-skin-bg px-3 text-left leading-[42px] outline-none transition-colors\"\n  >\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/Input/SelectSafe.vue",
    "content": "<script setup lang=\"ts\">\nimport { GnosisSafe } from '../../types';\nimport ModalSafe from '../TransactionBuilder/ModalSafe.vue';\n\ndefineProps<{\n  safes: GnosisSafe[];\n  selectedSafe: GnosisSafe | null;\n}>();\n\nconst emit = defineEmits<{\n  updateSafe: [safe: GnosisSafe];\n}>();\n\nconst isModalOpen = ref(false);\n</script>\n\n<template>\n  <div class=\"mb-2\">\n    <TuneButtonSelect\n      :model-value=\"\n        safes.find(\n          safe =>\n            safe.safeAddress === selectedSafe?.safeAddress &&\n            safe.network === selectedSafe.network\n        )?.safeName || 'Select Safe'\n      \"\n      @select=\"isModalOpen = true\"\n    />\n    <teleport to=\"#modal\">\n      <ModalSafe\n        :selected=\"selectedSafe\"\n        :open=\"isModalOpen\"\n        :safes=\"safes\"\n        @update-safe=\"emit('updateSafe', $event)\"\n        @close=\"isModalOpen = false\"\n      />\n    </teleport>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/Input/TransactionType.vue",
    "content": "<script setup lang=\"ts\">\nimport { TransactionType } from '../../types';\nimport ModalTransactionType from '../TransactionBuilder/ModalTransactionType.vue';\n\ndefineProps<{\n  selectedTransactionType: TransactionType;\n}>();\n\nconst emit = defineEmits<{\n  updateTransactionType: [type: TransactionType];\n}>();\n\nconst isModalOpen = ref(false);\n\nconst transactionTypesWithDetails: {\n  type: TransactionType;\n  title: string;\n  description: string;\n  hidden?: boolean;\n}[] = [\n  {\n    type: 'transferFunds',\n    title: 'Send tokens',\n    description: 'Transfer funds to another address'\n  },\n  {\n    type: 'transferNFT',\n    title: 'Send NFT',\n    description: 'Transfer a collectible to another address'\n  },\n  {\n    type: 'contractInteraction',\n    title: 'Contract interaction',\n    description: 'Interact with a smart contract'\n  },\n  {\n    type: 'raw',\n    title: 'Raw transaction',\n    description: 'Send a raw transaction'\n  },\n  {\n    type: 'safeImport',\n    title: 'Import Safe file',\n    description:\n      'Import JSON file exported from Gnosis Safe transaction builder',\n    hidden: true\n  }\n];\n</script>\n\n<template>\n  <div class=\"mb-2\">\n    <TuneButtonSelect\n      :model-value=\"\n        transactionTypesWithDetails.find(\n          typeAndDetails => typeAndDetails.type === selectedTransactionType\n        )?.title || 'Select transaction type'\n      \"\n      @select=\"isModalOpen = true\"\n    />\n    <teleport to=\"#modal\">\n      <ModalTransactionType\n        :selected=\"selectedTransactionType\"\n        :open=\"isModalOpen\"\n        :transaction-types-with-details=\"transactionTypesWithDetails\"\n        @update-transaction-type=\"emit('updateTransactionType', $event)\"\n        @close=\"isModalOpen = false\"\n      />\n    </teleport>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/OsnapMarketingWidget.vue",
    "content": "<template>\n    <a\n      :href=\"'https://uma.xyz/osnap'\"\n      target=\"_blank\"\n      class=\"group bg-skin-bg text-skin-text hover:text-skin-link hover:shadow-lg hover:border-skin-text text-sm border border-skin-border px-3 py-1 rounded-lg\"\n      rel=\"noopener noreferrer\"\n    >\n      Powered by\n      <span class=\"text-[#FF4A4A] relative\"\n        >oSnap\n        <svg\n          viewBox=\"0 0 7 7\"\n          fill=\"none\"\n          class=\"absolute top-0 right-[-0.4em] w-[0.5em] h-[0.5em]\"\n        >\n          <path\n            fill-rule=\"evenodd\"\n            clip-rule=\"evenodd\"\n            d=\"M1.802.286c.2.054.32.26.266.46l-.193.72a.376.376 0 0 1-.726-.195L1.34.552c.054-.2.26-.32.46-.266Zm-.578 2.157c.2.054.32.26.266.46l-.193.72a.376.376 0 0 1-.726-.195l.192-.719c.054-.2.26-.32.46-.266Zm3.405-.464a.376.376 0 0 1 0 .532l-.526.526a.376.376 0 0 1-.532-.532l.526-.526a.376.376 0 0 1 .532 0Zm-1.58 1.579a.376.376 0 0 1 0 .532l-.526.526a.376.376 0 0 1-.531-.532l.526-.526a.376.376 0 0 1 .532 0Zm2.996 1.698a.376.376 0 1 0-.195-.726l-.719.192a.376.376 0 1 0 .195.727l.719-.193Zm-2.157.578a.376.376 0 1 0-.195-.726l-.72.192a.376.376 0 1 0 .195.727l.72-.193Z\"\n            fill=\"#FF4A4A\"\n          />\n        </svg>\n      </span>\n    </a>\n  </template>\n  "
  },
  {
    "path": "src/plugins/oSnap/components/SafeLinkWithAvatar.vue",
    "content": "<script setup lang=\"ts\">\nimport { getIpfsUrl, shorten } from '@/helpers/utils';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { GnosisSafe } from '../types';\nimport { getSafeAppLink } from '../utils';\nimport ExternalLink from './ExternalLink.vue';\n\nconst props = defineProps<{\n  safe: GnosisSafe;\n}>();\n\nconst safeLink = computed(() =>\n  getSafeAppLink(props.safe.network, props.safe.safeAddress)\n);\nconst networkLogo = networks[props.safe.network].logo;\nconst networkLogoUrl = getIpfsUrl(networkLogo) as string;\n</script>\n\n<template>\n  <ExternalLink v-if=\"safe.safeAddress\" :link=\"safeLink\">\n    <BaseAvatar :src=\"networkLogoUrl\" size=\"24\" />\n    {{ safe.safeName }}\n\n    <span class=\"font-normal\">\n      {{ shorten(safe.safeAddress) }}\n    </span>\n  </ExternalLink>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/ContractInteraction.vue",
    "content": "<script setup lang=\"ts\">\nimport { FunctionFragment } from '@ethersproject/abi';\nimport { isAddress } from '@ethersproject/address';\nimport { asyncComputed, useDebounceFn } from '@vueuse/core';\n\nimport { ContractInteractionTransaction, Network, Status } from '../../types';\nimport {\n  checkIsContract,\n  createContractInteractionTransaction,\n  fetchImplementationAddress,\n  getABIWriteFunctions,\n  getContractABI,\n  parseValueInput\n} from '../../utils';\nimport AddressInput from '../Input/Address.vue';\nimport { sleep } from '@snapshot-labs/snapshot.js/src/utils';\nimport MethodParameterInput from '../Input/MethodParameter/MethodParameter.vue';\n\nconst props = defineProps<{\n  network: Network;\n  transaction: ContractInteractionTransaction;\n  setTransactionAsInvalid: () => void;\n}>();\n\nconst emit = defineEmits<{\n  updateTransaction: [transaction: ContractInteractionTransaction];\n}>();\n\nconst to = ref('');\n\nconst isToContract = asyncComputed(() => {\n  if (!isAddress(to.value)) {\n    return true;\n  }\n  return checkIsContract(to.value, props.network);\n}, true);\n\nconst isToValid = computed(() => {\n  return to.value !== '' && isAddress(to.value);\n});\n\nconst abi = ref('');\nconst abiFetchStatus = ref<Status>(Status.IDLE);\nconst implementationAddress = ref('');\nconst showAbiChoiceModal = ref(false);\n\nconst isAbiValid = ref(true);\nconst abiError = ref<string>();\nconst value = ref(props.transaction.value ?? '0');\nconst isValueValid = ref(true);\nconst methods = ref<FunctionFragment[]>([]);\nconst selectedMethodName = ref(props.transaction.methodName ?? '');\nconst selectedMethod = computed(\n  () =>\n    methods.value.find(method => method.name === selectedMethodName.value) ??\n    methods.value[0]\n);\n\nconst parameters = ref<string[]>([]);\n\nfunction updateTransaction() {\n  try {\n    if (!isToValid.value) {\n      throw new Error('\"TO\" address invalid');\n    }\n    if (!isAbiValid.value) {\n      throw new Error('ABI invalid');\n    }\n    if (!isValueValid.value) {\n      throw new Error('Value invalid');\n    }\n    // throws is method params are invalid\n    const transaction = createContractInteractionTransaction({\n      to: to.value,\n      value: value.value,\n      abi: abi.value,\n      method: selectedMethod.value,\n      parameters: parameters.value\n    });\n    emit('updateTransaction', transaction);\n  } catch (error) {\n    console.error(error);\n    props.setTransactionAsInvalid();\n  }\n}\n\nasync function handleFail() {\n  abiFetchStatus.value = Status.FAIL;\n  await sleep(3000);\n  if (abiFetchStatus.value === Status.FAIL) {\n    abiFetchStatus.value = Status.IDLE;\n  }\n}\n\nfunction updateParameter(index: number, value: string) {\n  parameters.value[index] = value;\n}\n\nfunction updateMethod(methodName: string) {\n  parameters.value = [];\n  selectedMethodName.value = methodName;\n}\n\nfunction updateAbi(newAbi: string) {\n  if (newAbi === abi.value) {\n    return;\n  }\n  try {\n    abi.value = newAbi;\n    methods.value = getABIWriteFunctions(abi.value);\n    isAbiValid.value = true;\n    updateMethod(methods.value[0].name);\n    parameters.value = [];\n  } catch (error) {\n    handleFail();\n    abiError.value = 'Error extracting write methods.';\n  }\n}\n\nconst debouncedUpdateAddress = useDebounceFn(() => {\n  if (isAddress(to.value)) {\n    fetchABI();\n  }\n}, 300);\n\nasync function handleUseProxyAbi() {\n  showAbiChoiceModal.value = false;\n  try {\n    const res = await getContractABI(props.network, to.value);\n    if (!res) {\n      throw new Error('Failed to fetch ABI.');\n    }\n    updateAbi(res);\n    abiFetchStatus.value = Status.SUCCESS;\n  } catch (error) {\n    handleFail();\n    console.error(error);\n  }\n}\n\nasync function handleUseImplementationAbi() {\n  showAbiChoiceModal.value = false;\n  try {\n    if (!implementationAddress.value) {\n      throw new Error('No Implementation address');\n    }\n    const res = await getContractABI(\n      props.network,\n      implementationAddress.value\n    );\n    if (!res) {\n      throw new Error('Failed to fetch ABI.');\n    }\n    abiFetchStatus.value = Status.SUCCESS;\n    updateAbi(res);\n  } catch (error) {\n    handleFail();\n    console.error(error);\n  }\n}\n\nasync function fetchABI() {\n  try {\n    abiFetchStatus.value = Status.LOADING;\n    if (!isToContract.value) {\n      throw new Error('Address provided is not a contract on this network');\n    }\n    const res = await fetchImplementationAddress(to.value, props.network);\n    if (!res) {\n      handleUseProxyAbi();\n      return;\n    }\n    // if proxy, let user decide which ABI we should fetch\n    implementationAddress.value = res;\n    showAbiChoiceModal.value = true;\n  } catch (error) {\n    handleFail();\n    console.error(error);\n  }\n}\n\nfunction updateValue(newValue: string) {\n  try {\n    const parsed = parseValueInput(newValue);\n    value.value = parsed;\n    isValueValid.value = true;\n  } catch (error) {\n    isValueValid.value = false;\n  } finally {\n    updateTransaction();\n  }\n}\n\nwatch(to, updateTransaction);\nwatch(abi, updateTransaction);\nwatch(selectedMethodName, updateTransaction);\nwatch(selectedMethod, updateTransaction);\nwatch(parameters, updateTransaction, { deep: true });\n\nfunction handleDismissModal() {\n  abiFetchStatus.value = Status.IDLE;\n  showAbiChoiceModal.value = false;\n}\n</script>\n\n<template>\n  <div class=\"space-y-2\">\n    <AddressInput\n      v-model=\"to\"\n      :label=\"$t('safeSnap.to')\"\n      :disabled=\"abiFetchStatus === Status.LOADING\"\n      :error=\"!isToContract ? 'Not Contract address' : undefined\"\n      :network=\"network\"\n      @update:model-value=\"debouncedUpdateAddress()\"\n    />\n\n    <UiInput\n      :error=\"!isValueValid && $t('safeSnap.invalidValue')\"\n      :model-value=\"value\"\n      @update:model-value=\"updateValue($event)\"\n    >\n      <template #label>{{ $t('safeSnap.value') }}</template>\n    </UiInput>\n\n    <UiInput\n      :disabled=\"abiFetchStatus === Status.LOADING\"\n      :error=\"!isAbiValid && (abiError ?? $t('safeSnap.invalidAbi'))\"\n      :model-value=\"abi\"\n      @update:model-value=\"updateAbi($event)\"\n    >\n      <template #label>ABI</template>\n    </UiInput>\n    <div\n      v-if=\"abiFetchStatus === Status.LOADING\"\n      class=\"flex items-center justify-start gap-2 p-2\"\n    >\n      <LoadingSpinner />\n      <p>Fetching ABI...</p>\n    </div>\n\n    <div\n      v-if=\"abiFetchStatus === Status.FAIL\"\n      class=\"flex items-center justify-start gap-2 p-2 text-red\"\n    >\n      <BaseIcon name=\"warning\" class=\"text-inherit\" />\n      <p>Failed to fetch ABI</p>\n    </div>\n\n    <div v-if=\"methods.length\">\n      <UiSelect v-model=\"selectedMethodName\" @change=\"updateMethod($event)\">\n        <template #label>function</template>\n        <option v-for=\"(method, i) in methods\" :key=\"i\" :value=\"method.name\">\n          {{ method.name }}()\n        </option>\n      </UiSelect>\n\n      <div\n        v-if=\"selectedMethod && selectedMethod.inputs.length\"\n        class=\"flex flex-col gap-2\"\n      >\n        <div class=\"divider h-[1px] bg-skin-border my-3\" />\n\n        <MethodParameterInput\n          v-for=\"(input, index) in selectedMethod.inputs\"\n          :key=\"input.name\"\n          :parameter=\"input\"\n          :value=\"parameters[index]\"\n          @update-parameter-value=\"updateParameter(index, $event)\"\n        />\n      </div>\n    </div>\n  </div>\n\n  <BaseModal :open=\"showAbiChoiceModal\" @close=\"handleDismissModal\">\n    <template #header>\n      <h3 class=\"text-left px-3\">Use Implementation ABI?</h3>\n    </template>\n    <div class=\"flex flex-col gap-4 p-3\">\n      <p class=\"pr-8\">\n        This contract looks like a proxy. Would you like to use the\n        implementation ABI?\n      </p>\n      <div class=\"flex gap-2 justify-center\">\n        <TuneButton @click=\"handleUseProxyAbi\"> Keep proxy ABI </TuneButton>\n        <TuneButton @click=\"handleUseImplementationAbi\">\n          Use Implementation ABI\n        </TuneButton>\n      </div>\n    </div>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/ModalSafe.vue",
    "content": "<script setup lang=\"ts\">\nimport { GnosisSafe } from '../../types';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\n\ndefineProps<{\n  open: boolean;\n  safes: GnosisSafe[];\n  selected: GnosisSafe | null;\n}>();\n\nconst emit = defineEmits<{\n  updateSafe: [type: GnosisSafe];\n  close: [];\n}>();\n\nfunction select(type: GnosisSafe) {\n  emit('updateSafe', type);\n  emit('close');\n}\n\nfunction makeSafeDescription(safe: GnosisSafe) {\n  const networkDetails = networks[safe.network];\n\n  return `${safe.safeAddress} (${networkDetails?.name})`;\n}\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>Select Safe</h3>\n    </template>\n    <div class=\"mx-0 my-4 flex flex-col space-y-3 md:mx-4\">\n      <button v-for=\"(safe, key) in safes\" :key=\"key\" @click=\"select(safe)\">\n        <BaseModalSelectItem\n          :selected=\"\n            safe.safeAddress === selected?.safeAddress &&\n            safe.network === selected.network\n          \"\n          :title=\"safe.safeName\"\n          :description=\"makeSafeDescription(safe)\"\n        />\n      </button>\n    </div>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/ModalTransactionType.vue",
    "content": "<script setup lang=\"ts\">\nimport { TransactionType } from '../../types';\n\ndefineProps<{\n  open: boolean;\n  selected: TransactionType;\n  transactionTypesWithDetails: {\n    type: TransactionType;\n    title: string;\n    description: string;\n    hidden?: boolean;\n  }[];\n}>();\n\nconst emit = defineEmits<{\n  updateTransactionType: [type: TransactionType];\n  close: [];\n}>();\n\nfunction select(type: TransactionType) {\n  emit('updateTransactionType', type);\n  emit('close');\n}\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3>Select transaction type</h3>\n    </template>\n    <div class=\"mx-0 my-4 flex flex-col space-y-3 md:mx-4\">\n      <template v-for=\"(typeAndDetails, key) in transactionTypesWithDetails\">\n        <button\n          v-if=\"!typeAndDetails.hidden\"\n          :key=\"key\"\n          @click=\"select(typeAndDetails.type)\"\n        >\n          <BaseModalSelectItem\n            :selected=\"typeAndDetails.type === selected\"\n            :title=\"typeAndDetails.title\"\n            :description=\"typeAndDetails.description\"\n          />\n        </button>\n      </template>\n    </div>\n  </BaseModal>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/RawTransaction.vue",
    "content": "<script setup lang=\"ts\">\nimport { isAddress } from '@ethersproject/address';\nimport { isHexString } from '@ethersproject/bytes';\nimport { RawTransaction } from '../../types';\nimport { createRawTransaction, parseValueInput } from '../../utils';\nimport AddressInput from '../Input/Address.vue';\n\nconst props = defineProps<{\n  transaction: RawTransaction;\n  setTransactionAsInvalid(): void;\n}>();\n\nconst emit = defineEmits<{\n  updateTransaction: [transaction: RawTransaction];\n}>();\n\nconst to = ref(props.transaction.to ?? '');\nconst value = ref(props.transaction.value ?? '0');\nconst data = ref(props.transaction.data ?? '0x');\n\nconst isValueValid = ref(true);\n\nconst isToValid = computed(() => {\n  return to.value === '' || isAddress(to.value);\n});\n\nconst isDataValid = computed(() => {\n  return data.value === '' || isHexString(data.value);\n});\n\nwatch(to, updateTransaction);\nwatch(value, updateTransaction);\nwatch(data, updateTransaction);\n\nfunction updateTransaction() {\n  try {\n    if (!isToValid.value) {\n      throw new Error('\"To\" address invalid');\n    }\n    if (!isValueValid.value) {\n      throw new Error('\"Value\" amount invalid invalid');\n    }\n    if (!isDataValid.value) {\n      throw new Error('\"Data\" field invalid');\n    }\n\n    const transaction = createRawTransaction({\n      to: to.value,\n      value: value.value,\n      data: data.value\n    });\n    emit('updateTransaction', transaction);\n  } catch (error) {\n    console.error(error);\n    props.setTransactionAsInvalid();\n  }\n}\n\nfunction updateValue(newValue: string) {\n  try {\n    const parsed = parseValueInput(newValue);\n    value.value = parsed;\n    isValueValid.value = true;\n  } catch (error) {\n    isValueValid.value = false;\n  } finally {\n    updateTransaction();\n  }\n}\n</script>\n\n<template>\n  <div class=\"space-y-2\">\n    <AddressInput\n      v-model=\"to\"\n      :input-props=\"{ required: false }\"\n      :error=\"!isToValid ? $t('safeSnap.invalidAddress') : undefined\"\n      :label=\"$t('safeSnap.to')\"\n    />\n\n    <UiInput\n      v-model=\"value\"\n      :error=\"!isValueValid && $t('safeSnap.invalidValue')\"\n      @update:model-value=\"updateValue($event)\"\n    >\n      <template #label>{{ $t('safeSnap.value') }}</template>\n    </UiInput>\n\n    <UiInput v-model=\"data\" :error=\"!isDataValid && $t('safeSnap.invalidData')\">\n      <template #label>{{ $t('safeSnap.data') }}</template>\n    </UiInput>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/SafeImport.vue",
    "content": "<script setup lang=\"ts\">\nimport { SafeImportTransaction, Network } from '../../types';\nimport { createSafeImportTransaction, parseValueInput } from '../../utils';\nimport MethodParameterInput from '../../components/Input/MethodParameter/MethodParameter.vue';\nimport AddressInput from '../../components/Input/Address.vue';\nimport { isAddress } from '@ethersproject/address';\n\nconst props = defineProps<{\n  transaction: SafeImportTransaction;\n  network: Network;\n}>();\n\nconst emit = defineEmits<{\n  updateTransaction: [transaction: SafeImportTransaction];\n}>();\n\nconst isValueValid = ref(true);\n\nconst isToValid = computed(() => {\n  if (!props.transaction?.to) {\n    return true;\n  }\n  return isAddress(props.transaction.to);\n});\n\nfunction updateFinalTransaction(tx: Partial<SafeImportTransaction>) {\n  const _tx = {\n    ...props.transaction,\n    ...tx\n  };\n  const formatted = createSafeImportTransaction(_tx);\n  emit('updateTransaction', formatted);\n}\n\nfunction updateParams(paramsToUpdate: SafeImportTransaction['parameters']) {\n  const _tx = {\n    ...props.transaction,\n    parameters: {\n      ...props.transaction?.parameters,\n      ...paramsToUpdate\n    }\n  };\n  updateFinalTransaction(_tx);\n}\n\nfunction updateValue(newValue: string) {\n  try {\n    if (!props.transaction) {\n      return;\n    }\n    const parsed = parseValueInput(newValue);\n    updateFinalTransaction({\n      value: parsed\n    });\n    isValueValid.value = true;\n  } catch (error) {\n    isValueValid.value = false;\n  }\n}\n</script>\n\n<template>\n  <div class=\"text-skin-text text-left\">\n    {{\n      props.transaction?.method?.name\n        ? `Contract interaction (${props.transaction.method.name})`\n        : 'Native Transfer'\n    }}\n  </div>\n\n  <div v-if=\"props.transaction\" class=\"flex flex-col gap-2 mt-2\">\n    <AddressInput\n      @update:model-value=\"(e: string) => updateFinalTransaction({ to: e })\"\n      :model-value=\"props.transaction.to\"\n      :label=\"$t('safeSnap.to')\"\n      :error=\"!isToValid ? 'Invalid address' : undefined\"\n    />\n\n    <UiInput\n      placeholder=\"123456\"\n      :error=\"!isValueValid && 'Invalid value'\"\n      :model-value=\"props.transaction.value\"\n      @update:model-value=\"(e: string) => updateValue(e)\"\n    >\n      <template #label>Value (wei)</template>\n    </UiInput>\n\n    <!-- ContractInteraction Parameters -->\n    <div\n      class=\"flex flex-col gap-2\"\n      v-if=\"props.transaction.method?.inputs?.length\"\n    >\n      <div class=\"text-left mt-3\">Function Parameters</div>\n      <div class=\"divider h-[1px] bg-skin-border mb-3\" />\n      <MethodParameterInput\n        v-for=\"input in props.transaction.method.inputs\"\n        :key=\"input.name\"\n        :validateOnMount=\"true\"\n        :parameter=\"input\"\n        :value=\"props.transaction?.parameters?.[input.name] ?? ''\"\n        @update-parameter-value=\"\n          (e: string) => updateParams({ [input.name]: e })\n        \"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/TenderlySimulation.vue",
    "content": "<script setup lang=\"ts\">\nimport { sleep } from '@snapshot-labs/snapshot.js/src/utils';\nimport {\n  Transaction as TTransaction,\n  Network,\n  TenderlySimulationResult,\n  GnosisSafe,\n  isErrorWithMessage\n} from '../../types';\nimport { ref } from 'vue';\nimport {\n  SIMULATION_ENDPOINT,\n  exceedsOsnapGasSubsidy,\n  prepareTenderlySimulationPayload,\n  validatePayload,\n  OSNAP_GAS_SUBSIDY\n} from '../../utils/tenderly';\n\nconst props = defineProps<{\n  transactions: TTransaction[];\n  safe: GnosisSafe | null;\n  network: Network;\n}>();\n\ntype State =\n  | {\n      status: 'SUCCESS';\n      simulationLink: TenderlySimulationResult['resultUrl'];\n      gasUsed: TenderlySimulationResult['gasUsed'];\n      exceedsGasSubsidy: boolean;\n    }\n  | {\n      status: 'FAIL';\n      simulationLink: TenderlySimulationResult['resultUrl'];\n      gasUsed: TenderlySimulationResult['gasUsed'];\n      exceedsGasSubsidy: boolean;\n    }\n  | {\n      status: 'ERROR';\n      error: string;\n    }\n  | {\n      status: 'LOADING';\n    }\n  | {\n      status: 'IDLE';\n    };\n\nconst simulationState = ref<State>({ status: 'IDLE' });\n\nconst resetState = () => {\n  simulationState.value = { status: 'IDLE' };\n};\n\nfunction handleSimulationResult(res: TenderlySimulationResult) {\n  // if gas exceeds osnap gas subsidy, tx will not be automatically executed\n  const exceedsGasSubsidy = exceedsOsnapGasSubsidy(res);\n\n  if (res.status === true) {\n    simulationState.value = {\n      status: 'SUCCESS',\n      simulationLink: res.resultUrl,\n      gasUsed: res.gasUsed,\n      exceedsGasSubsidy\n    };\n  } else {\n    simulationState.value = {\n      status: 'FAIL',\n      simulationLink: res.resultUrl,\n      gasUsed: res.gasUsed,\n      exceedsGasSubsidy\n    };\n  }\n}\n\nasync function simulate() {\n  simulationState.value = { status: 'LOADING' };\n  try {\n    const payload = prepareTenderlySimulationPayload(props);\n\n    // throws if invalid\n    validatePayload(payload);\n\n    const response = await fetch(SIMULATION_ENDPOINT, {\n      headers: new Headers({\n        'content-type': 'application/json'\n      }),\n      method: 'POST',\n      body: JSON.stringify(payload)\n    });\n\n    if (!response.ok) {\n      throw new Error('Error running simulation');\n    }\n\n    const data: TenderlySimulationResult = await response.json();\n    handleSimulationResult(data);\n  } catch (error) {\n    console.error(error);\n    if (isErrorWithMessage(error)) {\n      simulationState.value = { status: 'ERROR', error: error.message };\n    } else {\n      simulationState.value = {\n        status: 'ERROR',\n        error: 'Failed to simulate!'\n      };\n    }\n    await sleep(5_000);\n    simulationState.value = {\n      status: 'IDLE'\n    };\n  }\n}\n</script>\n\n<template>\n  <div>\n    <button\n      v-if=\"\n        !(\n          simulationState.status === 'SUCCESS' ||\n          simulationState.status === 'FAIL'\n        )\n      \"\n      @click=\"simulate\"\n      :disabled=\"simulationState.status !== 'IDLE'\"\n      :class=\"[\n        'flex w-full enabled:hover:border-skin-text gap-2 justify-center h-[48px] px-[20px] items-center border disabled:cursor-not-allowed rounded-full border-skin-border',\n        {\n          'text-red': simulationState.status === 'ERROR'\n        }\n      ]\"\n    >\n      <i-s-tenderly class=\"text-skin-link inline h-[20px] w-[20px]\" />\n      <span v-if=\"simulationState.status === 'IDLE'\">Simulate Transaction</span>\n      <span v-if=\"simulationState.status === 'LOADING'\"\n        >Checking transaction...</span\n      >\n      <span class=\"text-xs\" v-if=\"simulationState.status === 'ERROR'\">{{\n        simulationState.error\n      }}</span>\n\n      <LoadingSpinner\n        class=\"ml-auto\"\n        v-if=\"simulationState.status === 'LOADING'\"\n      />\n    </button>\n    <div class=\"flex flex-col gap-2\" v-else>\n      <div\n        :class=\"[\n          'flex w-full text-sm md:text-[18px] justify-between h-[48px] px-[20px] items-center rounded-full',\n          {\n            'bg-green/20 text-green': simulationState.status === 'SUCCESS',\n            'bg-red/20 text-red': simulationState.status === 'FAIL'\n          }\n        ]\"\n      >\n        <div class=\"flex items-center gap-2\">\n          <i-s-tenderly class=\"inline h-[20px] w-[20px] text-inherit\" />\n          <span v-if=\"simulationState.status === 'SUCCESS'\">Success!</span>\n          <span v-if=\"simulationState.status === 'FAIL'\"\n            >Transaction failed!</span\n          >\n        </div>\n        <a\n          v-if=\"simulationState.simulationLink.public\"\n          target=\"_blank\"\n          class=\"flex items-center gap-1 text-inherit hover:underline\"\n          :href=\"simulationState.simulationLink.url\"\n        >\n          <span>View on Tenderly</span>\n          <IHoExternalLink class=\"text-inherit inline w-[1.2em] h-[1.2em]\" />\n        </a>\n        <div v-else class=\"text-inherit\">Simulation not public</div>\n      </div>\n\n      <TuneButton\n        class=\"group text-sm md:text-[18px] hover:cursor-pointer justify-center w-full flex gap-2 mx-auto items-center\"\n        :tooltip=\"'Reset Simulation'\"\n        @click=\"resetState\"\n      >\n        Reset Simulation\n        <IHoRefresh class=\"text-inherit w-[1em] h-[1em]\" />\n      </TuneButton>\n\n      <p v-if=\"simulationState.exceedsGasSubsidy\" class=\"text-sm text-left\">\n        <strong class=\"text-skins text-base text-red\">Warning:</strong>\n        This transaction will\n        <strong class=\"underline\"\n          >not be automatically executed by oSnap.</strong\n        >\n        This transaction used\n        {{ simulationState.gasUsed.toLocaleString() }} gas, which exceeds\n        oSnap's maximum subsidized amount of\n        {{ OSNAP_GAS_SUBSIDY.toLocaleString() }}.\n      </p>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/TokensModal.vue",
    "content": "<script setup lang=\"ts\">\nimport { useConfirmDialog } from '@vueuse/core';\nimport { Network, Token } from '../../types';\nimport TokensModalItem from './TokensModalItem.vue';\n\nconst props = defineProps<{\n  open: boolean;\n  tokenAddress: string;\n  tokens: Token[];\n  network: Network;\n}>();\n\nconst emit = defineEmits<{\n  close: [];\n  tokenAddress: [tokenAddress: string];\n}>();\n\nconst searchInput = ref('');\nconst showUnverifiedTokens = ref(false);\n\nconst confirmDialogOpen = ref(false);\nconst confirmDialogData = ref(null);\nconst confirmDialog = useConfirmDialog(confirmDialogOpen);\nconfirmDialog.onConfirm(token => {\n  emit('tokenAddress', token.address);\n  emit('close');\n});\n\nconst tokensFiltered = computed(() => {\n  const filterTokens = (token: Token) => {\n    const tokenProperties = [token.symbol, token.name, token.address].map(\n      property => property.toLowerCase()\n    );\n\n    const searchQuery = searchInput.value.toLowerCase();\n\n    const searchMatch = tokenProperties.some(property =>\n      property.includes(searchQuery)\n    );\n    const isVerified = token.address === 'main' || token.verified;\n\n    return (\n      (searchMatch || !searchInput.value) &&\n      (showUnverifiedTokens.value || isVerified)\n    );\n  };\n\n  return props.tokens.filter(filterTokens);\n});\n\nfunction handleTokenClick(token) {\n  const isVerified = token.address === 'main' || token.verified;\n\n  if (!isVerified) {\n    confirmDialogData.value = token;\n    return confirmDialog.reveal();\n  }\n\n  emit('tokenAddress', token.address);\n  emit('close');\n}\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <div\n        class=\"flex flex-col content-center items-center justify-center gap-x-4\"\n      >\n        <h3>Assets</h3>\n        <BaseSearch\n          v-model=\"searchInput\"\n          :placeholder=\"$t('searchPlaceholderTokens')\"\n          modal\n          focus-on-mount\n          class=\"min-h-[60px] w-full flex-auto !px-3 pb-3 sm:!px-4\"\n        >\n          <template #after>\n            <BasePopover :focus=\"false\">\n              <template #button>\n                <BaseButtonIcon>\n                  <i-ho-adjustments class=\"text-skin-link\" />\n                </BaseButtonIcon>\n              </template>\n              <template #content>\n                <h3 class=\"-mb-2 mt-3 text-center text-skin-heading\">\n                  Filters\n                </h3>\n                <div class=\"m-4 space-y-3\">\n                  <div class=\"space-y-2\">\n                    <div class=\"space-y-2\">\n                      <TuneCheckbox\n                        id=\"show-unverified-tokens\"\n                        v-model=\"showUnverifiedTokens\"\n                        hint=\"Show unverified tokens\"\n                        name=\"searchOnlyWithReason\"\n                      />\n                    </div>\n                  </div>\n                </div>\n              </template>\n            </BasePopover>\n          </template>\n        </BaseSearch>\n      </div>\n    </template>\n\n    <template #default=\"{ maxHeight }\">\n      <div\n        class=\"flex w-full flex-col overflow-auto\"\n        :style=\"{ minHeight: maxHeight }\"\n      >\n        <TokensModalItem\n          v-for=\"token in tokensFiltered\"\n          :key=\"token.address\"\n          :token=\"token\"\n          :is-selected=\"token.address === tokenAddress\"\n          :network=\"network\"\n          @select=\"handleTokenClick\"\n        />\n\n        <div\n          v-if=\"searchInput.length && tokensFiltered.length === 0\"\n          class=\"flex flex-row content-start items-start justify-center py-4\"\n        >\n          <span>{{ $t('noResultsFound') }}</span>\n        </div>\n      </div>\n    </template>\n  </BaseModal>\n\n  <teleport to=\"#modal\">\n    <ModalConfirmAction\n      :open=\"confirmDialogOpen\"\n      show-cancel\n      @close=\"confirmDialog.cancel\"\n      @confirm=\"confirmDialog.confirm(confirmDialogData)\"\n    >\n      <BaseMessageBlock level=\"warning-red\" class=\"m-4\">\n        This token isn't known to us. Please make sure it is the correct address\n        before proceeding.\n        <BaseLink\n          link=\"https://docs.snapshot.org/user-guides/token-verification\"\n        >\n          {{ $t('learnMore') }}</BaseLink\n        >\n      </BaseMessageBlock>\n    </ModalConfirmAction>\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/TokensModalItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { ETH_CONTRACT } from '@/helpers/constants';\nimport { explorerUrl, shorten } from '@/helpers/utils';\nimport { Token } from '../../types';\n\nconst props = defineProps<{\n  token: Token;\n  isSelected: boolean;\n  network: string;\n}>();\n\nconst emit = defineEmits<{\n  select: [token: Token];\n}>();\n\nconst { formatNumber, getNumberFormatter } = useIntl();\n\nconst exploreUrl = computed(() => {\n  return explorerUrl(props.network, props.token.address);\n});\n</script>\n\n<template>\n  <button\n    class=\"flex h-[64px] w-full cursor-pointer items-center justify-between border-b border-skin-border px-3 py-2 hover:bg-skin-border sm:px-4\"\n    :class=\"{\n      '!bg-skin-border': isSelected\n    }\"\n    @click=\"emit('select', token)\"\n  >\n    <div class=\"flex items-center\">\n      <div class=\"mr-3 flex\">\n        <AvatarToken\n          :address=\"token.address === 'main' ? ETH_CONTRACT : token.address\"\n          size=\"38\"\n        />\n      </div>\n\n      <div class=\"pr-4\">\n        <div class=\"flex w-full items-center text-skin-link\">\n          <div class=\"flex items-center gap-1\">\n            {{ token.symbol }}\n            <i-ho-check-badge\n              v-if=\"token.verified || token.address === 'main'\"\n              v-tippy=\"{ content: $t('verified') }\"\n              class=\"text-sm text-green\"\n            />\n          </div>\n        </div>\n        <span class=\"line-clamp-1 text-left text-skin-text\">\n          {{ token.name }}\n        </span>\n      </div>\n    </div>\n\n    <div class=\"h-full text-right\">\n      <span class=\"text-skin-link\">\n        {{\n          formatNumber(\n            Number(token.balance),\n            getNumberFormatter({ maximumFractionDigits: 6 }).value\n          )\n        }}\n      </span>\n      <div>\n        <BaseLink\n          v-if=\"token.address !== 'main' && exploreUrl\"\n          :link=\"exploreUrl\"\n          @click.stop\n        >\n          {{ shorten(token.address) }}</BaseLink\n        >\n      </div>\n    </div>\n  </button>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/Transaction.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  ContractInteractionTransaction,\n  TransferNftTransaction,\n  type NFT,\n  type Network,\n  type RawTransaction as TRawTransaction,\n  type Transaction as TTransaction,\n  type TransactionType as TTransactionType,\n  type Token,\n  type TransferFundsTransaction,\n  SafeImportTransaction\n} from '../../types';\nimport TransactionType from '../Input/TransactionType.vue';\nimport ContractInteraction from './ContractInteraction.vue';\nimport RawTransaction from './RawTransaction.vue';\nimport TransferFunds from './TransferFunds.vue';\nimport TransferNFT from './TransferNFT.vue';\nimport SafeImport from './SafeImport.vue';\n\nconst props = defineProps<{\n  transaction: TTransaction;\n  transactionIndex: number;\n  safeAddress: string;\n  moduleAddress: string;\n  tokens: Token[];\n  collectables: NFT[];\n  network: Network;\n}>();\n\nconst emit = defineEmits<{\n  updateTransaction: [transaction: TTransaction, transactionIndex: number];\n  removeTransaction: [transactionIndex: number];\n}>();\n\nfunction updateTransactionType(transactionType: TTransactionType) {\n  emit(\n    'updateTransaction',\n    {\n      type: transactionType,\n      to: '',\n      value: '0',\n      data: '0x',\n      formatted: ['', 0, '0', '0x']\n    },\n    props.transactionIndex\n  );\n}\n\nfunction updateTransaction(transaction: TTransaction) {\n  emit('updateTransaction', transaction, props.transactionIndex);\n}\n\nfunction setTransactionAsInvalid() {\n  const tx: TTransaction = {\n    ...props.transaction,\n    isValid: false\n  };\n  emit('updateTransaction', tx, props.transactionIndex);\n}\n</script>\n\n<template>\n  <div class=\"mt-4 pb-4 first:mt-0\">\n    <div class=\"flex items-center justify-between\">\n      <h3 class=\"text-left text-base\">\n        Transaction {{ transactionIndex + 1 }}\n      </h3>\n      <button\n        class=\"p-[6px] transition-colors duration-200 group\"\n        @click=\"emit('removeTransaction', transactionIndex)\"\n      >\n        <BaseIcon\n          class=\"text-red/80 group-hover:text-red\"\n          name=\"close\"\n          size=\"14\"\n        />\n      </button>\n    </div>\n    <TransactionType\n      :selected-transaction-type=\"transaction.type\"\n      @update-transaction-type=\"updateTransactionType\"\n    />\n    <ContractInteraction\n      v-if=\"transaction.type === 'contractInteraction'\"\n      :transaction=\"transaction as ContractInteractionTransaction\"\n      :network=\"network\"\n      :setTransactionAsInvalid=\"setTransactionAsInvalid\"\n      @update-transaction=\"updateTransaction\"\n    />\n\n    <TransferFunds\n      v-if=\"transaction.type === 'transferFunds'\"\n      :network=\"network\"\n      :tokens=\"tokens\"\n      :transaction=\"transaction as TransferFundsTransaction\"\n      :setTransactionAsInvalid=\"setTransactionAsInvalid\"\n      @update-transaction=\"updateTransaction\"\n    />\n\n    <TransferNFT\n      v-if=\"transaction.type === 'transferNFT'\"\n      :network=\"network\"\n      :safe-address=\"safeAddress\"\n      :collectables=\"collectables\"\n      :transaction=\"transaction as TransferNftTransaction\"\n      :setTransactionAsInvalid=\"setTransactionAsInvalid\"\n      @update-transaction=\"updateTransaction\"\n    />\n\n    <RawTransaction\n      v-if=\"transaction.type === 'raw'\"\n      :transaction=\"transaction as TRawTransaction\"\n      :setTransactionAsInvalid=\"setTransactionAsInvalid\"\n      @update-transaction=\"updateTransaction\"\n    />\n\n    <SafeImport\n      v-if=\"transaction.type === 'safeImport'\"\n      :transaction=\"transaction as SafeImportTransaction\"\n      :network=\"network\"\n      @update-transaction=\"updateTransaction\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/TransactionBuilder.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\nimport {\n  GnosisSafe,\n  NFT,\n  SafeImportTransaction,\n  Transaction as TTransaction,\n  Token\n} from '../../types';\nimport { getSafeAppLink } from '../../utils';\nimport Transaction from './Transaction.vue';\nimport TenderlySimulation from './TenderlySimulation.vue';\nimport TransactionImport from './TransactionImport.vue';\n\nconst props = defineProps<{\n  tokens: Token[];\n  collectibles: NFT[];\n  safe: GnosisSafe;\n}>();\n\nconst emit = defineEmits<{\n  addTransaction: [transaction: TTransaction];\n  removeTransaction: [transactionIndex: number];\n  updateTransaction: [transaction: TTransaction, transactionIndex: number];\n}>();\n\nconst safeLink = computed(() =>\n  getSafeAppLink(props.safe.network, props.safe.safeAddress)\n);\n\nfunction addImportedTransactions(transactions: SafeImportTransaction[]) {\n  transactions.forEach(tx => emit('addTransaction', tx));\n}\n</script>\n\n<template>\n  <p class=\"my-2\">\n    <strong>Safe app link</strong\n    ><a\n      :href=\"safeLink\"\n      class=\"ml-2 inline-flex font-normal text-skin-text\"\n      target=\"_blank\"\n    >\n      {{ shorten(safe.safeAddress) }}\n      <i-ho-external-link class=\"ml-1\" />\n    </a>\n  </p>\n  <p class=\"my-2\">\n    <strong>Module address</strong\n    ><span class=\"ml-2 inline-block break-all\">{{\n      shorten(safe.moduleAddress)\n    }}</span>\n  </p>\n  <p class=\"my-2\">\n    <strong>Number of transactions</strong\n    ><span class=\"ml-2 inline-block\">{{ safe.transactions.length }}</span>\n    <TransactionImport\n      @update:imported-transactions=\"addImportedTransactions\"\n      :safe=\"props.safe\"\n      :network=\"safe.network\"\n    />\n  </p>\n  <div class=\"text-center\">\n    <Transaction\n      v-for=\"(transaction, index) in safe.transactions\"\n      :key=\"index\"\n      :transaction=\"transaction\"\n      :transaction-index=\"index\"\n      :safe-address=\"safe.safeAddress\"\n      :module-address=\"safe.moduleAddress\"\n      :tokens=\"tokens\"\n      :collectables=\"collectibles\"\n      :network=\"safe.network\"\n      @update-transaction=\"(...args) => emit('updateTransaction', ...args)\"\n      @remove-transaction=\"(...args) => emit('removeTransaction', ...args)\"\n    />\n    <TenderlySimulation\n      v-if=\"safe.transactions.length\"\n      :transactions=\"safe.transactions\"\n      :safe=\"props.safe\"\n      :network=\"safe.network\"\n      class=\"mt-4\"\n    />\n  </div>\n\n  <TuneButton\n    class=\"mt-4 w-full\"\n    @click=\"\n      emit('addTransaction', {\n        type: 'transferFunds',\n        to: '',\n        value: '0',\n        data: '0x',\n        formatted: ['', 0, '0', '0x']\n      })\n    \"\n  >\n    Add transaction +\n  </TuneButton>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/TransactionImport.vue",
    "content": "<script setup lang=\"ts\">\nimport {\n  GnosisSafe,\n  Network,\n  SafeImportTransaction,\n  isErrorWithMessage\n} from '../../types';\nimport { initializeSafeImportTransaction } from '../../utils';\nimport { parseGnosisSafeFile } from '../../utils/safeImport';\nimport FileInput from '../Input/FileInput/FileInput.vue';\n\nconst props = defineProps<{\n  network: Network;\n  safe: GnosisSafe | null;\n}>();\n\n// Emits definition\nconst emit = defineEmits<{\n  (\n    event: 'update:importedTransactions',\n    importedTransactions: SafeImportTransaction[]\n  ): void;\n}>();\n\nconst file = ref<File>(); // raw file, if valid type\nconst safeFile = ref<GnosisSafe.BatchFile>(); // parsed, type-safe file\n\nconst error = ref<string>();\n\nfunction resetState() {\n  file.value = undefined;\n  safeFile.value = undefined;\n  error.value = undefined;\n}\n\nwatch(file, async () => {\n  if (!file.value) return;\n  parseGnosisSafeFile(file.value, props.safe)\n    .then(result => {\n      safeFile.value = result;\n    })\n    .catch(e => {\n      safeFile.value = undefined;\n      if (isErrorWithMessage(e)) {\n        error.value = e.message;\n        return;\n      }\n      error.value = 'Safe file corrupted. Please select another.';\n    });\n});\n\nfunction handleFileChange(_file: File | null) {\n  if (_file) {\n    resetState();\n    file.value = _file;\n  }\n}\n\nwatch(safeFile, safeFile => {\n  if (safeFile) {\n    const convertedTxs = safeFile.transactions.map(\n      initializeSafeImportTransaction\n    );\n    emit('update:importedTransactions', convertedTxs);\n  }\n});\n</script>\n\n<template>\n  <FileInput\n    :error=\"error\"\n    @update:file=\"handleFileChange\"\n    :file-type=\"'application/json'\"\n    :defaultLabel=\"'Import transactions from Safe file, drag and drop'\"\n  />\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/TransferFunds.vue",
    "content": "<script setup lang=\"ts\">\nimport { ETH_CONTRACT } from '@/helpers/constants';\nimport { shorten } from '@/helpers/utils';\nimport { Network, Token, TransferFundsTransaction } from '../../types';\nimport {\n  createTransferFundsTransaction,\n  getERC20TokenTransferTransactionData,\n  isTransferFundsValid\n} from '../../utils';\nimport AddressInput from '../Input/Address.vue';\nimport AmountInput from '../Input/Amount.vue';\nimport TokensModal from './TokensModal.vue';\n\nconst props = defineProps<{\n  network: Network;\n  tokens: Token[];\n  transaction: TransferFundsTransaction;\n  setTransactionAsInvalid: () => void;\n}>();\n\nconst emit = defineEmits<{\n  updateTransaction: [transaction: TransferFundsTransaction];\n}>();\n\nconst amount = ref(props.transaction.amount ?? '');\nconst recipient = ref(props.transaction.recipient ?? '');\nconst tokens = ref<Token[]>(props.tokens);\n\nconst selectedTokenAddress = ref<Token['address']>(\n  props.transaction?.token?.address ?? 'main'\n);\n\nconst selectedToken = computed(\n  () =>\n    tokens.value.find(token => token.address === selectedTokenAddress.value) ??\n    tokens.value.find(token => token.address === 'main') ??\n    tokens.value[0]\n);\n\nconst isTokenModalOpen = ref(false);\n\nfunction updateTransaction() {\n  try {\n    if (\n      !isTransferFundsValid({\n        amount: amount.value,\n        recipient: recipient.value,\n        token: selectedToken.value\n      })\n    ) {\n      throw new Error('Validation error');\n    }\n    const data =\n      selectedToken.value.address === 'main'\n        ? '0x'\n        : getERC20TokenTransferTransactionData(recipient.value, amount.value);\n    const transaction = createTransferFundsTransaction({\n      data,\n      amount: amount.value,\n      recipient: recipient.value,\n      token: selectedToken.value\n    });\n    emit('updateTransaction', transaction);\n  } catch {\n    props.setTransactionAsInvalid();\n  }\n}\n\nfunction openModal() {\n  isTokenModalOpen.value = true;\n}\n\nwatch(recipient, updateTransaction);\nwatch(amount, updateTransaction);\nwatch(selectedTokenAddress, updateTransaction);\n</script>\n\n<template>\n  <TuneButton\n    class=\"mb-2 flex w-full flex-row items-center justify-between !px-3\"\n    @click=\"openModal()\"\n  >\n    <div class=\"flex flex-row space-x-2\">\n      <span class=\"text-skin-text\">{{ $t('safeSnap.asset') }}</span>\n      <AvatarToken\n        :address=\"\n          selectedToken.address === 'main'\n            ? ETH_CONTRACT\n            : selectedToken.address\n        \"\n        class=\"ml-2\"\n        v-if=\"selectedToken\"\n      />\n      <span v-if=\"selectedToken\">{{ selectedToken.symbol }}</span>\n      <span v-if=\"selectedToken\">\n        {{\n          selectedToken.address === 'main'\n            ? ''\n            : `(${shorten(selectedToken.address)})`\n        }}\n      </span>\n    </div>\n    <i-ho-chevron-down class=\"text-xs text-skin-link\" />\n  </TuneButton>\n\n  <div class=\"space-y-2\">\n    <AddressInput v-model=\"recipient\" :label=\"$t('safeSnap.to')\" />\n    <AmountInput\n      :enforcePositiveValue=\"true\"\n      :key=\"selectedToken?.decimals\"\n      v-model=\"amount\"\n      :label=\"$t('safeSnap.amount')\"\n      :decimals=\"selectedToken?.decimals\"\n    />\n  </div>\n\n  <teleport to=\"#modal\">\n    <TokensModal\n      :tokens=\"tokens\"\n      :token-address=\"selectedTokenAddress\"\n      :open=\"isTokenModalOpen\"\n      :network=\"network\"\n      @token-address=\"selectedTokenAddress = $event\"\n      @close=\"isTokenModalOpen = false\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/components/TransactionBuilder/TransferNFT.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\nimport { NFT, Network, TransferNftTransaction } from '../../types';\nimport {\n  createTransferNftTransaction,\n  getERC721TokenTransferTransactionData,\n  isTransferNftValid\n} from '../../utils';\nimport AddressInput from '../Input/Address.vue';\n\nconst props = defineProps<{\n  network: Network;\n  collectables: NFT[];\n  transaction: TransferNftTransaction;\n  safeAddress: string;\n  setTransactionAsInvalid: () => void;\n}>();\n\nconst emit = defineEmits<{\n  updateTransaction: [transaction: TransferNftTransaction];\n}>();\n\nconst recipient = ref(props.transaction.recipient ?? '');\nconst selectedCollectableAddress = ref(\n  props.transaction.collectable?.address ?? ''\n);\n\nconst selectedCollectable = computed(() => {\n  return props.collectables.find(\n    collectable => collectable?.address === selectedCollectableAddress.value\n  );\n});\n\nfunction updateTransaction() {\n  try {\n    if (!selectedCollectable.value) {\n      throw new Error('No token selected');\n    }\n    if (\n      !isTransferNftValid({\n        recipient: recipient.value,\n        collectable: selectedCollectable.value\n      })\n    ) {\n      throw new Error('Validation error');\n    }\n\n    const data = getERC721TokenTransferTransactionData(\n      props.safeAddress,\n      recipient.value,\n      selectedCollectable.value.id\n    );\n\n    const transaction = createTransferNftTransaction({\n      data,\n      recipient: recipient.value,\n      collectable: selectedCollectable.value\n    });\n    emit('updateTransaction', transaction);\n  } catch {\n    props.setTransactionAsInvalid();\n  }\n}\n\nwatch(recipient, updateTransaction);\nwatch(selectedCollectableAddress, updateTransaction);\n</script>\n\n<template>\n  <UiSelect v-model=\"selectedCollectableAddress\">\n    <template #label>{{ $t('safeSnap.asset') }}</template>\n    <template\n      v-if=\"\n        selectedCollectable &&\n        (selectedCollectable.imageUri || selectedCollectable.logoUri)\n      \"\n      #image\n    >\n      <img\n        :src=\"selectedCollectable.imageUri || selectedCollectable.logoUri\"\n        alt=\"\"\n        class=\"tokenImage\"\n      />\n    </template>\n    <option v-if=\"!collectables.length\" disabled selected>\n      - {{ $t('safeSnap.noCollectibles') }} -\n    </option>\n    <option\n      v-for=\"(collectable, index) in collectables\"\n      :key=\"index\"\n      :value=\"collectable.address\"\n    >\n      {{ collectable.name ?? collectable.tokenName }} #{{\n        shorten(collectable.id, 10)\n      }}\n    </option>\n  </UiSelect>\n\n  <AddressInput\n    v-model=\"recipient\"\n    :input-props=\"{\n      required: true\n    }\"\n    :label=\"$t('safeSnap.to')\"\n  />\n</template>\n"
  },
  {
    "path": "src/plugins/oSnap/constants.ts",
    "content": "export const safePrefixes = {\n  1: 'eth',\n  2: 'exp',\n  3: 'rop',\n  4: 'rin',\n  5: 'gor',\n  6: 'kot',\n  7: 'tch',\n  8: 'ubq',\n  9: 'tubq',\n  10: 'oeth',\n  11: 'meta',\n  12: 'kal',\n  13: 'dstg',\n  14: 'flr',\n  15: 'diode',\n  16: 'cflr',\n  17: 'tfi',\n  18: 'TST',\n  19: 'sgb',\n  20: 'esc',\n  21: 'esct',\n  22: 'eladid',\n  23: 'eladidt',\n  24: 'kardiachain',\n  25: 'cro',\n  26: 'L1test',\n  27: 'shib',\n  28: 'BobaRinkeby',\n  29: 'L1',\n  30: 'rsk',\n  31: 'trsk',\n  32: 'GooDT',\n  33: 'GooD',\n  34: 'dth',\n  35: 'tbwg',\n  36: 'dx',\n  37: 'xpla',\n  38: 'val',\n  39: 'u2u',\n  40: 'TelosEVM',\n  41: 'TelosEVMTestnet',\n  42: 'lukso',\n  43: 'pangolin',\n  44: 'crab',\n  45: 'pangoro',\n  46: 'darwinia',\n  47: 'aic',\n  48: 'etmp',\n  49: 'etmpTest',\n  50: 'xdc',\n  51: 'txdc',\n  52: 'cet',\n  53: 'tcet',\n  54: 'OP',\n  55: 'ZYX',\n  56: 'bnb',\n  57: 'sys',\n  58: 'OntologyMainnet',\n  59: 'eos-legacy',\n  60: 'go',\n  61: 'etc',\n  62: 'tetc',\n  63: 'metc',\n  64: 'ellaism',\n  65: 'tokt',\n  66: 'okt',\n  67: 'dbm',\n  68: 'SO1',\n  69: 'okov',\n  70: 'hsc',\n  71: 'cfxtest',\n  72: 'dxc',\n  73: 'FNCY',\n  74: 'idchain',\n  75: 'DSC',\n  76: 'mix',\n  77: 'spoa',\n  78: 'primuschain',\n  79: 'zenith',\n  80: 'GeneChain',\n  81: 'joc',\n  82: 'Meter',\n  83: 'MeterTest',\n  84: 'linqto-devnet',\n  85: 'gttest',\n  86: 'gt',\n  87: 'nnw',\n  88: 'tomo',\n  89: 'tomot',\n  90: 'gar-s0',\n  91: 'gar-s1',\n  92: 'gar-s2',\n  93: 'gar-s3',\n  94: 'sdlt',\n  95: 'camdl',\n  96: 'bkc',\n  97: 'bnbt',\n  98: 'six',\n  99: 'poa',\n  100: 'gno',\n  101: 'eti',\n  102: 'tw3g',\n  103: 'WLC',\n  104: 'tklc',\n  105: 'dw3g',\n  106: 'vlx',\n  107: 'ntn',\n  108: 'TT',\n  109: 'shibariumecosystem',\n  110: 'xpr',\n  111: 'ETL',\n  112: 'coinbit',\n  113: 'deh',\n  114: 'c2flr',\n  115: 'debank-testnet',\n  116: 'debank-mainnet',\n  117: 'auptick',\n  118: 'arcology',\n  119: 'enuls',\n  120: 'enulst',\n  121: 'REAL',\n  122: 'fuse',\n  123: 'spark',\n  124: 'dwu',\n  125: 'OYchainTestnet',\n  126: 'OYchainMainnet',\n  127: 'feth',\n  128: 'heco',\n  134: 'rlc',\n  135: 'AlyxTestnet',\n  136: 'deam',\n  137: 'matic',\n  138: 'dfio-meta-main',\n  139: 'woop',\n  141: 'OPtest',\n  142: 'dax',\n  144: 'PHI',\n  148: 'shimmerevm-mainnet',\n  150: 'sixt',\n  151: 'rbn',\n  152: 'rbn-devnet',\n  153: 'rbn-testnet',\n  154: 'rbn-tge',\n  155: 'tenet-testnet',\n  156: 'obe',\n  160: 'eva',\n  161: 'wall-e',\n  162: 'tpht',\n  163: 'pht',\n  165: 'omni_testnet',\n  167: 'atoshi',\n  168: 'aioz',\n  169: 'manta',\n  170: 'hoosmartchain',\n  172: 'resil',\n  180: 'ame',\n  186: 'Seele',\n  188: 'BMC',\n  189: 'BMCT',\n  193: 'cem',\n  195: 'tokb',\n  196: 'okb',\n  197: 'NEUTR',\n  198: 'bit',\n  199: 'BTT',\n  200: 'aox',\n  201: 'moactest',\n  204: 'obnb',\n  208: 'utx',\n  210: 'BTN',\n  211: 'EDI',\n  212: 'makalu',\n  217: 'SIN2',\n  218: 'SO1-old',\n  222: 'ASK',\n  225: 'LA',\n  226: 'TLA',\n  230: 'SDX',\n  236: 'deamtest',\n  242: 'plgchain',\n  246: 'ewt',\n  248: 'OAS',\n  250: 'ftm',\n  255: 'kroma',\n  256: 'hecot',\n  258: 'setm',\n  259: 'neon',\n  262: 'SUR',\n  269: 'hpb',\n  271: 'EGONm',\n  274: 'lachain',\n  280: 'zksync-goerli',\n  288: 'Boba',\n  291: 'orderly',\n  295: 'hedera-mainnet',\n  296: 'hedera-testnet',\n  297: 'hedera-previewnet',\n  298: 'hedera-localnet',\n  300: 'ogc',\n  301: 'Bobaopera',\n  303: 'ncnt',\n  309: 'wyz',\n  311: 'omax',\n  313: 'ncn',\n  314: 'filecoin',\n  321: 'kcs',\n  322: 'kcst',\n  324: 'zksync',\n  333: 'w3q',\n  335: 'DFKTEST',\n  336: 'sdn',\n  338: 'tcro',\n  345: 'YVM',\n  361: 'theta-mainnet',\n  363: 'theta-sapphire',\n  364: 'theta-amber',\n  365: 'theta-testnet',\n  369: 'pls',\n  371: 'tCNT',\n  385: 'lisinski',\n  400: 'hpn',\n  401: 'ozo_tst',\n  411: 'pepe',\n  416: 'SX',\n  418: 'latestnet',\n  420: 'ogor',\n  424: 'PGN',\n  427: 'zeeth',\n  443: 'obs-testnet',\n  444: 'synapse-sepolia',\n  456: 'arzio',\n  462: 'tarea',\n  499: 'rupx',\n  500: 'Camino',\n  501: 'Columbus',\n  512: 'aac',\n  513: 'aact',\n  516: 'gz-mainnet',\n  520: 'xt',\n  529: 'fire',\n  530: 'FxCore',\n  534: 'CNDL',\n  542: 'PAW',\n  555: 'CLASS',\n  558: 'tao',\n  568: 'dct',\n  570: 'sys-rollux',\n  588: 'metis-stardust',\n  592: 'astr',\n  595: 'maca',\n  596: 'tkar',\n  597: 'taca',\n  599: 'metis-goerli',\n  600: 'mesh-chain-testnet',\n  601: 'PEER',\n  614: 'glq',\n  634: 'avocado',\n  647: 'SX-Testnet',\n  648: 'ace',\n  666: 'pixie-chain-testnet',\n  667: 'laos',\n  668: 'junca',\n  669: 'juncat',\n  686: 'kar',\n  700: 'SNS',\n  707: 'bcs',\n  708: 'tbcs',\n  710: 'fury',\n  719: 'shibarium',\n  721: 'LYC',\n  740: 'tcanto',\n  741: 'vsct',\n  742: 'SPAY',\n  766: 'qom',\n  776: 'opc',\n  777: 'cth',\n  786: 'maal',\n  787: 'aca',\n  788: 'taero',\n  789: 'peth',\n  800: 'LUCID',\n  803: 'haic',\n  808: 'PFTEST',\n  813: 'meer',\n  818: 'BOC',\n  820: 'clo',\n  821: 'tclo',\n  841: 'tara',\n  842: 'taratest',\n  859: 'zeethdev',\n  868: 'FSCMainnet',\n  876: 'BNKEN',\n  877: 'DXT',\n  880: 'ambros',\n  888: 'wan',\n  900: 'gar-test-s0',\n  901: 'gar-test-s1',\n  902: 'gar-test-s2',\n  903: 'gar-test-s3',\n  909: 'PF',\n  910: 'DBONE',\n  917: 'tfire',\n  919: 'modesep',\n  927: 'ydk',\n  940: 'tpls',\n  941: 't2bpls',\n  942: 't3pls',\n  943: 't4pls',\n  956: 'munode',\n  963: 'btc20',\n  970: 'ccn',\n  971: 'Huygens',\n  972: 'Ascraeus',\n  977: 'yeti',\n  980: 'top_evm',\n  985: 'memochain',\n  989: 'top',\n  990: 'ELm',\n  997: '5ire',\n  998: 'ln',\n  999: 'twan',\n  1000: 'gton',\n  1001: 'Baobab',\n  1002: 'kai',\n  1003: 'tet',\n  1004: 't-ekta',\n  1007: 'tnew',\n  1008: 'eun',\n  1010: 'EVC',\n  1012: 'new',\n  1022: 'sku',\n  1023: 'tclv',\n  1024: 'clv',\n  1028: 'tbtt',\n  1030: 'cfx',\n  1031: 'prx',\n  1038: 'bronos-testnet',\n  1039: 'bronos-mainnet',\n  1071: 'shimmerevm-testnet-deprecated',\n  1072: 'shimmerevm-testnet',\n  1079: 'mintara-testnet',\n  1080: 'mintara',\n  1088: 'metis-andromeda',\n  1089: 'humans',\n  1099: 'moac',\n  1101: 'zkevm',\n  1107: 'tblxq',\n  1108: 'blxq',\n  1111: 'wemix',\n  1112: 'twemix',\n  1115: 'tcore',\n  1116: 'core',\n  1117: 'DOGSm',\n  1130: 'DFI',\n  1131: 'DFI-T',\n  1133: 'changi',\n  1138: 'ASARt',\n  1139: 'MATH',\n  1140: 'tMATH',\n  1149: 'Plexchain',\n  1170: 'auoc',\n  1177: 'sht',\n  1197: 'iora',\n  1201: 'avis',\n  1202: 'wtt',\n  1213: 'popcat',\n  1214: 'enter',\n  1229: 'xzo',\n  1230: 'UltronTestnet',\n  1231: 'UtronMainnet',\n  1234: 'step',\n  1243: 'ARC',\n  1244: 'TARC',\n  1246: 'om',\n  1252: 'CICT',\n  1280: 'HO',\n  1284: 'mbeam',\n  1285: 'mriver',\n  1286: 'mrock-old',\n  1287: 'mbase',\n  1288: 'mrock',\n  1291: 'swtr',\n  1294: 'Bobabeam',\n  1297: 'Bobabase',\n  1311: 'TDOS',\n  1314: 'alyx',\n  1319: 'aia',\n  1320: 'aiatestnet',\n  1337: 'geth',\n  1338: 'ELST',\n  1339: 'ELSM',\n  1353: 'CIC',\n  1369: 'zafic',\n  1379: 'KLC',\n  1388: 'ASAR',\n  1392: 'mun',\n  1402: 'zkevmtest',\n  1422: 'testnet-zkEVM-mango-pre-audit-upgraded',\n  1433: 'RIK',\n  1440: 'LAS',\n  1442: 'testnet-zkEVM-mango',\n  1452: 'gil',\n  1455: 'CTEX',\n  1501: 'chainx',\n  1506: 'Sherpax',\n  1507: 'SherpaxTestnet',\n  1515: 'beagle',\n  1559: 'tenet',\n  1618: 'cate',\n  1620: 'ath',\n  1657: 'bta',\n  1662: 'Yuma',\n  1663: 'Gobi',\n  1688: 'LUDAN',\n  1701: 'AnytypeChain',\n  1707: 'TBSI',\n  1708: 'tTBSI',\n  1718: 'PCM',\n  1773: 'TeaParty',\n  1777: 'gauss',\n  1804: 'kerleano',\n  1807: 'rAna',\n  1818: 'cube',\n  1819: 'cubet',\n  1856: 'tsf',\n  1875: 'wbt',\n  1881: 'gitshockchain',\n  1890: 'lightlink_phoenix',\n  1891: 'lightlink_pegasus',\n  1898: 'boya',\n  1907: 'bitci',\n  1908: 'tbitci',\n  1945: 'onus-testnet',\n  1951: 'dchain-mainnet',\n  1954: 'Dexilla',\n  1967: 'mtc',\n  1969: 'tscs',\n  1970: 'scs',\n  1971: 'atlr',\n  1975: 'onus-mainnet',\n  1984: 'euntest',\n  1985: 'satoshie',\n  1986: 'satoshie_testnet',\n  1987: 'egem',\n  1994: 'ekta',\n  1995: 'edx',\n  2000: 'dc',\n  2001: 'milkAda',\n  2002: 'milkALGO',\n  2008: 'cloudwalk_testnet',\n  2009: 'cloudwalk_mainnet',\n  2016: 'NetZm',\n  2018: 'pmint_dev',\n  2019: 'pmint_test',\n  2020: 'pmint',\n  2021: 'edg',\n  2022: 'edgt',\n  2023: 'taycan-testnet',\n  2025: 'rpg',\n  2031: 'cfg',\n  2032: 'ncfg',\n  2037: 'kiwi',\n  2038: 'shraptest',\n  2043: 'otp',\n  2044: 'Shrapnel',\n  2047: 'stos-testnet',\n  2048: 'stos-mainnet',\n  2049: 'movo',\n  2077: 'QKA',\n  2088: 'air',\n  2089: 'algl',\n  2100: 'eco',\n  2101: 'esp',\n  2109: 'exn',\n  2122: 'Metad',\n  2124: 'MEU',\n  2137: 'bigsb',\n  2138: 'dfio-meta-test',\n  2151: 'boa',\n  2152: 'fra',\n  2153: 'findora-testnet',\n  2154: 'findora-forge',\n  2199: 'msn',\n  2202: 'ABNm',\n  2203: 'BTC',\n  2213: 'evanesco',\n  2221: 'tkava',\n  2222: 'kava',\n  2223: 'VChain',\n  2241: 'KRST',\n  2300: 'bomb',\n  2309: 'arevia',\n  2323: 'sma',\n  2330: 'alt',\n  2332: 'smam',\n  2357: 'deprecated-kroma-sepolia',\n  2358: 'kroma-sepolia',\n  2399: 'bombt',\n  2400: 'TCGV',\n  2415: 'xodex',\n  2484: 'u2u_nebulas',\n  2559: 'ktoc',\n  2569: 'tpc',\n  2606: 'pocrnet',\n  2611: 'REDLC',\n  2612: 'EZChain',\n  2613: 'Fuji-EZChain',\n  2625: 'twbt',\n  2710: 'tmorph',\n  2888: 'BobaGoerli',\n  2999: 'bty',\n  3000: 'cennz-r',\n  3001: 'cennz-n',\n  3003: 'cau',\n  3011: '3ULL',\n  3031: 'ORL',\n  3068: 'bfc',\n  3141: 'filecoin-hyperspace',\n  3269: 'dubx',\n  3270: 'testdubx',\n  3306: 'debounce-devnet',\n  3331: 'zcrbeach',\n  3333: 'w3q-t',\n  3334: 'w3q-g',\n  3400: 'prb',\n  3434: 'SCAIt',\n  3500: 'prbtestnet',\n  3501: 'jfin',\n  3601: 'pando-mainnet',\n  3602: 'pando-testnet',\n  3636: 'BTCt',\n  3637: 'BTCm',\n  3666: 'jouleverse',\n  3690: 'btx',\n  3693: 'empire',\n  3698: 'SPCt',\n  3699: 'SPCm',\n  3701: 'xplatest',\n  3737: 'csb',\n  3797: 'alv',\n  3888: 'kalymainnet',\n  3889: 'kalytestnet',\n  3912: 'drac',\n  3939: 'dost',\n  3966: 'dyno',\n  3967: 'tdyno',\n  3999: 'ycc',\n  4000: 'ozo',\n  4001: 'PERIUM',\n  4002: 'tftm',\n  4051: 'BobaoperaTestnet',\n  4061: 'Nahmii3Mainnet',\n  4062: 'Nahmii3Testnet',\n  4090: 'Oasis',\n  4096: 'BNIt',\n  4099: 'BNIm',\n  4102: 'aioz-testnet',\n  4139: 'humans_testnet',\n  4141: 'TPBXt',\n  4181: 'PHIv1',\n  4201: 'lukso-testnet',\n  4242: 'nexi',\n  4328: 'BobaFujiTestnet',\n  4337: 'beam',\n  4444: 'html',\n  4460: 'orderlyl2',\n  4689: 'iotex-mainnet',\n  4690: 'iotex-testnet',\n  4759: 'TESTMEV',\n  4777: 'TBXN',\n  4918: 'txvm',\n  4919: 'xvm',\n  4999: 'BXN',\n  5000: 'mantle',\n  5001: 'mantle-testnet',\n  5002: 'treasurenet',\n  5005: 'tntest',\n  5165: 'ftn',\n  5177: 'tlc',\n  5197: 'es',\n  5234: 'hmnd',\n  5290: '_old_fire',\n  5315: 'UZMI',\n  5353: 'ttrn',\n  5522: 'VEX',\n  5551: 'Nahmii',\n  5553: 'NahmiiTestnet',\n  5555: 'cverse',\n  5611: 'obnbt',\n  5616: 'ARCT',\n  5678: 'TanssiCC',\n  5700: 'tsys',\n  5729: 'hik',\n  5758: 'satst',\n  5777: 'ggui',\n  5851: 'OntologyTestnet',\n  5869: 'rbd',\n  6065: 'TRESTEST',\n  6066: 'TRESMAIN',\n  6102: 'cascadia',\n  6118: 'UPTN-TEST',\n  6119: 'UPTN',\n  6502: 'Peerpay',\n  6552: 'SRC-test',\n  6565: 'fox',\n  6626: 'pixie-chain',\n  6688: 'iris',\n  6789: 'STANDm',\n  6969: 'tombchain',\n  6999: 'psc',\n  7000: 'zetachain-mainnet',\n  7001: 'zetachain-athens',\n  7027: 'ELLA',\n  7070: 'planq',\n  7171: 'bitrock',\n  7331: 'kly',\n  7332: 'EON',\n  7341: 'shyft',\n  7484: 'raba',\n  7518: 'MEV',\n  7575: 'tadil',\n  7576: 'adil',\n  7668: 'trn-mainnet',\n  7672: 'trn-porcini',\n  7700: 'canto',\n  7701: 'TestnetCanto',\n  7771: 'tbitrock',\n  7777: 'RiseOfTheWarbotsTestnet',\n  7878: 'tscas',\n  7895: 'ard',\n  7979: 'dos',\n  8000: 'teleport',\n  8001: 'teleport-testnet',\n  8029: 'mdgl',\n  12357: 'rei',\n  7363: 'dnd',\n  9052: 'acrechain',\n  8080: 'Liberty10',\n  8081: 'Liberty20',\n  8082: 'Sphinx10',\n  8086: 'BitEth',\n  8098: 'StreamuX',\n  8131: 'meertest',\n  8132: 'meermix',\n  8133: 'meerpriv',\n  8134: 'amana',\n  8135: 'flana',\n  8136: 'mizana',\n  8181: 'tBOC',\n  8217: 'Cypress',\n  8272: 'BTON',\n  8285: 'Kortho',\n  8387: 'fuck',\n  8453: 'base',\n  8654: 'toki',\n  8655: 'toki-testnet',\n  8723: 'olo',\n  8724: 'tolo',\n  8738: 'alph',\n  8768: 'tmy',\n  8848: 'maro',\n  8880: 'unq',\n  8881: 'qtz',\n  8882: 'opl',\n  8883: 'sph',\n  8888: 'XANAChain',\n  8889: 'vsc',\n  8898: 'mmt',\n  8899: 'jbc',\n  8989: 'gmmt',\n  8995: 'berg',\n  9000: 'evmos-testnet',\n  9001: 'evmos',\n  9012: 'brb',\n  9100: 'GENEC',\n  9170: '_old_tfire',\n  9223: 'COF',\n  9339: 'DOGSt',\n  9527: 'trpg',\n  9528: 'QETTest',\n  9559: 'testneon',\n  9700: 'MainnetDev',\n  9728: 'BobaBnbTestnet',\n  9768: 'NetZt',\n  9779: 'pn',\n  9790: 'carbon',\n  9792: 'carbon-testnet',\n  9818: 'tIMP',\n  9819: 'IMP',\n  9977: 'tMIND',\n  9990: 'AGNG',\n  9996: 'MIND',\n  9997: 'alt-testnet',\n  9999: 'myn',\n  10000: 'smartbch',\n  10001: 'smartbchtest',\n  10024: 'gon',\n  10081: 'joct',\n  10086: 'SJ',\n  10101: 'GEN',\n  10200: 'chi',\n  10201: 'PWR',\n  10243: 'aa',\n  10248: '0xt',\n  10395: 'TWLC',\n  10507: 'Jade',\n  10508: 'Snow',\n  10823: 'CCP',\n  10946: 'quadrans',\n  10947: 'quadranstestnet',\n  11110: 'astra',\n  11111: 'WAGMI',\n  11115: 'astra-testnet',\n  11119: 'hbit',\n  11235: 'ISLM',\n  11437: 'shyftt',\n  11612: 'SRDXt',\n  11888: 'SAN',\n  11891: 'Arianee',\n  12009: 'sats',\n  12051: 'tZERO',\n  12052: 'ZERO',\n  12123: 'BRC',\n  12306: 'fibo',\n  12321: 'blgchain',\n  12345: 'steptest',\n  12611: 'astrzk',\n  12715: 'tRIK',\n  12890: 'tqnet',\n  13000: 'SPS',\n  13308: 'Credit',\n  13337: 'beam-testnet',\n  13381: 'Phoenix',\n  13812: 'sus',\n  14000: 'SPS-Test',\n  14853: 'hmnd-t5',\n  15551: 'loop',\n  15555: 'TrustTestnet',\n  15557: 'eos-testnet',\n  16000: 'mtt',\n  16001: 'mtttest',\n  16507: 'Genesys',\n  16688: 'nyancat',\n  16718: 'airdao',\n  16888: 'tivar',\n  17000: 'holesky',\n  17171: 'G8Cm',\n  17180: 'PCT',\n  17777: 'eos',\n  18000: 'ZKST',\n  18122: 'STN',\n  18159: 'pom',\n  18181: 'G8Ct',\n  18686: 'MXCzkEVM',\n  19011: 'HMV',\n  19845: 'btcix',\n  20001: 'Camelark',\n  20729: 'CLOTestnet',\n  20736: 'p12',\n  21337: 'cennz-a',\n  21816: 'omc',\n  22023: 'SFL',\n  22040: 'airdao-test',\n  22222: 'NAUTCHAIN',\n  22776: 'map',\n  23006: 'ABNt',\n  23118: 'opside',\n  23294: 'sapphire',\n  23295: 'sapphire-testnet',\n  24484: 'web',\n  24734: 'mintme',\n  25888: 'GOLDT',\n  25925: 'bkct',\n  26026: 'frm',\n  26600: 'HTZ',\n  26863: 'OAC',\n  28528: 'obgor',\n  30067: 'Piece',\n  30103: 'ceri',\n  31102: 'esn',\n  31223: 'CLDTX',\n  31224: 'CLD',\n  31337: 'got',\n  31415: 'filecoin-wallaby',\n  32520: 'Brise',\n  32659: 'fsn',\n  32769: 'zil',\n  32990: 'zil-isolated-server',\n  33101: 'zil-testnet',\n  33333: 'avs',\n  33385: 'zil-devnet',\n  33469: 'zq2-devnet',\n  35011: 'j2o',\n  35441: 'q',\n  35443: 'q-testnet',\n  38400: 'cmrpg',\n  38401: 'ttrpg',\n  39797: 'nrg',\n  39815: 'oho',\n  41500: 'ox-beta',\n  42069: 'PC',\n  42161: 'arb1',\n  42170: 'arb-nova',\n  42220: 'celo',\n  42261: 'emerald-testnet',\n  42262: 'emerald',\n  42801: 'GST',\n  42888: 'keth',\n  43110: 'avaeth',\n  43113: 'Fuji',\n  43114: 'avax',\n  43288: 'bobaavax',\n  44444: 'FREN',\n  44787: 'ALFA',\n  45000: 'AutobahnNetwork',\n  46688: 'tfsn',\n  47805: 'REI',\n  49049: 'floripa',\n  49088: 'tbfc',\n  49797: 'tnrg',\n  50001: 'LOE',\n  50021: 'tgton',\n  51178: 'Opside-Testnet',\n  51712: 'SRDXm',\n  53935: 'DFK',\n  54211: 'ISLMT',\n  54321: 'ToronetTestnet',\n  55004: 'teth',\n  55555: 'reichain',\n  55556: 'trei',\n  56288: 'BobaBnb',\n  56789: 'VELO',\n  57000: 'tsys-rollux',\n  58008: 'sepPGN',\n  59140: 'linea-testnet',\n  59144: 'linea',\n  60000: 'TKM-test0',\n  60001: 'TKM-test1',\n  60002: 'TKM-test2',\n  60103: 'TKM-test103',\n  61800: 'aium-dev',\n  61803: 'Etica',\n  61916: 'DoKEN',\n  62320: 'BKLV',\n  62621: 'mtv',\n  63000: 'ecs',\n  63001: 'ecs-testnet',\n  65450: 'SRC',\n  67390: 'mcl',\n  67588: 'Cosmic',\n  69420: 'cndr',\n  70000: 'TKM0',\n  70001: 'TKM1',\n  70002: 'TKM2',\n  70103: 'TKM103',\n  71111: 'GuapX',\n  71393: 'ckb',\n  71401: 'gw-testnet-v1',\n  71402: 'gw-mainnet-v1',\n  73799: 'vt',\n  73927: 'mvm',\n  75000: 'resin',\n  77238: 'fnc',\n  77612: 'vscm',\n  77777: 'Toronet',\n  78110: 'firenze',\n  78281: 'dfly',\n  78430: 'amplify',\n  78431: 'bulletin',\n  78432: 'conduit',\n  79879: 'STANDt',\n  80001: 'maticmum',\n  81341: 'amanatest',\n  81342: 'amanamix',\n  81343: 'amanapriv',\n  81351: 'flanatest',\n  81352: 'flanamix',\n  81353: 'flanapriv',\n  81361: 'mizanatest',\n  81362: 'mizanamix',\n  81363: 'mizanapriv',\n  81720: 'qnet',\n  84531: 'basegor',\n  84886: 'Aerie',\n  85449: 'Cyber',\n  88002: 'NAUTTest',\n  88880: 'chz',\n  88888: 'ivar',\n  90210: 'bvhl',\n  91002: 'NAUT',\n  92001: 'lambda-testnet',\n  96970: 'mantis',\n  97288: 'BobaBnbOld',\n  99099: 'ELt',\n  99998: 'usctest',\n  99999: 'usc',\n  100000: 'qkc-r',\n  100001: 'qkc-s0',\n  100002: 'qkc-s1',\n  100003: 'qkc-s2',\n  100004: 'qkc-s3',\n  100005: 'qkc-s4',\n  100006: 'qkc-s5',\n  100007: 'qkc-s6',\n  100008: 'qkc-s7',\n  100009: 'vechain',\n  100010: 'vechain-testnet',\n  100100: 'chi1',\n  101010: 'SVRNt',\n  103090: 'CRFI',\n  108801: 'bro',\n  110000: 'qkc-d-r',\n  110001: 'qkc-d-s0',\n  110002: 'qkc-d-s1',\n  110003: 'qkc-d-s2',\n  110004: 'qkc-d-s3',\n  110005: 'qkc-d-s4',\n  110006: 'qkc-d-s5',\n  110007: 'qkc-d-s6',\n  110008: 'qkc-d-s7',\n  111000: 'testsbr',\n  111111: 'sbr',\n  112358: 'metao',\n  123456: 'dadil',\n  131419: 'ETND',\n  142857: 'ICPlaza',\n  167004: 'taiko-a2',\n  167005: 'taiko-l2',\n  167006: 'taiko-l3',\n  167007: 'tko-jolnir',\n  188881: 'condor',\n  200101: 'milkTAda',\n  200202: 'milkTAlgo',\n  200625: 'aka',\n  201018: 'alaya',\n  201030: 'alayadev',\n  201804: 'myth',\n  202020: 'tDSC',\n  202624: 'twl-jellie',\n  210425: 'platon',\n  220315: 'mas',\n  221230: 'reap',\n  221231: 'reap-testnet',\n  224168: 'TAFECO',\n  230315: 'hsktest',\n  234666: 'hym',\n  246529: 'ats',\n  246785: 'atstau',\n  247253: 'saakuru-testnet',\n  256256: 'cmp-mainnet',\n  266256: 'gz-testnet',\n  271271: 'EGONt',\n  281121: 'SoChain',\n  314159: 'filecoin-calibration',\n  330844: 'tc',\n  333331: 'avst',\n  333666: 'oonetest',\n  333777: 'oonedev',\n  333888: 'sparta',\n  333999: 'olympus',\n  355113: 'Bitfinity',\n  373737: 'hap-testnet',\n  381931: 'metal',\n  381932: 'Tahoe',\n  404040: 'TPBXm',\n  420420: 'KEK',\n  420666: 'tKEK',\n  420692: 'alterium',\n  421611: 'arb-rinkeby',\n  421613: 'arb-goerli',\n  421614: 'arb-sep',\n  424242: 'fastexTestnet',\n  431140: 'markr-go',\n  432201: 'dexalot-testnet',\n  432204: 'dexalot',\n  444900: 'wlkt',\n  471100: 'psep',\n  474142: 'oc',\n  512512: 'cmp',\n  513100: 'ethf',\n  534351: 'scr-sepolia',\n  534352: 'scr',\n  534353: 'scr-alpha',\n  534354: 'scr-prealpha',\n  534849: 'shi',\n  535037: 'BESC',\n  622277: 'rth',\n  641230: 'BRNKC',\n  651940: 'ALL',\n  666666: 'vpioneer',\n  751230: 'BRNKCTEST',\n  761412: 'Miexs',\n  776877: 'mdlrm',\n  800001: 'octa',\n  827431: 'CURVEm',\n  846000: 'bloqs4good',\n  888888: 'vision',\n  900000: 'psc-s0',\n  910000: 'psc-t-s0',\n  920000: 'psc-d-s0',\n  920001: 'psc-d-s1',\n  923018: 'tFNCY',\n  955305: 'elv',\n  1261120: 'azktn',\n  1313114: 'etho',\n  1313500: 'xero',\n  1337702: 'kintsugi',\n  1337802: 'kiln',\n  1337803: 'zhejiang',\n  2021398: 'dbk',\n  2099156: 'plian-mainnet',\n  2203181: 'platondev',\n  2206132: 'platondev2',\n  3141592: 'filecoin-butterfly',\n  3441005: 'mantaTestnet',\n  4000003: 'alt-zerogas',\n  4281033: 'worldscal',\n  5167003: 'MXC',\n  5555555: 'imversed',\n  5555558: 'imversed-testnet',\n  7225878: 'saakuru',\n  7355310: 'vsl',\n  7668378: 'tqom',\n  7762959: 'music',\n  7777777: 'zora',\n  8007736: 'plian-mainnet-l2',\n  8794598: 'hap',\n  8888881: 'quarix-testnet',\n  8888888: 'quarix',\n  10067275: 'plian-testnet-l2',\n  10101010: 'SVRNm',\n  11155111: 'sep',\n  13371337: 'tpep',\n  14288640: 'anduschain-mainnet',\n  16658437: 'plian-testnet',\n  18289463: 'ilt',\n  20180430: 'spectrum',\n  20181205: 'qki',\n  20201022: 'pg',\n  22052002: 'xlon',\n  27082017: 'exlvolta',\n  27082022: 'exl',\n  28945486: 'auxi',\n  29032022: 'fla',\n  31415926: 'filecoin-local',\n  35855456: 'JOYS',\n  43214913: 'mais',\n  61717561: 'aqua',\n  65010000: 'bakerloo-0',\n  65100000: 'piccadilly-0',\n  88888888: 'team',\n  99415706: 'TOYS',\n  192837465: 'GTH',\n  222000222: 'kanazawa',\n  245022926: 'neonevm-devnet',\n  245022934: 'neonevm-mainnet',\n  245022940: 'neonevm-testnet',\n  278611351: 'razor',\n  311752642: 'oneledger',\n  333000333: 'meld',\n  344106930: 'calypso-testnet',\n  356256156: 'tGTH',\n  486217935: 'dGTH',\n  503129905: 'nebula-staging',\n  1122334455: 'ipos',\n  1146703430: 'cyb',\n  1273227453: 'human-mainnet',\n  1313161554: 'aurora',\n  1313161555: 'aurora-testnet',\n  1313161556: 'aurora-betanet',\n  1351057110: 'chaos-tenet',\n  1380996178: 'rptr',\n  1482601649: 'nebula-mainnet',\n  1564830818: 'calypso-mainnet',\n  1666600000: 'hmy-s0',\n  1666600001: 'hmy-s1',\n  1666600002: 'hmy-s2',\n  1666600003: 'hmy-s3',\n  1666700000: 'hmy-b-s0',\n  1666700001: 'hmy-b-s1',\n  1666900000: 'hmy-ps-s0',\n  1666900001: 'hmy-ps-s1',\n  2021121117: 'hop',\n  2046399126: 'europa',\n  2863311531: 'a8',\n  3125659152: 'pirl',\n  4216137055: 'frankenstein',\n  11297108099: 'tpalm',\n  11297108109: 'palm',\n  111222333444: 'alphabet',\n  197710212030: 'ntt',\n  197710212031: 'ntt-haradev',\n  383414847825: 'zeniq',\n  666301171999: 'ipdc',\n  6022140761023: 'mole',\n  868455272153094: 'gw-testnet-v1-deprecated'\n} as const;\n\nexport const EXPLORER_API_URLS = {\n  '1': 'https://api.etherscan.io/api',\n  '5': 'https://api-goerli.etherscan.io/api',\n  '10': 'https://api-optimistic.etherscan.io/api',\n  '100': 'https://gnosis.blockscout.com/api',\n  '73799': 'https://volta-explorer.energyweb.org/api',\n  '246': 'https://explorer.energyweb.org/api',\n  '137': 'https://api.polygonscan.com/api',\n  '56': 'https://api.bscscan.com/api',\n  '42161': 'https://api.arbiscan.io/api',\n  '8453' : 'https://api.basescan.org/api',\n  // '1116': Add 'https://openapi.coredao.org/api' if API key requirement is removed\n  '11155111': 'https://api-sepolia.etherscan.io/api',\n} as const;\n\nexport const GNOSIS_SAFE_TRANSACTION_API_URLS = {\n  '1': 'https://safe-transaction-mainnet.safe.global/api',\n  '5': 'https://safe-transaction-goerli.safe.global/api',\n  '10': 'https://safe-transaction-optimism.safe.global/api',\n  '100': 'https://safe-transaction-gnosis-chain.safe.global/api',\n  '8453': 'https://safe-transaction-base.safe.global/api',\n  '73799': 'https://safe-transaction-volta.safe.global/api',\n  '246': 'https://safe-transaction-ewc.safe.global/api',\n  '137': 'https://safe-transaction-polygon.safe.global/api',\n  '56': 'https://safe-transaction-bsc.safe.global/api',\n  '42161': 'https://safe-transaction-arbitrum.safe.global/api',\n  '1116': 'https://safetx.coredao.org/api',\n  '11155111': 'https://safe-transaction-sepolia.safe.global/api',\n} as const;\n\nexport const SAFE_APP_URLS = {\n  '1': 'https://app.safe.global/apps/open',\n  '5': 'https://app.safe.global/apps/open',\n  '100': 'https://app.safe.global/apps/open',\n  '73799': 'https://app.safe.global/apps/open',\n  '246': 'https://app.safe.global/apps/open',\n  '137': 'https://app.safe.global/apps/open',\n  '56': 'https://app.safe.global/apps/open',\n  '42161': 'https://app.safe.global/apps/open',\n  '1116': 'https://safe.coredao.org/apps/open',\n  '8453': 'https://app.safe.global/apps/open',\n  '11155111': 'https://app.safe.global/apps/open',\n} as const;\n\n// ABIs\nexport const OPTIMISTIC_GOVERNOR_ABI = [\n  'constructor(address _finder, address _owner, address _collateral, uint256 _bondAmount, string _rules, bytes32 _identifier, uint64 _liveness)',\n  'error NotIERC165Compliant(address guard_)',\n  'event AvatarSet(address indexed previousAvatar, address indexed newAvatar)',\n  'event ChangedGuard(address guard)',\n  'event Initialized(uint8 version)',\n  'event OptimisticGovernorDeployed(address indexed owner, address indexed avatar, address target)',\n  'event OptimisticOracleChanged(address indexed newOptimisticOracleV3)',\n  'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)',\n  'event ProposalDeleted(bytes32 indexed proposalHash, bytes32 indexed assertionId)',\n  'event ProposalExecuted(bytes32 indexed proposalHash, bytes32 indexed assertionId)',\n  'event SetCollateralAndBond(address indexed collateral, uint256 indexed bondAmount)',\n  'event SetEscalationManager(address indexed escalationManager)',\n  'event SetIdentifier(bytes32 indexed identifier)',\n  'event SetLiveness(uint64 indexed liveness)',\n  'event SetRules(string rules)',\n  'event TargetSet(address indexed previousTarget, address indexed newTarget)',\n  'event TransactionExecuted(bytes32 indexed proposalHash, bytes32 indexed assertionId, uint256 indexed transactionIndex)',\n  'event TransactionsProposed(address indexed proposer, uint256 indexed proposalTime, bytes32 indexed assertionId, tuple(tuple(address to, uint8 operation, uint256 value, bytes data)[] transactions, uint256 requestTime) proposal, bytes32 proposalHash, bytes explanation, string rules, uint256 challengeWindowEnds)',\n  'function EXPLANATION_KEY() view returns (bytes)',\n  'function PROPOSAL_HASH_KEY() view returns (bytes)',\n  'function RULES_KEY() view returns (bytes)',\n  'function assertionDisputedCallback(bytes32 assertionId)',\n  'function assertionIds(bytes32) view returns (bytes32)',\n  'function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully)',\n  'function avatar() view returns (address)',\n  'function bondAmount() view returns (uint256)',\n  'function collateral() view returns (address)',\n  'function deleteProposalOnUpgrade(bytes32 proposalHash)',\n  'function escalationManager() view returns (address)',\n  'function executeProposal(tuple(address to, uint8 operation, uint256 value, bytes data)[] transactions)',\n  'function finder() view returns (address)',\n  'function getCurrentTime() view returns (uint256)',\n  'function getGuard() view returns (address _guard)',\n  'function getProposalBond() view returns (uint256)',\n  'function guard() view returns (address)',\n  'function identifier() view returns (bytes32)',\n  'function liveness() view returns (uint64)',\n  'function optimisticOracleV3() view returns (address)',\n  'function owner() view returns (address)',\n  'function proposalHashes(bytes32) view returns (bytes32)',\n  'function proposeTransactions(tuple(address to, uint8 operation, uint256 value, bytes data)[] transactions, bytes explanation)',\n  'function renounceOwnership()',\n  'function rules() view returns (string)',\n  'function setAvatar(address _avatar)',\n  'function setCollateralAndBond(address _collateral, uint256 _bondAmount)',\n  'function setEscalationManager(address _escalationManager)',\n  'function setGuard(address _guard)',\n  'function setIdentifier(bytes32 _identifier)',\n  'function setLiveness(uint64 _liveness)',\n  'function setRules(string _rules)',\n  'function setTarget(address _target)',\n  'function setUp(bytes initializeParams)',\n  'function sync()',\n  'function target() view returns (address)',\n  'function transferOwnership(address newOwner)'\n] as const;\n\nexport const OPTIMISTIC_ORACLE_V3_ABI = [\n  'constructor(address _finder, address _defaultCurrency, uint64 _defaultLiveness)',\n  'event AdminPropertiesSet(address defaultCurrency, uint64 defaultLiveness, uint256 burnedBondPercentage)',\n  'event AssertionDisputed(bytes32 indexed assertionId, address indexed caller, address indexed disputer)',\n  'event AssertionMade(bytes32 indexed assertionId, bytes32 domainId, bytes claim, address indexed asserter, address callbackRecipient, address escalationManager, address caller, uint64 expirationTime, address currency, uint256 bond, bytes32 indexed identifier)',\n  'event AssertionSettled(bytes32 indexed assertionId, address indexed bondRecipient, bool disputed, bool settlementResolution, address settleCaller)',\n  'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)',\n  'function assertTruth(bytes claim, address asserter, address callbackRecipient, address escalationManager, uint64 liveness, address currency, uint256 bond, bytes32 identifier, bytes32 domainId) returns (bytes32 assertionId)',\n  'function assertTruthWithDefaults(bytes claim, address asserter) returns (bytes32)',\n  'function assertions(bytes32) view returns (tuple(bool arbitrateViaEscalationManager, bool discardOracle, bool validateDisputers, address assertingCaller, address escalationManager) escalationManagerSettings, address asserter, uint64 assertionTime, bool settled, address currency, uint64 expirationTime, bool settlementResolution, bytes32 domainId, bytes32 identifier, uint256 bond, address callbackRecipient, address disputer)',\n  'function burnedBondPercentage() view returns (uint256)',\n  'function cachedCurrencies(address) view returns (bool isWhitelisted, uint256 finalFee)',\n  'function cachedIdentifiers(bytes32) view returns (bool)',\n  'function cachedOracle() view returns (address)',\n  'function defaultCurrency() view returns (address)',\n  'function defaultIdentifier() view returns (bytes32)',\n  'function defaultLiveness() view returns (uint64)',\n  'function disputeAssertion(bytes32 assertionId, address disputer)',\n  'function finder() view returns (address)',\n  'function getAssertion(bytes32 assertionId) view returns (tuple(tuple(bool arbitrateViaEscalationManager, bool discardOracle, bool validateDisputers, address assertingCaller, address escalationManager) escalationManagerSettings, address asserter, uint64 assertionTime, bool settled, address currency, uint64 expirationTime, bool settlementResolution, bytes32 domainId, bytes32 identifier, uint256 bond, address callbackRecipient, address disputer))',\n  'function getAssertionResult(bytes32 assertionId) view returns (bool)',\n  'function getCurrentTime() view returns (uint256)',\n  'function getMinimumBond(address currency) view returns (uint256)',\n  'function multicall(bytes[] data) returns (bytes[] results)',\n  'function numericalTrue() view returns (int256)',\n  'function owner() view returns (address)',\n  'function renounceOwnership()',\n  'function setAdminProperties(address _defaultCurrency, uint64 _defaultLiveness, uint256 _burnedBondPercentage)',\n  'function settleAndGetAssertionResult(bytes32 assertionId) returns (bool)',\n  'function settleAssertion(bytes32 assertionId)',\n  'function stampAssertion(bytes32 assertionId) view returns (bytes)',\n  'function syncUmaParams(bytes32 identifier, address currency)',\n  'function transferOwnership(address newOwner)'\n] as const;\n\nexport const VOTING_ABI = [\n  'constructor(uint128 _emissionRate, uint64 _unstakeCoolDown, uint64 _phaseLength, uint32 _maxRolls, uint32 _maxRequestsPerRound, uint128 _gat, uint64 _spat, address _votingToken, address _finder, address _slashingLibrary, address _previousVotingContract)',\n  'event DelegateSet(address indexed delegator, address indexed delegate)',\n  'event DelegatorSet(address indexed delegate, address indexed delegator)',\n  'event EncryptedVote(address indexed caller, uint32 indexed roundId, bytes32 indexed identifier, uint256 time, bytes ancillaryData, bytes encryptedVote)',\n  'event ExecutedUnstake(address indexed voter, uint128 tokensSent, uint128 voterStake)',\n  'event GatAndSpatChanged(uint128 newGat, uint64 newSpat)',\n  'event MaxRequestsPerRoundChanged(uint32 newMaxRequestsPerRound)',\n  'event MaxRollsChanged(uint32 newMaxRolls)',\n  'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)',\n  'event RequestAdded(address indexed requester, uint32 indexed roundId, bytes32 indexed identifier, uint256 time, bytes ancillaryData, bool isGovernance)',\n  'event RequestDeleted(bytes32 indexed identifier, uint256 indexed time, bytes ancillaryData, uint32 rollCount)',\n  'event RequestResolved(uint32 indexed roundId, uint256 indexed resolvedPriceRequestIndex, bytes32 indexed identifier, uint256 time, bytes ancillaryData, int256 price)',\n  'event RequestRolled(bytes32 indexed identifier, uint256 indexed time, bytes ancillaryData, uint32 rollCount)',\n  'event RequestedUnstake(address indexed voter, uint128 amount, uint64 unstakeTime, uint128 voterStake)',\n  'event SetNewEmissionRate(uint128 newEmissionRate)',\n  'event SetNewUnstakeCoolDown(uint64 newUnstakeCoolDown)',\n  'event SlashingLibraryChanged(address newAddress)',\n  'event Staked(address indexed voter, address indexed from, uint128 amount, uint128 voterStake, uint128 voterPendingUnstake, uint128 cumulativeStake)',\n  'event UpdatedReward(address indexed voter, uint128 newReward, uint64 lastUpdateTime)',\n  'event VoteCommitted(address indexed voter, address indexed caller, uint32 roundId, bytes32 indexed identifier, uint256 time, bytes ancillaryData)',\n  'event VoteRevealed(address indexed voter, address indexed caller, uint32 roundId, bytes32 indexed identifier, uint256 time, bytes ancillaryData, int256 price, uint128 numTokens)',\n  'event VoterSlashApplied(address indexed voter, int128 slashedTokens, uint128 postStake)',\n  'event VoterSlashed(address indexed voter, uint256 indexed requestIndex, int128 slashedTokens)',\n  'event VotingContractMigrated(address newAddress)',\n  'event WithdrawnRewards(address indexed voter, address indexed delegate, uint128 tokensWithdrawn)',\n  'function ANCILLARY_BYTES_LIMIT() view returns (uint256)',\n  'function UINT64_MAX() view returns (uint64)',\n  'function commitAndEmitEncryptedVote(bytes32 identifier, uint256 time, bytes ancillaryData, bytes32 hash, bytes encryptedVote)',\n  'function commitVote(bytes32 identifier, uint256 time, bytes ancillaryData, bytes32 hash)',\n  'function cumulativeStake() view returns (uint128)',\n  'function currentActiveRequests() view returns (bool)',\n  'function delegateToStaker(address) view returns (address)',\n  'function emissionRate() view returns (uint128)',\n  'function executeUnstake()',\n  'function finder() view returns (address)',\n  'function gat() view returns (uint128)',\n  'function getCurrentRoundId() view returns (uint32)',\n  'function getCurrentTime() view returns (uint256)',\n  'function getNumberOfPriceRequests() view returns (uint256 numberPendingPriceRequests, uint256 numberResolvedPriceRequests)',\n  'function getNumberOfPriceRequestsPostUpdate() returns (uint256 numberPendingPriceRequests, uint256 numberResolvedPriceRequests)',\n  'function getPendingRequests() view returns (tuple(uint32 lastVotingRound, bool isGovernance, uint64 time, uint32 rollCount, bytes32 identifier, bytes ancillaryData)[])',\n  'function getPrice(bytes32 identifier, uint256 time, bytes ancillaryData) view returns (int256)',\n  'function getPrice(bytes32 identifier, uint256 time) view returns (int256)',\n  'function getPriceRequestStatuses(tuple(bytes32 identifier, uint256 time, bytes ancillaryData)[] requests) view returns (tuple(uint8 status, uint32 lastVotingRound)[])',\n  'function getRoundEndTime(uint256 roundId) view returns (uint256)',\n  'function getRoundIdToVoteOnRequest(uint32 targetRoundId) view returns (uint32)',\n  'function getVotePhase() view returns (uint8)',\n  'function getVoterFromDelegate(address caller) view returns (address)',\n  'function getVoterParticipation(uint256 requestIndex, uint32 lastVotingRound, address voter) view returns (uint8)',\n  'function getVoterPendingStake(address voter, uint32 roundId) view returns (uint128)',\n  'function getVoterStakePostUpdate(address voter) returns (uint128)',\n  'function hasPrice(bytes32 identifier, uint256 time) view returns (bool)',\n  'function hasPrice(bytes32 identifier, uint256 time, bytes ancillaryData) view returns (bool)',\n  'function lastRoundIdProcessed() view returns (uint32)',\n  'function lastUpdateTime() view returns (uint64)',\n  'function maxRequestsPerRound() view returns (uint32)',\n  'function maxRolls() view returns (uint32)',\n  'function migratedAddress() view returns (address)',\n  'function multicall(bytes[] data) returns (bytes[] results)',\n  'function nextPendingIndexToProcess() view returns (uint64)',\n  'function outstandingRewards(address voter) view returns (uint256)',\n  'function owner() view returns (address)',\n  'function pendingPriceRequestsIds(uint256) view returns (bytes32)',\n  'function previousVotingContract() view returns (address)',\n  'function priceRequests(bytes32) view returns (uint32 lastVotingRound, bool isGovernance, uint64 time, uint32 rollCount, bytes32 identifier, bytes ancillaryData)',\n  'function processResolvablePriceRequests()',\n  'function processResolvablePriceRequestsRange(uint64 maxTraversals)',\n  'function renounceOwnership()',\n  'function requestGovernanceAction(bytes32 identifier, uint256 time, bytes ancillaryData)',\n  'function requestPrice(bytes32 identifier, uint256 time, bytes ancillaryData)',\n  'function requestPrice(bytes32 identifier, uint256 time)',\n  'function requestSlashingTrackers(uint256 requestIndex) view returns (tuple(uint256 wrongVoteSlashPerToken, uint256 noVoteSlashPerToken, uint256 totalSlashed, uint256 totalCorrectVotes, uint32 lastVotingRound))',\n  'function requestUnstake(uint128 amount)',\n  'function resolvedPriceRequestIds(uint256) view returns (bytes32)',\n  'function retrieveRewardsOnMigratedVotingContract(address voter, uint256 roundId, tuple(bytes32 identifier, uint256 time, bytes ancillaryData)[] toRetrieve) returns (uint256)',\n  'function revealVote(bytes32 identifier, uint256 time, int256 price, bytes ancillaryData, int256 salt)',\n  'function rewardPerToken() view returns (uint256)',\n  'function rewardPerTokenStored() view returns (uint128)',\n  'function rounds(uint256) view returns (address slashingLibrary, uint128 minParticipationRequirement, uint128 minAgreementRequirement, uint128 cumulativeStakeAtRound, uint32 numberOfRequestsToVote)',\n  'function setDelegate(address delegate)',\n  'function setDelegator(address delegator)',\n  'function setEmissionRate(uint128 newEmissionRate)',\n  'function setGatAndSpat(uint128 newGat, uint64 newSpat)',\n  'function setMaxRequestPerRound(uint32 newMaxRequestsPerRound)',\n  'function setMaxRolls(uint32 newMaxRolls)',\n  'function setMigrated(address newVotingAddress)',\n  'function setSlashingLibrary(address _newSlashingLibrary)',\n  'function setUnstakeCoolDown(uint64 newUnstakeCoolDown)',\n  'function slashingLibrary() view returns (address)',\n  'function spat() view returns (uint64)',\n  'function stake(uint128 amount)',\n  'function stakeTo(address recipient, uint128 amount)',\n  'function transferOwnership(address newOwner)',\n  'function unstakeCoolDown() view returns (uint64)',\n  'function updateTrackers(address voter)',\n  'function updateTrackersRange(address voter, uint64 maxTraversals)',\n  'function voteTiming() view returns (uint256 phaseLength)',\n  'function voterStakes(address) view returns (uint128 stake, uint128 pendingUnstake, uint128 rewardsPaidPerToken, uint128 outstandingRewards, int128 unappliedSlash, uint64 nextIndexToProcess, uint64 unstakeTime, address delegate)',\n  'function votingToken() view returns (address)',\n  'function withdrawAndRestake() returns (uint128)',\n  'function withdrawRewards() returns (uint128)'\n] as const;\n\nexport const UMA_FINDER_ABI = [\n  'event InterfaceImplementationChanged(bytes32 indexed interfaceName, address indexed newImplementationAddress)',\n  'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)',\n  'function changeImplementationAddress(bytes32 interfaceName, address implementationAddress)',\n  'function getImplementationAddress(bytes32 interfaceName) view returns (address)',\n  'function interfacesImplemented(bytes32) view returns (address)',\n  'function owner() view returns (address)',\n  'function renounceOwnership()',\n  'function transferOwnership(address newOwner)'\n] as const;\n\nexport const ERC20_ABI = [\n  //Read functions\n  'function balanceOf(address account) view returns (uint256)',\n  'function decimals() view returns (uint32)',\n  'function symbol() view returns (string)',\n  'function allowance(address owner, address spender) external view returns (uint256)',\n\n  // Write functions\n  'function approve(address spender, uint256 value) external returns (bool)',\n  'function transfer(address recipient, uint256 amount) public virtual override returns (bool)'\n] as const;\n\nexport const ERC721_ABI = [\n  'function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable'\n] as const;\n\n// to potentially cut down on event ranges we query, hard code some deploy blocks for contracts\nexport type ContractData = {\n  network: string;\n  name: string;\n  address?: string;\n  deployBlock?: number;\n  subgraph?: string;\n};\n// contract addresses pulled from https://github.com/UMAprotocol/protocol/tree/master/packages/core/networks\nexport const contractData = [\n  {\n    // mainnet\n    network: '1',\n    name: 'OptimisticOracleV3',\n    address: '0xfb55F43fB9F48F63f9269DB7Dde3BbBe1ebDC0dE',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/Bm3ytsa1YvcyFJahdfQQgscFQVCcMvoXujzkd3Cz6aof',\n    deployBlock: 16636058\n  },\n  {\n    // goerli\n    network: '5',\n    name: 'OptimisticOracleV3',\n    address: '0x9923D42eF695B5dd9911D05Ac944d4cAca3c4EAB',\n    subgraph:\n      'https://api.thegraph.com/subgraphs/name/md0x/goerli-optimistic-oracle-v3',\n    deployBlock: 8497481\n  },\n  {\n    // optimism\n    network: '10',\n    name: 'OptimisticOracleV3',\n    address: '0x072819Bb43B50E7A251c64411e7aA362ce82803B',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/FyJQyV5TLNeowZrL6kLUTB9JNPyWQNCNXJoxJWGEtBcn',\n    deployBlock: 74537234\n  },\n  {\n    // gnosis\n    network: '100',\n    name: 'OptimisticOracleV3',\n    address: '0x22A9AaAC9c3184f68C7B7C95b1300C4B1D2fB95C',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/9K2nctaB2rAh7Cgzx3wKtdHwWoEeEQ9AThGATak6Ngm9',\n    deployBlock: 27087150\n  },\n  {\n    // polygon\n    network: '137',\n    name: 'OptimisticOracleV3',\n    address: '0x5953f2538F613E05bAED8A5AeFa8e6622467AD3D',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/7KWbDhUE5Eqcfn3LXQtLbCfJLkNucnhzJLpi2jKhqNuf',\n    deployBlock: 39331673\n  },\n  {\n    //arbitrum\n    network: '42161',\n    name: 'OptimisticOracleV3',\n    address: '0xa6147867264374F324524E30C02C331cF28aa879',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/9wpkM5tHgJBHYTzKEKk4tK8a7q6MimfS9QnW7Japa8hW',\n    deployBlock: 61236565\n  },\n  {\n    // avalanche\n    network: '43114',\n    name: 'OptimisticOracleV3',\n    address: '0xa4199d73ae206d49c966cF16c58436851f87d47F',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/3k8gzGzTMV2vDZAGBFM2q642SUyVbE31bAUL8SjFQkre',\n    deployBlock: 27816737\n  },\n  {\n    // core\n    network: '1116',\n    name: 'OptimisticOracleV3',\n    address: '0xD84ACa67d683aF7702705141b3C7E57e4e5e7726',\n    subgraph:\n      'https://thegraph.coredao.org/subgraphs/name/umaprotocol/core-optimistic-oracle-v3',\n    deployBlock: 11341063\n  },\n  {\n    // base\n    network: '8453',\n    name: 'OptimisticOracleV3',\n    address: '0x2aBf1Bd76655de80eDB3086114315Eec75AF500c',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/2Q4i8YgVZd6bAmLyDxXgrKPL2B6QwySiEUqbTyQ4vm4C',\n    deployBlock: 12066343\n  },\n  {\n    // sepolia\n    network: '11155111',\n    name: 'OptimisticOracleV3',\n    address: '0xFd9e2642a170aDD10F53Ee14a93FcF2F31924944',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/78JbrMhcC9CVDZHDADvNcyhRrrccTJG4vCVBztyer1Xa',\n    deployBlock: 5421195\n  },\n  {\n    // mainnet\n    network: '1',\n    name: 'OptimisticGovernor',\n    address: '0x28CeBFE94a03DbCA9d17143e9d2Bd1155DC26D5d',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/DQpwhiRSPQJEuc8y6ZBGsFfNpfwFQ8NjmjLmfv8kBkLu',\n    deployBlock: 16890621\n  },\n  // Keep in mind, OG addresses are not the module addresses for each individual space, these addresses typically\n  // are not used, but are here for reference.\n  {\n    //goerli\n    network: '5',\n    name: 'OptimisticGovernor',\n    address: '0x07a7Be7AA4AaD42696A17e974486cb64A4daC47b',\n    deployBlock: 8700589,\n    subgraph:\n      'https://api.thegraph.com/subgraphs/name/md0x/goerli-optimistic-governor'\n  },\n  {\n    // optimism\n    network: '10',\n    name: 'OptimisticGovernor',\n    address: '0x357fe84E438B3150d2F68AB9167bdb8f881f3b9A',\n    deployBlock: 83168480,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/Fd5RvSfkajAJ8Mi9sPxFSMVPFf56SDivDQW3ocqTAW5'\n  },\n  {\n    // gnosis\n    network: '100',\n    name: 'OptimisticGovernor',\n    deployBlock: 27102135,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/RrkjZ6wTgLJkcjX68auzrEZHMRYwDx8kR5sFQQy4Phz'\n  },\n  {\n    // polygon\n    network: '137',\n    name: 'OptimisticGovernor',\n    address: '0x3Cc4b597E9c3f51288c6Cd0c087DC14c3FfdD966',\n    deployBlock: 40677035,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/7L2JM14PnZgxGnRX7xaz54zWS6KVK6ZqVRCxEKJrJTDG'\n  },\n  {\n    // arbitrum\n    network: '42161',\n    name: 'OptimisticGovernor',\n    address: '0x30679ca4ea452d3df8a6c255a806e08810321763',\n    deployBlock: 72850751,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/BfK867bnkQhnx1LspA99ypqiqxbAReQ92aZz66Ubv4tz'\n  },\n  {\n    // avalanche\n    network: '43114',\n    name: 'OptimisticGovernor',\n    address: '0xEF8b46765ae805537053C59f826C3aD61924Db45',\n    deployBlock: 28050250,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/5F8875fmvtnv8Vv4aeedUcwNWjuxUg54aTHdapFuMJi3'\n  },\n  {\n    // core\n    network: '1116',\n    name: 'OptimisticGovernor',\n    address: '0x596Fd6A5A185c67aBD1c845b39f593fBA9C233aa',\n    deployBlock: 11341122,\n    subgraph:\n    'https://thegraph.coredao.org/subgraphs/name/umaprotocol/core-optimistic-governor'\n  },\n  {\n    // base\n    network: '8453',\n    name: 'OptimisticGovernor',\n    address: '0x80bCA2E1c272239AdFDCdc87779BC8Af6E12e633',\n    deployBlock: 13062540,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/H1WyWZqh5pHebWRDCXs7GhvGj7XznSP7arPY6pYcCqLn'\n  },\n  {\n    // sepolia\n    network: '11155111',\n    name: 'OptimisticGovernor',\n    address: '0x40153DdFAd90C49dbE3F5c9F96f2a5B25ec67461',\n    deployBlock: 5421242,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/5pwrjCkpcpCd79k9MBS5yVgnsHQiw6afvXUfzqHjdRFw'\n  },\n] as const;\n\nexport const transactionTypes = [\n  'transferFunds',\n  'transferNFT',\n  'contractInteraction',\n  'raw',\n  'safeImport'\n] as const;\n\nexport const solidityZeroHexString =\n  '0x0000000000000000000000000000000000000000000000000000000000000000';\n"
  },
  {
    "path": "src/plugins/oSnap/plugin.json",
    "content": "{\n  \"name\": \"oSnap by UMA\",\n  \"version\": \"1.0.0\",\n  \"author\": \"UMA\",\n  \"website\": \"https://uma.xyz/osnap\",\n  \"icon\": \"https://osnap.uma.xyz/osnap-icon.svg\",\n  \"defaults\": {\n    \"proposal\": {\n      \"safe\": null\n    }\n  }\n}\n"
  },
  {
    "path": "src/plugins/oSnap/types.ts",
    "content": "import { BigNumber } from '@ethersproject/bignumber';\nimport { Contract, Event } from '@ethersproject/contracts';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { safePrefixes, transactionTypes } from './constants';\nimport { FunctionFragment } from '@ethersproject/abi';\n\n/**\n * Represents details about the chains that snapshot supports as described in the `networks` json file.\n *\n * This corresponds to one of the chain ids that snapshot supports.\n *\n * @see `@snapshot-labs/snapshot.js/src/networks.json`.\n */\ntype Networks = typeof networks;\n\n/**\n * One of the supported networks as defined in `@snapshot-labs/snapshot.js/src/networks.json`.\n * @see Networks\n */\nexport type Network = keyof Networks;\n\n/**\n * The supported network prefixes as defined in EIP-3770 used by Safe apps.\n * @see https://eips.ethereum.org/EIPS/eip-3770\n */\nexport type SafeNetworkPrefixes = typeof safePrefixes;\n\n/**\n * One of the supported network prefixes as defined in EIP-3770 used by Safe apps.\n * @see SafeNetworkPrefixes\n */\nexport type SafeNetworkPrefix = SafeNetworkPrefixes[keyof SafeNetworkPrefixes];\n\n/**\n * Represents the four different types of transactions that oSnap supports.\n *\n * - Transfer Funds\n * - Transfer NFT (also called Collectable)\n * - Contract Interaction\n * - Raw\n */\nexport type TransactionTypes = typeof transactionTypes;\n\n/**\n * One of the four different types of transactions that oSnap supports.\n *\n * - Transfer Funds\n * - Transfer NFT (also called Collectable)\n * - Contract Interaction\n * - Raw\n */\nexport type TransactionType = TransactionTypes[number];\n\n/**\n * The Optimistic Governor contract requires transactions that it executes to be formatted like this.\n *\n * This corresponds to the values found in the `Transaction` struct in the Optimistic Governor contract.\n *\n * @see https://github.com/UMAprotocol/protocol/blob/master/packages/core/contracts/optimistic-governor/implementation/OptimisticGovernor.sol\n *\n * NOTE: Since we don't support the contract's `delegatecall` feature in oSnap, the value for `operation` is always 0.\n */\nexport type OptimisticGovernorTransaction = [\n  to: string,\n  operation: 0,\n  value: string,\n  data: string\n];\n\n/**\n * Represents the data associated with the different types of transactions that oSnap supports.\n *\n * This is a discriminated union of the four object types that represent the data associated with a given transaction. The union discriminates on the `type` property of the objects.\n *\n * @see TransactionTypes\n */\nexport type Transaction =\n  | RawTransaction\n  | ContractInteractionTransaction\n  | TransferNftTransaction\n  | TransferFundsTransaction\n  | SafeImportTransaction;\n\n/**\n * Represents the fields that all transactions share.\n *\n * All the transaction types inherit from this type, adding their `type` field and any additional fields that they require.\n *\n * NOTE: the `formatted` field is what is actually sent to the Optimistic Governor contract.\n */\nexport type BaseTransaction = {\n  to: string;\n  value: string;\n  data: string;\n  formatted: OptimisticGovernorTransaction;\n  isValid?: boolean;\n};\n/**\n * Represents a transaction that interacts with an arbitrary contract from safe json file import.\n *\n * @field `abi` field is the ABI of the contract that the transaction interacts with, represented as a JSON string.\n *\n * @field `methodName` field is the name of the method on the contract that the transaction calls.\n *\n * @field `parameters` field is an array of strings that represent the parameters that the method takes. NOTE: some methods take arrays or tuples as arguments, so some of these strings in the array may be JSON formatted arrays or tuples.\n */\n\nexport type SafeImportTransaction = BaseTransaction & {\n  type: 'safeImport';\n  abi?: string; // represents partial ABI only\n  method?: FunctionFragment;\n  parameters?: { [key: string]: string };\n};\n\n/**\n * Represents a 'raw' transaction that does not have any additional fields.\n */\nexport type RawTransaction = BaseTransaction & {\n  type: 'raw';\n};\n\n/**\n * Represents a transaction that interacts with an arbitrary contract.\n *\n * @field `abi` field is the ABI of the contract that the transaction interacts with, represented as a JSON string.\n *\n * @field `methodName` field is the name of the method on the contract that the transaction calls.\n *\n * @field `parameters` field is an array of strings that represent the parameters that the method takes. NOTE: some methods take arrays or tuples as arguments, so some of these strings in the array may be JSON formatted arrays or tuples.\n */\nexport type ContractInteractionTransaction = BaseTransaction & {\n  type: 'contractInteraction';\n  abi?: string;\n  methodName?: string;\n  method?: FunctionFragment;\n  parameters?: string[];\n};\n\n/**\n * Represents a transaction that transfers an NFT (also called a Collectable).\n *\n * @field `recipient` field is the address of the recipient of the NFT.\n *\n * @field `collectable` field is the NFT that is being transferred.\n */\nexport type TransferNftTransaction = BaseTransaction & {\n  type: 'transferNFT';\n  recipient?: string;\n  collectable?: NFT;\n};\n\n/**\n * Represents a transaction that transfers funds.\n *\n * @field `amount` field is the amount of funds that are being transferred.\n *\n * @field `recipient` field is the address of the recipient of the funds.\n *\n * @field `token` field is the token that is being transferred.\n */\nexport type TransferFundsTransaction = BaseTransaction & {\n  type: 'transferFunds';\n  amount?: string;\n  recipient?: string;\n  token?: Token;\n};\n\n/**\n * The base type for assets that can be transferred by a transaction.\n *\n * Can represent either an NFT or an ERC20 token.\n *\n * @field `name` field is the name of the asset.\n * @field `address` field is the address of the asset.\n * @field `logoUri` field is the URI of the logo of the asset, if one exists.\n * @field `imageUri` field is the URI of the image of the asset, if one exists.\n *\n * @see https://miyauchi.dev/posts/typescript-literal-hack/ for details about the `(string & {})` syntax.\n */\nexport type Asset = {\n  name: string;\n  address: 'main' | (string & {});\n  logoUri?: string;\n  imageUri?: string;\n};\n\n/**\n * Represents an ERC20 token.\n *\n * @field `symbol` field is the symbol of the token.\n * @field `decimals` field is the number of decimals that the token has.\n * @field `balance` field is the balance of the token that the user has.\n * @field `verified` field is whether or not the token is verified by Gnosis or Snapshot contract.\n * @field `chainId` field is the chain id of the network that the token is on.\n */\nexport type Token = Asset & {\n  symbol: string;\n  decimals: number;\n  balance?: string;\n  verified?: boolean;\n  chainId?: Network;\n};\n\n/**\n * Represents an ERC-721 NFT (also called a Collectable).\n *\n * @field `id` field is the id of the NFT, usually used as the mint number.\n * @field `tokenName` field is the name of the NFT.\n */\nexport type NFT = Asset & {\n  id: string;\n  tokenName?: string;\n};\n\n/**\n * Represents the response from the Gnosis Safe API when fetching the balances of the tokens that the user has. This is immediately transformed after fetching into the `Token` type, which holds both the token and the balance.\n *\n * @field `tokenAddress` field is the address of the token.\n * @field `token` field is the token that the safe has.\n * @field `balance` field is the balance of the token that the user has.\n */\nexport type BalanceResponse = {\n  tokenAddress: string;\n  token: {\n    decimals: number;\n    logoUri: string;\n    name: string;\n    symbol: string;\n  };\n  balance: string;\n};\n\n/**\n * Represents the data associated with a safe.\n *\n * The plugin persists one object with this shape per proposal created. This object holds the `transactions` that the Optimistic Governor contract will execute.\n *\n * @field `safeName` field is the name of the safe.\n * @field `safeAddress` field is the address of the safe.\n * @field `network` field is the id for network that the safe is on.\n * @field `moduleAddress` field is the address of the Optimistic Governor contract that was deployed for this safe.\n * @field `transactions` field is the list of transactions that the Optimistic Governor contract will execute.\n */\nexport type GnosisSafe = {\n  safeName: string;\n  safeAddress: string;\n  network: Network;\n  moduleAddress: string;\n  transactions: Transaction[];\n};\n\n/**\n * Represents the data associated with the plugin.\n *\n * Holds one object with this shape per proposal created. This is the shape of the data that is persisted by the plugin.\n *\n * `safes` is null when first creating a plugin, but is then immediately populated once the user picks a safe.\n *\n * @field `safes` field is the array of safes that the plugin is currently working with.\n */\nexport type OsnapPluginData = MultiSafe;\n\nexport type LegacyOsnapPluginData = SingleSafe;\n\ntype MultiSafe = {\n  safes: GnosisSafe[] | null;\n};\n\ntype SingleSafe = {\n  safe: GnosisSafe | null;\n};\n\n/**\n * Represents the data associated with an assertion on the Optimistic Oracle V3 subgraph.\n *\n * @field `assertionId` field is the id of the assertion.\n * @field `expirationTime` field is the time that the assertion's challenge period ends.\n * @field `assertionHash` field is the transaction hash from when the assertion was made.\n * @field `settlementHash` field is the transaction hash from when the assertion was settled.\n * @field `disputeHash` field is the transaction hash from when the assertion was disputed.\n * @field `assertionLogIndex` field is the log index of the transaction from when the assertion was made.\n * @field `settlementResolution` field is whether or not the assertion was resolved in favor of the asserter.\n */\nexport type AssertionGql = {\n  assertionId: string;\n  expirationTime: string;\n  assertionHash: string;\n  settlementHash: string | null;\n  disputeHash: string | null;\n  assertionLogIndex: string;\n  settlementResolution: boolean | null;\n};\n\n/**\n * Represents the configuration of the Optimistic Governor module contract that was deployed for a given Safe.\n *\n * @field `moduleAddress` field is the address of the specific Optimistic Governor module contract that was deployed for a given Safe.\n * @field `oracleAddress` field is the address of the Optimistic Oracle V3 contract.\n * @field `rules` rules for this Optimistic Governor contract.\n * @field `minimumBond` field is the minimum bond that is required for an assertion to be made on this Optimistic Governor contract.\n * @field `challengePeriod` field is the challenge period that is required for an assertion to be made on this Optimistic Governor contract.\n */\nexport type OGModuleDetails = {\n  moduleAddress: string;\n  oracleAddress: string;\n  rules: string;\n  minimumBond: BigNumber;\n  challengePeriod: BigNumber;\n};\n\n/**\n * Represents the collateral configuration for a given Optimistic Governor contract.\n *\n * @field `erc20Contract` field is the ERC20 contract that is used for collateral.\n * @field `address` field is the address of the ERC20 contract that is used for collateral.\n * @field `symbol` field is the symbol of the ERC20 contract that is used for collateral.\n * @field `decimals` field is the number of decimals that the ERC20 contract that is used for collateral has.\n */\nexport type CollateralDetails = {\n  erc20Contract: Contract;\n  address: string;\n  symbol: string;\n  decimals: BigNumber;\n};\n\n/**\n * Event fired when an assertion is made on the Optimistic Oracle V3 contract.\n *\n * @field `assertionId` field is the id of the assertion.\n * @field `domainId` field is the domain id of the assertion.\n * @field `claim` field is the claim of the assertion.\n * @field `asserter` field is the address of the asserter.\n * @field `callbackRecipient` field is the address of the callback recipient.\n * @field `escalationManager` field is the address of the escalation manager.\n * @field `caller` field is the address of the caller.\n * @field `expirationTime` field is the time that the assertion's challenge period ends.\n * @field `currency` field is the currency that the assertion is made in.\n * @field `bond` field is the bond that is required for the assertion.\n * @field `identifier` field is the identifier of the assertion.\n */\nexport type AssertionMadeEvent = Event & {\n  args: {\n    assertionId: string; // indexed\n    domainId: string;\n    claim: string;\n    asserter: string; // indexed\n    callbackRecipient: string;\n    escalationManager: string;\n    caller: string;\n    expirationTime: BigNumber;\n    currency: string;\n    bond: BigNumber;\n    identifier: string; // indexed\n  };\n};\n\n/**\n * Event fired when transactions are proposed on the Optimistic Governor contract.\n *\n * @field `proposer` field is the address of the proposer.\n * @field `proposalTime` field is the time that the proposal was made.\n * @field `assertionId` field is the id of the assertion.\n * @field `proposal` field is the proposal that was made.\n * @field `proposalHash` field is the hash of the proposal.\n * @field `explanation` field is the explanation of the proposal, which in the case of oSnap is the ipfs url.\n * @field `rules` field is the rules of the proposal.\n * @field `challengeWindowEvents` field is the challenge window events of the proposal.\n */\nexport type TransactionsProposedEvent = Event & {\n  args: {\n    proposer: string; // indexed\n    proposalTime: BigNumber; // indexed\n    assertionId: string; // indexed\n    proposal: {\n      transactions: OptimisticGovernorTransaction[];\n      requestTime: BigNumber;\n    };\n    proposalHash: string;\n    explanation: string;\n    rules: string;\n    challengeWindowEvents: BigNumber;\n  };\n};\n\n/**\n * Event fired when an Optimistic Governor proposal's transactions are executed successfully.\n *\n * @field `proposalHash` field is the hash of the proposal.\n * @field `assertionId` field is the id of the assertion.\n */\nexport type ProposalExecutedEvent = Event & {\n  args: {\n    proposalHash: string; // indexed\n    assertionId: string; // indexed\n  };\n};\n\n/**\n * Represents the transaction hash and log index of an `AssertionMade` event.\n *\n * We need these for generating a link to the assertion on the Optimistic Oracle dapp.\n */\nexport type AssertionTransactionDetails = {\n  assertionHash: string;\n  assertionLogIndex: string;\n};\n\n/**\n * Represents the state of a proposal on the Optimistic Governor contract. When an assertion is associated with the proposal, we also include the assertion transaction hash and log index so that we can create a link to the assertion on the Optimistic Oracle dapp.\n *\n * There are four states that a proposal can be in:\n *\n * - `can-propose-to-og`: The user can propose transactions to the Optimistic Governor contract. This is the initial state of a proposal. We also indicate if this proposal has been disputed in the Oracle, so that we can warn the user to exercise caution and avoid losing their bond.\n *\n * - `in-oo-challenge-period`: The user has proposed transactions to the Optimistic Governor contract, and the proposal is currently in the challenge period on the Optimistic Oracle contract. We also indicate when the challenge period ends, so that we can warn the user to wait until the challenge period ends before proposing new transactions.\n *\n * - `can-request-tx-execution`: The user has proposed transactions to the Optimistic Governor contract, and the challenge period has ended. The user can now request that the Optimistic Governor contract execute the transactions.\n *\n * - `transactions-executed`: The user has proposed transactions to the Optimistic Governor contract, the challenge period has ended, and the transactions have been executed by the Optimistic Governor contract.\n */\nexport type OGProposalState =\n  | {\n      status: 'can-propose-to-og';\n      isDisputed: boolean;\n    }\n  | (AssertionTransactionDetails & {\n      status: 'in-oo-challenge-period';\n      expirationTime: number;\n    })\n  | (AssertionTransactionDetails & {\n      status: 'can-request-tx-execution';\n    })\n  | (AssertionTransactionDetails & {\n      status: 'transactions-executed';\n    });\n\ninterface ResultUrl {\n  url: string; // This is the URL to the simulation result page (public or private).\n  public: boolean; // This is false if the project is not publicly accessible.\n}\n\nexport interface TenderlySimulationResult {\n  id: string;\n  status: boolean; // True if the simulation succeeded, false if it reverted.\n  gasUsed: number;\n  resultUrl: ResultUrl;\n}\n\nexport type ErrorWithMessage = InstanceType<typeof Error> & {\n  message: string;\n};\n\n// predicate for better error handling\nexport function isErrorWithMessage(error: unknown): error is ErrorWithMessage {\n  return error !== null && typeof error === 'object' && 'message' in error;\n}\n\nexport const Status = {\n  IDLE: 'IDLE',\n  LOADING: 'LOADING',\n  SUCCESS: 'SUCCESS',\n  FAIL: 'FAIL',\n  ERROR: 'ERROR'\n} as const;\n\nexport type Status = keyof typeof Status;\n\nexport type SpaceConfigResponse =\n  | {\n      automaticExecution: true;\n    }\n  | {\n      automaticExecution: false;\n      rules: boolean;\n      bondToken: boolean;\n      bondAmount: boolean;\n    };\n\nexport namespace GnosisSafe {\n  export interface ProposedTransaction {\n    id: number;\n    contractInterface: ContractInterface | null;\n    description: {\n      to: string;\n      value: string;\n      customTransactionData?: string;\n      contractMethod?: ContractMethod;\n      contractFieldsValues?: Record<string, string>;\n      contractMethodIndex?: string;\n      nativeCurrencySymbol?: string;\n      networkPrefix?: string;\n    };\n    raw: { to: string; value: string; data: string };\n  }\n\n  export interface ContractInterface {\n    methods: ContractMethod[];\n  }\n\n  export interface Batch {\n    id: number | string;\n    name: string;\n    transactions: ProposedTransaction[];\n  }\n\n  export interface BatchFile {\n    version: string;\n    chainId: string;\n    createdAt: number;\n    meta: BatchFileMeta;\n    transactions: BatchTransaction[];\n  }\n\n  export interface BatchFileMeta {\n    txBuilderVersion?: string;\n    checksum?: string;\n    createdFromSafeAddress?: string;\n    createdFromOwnerAddress?: string;\n    name: string;\n    description?: string;\n  }\n\n  export interface BatchTransaction {\n    to: string;\n    value: string;\n    data?: string;\n    contractMethod?: ContractMethod;\n    contractInputsValues?: { [key: string]: string };\n  }\n\n  export interface ContractMethod {\n    inputs: ContractInput[];\n    name: string;\n    payable: boolean;\n  }\n\n  export interface ContractInput {\n    internalType: string;\n    name: string;\n    type: string;\n    components?: ContractInput[];\n  }\n}\n\nexport type InputTypes =\n  | 'bool'\n  | 'string'\n  | 'address'\n  | Integer\n  | 'bytes'\n  | 'bytes32';\n\nexport type Integer = `int${number}` | `uint${number}`;\n\nexport function isIntegerType(type: InputTypes): type is Integer {\n  return type.includes('int');\n}\n\nexport function nonNullable<T>(value: T): value is NonNullable<T> {\n  return value !== null;\n}\n\n//  for backwards compatibility\nexport function isLegacySingleSafe(\n  pluginData: LegacyOsnapPluginData | OsnapPluginData\n): pluginData is LegacyOsnapPluginData {\n  return 'safe' in pluginData;\n}\n"
  },
  {
    "path": "src/plugins/oSnap/utils/abi.ts",
    "content": "import { FunctionFragment, Interface, ParamType } from '@ethersproject/abi';\nimport { BigNumberish } from '@ethersproject/bignumber';\nimport { memoize } from 'lodash';\nimport { ERC20_ABI, ERC721_ABI, EXPLORER_API_URLS } from '../constants';\nimport {\n  mustBeEthereumAddress,\n  mustBeEthereumContractAddress\n} from './validators';\nimport { GnosisSafe, SafeImportTransaction } from '../types';\nimport { createSafeImportTransaction } from './transactions';\n\n/**\n * Checks if the `parameter` of a contract method `method` takes an array or tuple as input, based on the `baseType` of the parameter.\n *\n * If this is the case, we must parse the value as JSON and verify that it is valid.\n */\nexport function isArrayParameter(parameter: string): boolean {\n  return ['tuple', 'array'].includes(parameter);\n}\n\nconst fetchContractABI = memoize(\n  async (url: string, contractAddress: string) => {\n    const params = new URLSearchParams({\n      module: 'contract',\n      action: 'getAbi',\n      address: contractAddress\n    });\n\n    const response = await fetch(`${url}?${params}`);\n\n    if (!response.ok) {\n      return { status: 0, result: '' };\n    }\n\n    return response.json();\n  },\n  (url, contractAddress) => `${url}_${contractAddress}`\n);\n\n/**\n * Returns the ABI of a contract at the given address\n */\nexport async function getContractABI(\n  network: string,\n  contractAddress: string\n): Promise<string> {\n  const apiUrl = EXPLORER_API_URLS[network as keyof typeof EXPLORER_API_URLS];\n\n  if (!apiUrl) {\n    return '';\n  }\n\n  const isEthereumAddress = mustBeEthereumAddress(contractAddress);\n  const isEthereumContractAddress = await mustBeEthereumContractAddress(\n    network,\n    contractAddress\n  );\n\n  if (!isEthereumAddress || !isEthereumContractAddress) {\n    return '';\n  }\n\n  try {\n    const { result, status } = await fetchContractABI(apiUrl, contractAddress);\n\n    if (status === '0') {\n      return '';\n    }\n\n    return result;\n  } catch (e) {\n    console.error('Failed to retrieve ABI', e);\n    return '';\n  }\n}\n\n/**\n * Checks if a method is a write function.\n *\n * Only write functions can be executed by the Optimistic Governor.\n */\nexport function isWriteFunction(method: FunctionFragment) {\n  if (!method.stateMutability) return true;\n  return !['view', 'pure'].includes(method.stateMutability);\n}\n\n/**\n * Returns the write functions of a contract ABI.\n */\nexport function getABIWriteFunctions(abi: string) {\n  const abiInterface = new Interface(abi);\n  return (\n    abiInterface.fragments\n      // Return only contract's functions\n      .filter(FunctionFragment.isFunctionFragment)\n      .map(FunctionFragment.fromObject)\n      // Return only write functions\n      .filter(isWriteFunction)\n      // Sort by name\n      .sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1))\n  );\n}\n\n/**\n * Handles the extraction of the method's arguments from the `values` array.\n *\n * If the parameter is an array or tuple, we parse the value as JSON.\n */\nexport function extractMethodArgs(values: string[]) {\n  return (param: ParamType, index: number) => {\n    const value = values[index];\n    if (isArrayParameter(param.baseType)) {\n      return JSON.parse(value);\n    }\n    return value;\n  };\n}\n\n/**\n * Encodes the method and parameters of a contract interaction.\n */\nexport function encodeMethodAndParams(\n  abi: string,\n  method: FunctionFragment,\n  values: string[]\n) {\n  const contractInterface = new Interface(abi);\n  const parameterValues = method.inputs.map(extractMethodArgs(values));\n  return contractInterface.encodeFunctionData(method, parameterValues);\n}\n\nexport function transformSafeMethodToFunctionFragment(\n  method: GnosisSafe.ContractMethod\n): FunctionFragment {\n  const fragment = FunctionFragment.from({\n    ...method,\n    type: 'function',\n    stateMutability: method.payable ? 'payable' : 'nonpayable'\n  });\n  return fragment;\n}\n\nexport function initializeSafeImportTransaction(\n  unprocessedTransactions: GnosisSafe.BatchTransaction\n): SafeImportTransaction {\n  return createSafeImportTransaction({\n    type: 'safeImport',\n    to: unprocessedTransactions.to,\n    value: unprocessedTransactions.value,\n    data: unprocessedTransactions?.data ?? '',\n    method: unprocessedTransactions.contractMethod\n      ? transformSafeMethodToFunctionFragment(\n          unprocessedTransactions.contractMethod\n        )\n      : undefined,\n    parameters: unprocessedTransactions.contractInputsValues,\n    formatted: ['', 0, '0', '0x']\n  });\n}\n\nexport function encodeSafeMethodAndParams(\n  method: SafeImportTransaction['method'],\n  params: SafeImportTransaction['parameters']\n) {\n  if (!params || !method) return;\n  const missingParams = Object.values(params).length !== method.inputs.length;\n  if (missingParams) {\n    throw new Error('Some params are missing');\n  }\n  const abiSlice = Array(method);\n  const contractInterface = new Interface(abiSlice);\n\n  const parameterValues = method.inputs.map(input => {\n    const value = params[input.name];\n    if (isArrayParameter(input.baseType)) {\n      return JSON.parse(value);\n    }\n    return value;\n  });\n  return contractInterface.encodeFunctionData(method.name, parameterValues);\n}\n\n/**\n * Returns the transaction data for an ERC20 transfer.\n */\nexport function getERC20TokenTransferTransactionData(\n  recipientAddress: string,\n  amount: BigNumberish\n): string {\n  const contractInterface = new Interface(ERC20_ABI);\n  return contractInterface.encodeFunctionData('transfer', [\n    recipientAddress,\n    amount\n  ]);\n}\n\n/**\n * Returns the transaction data for an ERC721 transfer.\n */\nexport function getERC721TokenTransferTransactionData(\n  fromAddress: string,\n  recipientAddress: string,\n  id: BigNumberish\n): string {\n  const contractInterface = new Interface(ERC721_ABI);\n  return contractInterface.encodeFunctionData('safeTransferFrom', [\n    fromAddress,\n    recipientAddress,\n    id\n  ]);\n}\n"
  },
  {
    "path": "src/plugins/oSnap/utils/coins.ts",
    "content": "import { Network, Token } from '../types';\n\nexport const ETHEREUM_COIN = {\n  name: 'Ether',\n  decimals: 18,\n  symbol: 'ETH',\n  logoUri:\n    'https://safe-transaction-assets.safe.global/chains/1/currency_logo.png',\n  address: 'main'\n} as const;\n\nexport const MATIC_COIN = {\n  name: 'MATIC',\n  decimals: 18,\n  symbol: 'MATIC',\n  address: 'main',\n  logoUri:\n    'https://safe-transaction-assets.safe.global/chains/137/currency_logo.png'\n} as const;\n\nconst EWC_COIN = {\n  name: 'Energy Web Token',\n  symbol: 'EWT',\n  address: 'main',\n  decimals: 18,\n  logoUri:\n    'https://safe-transaction-assets.safe.global/chains/246/currency_logo.png'\n} as const;\n\nconst XDAI_COIN = {\n  name: 'XDAI',\n  symbol: 'XDAI',\n  address: 'main',\n  decimals: 18,\n  logoUri:\n    'https://safe-transaction-assets.safe.global/chains/100/currency_logo.png'\n} as const;\nconst BNB_COIN = {\n  name: 'BNB',\n  symbol: 'BNB',\n  address: 'main',\n  decimals: 18,\n  logoUri:\n    'https://safe-transaction-assets.safe.global/chains/56/currency_logo.png'\n} as const;\nconst CORE_COIN = {\n  name: 'Core',\n  symbol: 'CORE',\n  address: 'main',\n  decimals: 18,\n  logoUri:\n    'https://cloudflare-ipfs.com/ipfs/bafkreigjv5yb7uhlrryzib7j2f73nnwqan2tmfnwjdu26vkk365fyesoiu'\n} as const;\n\nconst OPTIMISM_COIN = {\n  name: 'OPTIMISM',\n  symbol: 'OP',\n  address: 'main',\n  decimals: 18,\n  logoUri:\n    'https://optimistic.etherscan.io/images/svg/brands/optimism.svg?v=24.3.2.1'\n} as const;\n\nexport function getNativeAsset(network: Network) {\n  switch (parseInt(network)) {\n    case 137:\n    case 80001:\n      return MATIC_COIN;\n    case 10:\n      return OPTIMISM_COIN;\n    case 100:\n      return XDAI_COIN;\n    case 246:\n      return EWC_COIN;\n    case 56:\n      return BNB_COIN;\n    case 1116:\n      return CORE_COIN;\n  }\n\n  return ETHEREUM_COIN;\n}\n\nexport function isNativeAsset(token: Token | undefined) {\n  return token ? token.address === 'main' : false;\n}\n"
  },
  {
    "path": "src/plugins/oSnap/utils/events.ts",
    "content": "import { Contract, Event, EventFilter } from '@ethersproject/contracts';\nimport bb from 'bluebird';\n\n// This state is meant for adjusting a start/end block when querying events. Some apis will fail if the range\n// is too big, so the following functions will adjust range dynamically.\nexport type RangeState = {\n  startBlock: number;\n  endBlock: number;\n  maxRange: number;\n  currentRange: number;\n  currentStart: number; // This is the start value you want for your query.\n  currentEnd: number; // this is the end value you want for your query.\n  done: boolean; // Signals we successfully queried the entire range.\n  multiplier?: number; // Multiplier increases or decreases range by this value, depending on success or failure\n};\n\n/**\n * rangeStart. This starts a new range query and sets defaults for state.  Use this as the first call before starting your queries\n *\n * @param {Pick} state\n * @returns {RangeState}\n */\nexport function rangeStart(\n  state: Pick<RangeState, 'startBlock' | 'endBlock' | 'multiplier'> & {\n    maxRange?: number;\n  }\n): RangeState {\n  const { startBlock, endBlock, multiplier = 2 } = state;\n  if (state.maxRange && state.maxRange > 0) {\n    const range = endBlock - startBlock;\n    if (!(range >= 0)) {\n      throw new Error('End block must be higher than start block');\n    }\n    const currentRange = Math.min(state.maxRange, range);\n    const currentStart = endBlock - currentRange;\n    const currentEnd = endBlock;\n    return {\n      done: false,\n      startBlock,\n      endBlock,\n      maxRange: state.maxRange,\n      currentRange,\n      currentStart,\n      currentEnd,\n      multiplier\n    };\n  } else {\n    // the largest range we can have, since this is the users query for start and end\n    const maxRange = endBlock - startBlock;\n    if (!(maxRange > 0)) {\n      throw new Error('End block must be higher than start block');\n    }\n    const currentStart = startBlock;\n    const currentEnd = endBlock;\n    const currentRange = maxRange;\n\n    return {\n      done: false,\n      startBlock,\n      endBlock,\n      maxRange,\n      currentRange,\n      currentStart,\n      currentEnd,\n      multiplier\n    };\n  }\n}\n/**\n * rangeSuccessDescending. We have 2 ways of querying events, from oldest to newest, or newest to oldest. Typically we want them in order, from\n * oldest to newest, but for this particular case we want them newest to oldest, ie descending ( larger timestamp to smaller timestamp).\n * This function will increase the range between start/end block and return a new start/end to use since by calling this you are signalling\n * that the last range ended in a successful query.\n *\n * @param {RangeState} state\n * @returns {RangeState}\n */\nexport function rangeSuccessDescending(state: RangeState): RangeState {\n  const {\n    startBlock,\n    currentStart,\n    maxRange,\n    currentRange,\n    multiplier = 2\n  } = state;\n  // we are done if we succeeded querying where the currentStart matches are initial start block\n  const done = currentStart <= startBlock;\n  // increase range up to max range for every successful query\n  const nextRange = Math.min(Math.ceil(currentRange * multiplier), maxRange);\n  // move our end point to the previously successful start, ie moving from newest to oldest\n  const nextEnd = currentStart;\n  // move our start block to the next range down\n  const nextStart = Math.max(nextEnd - nextRange, startBlock);\n  return {\n    ...state,\n    currentStart: nextStart,\n    currentEnd: nextEnd,\n    currentRange: nextRange,\n    done\n  };\n}\n/**\n * rangeFailureDescending. Like the previous function, this will decrease the range between start/end for your query, because you are signalling\n * that the last query failed. It will also keep the end of your range the same, while moving the start range up. This is why\n * its considered descending, it will attempt to move from end to start, rather than start to end.\n *\n * @param {RangeState} state\n * @returns {RangeState}\n */\nexport function rangeFailureDescending(state: RangeState): RangeState {\n  const { startBlock, currentEnd, currentRange, multiplier = 2 } = state;\n  const nextRange = Math.floor(currentRange / multiplier);\n  // this will eventually throw an error if you keep calling this function, which protects us against re-querying a broken api in a loop\n  if (currentRange <= 0) throw new Error('Current range must be above 0');\n  if (!(nextRange > 0)) throw new Error('Range must be above 0');\n  // we stay at the same end block\n  const nextEnd = currentEnd;\n  // move our start block closer to the end block, shrinking the range\n  const nextStart = Math.max(nextEnd - nextRange, startBlock);\n  return {\n    ...state,\n    currentStart: nextStart,\n    currentEnd: nextEnd,\n    currentRange: nextRange\n  };\n}\n\n// The main interface to wrap the above pure functions up. requires you to pass in a generic function\n// which returns the events based on a start/end block query.\nexport async function pageEvents<E>(\n  startBlock: number,\n  endBlock: number,\n  maxRange: number,\n  //start and end block range to query\n  fetchEvents: (query: { start: number; end: number }) => Promise<E[]>,\n  concurrency: number = 5\n): Promise<E[]> {\n  let state = rangeStart({ startBlock, endBlock, maxRange });\n  const ranges: { start: number; end: number; index: number }[] = [];\n  let index = 0;\n  do {\n    ranges.push({\n      start: state.currentStart,\n      end: state.currentEnd,\n      index: index++\n    });\n    state = rangeSuccessDescending(state);\n  } while (!state.done);\n\n  return (await bb.map(ranges, fetchEvents, { concurrency })).flat();\n}\n\nexport async function getPagedEvents<EventType = Event>(params: {\n  contract: Contract;\n  eventFilter: EventFilter;\n  startBlock: number;\n  latestBlock: number;\n  maxRange: number;\n}) {\n  const { contract, eventFilter, startBlock, latestBlock, maxRange } = params;\n  const eventPager = ({ start, end }: { start: number; end: number }) => {\n    return contract.queryFilter(eventFilter, start, end);\n  };\n  const pagedEvents = await pageEvents(\n    startBlock,\n    latestBlock,\n    maxRange,\n    eventPager\n  );\n  return pagedEvents as EventType[];\n}\n"
  },
  {
    "path": "src/plugins/oSnap/utils/getters.ts",
    "content": "import { TreasuryWallet } from '@/helpers/interfaces';\nimport { defaultAbiCoder } from '@ethersproject/abi';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { Contract } from '@ethersproject/contracts';\nimport { keccak256 } from '@ethersproject/keccak256';\nimport { StaticJsonRpcProvider } from '@ethersproject/providers';\nimport { pack } from '@ethersproject/solidity';\nimport { toUtf8Bytes } from '@ethersproject/strings';\nimport snapshot from '@snapshot-labs/snapshot.js';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\nimport memoize from 'lodash/memoize';\nimport detectProxyTarget from 'evm-proxy-detection';\nimport {\n  ERC20_ABI,\n  GNOSIS_SAFE_TRANSACTION_API_URLS,\n  OPTIMISTIC_GOVERNOR_ABI,\n  OPTIMISTIC_ORACLE_V3_ABI,\n  SAFE_APP_URLS,\n  contractData,\n  safePrefixes,\n  solidityZeroHexString\n} from '../constants';\nimport {\n  AssertionGql,\n  AssertionMadeEvent,\n  BalanceResponse,\n  CollateralDetails,\n  NFT,\n  Network,\n  OGModuleDetails,\n  OGProposalState,\n  OptimisticGovernorTransaction,\n  ProposalExecutedEvent,\n  SafeNetworkPrefix,\n  SpaceConfigResponse,\n  Token,\n  TransactionsProposedEvent\n} from '../types';\nimport { getPagedEvents } from './events';\nimport { shortenAddress, toChecksumAddress } from '@/helpers/utils';\nimport { getNativeAsset } from './coins';\nimport { formatUnits } from '@ethersproject/units';\n\n/**\n * Calls the Gnosis Safe Transaction API\n *\n * Ideal usage is to specify the shape of the response with the generic type parameter, assuming that the shape of the response is known.\n */\nasync function callGnosisSafeTransactionApi<TResult = any>(\n  network: Network,\n  url: string\n) {\n  if (!GNOSIS_SAFE_TRANSACTION_API_URLS[network])\n    throw new Error(`No gnosis safe api defined for network ${network}`);\n  const apiUrl = GNOSIS_SAFE_TRANSACTION_API_URLS[network];\n  const response = await fetch(apiUrl + url);\n  return response.json() as TResult;\n}\n\n/**\n * Fetches the balances of the tokens owned by a given Safe.\n */\nexport const getGnosisSafeBalances = memoize(\n  (network: Network, safeAddress: string) => {\n    const checksumAddress = toChecksumAddress(safeAddress);\n    const endpointPath = `/v1/safes/${checksumAddress}/balances?exclude_spam=true`;\n    return callGnosisSafeTransactionApi<Partial<BalanceResponse>[]>(\n      network,\n      endpointPath\n    );\n  },\n  (safeAddress, network) => `${safeAddress}_${network}`\n);\n\n/**\n * Fetches the collectibles owned by a given Safe.\n */\nexport const getGnosisSafeCollectibles = memoize(\n  (network: Network, safeAddress: string) => {\n    const endpointPath = `/v2/safes/${safeAddress}/collectibles/`;\n    // the endpoint returns the data in this form, most likely to allow you to page the data.\n    type Result = {\n      count: number;\n      next: unknown;\n      previous: unknown;\n      results: NFT[];\n    };\n    return callGnosisSafeTransactionApi<Result>(network, endpointPath);\n  },\n  (safeAddress, network) => `${safeAddress}_${network}`\n);\n\n/**\n * Fetches the block number of a given contract's deployment.\n */\nfunction getDeployBlock(params: { network: Network; name: string }): number {\n  const results = contractData.filter(\n    contract =>\n      contract.network === params.network && contract.name === params.name\n  );\n  if (results.length === 1) return results[0].deployBlock ?? 0;\n  return 0;\n}\n\nexport class ConfigError extends Error {\n  constructor(message: string, responsibleVar: string) {\n    super(message);\n    this.name = 'CONFIG_ERROR';\n  }\n}\n\nexport function logIfErrorMessage(e: unknown, overrideMessage: string) {\n  console.error(e instanceof Error ? e.message : overrideMessage);\n}\n\n/**\n * Fetches the subgraph url for a given contract on a given network.\n */\nfunction getContractSubgraph(params: { network: Network; name: string }) {\n  const results = contractData.filter(\n    contract =>\n      contract.network === params.network && contract.name === params.name\n  );\n  if (results.length > 1)\n    throw new ConfigError(\n      `Too many results finding ${params.name} subgraph on network ${params.network}`,\n      'subgraph'\n    );\n\n  if (results.length < 1 || !results[0].subgraph)\n    throw new ConfigError(\n      `No subgraph url defined for ${params.name} on network ${params.network}`,\n      'subgraph'\n    );\n\n  return results[0].subgraph;\n}\n\n/**\n * A helper that wraps the getContractSubgraph function to return the subgraph url for the OptimisticGovernor contract on a given network.\n */\nfunction getOptimisticGovernorSubgraph(network: Network): string {\n  return getContractSubgraph({ network, name: 'OptimisticGovernor' });\n}\n\n/**\n * A helper that wraps the getContractSubgraph function to return the subgraph url for the OptimisticOracleV3 contract on a given network.\n */\nfunction getOracleV3Subgraph(network: Network): string {\n  return getContractSubgraph({ network, name: 'OptimisticOracleV3' });\n}\n\n/**\n * Executes a graphql query.\n *\n * Ideal usage is to specify the shape of the response with the generic type parameter, assuming that the shape of the response is known.\n */\nexport const queryGql = async <Result = any>(url: string, query: string) => {\n  try {\n    const response = await fetch(url, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Accept: 'application/json'\n      },\n      body: JSON.stringify({ query: query })\n    });\n    if (!response.ok) {\n      const errorData = await response.json();\n      throw new Error(\n        `Network Error: ${response.status}, message: ${errorData.message}`\n      );\n    }\n    const data = await response.json();\n    // Throw an error if there are errors in the GraphQL response\n    if (data.errors) {\n      throw new Error(\n        `GraphQL Error: ${data.errors.map(error => error.message).join(', ')}`\n      );\n    }\n    return data.data as Result;\n  } catch (error) {\n    throw new Error(\n      `Network error: ${error instanceof Error ? error.message : error}`\n    );\n  }\n};\n\n/**\n * Returns the address of the Optimistic Governor contract deployment associated with a given treasury (Safe) from the graph.\n */\nexport const getModuleAddressForTreasury = async (\n  network: Network,\n  treasuryAddress: string\n) => {\n  try {\n    const subgraph = getOptimisticGovernorSubgraph(network);\n    const query = `\n    query getModuleAddressForTreasury {\n        safe(id: \"${treasuryAddress.toLowerCase()}\") {\n          optimisticGovernor {\n            id\n          }\n        }\n    }\n    `;\n\n    type Result = {\n      safe: { optimisticGovernor: { id: string } };\n    };\n\n    const result = await queryGql<Result>(subgraph, query);\n    return result?.safe?.optimisticGovernor?.id ?? '';\n  } catch (error) {\n    logIfErrorMessage(\n      error,\n      `Unable to get module address for treasury ${shortenAddress(\n        treasuryAddress\n      )} on network ${network}`\n    );\n\n    throw error;\n  }\n};\n\n/**\n * Checks if a given treasury (Safe) has enabled oSnap.\n */\nexport const getIsOsnapEnabled = async (\n  network: Network,\n  safeAddress: string\n) => {\n  try {\n    const subgraph = getOptimisticGovernorSubgraph(network);\n    const query = `\n        query isOSnapEnabled {\n          safe(id:\"${safeAddress.toLowerCase()}\"){\n            isOptimisticGovernorEnabled\n          }\n        }\n      `;\n    type Result = {\n      safe: { isOptimisticGovernorEnabled: boolean };\n    };\n    const result = await queryGql<Result>(subgraph, query);\n    return result?.safe?.isOptimisticGovernorEnabled ?? false;\n  } catch (error) {\n    logIfErrorMessage(\n      error,\n      `Unable to check if oSnap is enable for address ${shortenAddress(\n        safeAddress\n      )} on network ${network}`\n    );\n    throw error;\n  }\n};\n\n/**\n * Takes an array of treasuries and checks if any of them have oSnap enabled.\n */\nexport const getSpaceHasOsnapEnabledTreasury = async (\n  treasuries: TreasuryWallet[]\n) => {\n  const isOsnapEnabledOnTreasuriesResult = await Promise.all(\n    treasuries.map(treasury =>\n      getIsOsnapEnabled(treasury.network as Network, treasury.address)\n    )\n  );\n  return isOsnapEnabledOnTreasuriesResult.some(\n    isOsnapEnabled => isOsnapEnabled\n  );\n};\n\n/**\n * Creates the url for the Safe app to configure oSnap.\n *\n * The data that the Safe app needs is encoded as URL search params.\n */\nexport function makeConfigureOsnapUrl(params: {\n  safeAddress: string;\n  network: Network;\n  spaceName: string;\n  spaceUrl: string;\n  baseUrl?: string;\n  appUrl?: string;\n}) {\n  const {\n    safeAddress,\n    network,\n    spaceName,\n    spaceUrl,\n    appUrl = 'https://osnap.uma.xyz/'\n  } = params;\n  const baseUrl =\n    params.baseUrl ??\n    SAFE_APP_URLS[network] ??\n    'https://app.safe.global/apps/open';\n  const safeAddressPrefix = getSafeNetworkPrefix(network);\n  const appUrlSearchParams = new URLSearchParams();\n  appUrlSearchParams.set('spaceName', spaceName);\n  appUrlSearchParams.set('spaceUrl', spaceUrl);\n  const appUrlSearch = appUrlSearchParams.toString();\n  const safeAppSearchParams = new URLSearchParams();\n  safeAppSearchParams.set('safe', `${safeAddressPrefix}:${safeAddress}`);\n  safeAppSearchParams.set('appUrl', `${appUrl}?${appUrlSearch}`);\n  const safeAppSearch = safeAppSearchParams.toString();\n  const url = `${baseUrl}?${safeAppSearch}`;\n  return url;\n}\n\n/**\n * Fetches the details of a given assertion from the Optimistic Oracle V3 subgraph.\n */\nasync function getAssertionGql(params: {\n  network: Network;\n  assertionId: string;\n}) {\n  const { assertionId, network } = params;\n  const oracleUrl = getOracleV3Subgraph(network);\n  const request = `\n  {\n    assertion(id:\"${assertionId}\"){\n      assertionId\n      expirationTime\n      assertionHash\n      disputeHash\n      settlementHash\n      assertionLogIndex\n      settlementResolution\n    }\n  }\n  `;\n  type Result = {\n    assertion: AssertionGql | undefined;\n  };\n  const result = await queryGql<Result>(oracleUrl, request);\n  return result?.assertion;\n}\n/**\n * Fetches the details of a given proposal from the Optimistic Governor subgraph.\n *\n * The subgraph uses the `assertionId` that comes from assertion events as the primary key for proposals.\n * However, this `assertionId` will be deleted if the proposal is disputed, so we can't use it to query the subgraph.\n * Instead, we use the `proposalHash` and `explanation` to query the subgraph.\n * The `explanation` contains the ipfs url of the proposal, which is the only way to distinguish between proposals with the same `proposalHash`.\n * This means we must use a `where` clause to filter the results, which is not ideal.\n */\nasync function getOgProposalGql(params: {\n  network: Network;\n  explanation: string;\n  moduleAddress: string;\n  proposalHash: string;\n}) {\n  const { network, explanation, moduleAddress, proposalHash } = params;\n  const encodedExplanation = pack(\n    ['bytes'],\n    [toUtf8Bytes(explanation.replace(/^0x/, ''))]\n  );\n  const subgraph = getOptimisticGovernorSubgraph(network);\n  const request = `\n{\n  proposals(where:{proposalHash:\"${proposalHash}\",explanation:\"${encodedExplanation}\",optimisticGovernor:\"${moduleAddress.toLowerCase()}\"}){\n    id\n    executed\n    deleted\n    assertionId\n  }\n}\n`;\n  type Result = {\n    proposals: {\n      id: string;\n      executed: boolean;\n      assertionId: string;\n      deleted: boolean;\n    }[];\n  };\n  const result = await queryGql<Result>(subgraph, request);\n  // we can only use the gql `where` clause when querying a list, but we know that there will only be one result.\n  return result?.proposals[0];\n}\n\n/**\n * Fetches the details of a Optimistic Governor module's collateral token.\n *\n * Returns the address, symbol, and decimals of the collateral token, along with the token contract for further querying.\n */\nexport async function getCollateralDetailsForProposal(\n  provider: StaticJsonRpcProvider,\n  moduleAddress: string\n): Promise<CollateralDetails> {\n  const moduleContract = new Contract(\n    moduleAddress,\n    OPTIMISTIC_GOVERNOR_ABI,\n    provider\n  );\n\n  const erc20Contract = new Contract(\n    await moduleContract.collateral(),\n    ERC20_ABI,\n    provider\n  );\n\n  const address = erc20Contract.address;\n  const symbol: string = await erc20Contract.symbol();\n  const decimals: BigNumber = await erc20Contract.decimals();\n\n  return { erc20Contract, address, symbol, decimals };\n}\n\n/**\n * Fetches the allowance of a given collateral token for a given user.\n */\nexport async function getUserCollateralAllowance(\n  erc20Contract: Contract,\n  userAddress: string,\n  moduleAddress: string\n) {\n  return erc20Contract.allowance(userAddress, moduleAddress);\n}\n\n/**\n * Fetches the balance of a given collateral token for a given user.\n */\nexport async function getUserCollateralBalance(\n  erc20Contract: Contract,\n  userAddress: string\n) {\n  return erc20Contract.balanceOf(userAddress);\n}\n\n/**\n * Fetches the details of a given Optimistic Governor module from the chain.\n *\n * Performs a multicall to fetch the oracle address, rules, minimum bond, and challenge period.\n */\nexport async function getOGModuleDetails(params: {\n  provider: StaticJsonRpcProvider;\n  network: Network;\n  moduleAddress: string;\n  transactions: OptimisticGovernorTransaction[];\n}): Promise<OGModuleDetails> {\n  const { provider, network, moduleAddress } = params;\n  const moduleDetails: [\n    [oracleAddress: string],\n    [rules: string],\n    [minimumBond: BigNumber],\n    [challengePeriod: BigNumber]\n  ] = await snapshot.utils.multicall(network, provider, OPTIMISTIC_GOVERNOR_ABI as any, [\n    [moduleAddress, 'optimisticOracleV3'],\n    [moduleAddress, 'rules'],\n    [moduleAddress, 'getProposalBond'],\n    [moduleAddress, 'liveness']\n  ]);\n\n  const oracleAddress = moduleDetails[0][0];\n  const rules = moduleDetails[1][0];\n  const minimumBond = moduleDetails[2][0];\n  const challengePeriod = moduleDetails[3][0];\n\n  return {\n    moduleAddress,\n    oracleAddress,\n    rules,\n    minimumBond,\n    challengePeriod\n  };\n}\n\n/**\n * Fetches the state of an Optimistic Governor proposal from the chain.\n *\n * This is a fallback function that should only be used if the subgraph is not available, because it is very slow.\n *\n * The contract is designed in such a way that it deletes the `assertionId` from the proposal if the proposal is disputed, _or_ if the transactions are executed successfully. This means we can't tell the difference between a proposal that has not yet been proposed, has been disputed, or that has been executed by querying the chain.\n *\n * Instead, we must query the chain for the proposal events, and then query the chain for the execution events, and then compare the two to determine the state of the proposal. This is very slow.\n */\nexport async function getOgProposalStateFromChain(params: {\n  moduleDetails: OGModuleDetails;\n  network: Network;\n  proposalHash: string;\n  explanation: string;\n}): Promise<OGProposalState> {\n  const { network, moduleDetails, explanation, proposalHash } = params;\n  const { moduleAddress, oracleAddress } = moduleDetails;\n\n  const provider = getProvider(network);\n  const latestBlock = (await provider.getBlock('latest')).number;\n  const oGstartBlock = getDeployBlock({ network, name: 'OptimisticGovernor' });\n  const oOStartBlock = getDeployBlock({ network, name: 'OptimisticOracleV3' });\n  const maxRange = 3000;\n\n  const moduleContract = new Contract(\n    moduleAddress,\n    OPTIMISTIC_GOVERNOR_ABI,\n    provider\n  );\n  const oracleContract = new Contract(\n    oracleAddress,\n    OPTIMISTIC_ORACLE_V3_ABI,\n    provider\n  );\n\n  const assertionId: string = await moduleContract.assertionIds(proposalHash);\n  const hasAssertionId = assertionIdIsNotZero(assertionId);\n\n  if (hasAssertionId) {\n    const assertionMadeEventForCurrentAssertionIdFilter =\n      oracleContract.filters.AssertionMade(assertionId);\n\n    const assertionMadeEvents = await getPagedEvents<AssertionMadeEvent>({\n      contract: oracleContract,\n      eventFilter: assertionMadeEventForCurrentAssertionIdFilter,\n      startBlock: oOStartBlock,\n      latestBlock,\n      maxRange\n    });\n\n    // assertion ids are unique, so this will have only one result\n    // we need to get an event instead of getting the `Assertion` struct from the chain because the oracle dapp needs the assertion transaction hash and the log index to link to the oracle dapp.\n    const assertionMadeEvent = assertionMadeEvents[0];\n\n    const expirationTime = assertionMadeEvent.args?.expirationTime.toNumber();\n    const isInChallengePeriod = expirationTime * 1000 > Date.now();\n    const assertionHash = assertionMadeEvent.transactionHash;\n    const assertionLogIndex = assertionMadeEvent.logIndex.toString();\n\n    if (isInChallengePeriod) {\n      return {\n        status: 'in-oo-challenge-period',\n        assertionHash,\n        assertionLogIndex,\n        expirationTime\n      };\n    }\n\n    return {\n      status: 'can-request-tx-execution',\n      assertionHash,\n      assertionLogIndex\n    };\n  }\n\n  const transactionsProposedEventFilter =\n    moduleContract.filters.TransactionsProposed();\n\n  const transactionsProposedEvents =\n    await getPagedEvents<TransactionsProposedEvent>({\n      contract: moduleContract,\n      eventFilter: transactionsProposedEventFilter,\n      startBlock: oGstartBlock,\n      latestBlock,\n      maxRange\n    });\n\n  const transactionsProposedEventsThatMatch = transactionsProposedEvents.filter(\n    event =>\n      event.args?.proposalHash === proposalHash &&\n      event.args?.explanation === explanation\n  );\n\n  const hasTransactionProposedEvents =\n    transactionsProposedEventsThatMatch.length > 0;\n\n  // we can return early and skip querying for execution events if there are no proposal events\n  if (!hasTransactionProposedEvents) {\n    return {\n      status: 'can-propose-to-og',\n      isDisputed: false\n    };\n  }\n\n  // the proposal hash is not indexed on the transactions proposed event unfortunately, but it is on the proposal executed event.\n  // so at least we can use it for this one to narrow down the results.\n  const proposalExecutedEventFilter =\n    moduleContract.filters.ProposalExecuted(proposalHash);\n\n  const proposalExecutedEvents = await getPagedEvents<ProposalExecutedEvent>({\n    contract: moduleContract,\n    eventFilter: proposalExecutedEventFilter,\n    startBlock: oGstartBlock,\n    latestBlock,\n    maxRange\n  });\n\n  // we know that the transactions have been executed if there is an execution event with an assertion id that matches an assertion id for a proposal event\n  let proposalExecuted = false;\n  for (const proposalExecutedEvent of proposalExecutedEvents) {\n    for (const transactionsProposedEvent of transactionsProposedEventsThatMatch) {\n      if (\n        proposalExecutedEvent.args?.assertionId ===\n        transactionsProposedEvent.args?.assertionId\n      ) {\n        proposalExecuted = true;\n        break;\n      }\n    }\n  }\n\n  if (proposalExecuted) {\n    const assertionMadeEventForExecutedAssertionIdFilter =\n      oracleContract.filters.AssertionMade(assertionId);\n\n    const assertionMadeEvents = await getPagedEvents<AssertionMadeEvent>({\n      contract: oracleContract,\n      eventFilter: assertionMadeEventForExecutedAssertionIdFilter,\n      startBlock: oOStartBlock,\n      latestBlock,\n      maxRange\n    });\n\n    // assertion ids are unique, so this will have only one result\n    const assertionMadeEvent = assertionMadeEvents[0];\n\n    return {\n      status: 'transactions-executed',\n      assertionHash: assertionMadeEvent.transactionHash,\n      assertionLogIndex: assertionMadeEvent.logIndex.toString()\n    };\n  }\n\n  return {\n    status: 'can-propose-to-og',\n    isDisputed: false\n  };\n}\n\n/**\n * Fetches the state of an Optimistic Governor proposal from the subgraph.\n *\n * This is the preferred method of fetching the state of a proposal, because it is much faster than querying the chain.\n */\nexport async function getOGProposalStateGql(params: {\n  network: Network;\n  moduleAddress: string;\n  proposalHash: string;\n  explanation: string;\n}): Promise<OGProposalState> {\n  const { network } = params;\n  const oGproposal = await getOgProposalGql(params);\n\n  if (!oGproposal) {\n    return { status: 'can-propose-to-og', isDisputed: false };\n  }\n\n  const { executed, assertionId, deleted } = oGproposal;\n\n  const hasAssertionId = assertionIdIsNotZero(assertionId);\n\n  // the subgraph records `ProposalDeleted` events, which are fired when a proposal is disputed.\n  if (!hasAssertionId && deleted) {\n    return { status: 'can-propose-to-og', isDisputed: true };\n  }\n\n  // no assertion made yet\n  if (!hasAssertionId) {\n    return { status: 'can-propose-to-og', isDisputed: false };\n  }\n\n  const assertion = await getAssertionGql({ network, assertionId });\n\n  // cannot find assertion for some reason\n  if (!assertion) {\n    return { status: 'can-propose-to-og', isDisputed: false };\n  }\n\n  const {\n    assertionHash,\n    settlementHash,\n    assertionLogIndex,\n    settlementResolution\n  } = assertion;\n\n  // if the assertion is settled and the graph says the transactions have been executed, then we know the assertion passed and we can return early\n  if (executed && settlementHash) {\n    return {\n      status: 'transactions-executed',\n      assertionHash,\n      assertionLogIndex\n    };\n  }\n\n  // if the settlement hash exists and the settlement resolution is true, then we know that the assertion passed.\n  // we already checked if the transactions were executed, so we know that the assertion passed but the transactions were not executed yet.\n  if (settlementHash && settlementResolution) {\n    return {\n      status: 'can-request-tx-execution',\n      assertionHash,\n      assertionLogIndex\n    };\n  }\n\n  const expirationTime = Number(assertion.expirationTime);\n  const isExpired = Math.floor(Date.now() / 1000) >= expirationTime;\n\n  // from the above checks, we know that the transactions have not been executed, the assertion has not been disputed, and the assertion has not been settled.\n  // if the assertion challenge period has not yet expired, then we know we are still in the challenge period.\n  if (!isExpired) {\n    return {\n      status: 'in-oo-challenge-period',\n      assertionHash,\n      assertionLogIndex,\n      expirationTime\n    };\n  }\n\n  // request execution if there is no settlement yet and liveness has expired\n  return {\n    status: 'can-request-tx-execution',\n    assertionHash,\n    assertionLogIndex\n  };\n}\n\n/**\n * Querying for an assertion ID that does not map to a proposal hash will return '0x0000000000000000000000000000000000000000000000000000000000000000'\n */\nfunction assertionIdIsNotZero(assertionId: string) {\n  return assertionId !== solidityZeroHexString;\n}\n\n/**\n * Fetches the state of an Optimistic Governor proposal.\n *\n * This function will attempt to fetch the state of a proposal from the subgraph, and if that fails, it will fall back to querying the chain.\n */\nexport async function getOGProposalState(params: {\n  moduleDetails: OGModuleDetails;\n  network: Network;\n  explanation: string;\n  transactions: OptimisticGovernorTransaction[];\n}): Promise<OGProposalState> {\n  const { network, moduleDetails, explanation, transactions } = params;\n  const { moduleAddress } = moduleDetails;\n  const proposalHash = getProposalHashFromTransactions(transactions);\n  try {\n    return await getOGProposalStateGql({\n      network,\n      moduleAddress,\n      explanation,\n      proposalHash\n    });\n  } catch (error) {\n    console.warn(\n      'Error fetching OG proposal state from subgraph, falling back to chain',\n      error\n    );\n    return getOgProposalStateFromChain({\n      network,\n      moduleDetails,\n      explanation,\n      proposalHash\n    });\n  }\n}\n\n/**\n * The `proposalHash` as represented in the Optimistic Governor contract is the keccak256 hash of the transactions that make up the proposal.\n */\nexport function getProposalHashFromTransactions(\n  transactions: OptimisticGovernorTransaction[]\n) {\n  return keccak256(\n    defaultAbiCoder.encode(\n      ['(address to, uint8 operation, uint256 value, bytes data)[]'],\n      [transactions]\n    )\n  );\n}\n\n/**\n * Returns the EIP-3770 prefix for a given network.\n *\n * @see SafeNetworkPrefix\n */\nexport function getSafeNetworkPrefix(network: Network): SafeNetworkPrefix {\n  return safePrefixes[network];\n}\n\n/**\n * Returns the url for a given Safe app on a given network.\n */\nexport function getSafeAppLink(\n  network: Network,\n  safeAddress: string,\n  { appUrl = 'https://app.safe.global', path = '/home' } = {\n    appUrl: 'https://app.safe.global',\n    path: '/home'\n  }\n) {\n  const prefix = getSafeNetworkPrefix(network);\n  return new URL(`${path}?safe=${prefix}:${safeAddress}`, appUrl).toString();\n}\n\n/**\n * Returns the url for an Optimistic Governor proposal's assertion on the Optimistic Oracle dapp.\n */\nexport function getOracleUiLink(\n  chain: string,\n  txHash: string,\n  logIndex: number\n) {\n  if (Number(chain) !== 5 && Number(chain) !== 80001) {\n    return `https://oracle.uma.xyz?transactionHash=${txHash}&eventIndex=${logIndex}`;\n  }\n  return `https://testnet.oracle.uma.xyz?transactionHash=${txHash}&eventIndex=${logIndex}`;\n}\n\nexport async function fetchImplementationAddress(\n  proxyAddress: string,\n  network: string\n): Promise<string | undefined> {\n  try {\n    const provider = getProvider(network);\n    const requestFunc = ({ method, params }) => provider.send(method, params);\n    return (await detectProxyTarget(proxyAddress, requestFunc)) ?? undefined;\n  } catch (error) {\n    console.error(error);\n  }\n}\n\n/**\n * Check if a space's deployed (on-chain) settings are supported by oSnap bots for auto execution\n */\nexport async function isConfigCompliant(safeAddress: string, chainId: string) {\n  const res = await fetch(\n    `https://osnap.uma.xyz/api/space-config?address=${safeAddress}&chainId=${chainId}`\n  );\n  if (!res.ok) {\n    throw new Error('Unable to fetch setting status');\n  }\n  const data = await res.json();\n  return data as unknown as SpaceConfigResponse;\n}\n\nexport async function fetchBalances(network: Network, safeAddress: string) {\n  if (!safeAddress) {\n    return [];\n  }\n  try {\n    const balances = await getGnosisSafeBalances(network, safeAddress);\n    const balancesWithNative = balances.map(balance => {\n      if (!balance.tokenAddress || !balance.token) {\n        return {\n          ...balance,\n          token: getNativeAsset(network),\n          tokenAddress: 'main'\n        };\n      }\n      return balance;\n    });\n\n    const tokens = await fetchTokens('https://tokens.uniswap.org');\n\n    return enhanceTokensWithBalances(balancesWithNative, tokens, network);\n  } catch (e) {\n    console.warn('Error fetching balances', e);\n    return [];\n  }\n}\n\nexport async function fetchTokens(url: string): Promise<Token[]> {\n  try {\n    const response = await fetch(url);\n    const data = await response.json();\n    return data.verifiedTokens?.tokens || data.tokens || [];\n  } catch {\n    return [];\n  }\n}\n\nexport function enhanceTokensWithBalances(\n  balances: Partial<BalanceResponse>[],\n  tokens: Token[],\n  network: Network\n) {\n  return balances\n    .filter(\n      (balance): balance is BalanceResponse =>\n        !!balance.token && !!balance.tokenAddress && !!balance.balance\n    )\n    .map(balance => enhanceTokenWithBalance(balance, tokens, network))\n    .sort((a, b) => {\n      if (a.address === 'main' && b.address !== 'main') return -1;\n      if (!(a.address === 'main') && b.address === 'main') return 1;\n      if (a.verified && !b.verified) return -1;\n      if (!a.verified && b.verified) return +1;\n      if (!a.balance || !b.balance) return 0;\n      if (parseFloat(a.balance) > parseFloat(b.balance)) return -1;\n      return 0;\n    });\n}\n\n// gets token balances and also determines if the token is verified\nfunction enhanceTokenWithBalance(\n  balance: BalanceResponse,\n  tokens: Token[],\n  network: Network\n): Token {\n  const verifiedToken = getVerifiedToken(balance.tokenAddress, tokens);\n  return {\n    ...balance.token,\n    address: balance.tokenAddress,\n    balance: balance.balance\n      ? formatUnits(balance.balance, balance.token.decimals)\n      : '0',\n    verified: !!verifiedToken,\n    chainId: network\n  };\n}\n\nfunction getVerifiedToken(tokenAddress: string, tokens: Token[]) {\n  return tokens.find(\n    token => token.address.toLowerCase() === tokenAddress.toLowerCase()\n  );\n}\n\nexport async function fetchCollectibles(\n  network: Network,\n  gnosisSafeAddress: string\n) {\n  try {\n    const response = await getGnosisSafeCollectibles(\n      network,\n      gnosisSafeAddress\n    );\n    return response.results;\n  } catch (error) {\n    console.warn('Error fetching collectibles');\n  }\n  return [];\n}\n"
  },
  {
    "path": "src/plugins/oSnap/utils/index.ts",
    "content": "export * from './abi';\nexport * from './coins';\nexport * from './events';\nexport * from './getters';\nexport * from './proposal';\nexport * from './transactions';\nexport * from './validators';\n"
  },
  {
    "path": "src/plugins/oSnap/utils/proposal.ts",
    "content": "import { BigNumber } from '@ethersproject/bignumber';\nimport { toUtf8Bytes } from '@ethersproject/strings';\nimport { sendTransaction } from '@snapshot-labs/snapshot.js/src/utils';\nimport { ERC20_ABI, OPTIMISTIC_GOVERNOR_ABI } from '../constants';\nimport { OptimisticGovernorTransaction } from '../types';\n\n/**\n * The user must approve the spend of the collateral token before they can submit a proposal.\n *\n * If the proposal is disputed and fails a vote, the user will lose their bond.\n */\nexport async function* approveBond(\n  web3: any,\n  moduleAddress: string,\n  collateralAddress: string,\n  minimumBond: BigNumber\n) {\n  const approveTx = await sendTransaction(\n    web3,\n    collateralAddress,\n    ERC20_ABI as any,\n    'approve',\n    [moduleAddress, minimumBond],\n    {}\n  );\n  yield approveTx;\n  const approvalReceipt = await approveTx.wait();\n  console.log('[DAO module] token transfer approved:', approvalReceipt);\n  yield;\n}\n\n/**\n * Submits a proposal to the Optimistic Governor.\n */\nexport async function* submitProposal(\n  web3: any,\n  moduleAddress: string,\n  explanation: string,\n  transactions: OptimisticGovernorTransaction[]\n) {\n  const explanationBytes = toUtf8Bytes(explanation);\n  const tx = await sendTransaction(\n    web3,\n    moduleAddress,\n    OPTIMISTIC_GOVERNOR_ABI as any,\n    'proposeTransactions',\n    [transactions, explanationBytes]\n    // [[[\"0xB8034521BB1a343D556e5005680B3F17FFc74BeD\", 0, \"0\", \"0x\"]], '0x']\n  );\n  yield tx;\n  const receipt = await tx.wait();\n  console.log('[DAO module] submitted proposal:', receipt);\n}\n\n/**\n * Executes a proposal on the Optimistic Governor.\n *\n * This can only be done after the dispute window has ended.\n */\nexport async function* executeProposal(\n  web3: any,\n  moduleAddress: string,\n  transactions: OptimisticGovernorTransaction[]\n) {\n  const tx = await sendTransaction(\n    web3,\n    moduleAddress,\n    OPTIMISTIC_GOVERNOR_ABI as any,\n    'executeProposal',\n    [transactions]\n  );\n  yield tx;\n  const receipt = await tx.wait();\n  console.log('[DAO module] executed proposal:', receipt);\n}\n"
  },
  {
    "path": "src/plugins/oSnap/utils/safeImport.ts",
    "content": "import { addressEqual } from '@/helpers/utils';\nimport { GnosisSafe } from '../types';\nimport { isSafeFile } from './validators';\n\nexport async function parseGnosisSafeFile(\n  file: File,\n  safe: GnosisSafe | null\n): Promise<GnosisSafe.BatchFile> {\n  return new Promise((res, rej) => {\n    const reader = new FileReader();\n    reader.readAsText(file);\n    reader.onload = async () => {\n      try {\n        if (typeof reader.result !== 'string') {\n          return rej(new Error('Buffer can not be parsed'));\n        }\n        const json = JSON.parse(reader.result);\n        if (!isSafeFile(json)) {\n          return rej(new Error('Not a valid Safe transaction file.'));\n        }\n        if (!isCreatedFromSafe(json, safe)) {\n          return rej(\n            new Error(\n              \"Safe file does not match the selected treasury's address or chain ID\"\n            )\n          );\n        }\n        return res(json);\n      } catch (err) {\n        return rej(new Error('Safe file corrupted. Please select another.'));\n      }\n    };\n  });\n}\n\nfunction isCreatedFromSafe(\n  batchFile: GnosisSafe.BatchFile,\n  safe: GnosisSafe | null\n): boolean {\n  if (safe && batchFile.meta.createdFromSafeAddress) {\n    return (\n      safe.network === batchFile.chainId &&\n      addressEqual(safe.safeAddress, batchFile.meta.createdFromSafeAddress)\n    );\n  }\n  return false;\n}\n"
  },
  {
    "path": "src/plugins/oSnap/utils/tenderly.ts",
    "content": "import {\n  OsnapPluginData,\n  Transaction as TTransaction,\n  GnosisSafe,\n  Network,\n  TenderlySimulationResult\n} from '../types';\nimport {\n  validateModuleAddress,\n  validateTransaction\n} from '../utils/validators';\n\nexport const SIMULATION_ENDPOINT =\n  'https://ethereum-api-read-prod-77jg7zf4ea-ue.a.run.app/osnap/simulate';\n\nexport const OSNAP_GAS_SUBSIDY = 500_000;\n\nexport function validatePayload(data: OsnapPluginData): void | never {\n  const { safe } = data;\n  if (!safe) {\n    throw new Error('No safe data');\n  }\n  if (!validateModuleAddress(safe.network, safe?.moduleAddress)) {\n    throw new Error('Module address is incorrect for this network');\n  }\n  if (!(safe.transactions.length > 0)) {\n    throw new Error('No transactions to simulate');\n  }\n  safe.transactions.forEach((tx, i) => {\n    if (!tx.isValid) {\n      throw new Error(`Transaction ${i + 1} in invalid`);\n    }\n  });\n  return;\n}\n\nexport function prepareTenderlySimulationPayload(props: {\n  transactions: TTransaction[];\n  safe: GnosisSafe | null;\n  network: Network;\n}): OsnapPluginData {\n  // this will not happen in this component\n  if (!props.safe) {\n    throw new Error('No safe selected');\n  }\n\n  const { safeAddress, safeName } = props.safe;\n\n  const payload: GnosisSafe = {\n    safeAddress,\n    safeName,\n    network: props.network,\n    moduleAddress: props.safe.moduleAddress,\n    transactions: props.transactions\n  };\n\n  return { safe: payload };\n}\n\nexport function exceedsOsnapGasSubsidy(res: TenderlySimulationResult): boolean {\n  return res.gasUsed > OSNAP_GAS_SUBSIDY;\n}\n"
  },
  {
    "path": "src/plugins/oSnap/utils/transactions.ts",
    "content": "import { FunctionFragment } from '@ethersproject/abi';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport {\n  ContractInteractionTransaction,\n  NFT,\n  OptimisticGovernorTransaction,\n  RawTransaction,\n  Token,\n  TransferFundsTransaction,\n  TransferNftTransaction,\n  SafeImportTransaction\n} from '../types';\nimport { encodeMethodAndParams, encodeSafeMethodAndParams } from './abi';\nimport { isAddress } from '@ethersproject/address';\nimport { validateTransaction } from './validators';\n\n/**\n * Creates a formatted transaction for the Optimistic Governor to execute\n *\n * note: the value for `operation` is always zero because we do not support delegatecall.\n *\n * @see OptimisticGovernorTransaction\n */\nexport function createFormattedOptimisticGovernorTransaction({\n  to,\n  value,\n  data\n}: {\n  to: string;\n  value: string;\n  data: string;\n}): OptimisticGovernorTransaction {\n  return [to, 0, value, data];\n}\n\n/**\n * Creates a raw transaction for the Optimistic Governor to execute\n *\n * @see RawTransaction\n */\nexport function createRawTransaction(params: {\n  to: string;\n  value: string;\n  data: string;\n}): RawTransaction {\n  const type = 'raw';\n  const formatted = createFormattedOptimisticGovernorTransaction(params);\n  return {\n    ...params,\n    type,\n    formatted,\n    isValid: true\n  };\n}\n\n/**\n * Creates a transaction to transfer an NFT\n *\n * @see TransferNftTransaction\n */\nexport function createTransferNftTransaction(params: {\n  recipient: string;\n  collectable: NFT;\n  data: string;\n}): TransferNftTransaction {\n  const type = 'transferNFT';\n  const to = params.collectable.address;\n  const value = '0';\n  const data = params.data;\n  const formatted = createFormattedOptimisticGovernorTransaction({\n    to,\n    value,\n    data\n  });\n\n  return {\n    ...params,\n    type,\n    to,\n    value,\n    data,\n    formatted,\n    isValid: true\n  };\n}\n\n/**\n * Creates a transaction to transfer funds\n *\n * @see TransferFundsTransaction\n */\nexport function createTransferFundsTransaction(params: {\n  recipient: string;\n  amount: string;\n  token: Token;\n  data: string;\n}): TransferFundsTransaction {\n  if (!(parseFloat(params.amount) > 0)) {\n    throw new Error('Amount invalid');\n  }\n  const type = 'transferFunds';\n  const isNativeToken = params.token.address === 'main';\n  const data = isNativeToken ? '0x' : params.data;\n  const to = isNativeToken ? params.recipient : params.token.address;\n  const amount = parseAmount(params.amount);\n  const value = isNativeToken ? amount : '0';\n  const formatted = createFormattedOptimisticGovernorTransaction({\n    to,\n    value,\n    data\n  });\n  return {\n    ...params,\n    type,\n    data,\n    to,\n    value,\n    amount,\n    formatted,\n    isValid: true\n  };\n}\n\n/**\n * Creates a transaction to interact with a contract.\n *\n * the `method` is executed with the given `parameters`.\n *\n * @see ContractInteractionTransaction\n */\nexport function createContractInteractionTransaction(params: {\n  to: string;\n  value: string;\n  abi: string;\n  method: FunctionFragment;\n  parameters: string[];\n}): ContractInteractionTransaction {\n  try {\n    const type = 'contractInteraction';\n    const data = encodeMethodAndParams(\n      params.abi,\n      params.method,\n      params.parameters\n    );\n    const formatted = createFormattedOptimisticGovernorTransaction({\n      ...params,\n      data\n    });\n    return {\n      ...params,\n      data,\n      type,\n      formatted,\n      isValid: true\n    };\n  } catch (error) {\n    console.error(error);\n    throw new Error('Invalid function parameters');\n  }\n}\n\nexport function parseAmount(input: string) {\n  return BigNumber.from(input).toString();\n}\n\nexport function parseValueInput(input: string) {\n  if (input === '') {\n    return parseAmount('0');\n  }\n  return parseAmount(input);\n}\n\nexport function createSafeImportTransaction(\n  params: SafeImportTransaction\n): SafeImportTransaction {\n  try {\n    // check \"value\" & \"to\"\n    if (!validateTransaction(params)) {\n      throw new Error('Invalid transaction');\n    }\n    const abi = params.method\n      ? JSON.stringify(Array(params.method))\n      : undefined;\n    // is native transfer funds\n    if (!params.method) {\n      const data = '0x';\n      const formatted = createFormattedOptimisticGovernorTransaction({\n        to: params.to,\n        value: params.value,\n        data\n      });\n      return {\n        ...params,\n        isValid: true,\n        abi,\n        formatted,\n        data\n      };\n    }\n    // is contract interaction with NO args\n    if (!params.parameters) {\n      const data = params?.data || '0x';\n      const formatted = createFormattedOptimisticGovernorTransaction({\n        to: params.to,\n        value: params.value,\n        data\n      });\n      return {\n        ...params,\n        isValid: true,\n\n        formatted,\n        data\n      };\n    }\n\n    // is contract interaction WITH args\n    // will throw if args are invalid\n    const encodedData =\n      params?.data ||\n      encodeSafeMethodAndParams(params.method, params.parameters) ||\n      '0x';\n\n    const formatted = createFormattedOptimisticGovernorTransaction({\n      to: params.to,\n      value: params.value,\n      data: encodedData\n    });\n\n    return {\n      ...params,\n      isValid: true,\n      abi,\n      formatted,\n      data: encodedData\n    };\n  } catch (error) {\n    console.error(error);\n    return {\n      ...params,\n      isValid: false\n    };\n  }\n}\n"
  },
  {
    "path": "src/plugins/oSnap/utils/validators.ts",
    "content": "import { isAddress } from '@ethersproject/address';\nimport {\n  JsonRpcProvider,\n  StaticJsonRpcProvider\n} from '@ethersproject/providers';\nimport memoize from 'lodash/memoize';\nimport { Contract } from '@ethersproject/contracts';\nimport {\n  BigNumber,\n  isBigNumberish\n} from '@ethersproject/bignumber/lib/bignumber';\nimport { isBytesLike, isHexString } from '@ethersproject/bytes';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\nimport { OPTIMISTIC_GOVERNOR_ABI } from '../constants';\nimport {\n  BaseTransaction,\n  NFT,\n  Token,\n  Transaction,\n  GnosisSafe,\n  InputTypes,\n  isIntegerType\n} from '../types';\nimport { parseUnits } from '@ethersproject/units';\nimport { useMemoize } from '@vueuse/core';\nimport { parseInputArray } from '../components/Input/MethodParameter/utils';\n\n/**\n * Validates that the given `address` is a valid Ethereum address\n */\nexport const mustBeEthereumAddress = memoize((address: string) => {\n  const startsWith0x = address?.startsWith('0x');\n  const isValidAddress = isAddress(address);\n  return startsWith0x && isValidAddress;\n});\n\n/**\n * Validates that the given `address` is a valid Ethereum contract address\n */\nexport const mustBeEthereumContractAddress = memoize(\n  async (network: string, address: string) => {\n    const provider = getProvider(network) as JsonRpcProvider;\n    const contractCode = await provider.getCode(address);\n\n    return (\n      contractCode && contractCode.replace(/^0x/, '').replace(/0/g, '') !== ''\n    );\n  },\n  (url, contractAddress) => `${url}_${contractAddress}`\n);\n\n/**\n * Validates a transaction.\n */\nexport function validateTransaction(transaction: BaseTransaction) {\n  const addressNotEmptyOrInvalid =\n    transaction.to !== '' && isAddress(transaction.to);\n  const isBN = isBigNumberish(transaction.value);\n  return (\n    isBN &&\n    addressNotEmptyOrInvalid &&\n    (!transaction.data || isHexString(transaction.data))\n  );\n}\n\n/**\n * Validates a module address.\n */\nexport async function validateModuleAddress(\n  network: string,\n  moduleAddress: string\n): Promise<boolean> {\n  if (!isAddress(moduleAddress)) return false;\n  const provider: StaticJsonRpcProvider = getProvider(network);\n  const moduleContract = new Contract(\n    moduleAddress,\n    OPTIMISTIC_GOVERNOR_ABI,\n    provider\n  );\n\n  return moduleContract\n    .rules()\n    .then(() => true)\n    .catch(() => false);\n}\n\nexport function isTransferFundsValid(params: {\n  token: Token;\n  recipient: string;\n  amount: string;\n}): boolean {\n  if (!amountPositive(params.amount, params.token.decimals)) {\n    return false;\n  }\n  if (!params.recipient || !isAddress(params.recipient)) {\n    return false;\n  }\n  if (!(params.token.address === 'main') && !isAddress(params.token.address)) {\n    return false;\n  }\n\n  return true;\n}\n\nexport function isTransferNftValid(params: {\n  collectable: NFT | undefined;\n  recipient: string;\n}): boolean {\n  // check NFT transfer variables are correct\n  if (!params.collectable) {\n    return false;\n  }\n  if (\n    !isAddress(params.recipient) ||\n    !isAddress(params.collectable.address) ||\n    !params.collectable.id\n  ) {\n    return false;\n  }\n\n  return true;\n}\n\nexport function amountPositive(amount: string, decimals = 18) {\n  try {\n    const isBigNumber = isBigNumberish(parseUnits(amount, decimals)); // checks for underflow\n    const isPositive = parseFloat(amount) > 0;\n    return isBigNumber && isPositive;\n  } catch {\n    return false;\n  }\n}\n\nexport function isBytesLikeSafe(value: string): boolean {\n  try {\n    return isBytesLike(value);\n  } catch {\n    return false;\n  }\n}\n\nexport function allTransactionsValid(transactions: Transaction[]): boolean {\n  return transactions.every(tx => tx.isValid === true);\n}\n\nexport async function isContractAddress(\n  address: string,\n  network: string\n): Promise<boolean> {\n  const provider = getProvider(network);\n  const code = await provider.getCode(address);\n  return code !== '0x' && code !== '0x0';\n}\n\nexport const checkIsContract = useMemoize(\n  async (address: string, network: string) =>\n    await isContractAddress(address, network)\n);\n\nexport function isBool(value: string): boolean {\n  if (value === 'true' || value === 'false') {\n    return true;\n  }\n  return false;\n}\n\nexport function validateInput(inputValue: string, type: InputTypes): boolean {\n  if (type === 'address') {\n    return isAddress(inputValue);\n  }\n  if (type === 'bytes') {\n    return isBytesLike(inputValue);\n  }\n  if (type === 'bytes32') {\n    return isBytesLike(inputValue);\n  }\n  if (isIntegerType(type)) {\n    return isValidInt(inputValue, type);\n  }\n  if (type === 'bool') {\n    return isBool(inputValue);\n  }\n  return true;\n}\n\ntype Integer = `int${number}` | `uint${number}`;\n\nfunction isValidInt(value: string, type: Integer) {\n  // check if is number like\n  if (!isBigNumberish(value)) {\n    return false;\n  }\n\n  const unsigned = type.startsWith('uint');\n  const signed = type.startsWith('int');\n  if (!unsigned && !signed) {\n    throw new Error(\n      'Invalid type specified. Type must be either an unsigned integer (uint) or a signed integer (int).'\n    );\n  }\n\n  const bits = parseInt(type.slice(unsigned ? 4 : 3));\n\n  if (isNaN(bits) || bits % 8 !== 0 || bits < 8 || bits > 256) {\n    throw new Error(\n      'Invalid integer type specified. Bit size must be a multiple of 8 with a max of 256'\n    );\n  }\n\n  const number = BigNumber.from(value);\n  // range checks\n  if (unsigned) {\n    const max = BigNumber.from(2).pow(bits).sub(1);\n    return number.gte(0) && number.lte(max);\n  } else {\n    const halfRange = BigNumber.from(2).pow(bits - 1);\n    const min = halfRange.mul(-1);\n    const max = halfRange.sub(1);\n    return number.gte(min) && number.lte(max);\n  }\n}\n\n// recursive type\nexport type MaybeNestedArrays<T> = T | T[] | MaybeNestedArrays<T>[];\n\nfunction validateMaybeArray(\n  valuesMaybeArray: MaybeNestedArrays<string>,\n  typesMaybeArray: MaybeNestedArrays<InputTypes>\n): MaybeNestedArrays<boolean> {\n  try {\n    if (Array.isArray(valuesMaybeArray)) {\n      // handle single type arrays\n      if (!Array.isArray(typesMaybeArray)) {\n        return valuesMaybeArray.map(value =>\n          validateInput(value as string, typesMaybeArray)\n        );\n      } else {\n        if (valuesMaybeArray.length !== typesMaybeArray.length) {\n          throw new Error(\"Types and values don't match\");\n        }\n        // handle array of arrays\n        return valuesMaybeArray.map(\n          (value: MaybeNestedArrays<string>, index: number) =>\n            validateMaybeArray(value, typesMaybeArray[index])\n        );\n      }\n    } else {\n      if (Array.isArray(typesMaybeArray)) {\n        throw new Error(\"Types and values don't match\");\n      }\n      // handle single item\n      return validateInput(valuesMaybeArray, typesMaybeArray);\n    }\n  } catch {\n    return false;\n  }\n}\n\nexport function validateSingleOrArray(\n  value: string,\n  types: MaybeNestedArrays<InputTypes> //   ['bool', 'uint256'] | ['bool','uint32', ['bool', 'string', 'address'] ]\n): boolean {\n  const parsed = parseInputArray(value);\n  if (!parsed) {\n    return false;\n  }\n\n  const res = validateMaybeArray(parsed, types);\n\n  if (Array.isArray(res)) {\n    return res.flat().every(Boolean);\n  }\n\n  return res;\n}\n\n// check if json is a safe json type\nexport const isSafeFile = (input: any): input is GnosisSafe.BatchFile => {\n  const $io0 = (input: any): boolean =>\n    'string' === typeof input.version &&\n    'string' === typeof input.chainId &&\n    'number' === typeof input.createdAt &&\n    'object' === typeof input.meta &&\n    null !== input.meta &&\n    $io1(input.meta) &&\n    Array.isArray(input.transactions) &&\n    input.transactions.every(\n      (elem: any) => 'object' === typeof elem && null !== elem && $io2(elem)\n    );\n  const $io1 = (input: any): boolean =>\n    (null === input.txBuilderVersion ||\n      undefined === input.txBuilderVersion ||\n      'string' === typeof input.txBuilderVersion) &&\n    (null === input.checksum ||\n      undefined === input.checksum ||\n      'string' === typeof input.checksum) &&\n    (null === input.createdFromSafeAddress ||\n      undefined === input.createdFromSafeAddress ||\n      'string' === typeof input.createdFromSafeAddress) &&\n    (null === input.createdFromOwnerAddress ||\n      undefined === input.createdFromOwnerAddress ||\n      'string' === typeof input.createdFromOwnerAddress) &&\n    'string' === typeof input.name &&\n    (null === input.description ||\n      undefined === input.description ||\n      'string' === typeof input.description);\n  const $io2 = (input: any): boolean =>\n    'string' === typeof input.to &&\n    'string' === typeof input.value &&\n    (null === input.data ||\n      undefined === input.data ||\n      'string' === typeof input.data) &&\n    (null === input.contractMethod ||\n      undefined === input.contractMethod ||\n      ('object' === typeof input.contractMethod &&\n        null !== input.contractMethod &&\n        $io3(input.contractMethod))) &&\n    (null === input.contractInputsValues ||\n      undefined === input.contractInputsValues ||\n      ('object' === typeof input.contractInputsValues &&\n        null !== input.contractInputsValues &&\n        false === Array.isArray(input.contractInputsValues) &&\n        $io5(input.contractInputsValues)));\n  const $io3 = (input: any): boolean =>\n    Array.isArray(input.inputs) &&\n    input.inputs.every(\n      (elem: any) => 'object' === typeof elem && null !== elem && $io4(elem)\n    ) &&\n    'string' === typeof input.name &&\n    'boolean' === typeof input.payable;\n  const $io4 = (input: any): boolean =>\n    (undefined === input.internalType ||\n      'string' === typeof input.internalType) &&\n    'string' === typeof input.name &&\n    'string' === typeof input.type &&\n    (null === input.components ||\n      undefined === input.components ||\n      (Array.isArray(input.components) &&\n        input.components.every(\n          (elem: any) => 'object' === typeof elem && null !== elem && $io4(elem)\n        )));\n  const $io5 = (input: any): boolean =>\n    Object.keys(input).every((key: any) => {\n      const value = input[key];\n      if (undefined === value) return true;\n      return 'string' === typeof value;\n    });\n  return 'object' === typeof input && null !== input && $io0(input);\n};\n"
  },
  {
    "path": "src/plugins/poap/ProposalSidebar.vue",
    "content": "<script setup>\nimport POAPCustomBlock from './components/CustomBlock.vue';\n\ndefineProps({\n  space: Object,\n  proposal: Object,\n  results: Object,\n  loadedResults: Boolean,\n  strategies: Object\n});\n</script>\n\n<template>\n  <POAPCustomBlock\n    v-if=\"space.plugins?.poap\"\n    :space=\"space\"\n    :proposal=\"proposal\"\n    :results=\"results\"\n    :loaded=\"loadedResults\"\n    :strategies=\"strategies\"\n  />\n</template>\n"
  },
  {
    "path": "src/plugins/poap/components/CustomBlock.vue",
    "content": "<script>\nimport Plugin from '../index';\n\nconst { notify } = useFlashNotification();\nconst { web3Account } = useWeb3();\n\nconst STATES = {\n  NOT_VOTED: {\n    headerImage: 'https://snapshotsplugin.s3.us-west-2.amazonaws.com/vote.svg',\n    header: 'poap.no_voted_header',\n    buttonText: 'poap.button_claim'\n  },\n  UNCLAIMED: {\n    headerImage: 'https://snapshotsplugin.s3.us-west-2.amazonaws.com/claim.svg',\n    header: 'poap.unclaimed_header',\n    buttonText: 'poap.button_claim'\n  },\n  CLAIMED: {\n    headerImage:\n      'https://snapshotsplugin.s3.us-west-2.amazonaws.com/succes.svg',\n    header: 'poap.claimed_header',\n    buttonText: 'poap.button_show'\n  },\n  LOADING: {\n    headerImage:\n      'https://snapshotsplugin.s3.us-west-2.amazonaws.com/succes.svg',\n    header: 'poap.loading_header',\n    buttonText: ''\n  }\n};\n\n// STATES\nconst NO_POAP = 'NO_POAP';\nconst NOT_VOTED = 'NOT_VOTED';\nconst LOADING = 'LOADING';\nconst UNCLAIMED = 'UNCLAIMED';\nconst CLAIMED = 'CLAIMED';\n\nexport default {\n  props: ['space', 'proposal', 'results', 'loaded', 'strategies'],\n  data() {\n    return {\n      loading: false,\n      plugin: new Plugin(),\n      currentState: LOADING,\n      address: '',\n      poapImg: '',\n      loadButton: false\n    };\n  },\n  computed: {\n    web3Account() {\n      return web3Account.value;\n    },\n    header() {\n      return STATES[this.currentState].header;\n    },\n    buttonText() {\n      return STATES[this.currentState].buttonText;\n    },\n    mainImg() {\n      return this.poapImg ? this.poapImg : STATES[this.currentState].mainImage;\n    },\n    headerImg() {\n      return STATES[this.currentState].headerImage;\n    },\n    actionEnabled() {\n      return this.currentState !== NOT_VOTED && this.currentState !== NO_POAP;\n    },\n    actionLoading() {\n      return this.currentState === LOADING || this.loadButton;\n    }\n  },\n  watch: {\n    currentState: async function (newCurrentState) {\n      if (newCurrentState === LOADING) {\n        // If the state is loading: start updating the state\n        this.checkStateLoop(this.updateState);\n      }\n    },\n    web3Account: async function (newAccount) {\n      // Update the state if the address\n      this.loading = true;\n      this.address = newAccount;\n      await this.updateState();\n      this.loading = false;\n    }\n  },\n  async created() {\n    this.address = web3Account.value;\n    this.loading = true;\n    await this.updateState();\n  },\n  methods: {\n    async action() {\n      switch (this.currentState) {\n        case CLAIMED:\n          this.plugin.openScanPage(this.address);\n          break;\n        case UNCLAIMED:\n          this.loadButton = true;\n          this.currentState = await this.plugin.claim(\n            this.proposal.id,\n            this.address\n          );\n          this.loadButton = false;\n          break;\n      }\n    },\n    // Check the state if the current state is loading\n    async checkStateLoop() {\n      await this.updateState();\n      switch (this.currentState) {\n        case LOADING:\n          setTimeout(() => this.checkStateLoop(), 5000);\n          break;\n        case UNCLAIMED:\n          notify(['red', this.$t('poap.error_claim')]);\n          break;\n        case CLAIMED:\n          notify(['green', this.$t('poap.success_claim')]);\n      }\n    },\n    async updateState() {\n      const response = await this.plugin.getCurrentState(\n        this.proposal.id,\n        this.address\n      );\n\n      const { currentState, image_url } = response;\n\n      if (image_url) {\n        this.poapImg = image_url;\n      }\n\n      this.currentState = currentState;\n      this.loading = false;\n    }\n  }\n};\n</script>\n\n<template>\n  <TuneBlock\n    v-if=\"currentState === 'LOADING' || currentState !== 'NO_POAP'\"\n    :loading=\"loading\"\n  >\n    <div class=\"flex flex-col items-center\">\n      <img :src=\"headerImg\" alt=\"\" class=\"mb-2\" />\n      <div class=\"mb-2 text-center text-skin-link\">{{ $t(header) }}</div>\n      <img\n        :src=\"mainImg\"\n        alt=\"\"\n        class=\"mt-1\"\n        style=\"\n          vertical-align: middle;\n          width: auto;\n          height: auto;\n          max-width: 125px;\n        \"\n      />\n      <TuneButton\n        v-if=\"currentState !== 'NO_POAP'\"\n        class=\"mt-3 w-full\"\n        :disabled=\"!actionEnabled\"\n        :loading=\"actionLoading\"\n        @click=\"action\"\n      >\n        {{ $t(buttonText) }}\n      </TuneButton>\n    </div>\n  </TuneBlock>\n</template>\n"
  },
  {
    "path": "src/plugins/poap/index.ts",
    "content": "import { getProposalVotes } from '../../helpers/snapshot';\n\n// URLS\nconst API_BASE_URL = 'https://api.poap.tech';\nconst APP_BASE_URL = 'https://app.poap.xyz';\n\nexport default class Plugin {\n  public author = 'Poap-xyz';\n  public version = '1.0.0';\n  public name = 'POAP Module';\n  public options: any;\n  private static readonly POAP_API_API_KEY =\n    'e68BJLYJ1ns9Mdb9hd21j8Ci1e6dVYsAddQfOVQp3oYHo9bqLCutMewQP8NhbVvguMdHiHefNat3eZWiFReeV9nr7QH4xAl67fYASKHhFfGbVKKzak11PV6NM1wYy2eP';\n\n  openScanPage(address) {\n    window.open(`${APP_BASE_URL}/scan/${address}`, '_blank');\n  }\n  async getCurrentState(snapshot, address) {\n    // Fetch the event\n    const eventResponse = await fetch(\n      `${API_BASE_URL}/snapshot/proposal/${snapshot}`,\n      { headers: { 'x-api-key': Plugin.POAP_API_API_KEY } }\n    );\n    // If the fetch fails: the event doesn't exists for this poap yet\n    if (!eventResponse.ok) {\n      return { image_url: '', currentState: 'NO_POAP' };\n    }\n    // Get the image from the event\n    const { image_url } = await eventResponse.json();\n\n    // Check that the address is not empty\n    if (!address) {\n      return { image_url, currentState: 'NOT_VOTED' };\n    }\n\n    // Fetch the vote\n    const votes = await getProposalVotes(snapshot, { voter: address });\n    const voted = votes.length > 0;\n    if (!voted) {\n      // Address did not vote proposal\n      return { image_url, currentState: 'NOT_VOTED' };\n    }\n\n    // Fetch the claim info for the address\n    const addressResponse = await fetch(\n      `${API_BASE_URL}/snapshot/proposal/${snapshot}/${address}`,\n      { headers: { 'x-api-key': Plugin.POAP_API_API_KEY } }\n    );\n\n    // If the fetch failed return the NOT_VOTED state\n    if (!addressResponse.ok) {\n      return { image_url, currentState: 'NOT_VOTED' };\n    }\n    const { claimed, status } = await addressResponse.json();\n\n    if (claimed) {\n      // If the address claimed the token but the status is not passed\n      // it means that the token is being minted\n      if (claimed && status !== 'passed') {\n        return { image_url, currentState: 'LOADING' };\n      }\n      // If the status is passed: the token was claimed\n      return { image_url, currentState: 'CLAIMED' };\n    } else if (voted) {\n      // The token is not claimed but the address voted\n      return { image_url, currentState: 'UNCLAIMED' };\n    }\n\n    return { image_url, currentState: 'NOT_VOTED' };\n  }\n  async claim(snapshot, address) {\n    const body = {\n      snapshotProposalHash: snapshot,\n      address: address\n    };\n    const response = await fetch(`${API_BASE_URL}/claim/snapshot-proposal`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        'x-api-key': Plugin.POAP_API_API_KEY\n      },\n      body: JSON.stringify(body)\n    });\n    if (!response.ok) {\n      // If the response is not ok: return the UNCLAIMED state\n      console.log(response.json());\n      return 'UNCLAIMED';\n    }\n    return 'LOADING';\n  }\n}\n"
  },
  {
    "path": "src/plugins/poap/plugin.json",
    "content": "{\n  \"name\": \"Poap Module\",\n  \"version\": \"1.0.0\",\n  \"author\": \"Poap-xyz\",\n  \"website\": \"https://github.com/snapshot-labs/snapshot/tree/master/src/plugins/poap\",\n  \"icon\": \"ipfs://QmSH2PsJUSpHUwS9XAoqLKRvF7qibrsqmBTdmM9Mmyv5fb\"\n}\n"
  },
  {
    "path": "src/plugins/progress/ProposalSidebar.vue",
    "content": "<script setup>\nimport ProgressCustomBlock from './components/CustomBlock.vue';\n\ndefineProps({\n  space: Object,\n  proposal: Object,\n  results: Object,\n  loadedResults: Boolean,\n  strategies: Object\n});\n</script>\n\n<template>\n  <ProgressCustomBlock\n    :loaded=\"loadedResults\"\n    :space=\"space\"\n    :proposal=\"proposal\"\n    :results=\"results\"\n    :strategies=\"strategies\"\n  />\n</template>\n"
  },
  {
    "path": "src/plugins/progress/components/CustomBlock.vue",
    "content": "<script setup>\nimport Plugin from '../index';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { signMessage } from '@snapshot-labs/snapshot.js/src/utils/web3';\n\nconst { notify } = useFlashNotification();\nconst { web3Account } = useWeb3();\nconst { t } = useI18n();\n\nconst props = defineProps([\n  'space',\n  'proposal',\n  'results',\n  'loaded',\n  'strategies'\n]);\nlet loading = false;\nlet addIsLoading = ref(false);\nlet deleteIsLoading = ref(false);\nlet completeIsLoading = ref(false);\nlet editMode = ref(false);\nlet newStepDescription = ref('');\nlet steps = ref([]);\nlet closeModal = ref(false);\nlet stepToDelete = ref({});\nlet currentlyLoadingStepId = ref(null);\n\nfunction isOwner() {\n  return web3Account.value === props.proposal.author;\n}\nfunction isComplete() {\n  return props.proposal?.state !== 'active';\n}\nfunction completedDate() {\n  return formatDate(new Date(props.proposal?.end * 1000));\n}\nfunction firstIncompleteStepId() {\n  return steps.value.find(step => step.stepStatus === 'active').id;\n}\nasync function requireSignature() {\n  const storedAuth = localStorage.getItem('snap-progress');\n  if (storedAuth) {\n    return storedAuth;\n  }\n\n  const auth = getInstance();\n  let signature = await signMessage(\n    auth.web3,\n    t('progress.confirmSignature'),\n    web3Account.value\n  );\n\n  const authHeader = `${props.proposal.id}-${signature}`;\n  localStorage.setItem('snap-progress', authHeader);\n\n  return authHeader;\n}\nasync function getActiveSteps() {\n  if (isComplete) {\n    const apiUrl = `https://jissr670k3.execute-api.us-east-1.amazonaws.com/dev/proposal/${props.proposal.id}`;\n    try {\n      fetch(apiUrl).then(response =>\n        response\n          .json()\n          .then(\n            data =>\n              (steps.value = data.Items.sort((a, b) =>\n                a.index > b.index ? 1 : -1\n              ))\n          )\n      );\n    } catch (e) {\n      notify(['red', t('progress.wentWrong')]);\n    }\n  }\n}\nasync function createNewStep() {\n  if (newStepDescription.value !== '') {\n    try {\n      const sig = await requireSignature();\n      addIsLoading.value = true;\n      const apiUrl = `https://jci7szds71.execute-api.us-east-1.amazonaws.com/dev/proposal/${props.proposal.id}`;\n      const requestOptions = {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          authorization: sig\n        },\n        body: JSON.stringify({\n          description: newStepDescription.value\n        })\n      };\n      fetch(apiUrl, requestOptions).then(response =>\n        response.json().then(() => {\n          getActiveSteps();\n          addIsLoading.value = false;\n          newStepDescription.value = '';\n        })\n      );\n    } catch (e) {\n      addIsLoading.value = false;\n      notify(['red', t('progress.wentWrong')]);\n    }\n  }\n}\nasync function setStepComplete(step) {\n  try {\n    const sig = await requireSignature();\n\n    currentlyLoadingStepId.value = step.id;\n    completeIsLoading.value = true;\n    const requestOptions = {\n      method: 'PUT',\n      headers: { 'Content-Type': 'application/json', authorization: sig },\n      body: JSON.stringify({ proposalId: step.proposalId })\n    };\n    fetch(\n      `https://jci7szds71.execute-api.us-east-1.amazonaws.com/dev/proposal/${step.id}`,\n      requestOptions\n    ).then(() => {\n      getActiveSteps();\n      completeIsLoading.value = false;\n      currentlyLoadingStepId.value = null;\n    });\n  } catch (error) {\n    completeIsLoading.value = false;\n    currentlyLoadingStepId.value = null;\n    notify(['red', t('progress.wentWrong')]);\n  }\n}\nfunction closeEvent() {\n  closeModal.value = false;\n}\nfunction showDeleteModal(step) {\n  stepToDelete.value = step;\n  closeModal.value = true;\n}\nasync function deleteStep() {\n  const step = stepToDelete.value;\n  try {\n    const sig = await requireSignature();\n    deleteIsLoading.value = true;\n    const apiUrl = `https://jci7szds71.execute-api.us-east-1.amazonaws.com/dev/proposal/${step.id}?proposalId=${props.proposal.id}`;\n    const requestOptions = {\n      method: 'DELETE',\n      headers: {\n        authorization: sig\n      }\n    };\n    fetch(apiUrl, requestOptions).then(() => {\n      closeEvent();\n      getActiveSteps();\n      deleteIsLoading.value = false;\n    });\n  } catch (error) {\n    deleteIsLoading.value = false;\n    notify(['red', t('progress.wentWrong')]);\n  }\n}\nfunction formatDate(date) {\n  const options = { year: 'numeric', month: 'long', day: 'numeric' };\n  return new Date(date).toLocaleDateString('en', options);\n}\nfunction toggleEditMode() {\n  editMode.value = !editMode.value;\n}\nfunction thisStepUpdating(step) {\n  if (step.id === currentlyLoadingStepId.value) {\n    return true;\n  }\n  return false;\n}\nonMounted(async () => {\n  getActiveSteps();\n});\n</script>\n\n<template>\n  <TuneBlock :loading=\"!loaded\">\n    <template #header>\n      <TuneBlockHeader title=\"Progress\">\n        <BaseButtonIcon class=\"!p-0\">\n          <i-ho-cog\n            v-if=\"isOwner() && isComplete()\"\n            class=\"text-base\"\n            @click=\"toggleEditMode()\"\n          />\n        </BaseButtonIcon>\n      </TuneBlockHeader>\n    </template>\n    <div v-if=\"!isComplete()\">{{ $t('progress.comeBack') }}</div>\n\n    <div v-else class=\"flex flex-col\">\n      <div>\n        <div\n          :class=\"{\n            'border-green': isComplete()\n          }\"\n          class=\"h-32 min-w-[178px] rounded-xl border-2 bg-skin-block-bg p-3 text-base\"\n        >\n          <div class=\"h-16 flex\">\n            <div>\n              <div class=\"step-counter\">1</div>\n            </div>\n            <div class=\"ml-3 w-full\">{{ $t('progress.voting') }}</div>\n          </div>\n          <div class=\"flex\">\n            <div class=\"w-[55%] text-xs\">\n              <div v-if=\"isComplete()\" class=\"leading-[.5rem]\">\n                {{ $t('progress.completed') }}\n              </div>\n              <div v-if=\"isComplete()\">\n                {{ completedDate() }}\n              </div>\n            </div>\n            <div class=\"ml-auto flex flex-col-reverse\">\n              <span v-if=\"isComplete()\" class=\"status mr-2 bg-green text-white\">\n                {{ $t('progress.completed') }}\n              </span>\n              <span\n                v-if=\"!isComplete()\"\n                class=\"status mr-2 bg-gray-500 text-white\"\n                >{{ $t('progress.inProgress') }}</span\n              >\n            </div>\n          </div>\n        </div>\n        <div v-if=\"steps.length > 0\" class=\"h-[1rem]\">\n          <div class=\"h-full w-[2.3rem] border-r-2 border-green\"></div>\n        </div>\n        <div v-for=\"(step, index) in steps\" :key=\"step.id\">\n          <div\n            :class=\"{\n              'border-green': step.stepStatus === 'complete'\n            }\"\n            class=\"h-32 min-w-[178px] rounded-xl border-2 bg-skin-block-bg p-3 text-base\"\n          >\n            <div class=\"mb-2 flex min-h-[4rem]\">\n              <div>\n                <div class=\"step-counter\">\n                  {{ index + 2 }}\n                </div>\n              </div>\n              <div class=\"ml-3 w-full\">\n                {{ step.description }}\n              </div>\n              <div class=\"pt-12px relative -top-2 text-right\">\n                <i\n                  v-if=\"step.stepStatus === 'active' && editMode\"\n                  class=\"gg-trash cursor-pointer\"\n                  @click=\"showDeleteModal(step)\"\n                ></i>\n              </div>\n            </div>\n            <div class=\"flex\">\n              <div class=\"w-[55%] text-xs\">\n                <div\n                  v-if=\"step.stepStatus === 'complete'\"\n                  class=\"leading-[.5rem]\"\n                >\n                  {{ $t('progress.completed') }}\n                </div>\n                <div v-if=\"step.stepStatus === 'complete'\">\n                  {{ formatDate(step.completedDate) }}\n                </div>\n              </div>\n              <div class=\"ml-auto flex flex-col-reverse\">\n                <span\n                  v-if=\"step.stepStatus === 'complete'\"\n                  class=\"status mr-2 bg-green text-white\"\n                  >{{ $t('progress.completed') }}\n                </span>\n                <span\n                  v-if=\"\n                    step.stepStatus !== 'complete' &&\n                    !editMode &&\n                    firstIncompleteStepId() === step.id\n                  \"\n                  class=\"status mr-2 bg-gray-500 text-white\"\n                  >{{ $t('progress.inProgress') }}\n                </span>\n\n                <TuneButton\n                  v-if=\"step.stepStatus !== 'complete' && editMode\"\n                  class=\"w-[7rem]\"\n                  :disabled=\"firstIncompleteStepId() !== step.id\"\n                  @click=\"setStepComplete(step)\"\n                >\n                  <span v-if=\"!thisStepUpdating(step)\">{{\n                    $t('progress.complete')\n                  }}</span>\n                  <div\n                    v-if=\"thisStepUpdating(step)\"\n                    class=\"spinner relative\"\n                  ></div>\n                </TuneButton>\n\n                <span\n                  v-if=\"\n                    step.stepStatus !== 'complete' &&\n                    !editMode &&\n                    firstIncompleteStepId() !== step.id\n                  \"\n                  class=\"status mr-2 bg-gray-500 text-white\"\n                  >{{ $t('progress.soon') }}\n                </span>\n              </div>\n            </div>\n          </div>\n          <div class=\"h-[1rem]\">\n            <div\n              :hidden=\"index === steps.length - 1\"\n              :class=\"{\n                'border-green': step.stepStatus === 'complete',\n                'border-gray': step.stepStatus !== 'complete'\n              }\"\n              class=\"h-full w-[2.3rem] border-r-2\"\n            ></div>\n          </div>\n        </div>\n      </div>\n      <div class=\"w-100\">\n        <div v-if=\"editMode\" class=\"mt-4\">\n          <span class=\"text-xl\">{{ $t('progress.newStep') }}</span>\n          <textarea\n            v-model=\"newStepDescription\"\n            :placeholder=\"[$t('progress.description')]\"\n            class=\"input h-full w-full rounded-3xl border border-skin-border px-4 py-3 text-left focus-within:!border-skin-link hover:border-skin-text\"\n          />\n          <TuneButton\n            v-if=\"isAdmin || isOwner\"\n            class=\"button button--primary ml-2 mt-2 w-full px-[24px] hover:brightness-95\"\n            @click=\"createNewStep()\"\n          >\n            <span v-if=\"!addIsLoading\">{{ $t('progress.add') }}</span>\n            <div v-if=\"addIsLoading\" class=\"spinner relative\"></div>\n          </TuneButton>\n        </div>\n      </div>\n    </div>\n  </TuneBlock>\n  <BaseModal :open=\"closeModal\" @close=\"closeEvent\">\n    <template #header>\n      <h3>{{ $t('progress.deleteStep') }}</h3>\n    </template>\n    <div class=\"mt-3 text-center\">\n      <p>{{ $t('progress.deleteConfirm') }}</p>\n    </div>\n    <div\n      class=\"mb-2 mt-3 flex content-center items-center justify-center text-center\"\n    >\n      <TuneButton\n        class=\"w-[6rem] !bg-primary !text-white\"\n        :loading=\"loading\"\n        @click=\"deleteStep\"\n      >\n        <span v-if=\"!deleteIsLoading\">{{ $t('progress.delete') }}</span>\n        <div v-if=\"deleteIsLoading\" class=\"spinner relative\"></div>\n      </TuneButton>\n      <TuneButton :disabled=\"loading\" class=\"ml-2\" @click=\"closeEvent\">\n        {{ $t('progress.cancel') }}\n      </TuneButton>\n    </div>\n  </BaseModal>\n</template>\n<style>\n.status {\n  font-size: 16px;\n  height: 26px;\n  vertical-align: middle;\n  padding: 0 12px;\n  border-radius: 14px;\n}\n.h-16 {\n  height: 4rem;\n}\n.pt-12px {\n  padding-top: 12px;\n}\na {\n  text-decoration: none;\n}\na.button .iconfont {\n  font-size: 24px;\n  transition: 0.4s;\n}\na.button:hover .label-hidden {\n  max-width: 200px;\n  margin-left: 8px;\n  opacity: 1;\n}\na.button .label-hidden {\n  max-width: 0;\n  opacity: 0;\n  white-space: nowrap;\n  transition: 0.4s;\n}\na.button {\n  position: relative;\n  display: inline-flex;\n  align-items: center;\n  justify-content: flex-start;\n  overflow: hidden;\n  padding: 8px;\n  border-radius: 8px;\n  transition: 0.2s;\n}\n.edit-icon {\n  top: -65px;\n}\n.stepper-wrapper {\n  margin-top: auto;\n  display: flex;\n  justify-content: space-between;\n  margin-bottom: 20px;\n}\n.stepper-item {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  flex: 1;\n\n  @media (max-width: 768px) {\n    font-size: 12px;\n  }\n}\n\n.stepper-item::before {\n  position: absolute;\n  content: '';\n  border-bottom: 2px solid #ccc;\n  width: 100%;\n  top: 20px;\n  left: -50%;\n  z-index: 2;\n}\n\n.stepper-item::after {\n  position: absolute;\n  content: '';\n  border-bottom: 2px solid #ccc;\n  width: 100%;\n  top: 20px;\n  left: 50%;\n  z-index: 2;\n}\n\n.step-counter {\n  position: relative;\n  color: white;\n  z-index: 5;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  width: 40px;\n  height: 40px;\n  border-radius: 50%;\n  background: #565656;\n  margin-bottom: 6px;\n}\n\n.stepper-item.active {\n  font-weight: bold;\n}\n\n.stepper-item.completed .step-counter {\n  background-color: #4bb543;\n}\n\n.stepper-item.completed::after {\n  position: absolute;\n  content: '';\n  border-bottom: 2px solid #4bb543;\n  width: 100%;\n  top: 20px;\n  left: 50%;\n  z-index: 3;\n}\n\n.stepper-item:first-child::before {\n  content: none;\n}\n.stepper-item:last-child::after {\n  content: none;\n}\n.step-name {\n  text-align: center;\n}\n\n@keyframes spinner {\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n.spinner:before {\n  content: '';\n  box-sizing: border-box;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  width: 20px;\n  height: 20px;\n  margin-top: -10px;\n  margin-left: -10px;\n  border-radius: 50%;\n  border: 2px solid #f6f;\n  border-top-color: #0e0;\n  border-right-color: #0dd;\n  border-bottom-color: #f90;\n  animation: spinner 0.6s linear infinite;\n}\n.gg-trash {\n  box-sizing: border-box;\n  position: relative;\n  display: block;\n  transform: scale(var(--ggs, 1));\n  width: 10px;\n  height: 12px;\n  border: 2px solid transparent;\n  box-shadow:\n    0 0 0 2px,\n    inset -2px 0 0,\n    inset 2px 0 0;\n  border-bottom-left-radius: 1px;\n  border-bottom-right-radius: 1px;\n  margin-top: 4px;\n}\n.gg-trash::after,\n.gg-trash::before {\n  content: '';\n  display: block;\n  box-sizing: border-box;\n  position: absolute;\n}\n.gg-trash::after {\n  background: currentColor;\n  border-radius: 3px;\n  width: 16px;\n  height: 2px;\n  top: -4px;\n  left: -5px;\n}\n.gg-trash::before {\n  width: 10px;\n  height: 4px;\n  border: 2px solid;\n  border-bottom: transparent;\n  border-top-left-radius: 2px;\n  border-top-right-radius: 2px;\n  top: -7px;\n  left: -2px;\n}\n</style>\n"
  },
  {
    "path": "src/plugins/progress/index.ts",
    "content": "export default class Plugin {\n  public author = 'nick';\n  public version = '0.1.0';\n  public name = 'progess';\n}\n"
  },
  {
    "path": "src/plugins/progress/plugin.json",
    "content": "{\n  \"name\": \"Progress\",\n  \"version\": \"1.0.0\",\n  \"author\": \"Deadeye07\",\n  \"website\": \"https://github.com/Deadeye07/snapshot-develop/tree/master/src/plugins/progress\",\n  \"icon\": \"ipfs://Qmd1Zpmscf5HBvh5ouu6AUcDTst28CKYyrpfGnPCqd73gZ\"\n}\n"
  },
  {
    "path": "src/plugins/progress/readme.md",
    "content": "# Progress\n\n## Keep Track of [Snapshot.org](http://Snapshot.org) Proposal Progress\n\nProgress is a small and simple plugin for [Snapshot.org](http://snapshot.org) that allows Spaces to inform DAO members the progress of a proposal _after_ voting has completed - how a proposal is progressing in being executed. Often, finding this information requires monitoring notifications on Discord, Telegram and Twitter - what if we could just check back right at the source of the proposal on Snapshot? Yes please.\n\n## Features\n\n- Creation of New Steps\n- Marking of steps as 'Completed'\n- Deletion of Steps (Only allowed for steps yet to be 'Completed')\n- Web3 Signature Authentication\n- Cool loading spinners\n\n## Who can add, complete, and delete steps?\n\nOnly a Proposals Author is able to view the Edit button, which displays options to do the following:\n\n- Create new Steps\n- Mark Steps as 'Complete'\n- Delete Steps\n"
  },
  {
    "path": "src/plugins/projectGalaxy/ProposalSidebar.vue",
    "content": "<script setup>\nimport ProjectGalaxyCustomBlock from './components/CustomBlock.vue';\n\ndefineProps({\n  space: Object,\n  proposal: Object,\n  results: Object,\n  loadedResults: Boolean,\n  strategies: Object\n});\n</script>\n\n<template>\n  <ProjectGalaxyCustomBlock\n    v-if=\"space.plugins?.projectGalaxy\"\n    :space=\"space\"\n    :proposal=\"proposal\"\n    :results=\"results\"\n    :loaded=\"loadedResults\"\n    :strategies=\"strategies\"\n  />\n</template>\n"
  },
  {
    "path": "src/plugins/projectGalaxy/README.md",
    "content": "# Galxe Plugin\n\nCreated time: July 21, 2022 5:51 PM\n\nThe **Galxe Plugin is designed for Snapshot.org. This will incentivize communities to participate in the proposal vote by rewarding them with OATs (On-Chain Achievement Tokens). Space Admin can add this plugin into your space. After users vote, they can claim an OAT directly from the Galxe Plugin section on the proposal page.**\n\n### 1. Preparation\n\n1. Create a Snapshot proposal on [https://snapshot.org/](https://snapshot.org/)\n2. Create a Snapshot Credential with the proposal ID created from step1 on [https://galxe.com/](https://galxe.com/)\n3. Create an OAT campaign ONLY with the credential created from step2 on [https://galxe.com/](https://galxe.com/)\n\n### 2. Add Galxe Plugin to your space\n\nIn Snapshot space setting, you can search and find the Galxe Plugin in the plugins section, and add it to your space.\n\n### 3. Set config JSON\n\nAdd campaign and proposal info to your plugin.\n\nFirst, click the plugin, then you will see an editor section. See example below:\n\n_Specific JSON format like this, and you could use more than 1 OAT in your space:_\n\n```javascript\n{\n\t\"oats\": {\n\t\t\"<proposal ID 1>\": \"<Space Name>/campaign/<Campaign ID>\",\n\t\t\"<proposal ID 2>\": \"<Space Name>/campaign/<Campaign ID>\",\n\t\t\"<proposal ID 3>\": \"<Space Name>/campaign/<Campaign ID>\",\n\t}\n}\n```\n\nnotice: The domain should not included in <Space Name>.\n\n#### Usually you don't need to change api part unless you know which api you are using.\n\n````javascript\n{\n\t\"api\": \"https://graphigo.stg.galaxy.eco/query\",\n\t\"oats\": {\n\t\t\"0x554ca2bd7d979e8b72c6ae6415946a7bb470da9f60a9cf931205f083c03632a3\": \"jokey/campaign/GCixQUUqfE\"\n\t}\n}\n\nSecond, fill it with the proposal ID and the information of the Campaign which you would like to link with the proposal. If you have multiple proposals which all distribute OATs to voters, you can also add multiple proposalID-campaignInfo pairs at one time.\n\n**Notice:** if you delete a proposalID-campaignInfo pair, people won’t see the OAT information on the page of your proposal even if the proposal ends or the OATs have already been distributed.\n\n*Here is a Demo:*\n```javascript\n{\n\t\"oats\": {\n\t\t\"0x554ca2bd7d979e8b72c6ae6415946a7bb470da9f60a9cf931205f083c03632a3\": \"galaxy/campaign/GCcqvUtDaM\"\n\t}\n}\n````\n\n### More\n\nFor Production Environment, we are using API Base Url https://graphigo.prd.galaxy.eco/query and only allows domain snapshot.org.\nFor Local Environment or any other test purpose, you can switch the API Base Url(_file index.ts_) to https://graphigo.stg.galaxy.eco/query and change port to 3000(_file package.json_).\n\nWhen you see \"Access-Control-Allow-Origin\" error on console, please check if you have set the correct API Base Url and correct port, then restart the server.\n"
  },
  {
    "path": "src/plugins/projectGalaxy/components/CustomBlock.vue",
    "content": "<script>\nimport Plugin from '../index';\n\nconst { web3Account } = useWeb3();\n\nconst APP_URL = 'https://galxe.com';\nconst NO_OAT_IMAGE =\n  'https://snapshotsplugin.s3.us-west-2.amazonaws.com/placeholder.png';\nconst IMG_LOGO_GALAXY =\n  'https://cdn-2.galxe.com/galaxy/images/galaxy/1666692826032589364.png';\nconst IMG_ICON_LINK =\n  'https://d257b89266utxb.cloudfront.net/galaxy/images/avatar/0x4960c283c45e1898c41633c39fb2015167b20dc3-1655712057.png';\nconst IMG_ICONS = {\n  CLAIMED:\n    'https://d257b89266utxb.cloudfront.net/galaxy/images/avatar/0x4960c283c45e1898c41633c39fb2015167b20dc3-1655712157.png',\n  CLAIMING:\n    'https://d257b89266utxb.cloudfront.net/galaxy/images/avatar/0x4960c283c45e1898c41633c39fb2015167b20dc3-1655712194.png'\n};\n\nconst STATES = {\n  NO_OAT: {\n    buttonText: 'No OAT'\n  },\n  WAIT_TO_START: {\n    buttonText: 'Wait to Start'\n  },\n  VOTE_TO_CLAIM: {\n    buttonText: 'Vote to Claim'\n  },\n  CLAIM: {\n    buttonText: 'Claim'\n  },\n  CLAIMING: {\n    buttonText: 'Claiming'\n  },\n  CLAIMED: {\n    buttonText: 'Claimed'\n  },\n  ENDED: {\n    buttonText: 'Campaign Ended'\n  },\n  REACHED_MINTING_CAP: {\n    buttonText: 'Reached Minting Cap'\n  }\n};\n\n// STATES\nconst NO_OAT = 'NO_OAT';\nconst WAIT_TO_START = 'WAIT_TO_START';\nconst CLAIM = 'CLAIM';\nconst CLAIMING = 'CLAIMING';\nconst CLAIMED = 'CLAIMED';\n\nexport default {\n  props: ['space', 'proposal', 'results', 'loaded', 'strategies'],\n  data() {\n    return {\n      disabled: false,\n      oats: {},\n      loading: false,\n      plugin: new Plugin(this.space.plugins.projectGalaxy.api),\n      currentState: NO_OAT,\n      currentCampaignUrl: '',\n      currentCampaignId: '',\n      address: '',\n      oatImg: '',\n      imgLogoGalaxy: IMG_LOGO_GALAXY,\n      imgIconLink: IMG_ICON_LINK,\n      imgIcons: IMG_ICONS\n    };\n  },\n  computed: {\n    web3Account() {\n      return web3Account.value;\n    },\n    buttonText() {\n      return STATES[this.currentState]\n        ? STATES[this.currentState].buttonText\n        : STATES[NO_OAT].buttonText;\n    },\n    mainImg() {\n      return this.oatImg ? this.oatImg : NO_OAT_IMAGE;\n    },\n    urlOAT() {\n      return `${APP_URL}/${this.currentCampaignUrl}`;\n    },\n    galaxyId() {\n      return `${APP_URL}/galxeid/${this.address}`;\n    },\n    actionEnabled() {\n      return (\n        this.currentState === CLAIM ||\n        this.currentState === CLAIMING ||\n        this.currentState === CLAIMED\n      );\n    }\n  },\n  watch: {\n    currentState: async function (newCurrentState) {\n      if (newCurrentState === CLAIMING) {\n        // If the state is loading: start updating the state\n        this.checkStateLoop(this.updateState);\n      }\n    },\n    web3Account: async function (newAccount) {\n      // Update the state if the address\n      this.loading = true;\n      this.address = newAccount;\n      await this.updateState();\n      this.loading = false;\n    }\n  },\n  async created() {\n    this.address = web3Account.value;\n    this.loading = true;\n    // get campain info from config\n    this.getCampainInfo();\n    // not empty oat\n    if (this.currentState !== NO_OAT) {\n      await this.getOAT(this.currentCampaignId);\n      await this.updateState();\n    }\n  },\n  methods: {\n    getCampainInfo() {\n      if (this.space.plugins.projectGalaxy?.oats) {\n        this.oats = this.space.plugins.projectGalaxy.oats;\n        this.currentCampaignUrl = this.oats[this.proposal.id];\n        if (this.currentCampaignUrl) {\n          this.currentCampaignId =\n            this.currentCampaignUrl.match(/[^/]+(?=\\/$|$)/g)[0];\n          this.currentState = WAIT_TO_START;\n        } else {\n          this.currentState = NO_OAT;\n        }\n      }\n    },\n    async action() {\n      if (this.currentState === CLAIM) {\n        await this.claimOAT();\n      }\n    },\n    // Check the state if the current state is loading\n    async checkStateLoop() {\n      await this.updateState();\n      if (this.currentState === CLAIMING) {\n        setTimeout(() => this.checkStateLoop(), 5000);\n      }\n    },\n    // update state\n    async updateState() {\n      try {\n        const response = await this.plugin.getCurrentState(\n          this.proposal.id,\n          this.address,\n          this.currentCampaignId\n        );\n\n        const { currentState } = response;\n\n        this.currentState = currentState;\n        this.loading = false;\n      } catch (e) {\n        this.disabled = true;\n      }\n    },\n    // get oat image\n    async getOAT(campainId) {\n      try {\n        this.oatImg = await this.plugin.getOATImage(campainId);\n      } catch (e) {\n        this.disabled = true;\n      }\n    },\n    // claim OAT\n    async claimOAT() {\n      try {\n        const success = await this.plugin.claim(\n          this.address,\n          this.currentCampaignId\n        );\n        if (success) {\n          this.currentState = CLAIMING;\n        } else {\n          await this.updateState();\n        }\n      } catch (e) {\n        await this.updateState();\n      }\n    }\n  }\n};\n</script>\n\n<template>\n  <TuneBlock\n    v-if=\"!disabled && currentState !== 'NO_OAT'\"\n    title=\"OAT for Vote\"\n    :loading=\"loading\"\n    class=\"overflow-hidden\"\n  >\n    <div class=\"relative overflow-hidden -m-3\">\n      <!-- background image -->\n      <div class=\"absolute bottom-0 left-0 right-0 top-0 z-0 w-full blur-3xl\">\n        <img\n          :src=\"mainImg\"\n          alt=\"\"\n          class=\"absolute bottom-0 left-0 right-0 top-0 z-0 w-full\"\n        />\n        <div\n          class=\"absolute bottom-0 left-0 right-0 top-0 z-0 w-full\"\n          :style=\"{\n            background:\n              'linear-gradient(180deg, #211f24 0%, rgba(33, 31, 36, 0) 51.04%, #211f24 100%)'\n          }\"\n        ></div>\n      </div>\n      <!-- main content -->\n      <div\n        class=\"relative bottom-0 left-0 right-0 top-0 z-10 flex flex-col items-center px-6 py-6\"\n      >\n        <img :src=\"imgLogoGalaxy\" alt=\"\" class=\"mb-4 h-3 w-auto\" style=\"\" />\n        <img\n          :src=\"mainImg\"\n          alt=\"\"\n          class=\"\"\n          style=\"\n            vertical-align: middle;\n            width: 180px;\n            height: 180px;\n            border-radius: 50%;\n          \"\n        />\n        <base-button-icon\n          class=\"flex w-full cursor-pointer items-center justify-center\"\n          :disabled=\"!actionEnabled\"\n          style=\"\n            border: 1px solid rgba(255, 255, 255, 0.2);\n            border-radius: 100px;\n            margin-top: 32px;\n            padding: 8px 0;\n          \"\n          @click=\"action\"\n        >\n          <img\n            v-if=\"currentState === 'CLAIMING' || currentState === 'CLAIMED'\"\n            :src=\"imgIcons[currentState]\"\n            alt=\"\"\n            class=\"mr-2\"\n            style=\"width: 20px; height: auto\"\n          />\n          <span\n            :class=\"[\n              'text-base font-semibold',\n              {\n                'text-white': actionEnabled,\n                'text-green': currentState === 'CLAIMED'\n              }\n            ]\"\n            >{{ buttonText }}</span\n          >\n        </base-button-icon>\n        <a\n          :href=\"galaxyId\"\n          target=\"_blank\"\n          class=\"mt-4 flex flex-nowrap items-center\"\n        >\n          <span\n            style=\"\n              font-style: normal;\n              font-weight: 400;\n              font-size: 16px;\n              line-height: 20px;\n            \"\n            >View My OAT Collection</span\n          >\n          <img\n            :src=\"imgIconLink\"\n            alt=\"\"\n            class=\"ml-1\"\n            style=\"width: 14px; height: 14px\"\n          />\n        </a>\n      </div>\n    </div>\n  </TuneBlock>\n</template>\n"
  },
  {
    "path": "src/plugins/projectGalaxy/index.ts",
    "content": "export default class Plugin {\n  API_BASE_URL = 'https://graphigo.prd.galaxy.eco/query';\n\n  constructor(apiBaseUrl) {\n    if (apiBaseUrl) {\n      this.API_BASE_URL = apiBaseUrl;\n    }\n  }\n\n  async fetchGQL({ query, variables }) {\n    return await fetch(this.API_BASE_URL, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify({\n        query,\n        variables\n      })\n    });\n  }\n\n  async getOATImage(campaignId) {\n    const response = await this.fetchGQL({\n      query: `query getOATImage($id: ID!) {\n        campaign(id: $id) {\n          thumbnail\n        }\n      }`,\n      variables: {\n        id: campaignId\n      }\n    });\n    if (!response.ok) {\n      return '';\n    }\n    const resultJSON = await response.json();\n    const { thumbnail } = resultJSON.data.campaign;\n    return thumbnail;\n  }\n\n  async claim(address, campaignID) {\n    const eventResponse = await this.fetchGQL({\n      query: `mutation claimOAT($input: PrepareParticipateInput!) {\n        prepareParticipate(input: $input) {\n          allow\n          disallowReason\n        }\n      }`,\n      variables: {\n        input: {\n          signature: '',\n          campaignID,\n          address\n        }\n      }\n    });\n\n    if (!eventResponse.ok) {\n      return false;\n    }\n\n    const responseJSON = await eventResponse.json();\n    return responseJSON.data.prepareParticipate.allow;\n  }\n\n  async getCurrentState(snapshot, address, campaign) {\n    // Fetch the event\n    const eventResponse = await this.fetchGQL({\n      query: `query getCurrentState($input: SnapshotClaimStatusInput!) {\n        snapshotClaimStatus(input: $input) {\n          status\n        }\n      }`,\n      variables: {\n        input: {\n          address,\n          campaign,\n          proposalId: snapshot\n        }\n      }\n    });\n    // If the fetch fails: the event doesn't exists for this poap yet\n    if (!eventResponse.ok) {\n      return { currentState: 'NO_OAT' };\n    }\n    // Get the status from the event\n    const responseJSON = await eventResponse.json();\n    const responseStatus = responseJSON.data.snapshotClaimStatus.status;\n\n    // Check that the address is not empty\n    if (!address) {\n      return { currentState: 'VOTE_TO_CLAIM' };\n    }\n    return { currentState: responseStatus };\n  }\n}\n"
  },
  {
    "path": "src/plugins/projectGalaxy/plugin.json",
    "content": "{\n  \"name\": \"Galxe\",\n  \"version\": \"0.0.1\",\n  \"author\": \"Galxe\",\n  \"description\": \"Galxe OAT\",\n  \"website\": \"https://github.com/snapshot-labs/snapshot/tree/master/src/plugins/projectGalaxy\",\n  \"icon\": \"https://ipfs.io/ipfs/bafkreibjxoeegwucimd4hjfn3xmxrmkes3je5gwa723gaqosirzgewutfq\",\n  \"defaults\": {\n    \"space\": {\n      \"oats\": {\n        \"<Proposal ID>\": \"<Space Name>/campaign/<Campaign ID>\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/plugins/quorum/examples.json",
    "content": "[\n  {\n    \"strategy\": \"static\",\n    \"total\": 1234\n  },\n  {\n    \"strategy\": \"balance\",\n    \"address\": \"0x892f481BD6E9d7D26aE365211D9B45175d5D00e4\",\n    \"decimals\": 18,\n    \"quorumModifier\": 0.5,\n    \"methodABI\": {\n      \"inputs\": [],\n      \"name\": \"totalVotes\",\n      \"outputs\": [\n        {\n          \"internalType\": \"uint256\",\n          \"name\": \"\",\n          \"type\": \"uint256\"\n        }\n      ],\n      \"stateMutability\": \"view\",\n      \"type\": \"function\"\n    }\n  },\n  {\n    \"strategy\": \"multichainBalance\",\n    \"network\": \"1\",\n    \"quorumModifier\": 0.05,\n    \"strategies\": [\n      {\n        \"network\": \"1\",\n        \"address\": \"0x0e42acBD23FAee03249DAFF896b78d7e79fBD58E\",\n        \"symbol\": \"veSTG\",\n        \"decimals\": 18,\n        \"methodABI\": {\n          \"inputs\": [],\n          \"name\": \"totalSupply\",\n          \"outputs\": [\n            {\n              \"internalType\": \"uint256\",\n              \"name\": \"\",\n              \"type\": \"uint256\"\n            }\n          ],\n          \"stateMutability\": \"view\",\n          \"type\": \"function\"\n        }\n      },\n      {\n        \"network\": \"56\",\n        \"address\": \"0xD4888870C8686c748232719051b677791dBDa26D\",\n        \"symbol\": \"veSTG\",\n        \"decimals\": 18,\n        \"methodABI\": {\n          \"inputs\": [],\n          \"name\": \"totalSupply\",\n          \"outputs\": [\n            {\n              \"internalType\": \"uint256\",\n              \"name\": \"\",\n              \"type\": \"uint256\"\n            }\n          ],\n          \"stateMutability\": \"view\",\n          \"type\": \"function\"\n        }\n      },\n      {\n        \"network\": \"43114\",\n        \"address\": \"0xCa0F57D295bbcE554DA2c07b005b7d6565a58fCE\",\n        \"symbol\": \"veSTG\",\n        \"decimals\": 18,\n        \"methodABI\": {\n          \"inputs\": [],\n          \"name\": \"totalSupply\",\n          \"outputs\": [\n            {\n              \"internalType\": \"uint256\",\n              \"name\": \"\",\n              \"type\": \"uint256\"\n            }\n          ],\n          \"stateMutability\": \"view\",\n          \"type\": \"function\"\n        }\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "src/plugins/quorum/plugin.json",
    "content": "{\n  \"name\": \"Quorum\",\n  \"version\": \"0.1.0\",\n  \"author\": \"lbeder\",\n  \"website\": \"https://github.com/snapshot-labs/snapshot/tree/master/src/plugins/quorum\",\n  \"icon\": \"ipfs://Qmbyq2emXpjv1oFFJnhS3jL8aXZAqnya7zmNtKYXEs8jXa\",\n  \"defaults\": {\n    \"space\": {\n      \"strategy\": \"static\",\n      \"total\": 1234\n    }\n  }\n}\n"
  },
  {
    "path": "src/plugins/safeSnap/Create.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport Config from './components/Config.vue';\n\ndefineProps<{\n  space: ExtendedSpace;\n  proposal: any;\n  modelValue: any;\n}>();\n\nconst emit = defineEmits(['update']);\nconst update = form => {\n  emit('update', { key: 'safeSnap', form });\n};\n</script>\n\n<template>\n  <Config\n    v-if=\"space.plugins.safeSnap\"\n    :proposal=\"proposal\"\n    :config=\"space.plugins.safeSnap\"\n    :network=\"space.network\"\n    :preview=\"false\"\n    :model-value=\"modelValue?.safeSnap || {}\"\n    @update:model-value=\"update\"\n  />\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/Proposal.vue",
    "content": "<script setup lang=\"ts\">\nimport Config from './components/Config.vue';\nimport { ExtendedSpace, Proposal, Results } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n  results: Results;\n}>();\n\nconst mapLegacyConfig = (config: Record<string, any>): Record<string, any> => {\n  if (config.safes) return config;\n\n  return {\n    ...config,\n    safes: [\n      {\n        network: props.space.network,\n        realityAddress: props.space.plugins.safeSnap.address,\n        // Some legacy proposals have a plain array of transactions instead\n        // of the current two-dimensional structure for batches.\n        txs:\n          config.txs[0] && !Array.isArray(config.txs[0])\n            ? [config.txs]\n            : config.txs\n      }\n    ]\n  };\n};\n\nconst safeSnapInput = computed(\n  () => mapLegacyConfig(props.proposal.plugins.safeSnap) || {}\n);\n</script>\n\n<template>\n  <Config\n    v-if=\"\n      proposal.plugins.safeSnap &&\n      safeSnapInput.safes.some(s => s.txs.length > 0)\n    \"\n    :model-value=\"safeSnapInput\"\n    :proposal=\"proposal\"\n    :preview=\"true\"\n    :config=\"space.plugins.safeSnap\"\n    :network=\"space.network\"\n    :space=\"space\"\n    :results=\"results\"\n  />\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Config.vue",
    "content": "<script>\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { coerceConfig, isValidInput, getSafeHash } from '../index';\nimport { getIpfsUrl } from '@/helpers/utils';\n\nimport SafeTransactions from './SafeTransactions.vue';\n\nexport default {\n  components: { SafeTransactions },\n  props: [\n    'modelValue', // proposal's plugins.safeSnap field or undefined when creating a new proposal\n    'config', // the safeSnap plugin config of the current space\n    'network', // network of the space (needed when mapping legacy plugin configs)\n    'proposal',\n    'preview', // if true, renders a read-only view\n    'space',\n    'results'\n  ],\n  emits: ['update:modelValue'],\n  data() {\n    let input;\n    if (!Object.keys(this.modelValue).length) {\n      input = {\n        safes: coerceConfig(this.config, this.network).safes.map(safe => ({\n          ...safe,\n          hash: null,\n          txs: []\n        })),\n        valid: true\n      };\n    } else {\n      const value = clone(this.modelValue);\n      if (value.safes && this.config && Array.isArray(this.config.safes)) {\n        value.safes = value.safes.map((safe, index) => ({\n          ...this.config.safes[index],\n          ...safe\n        }));\n      }\n      input = coerceConfig(value, this.network);\n    }\n\n    return { input, ipfs: getIpfsUrl(this?.proposal?.ipfs) };\n  },\n  methods: {\n    updateSafeTransactions() {\n      if (this.preview) return;\n      this.input.valid = isValidInput(this.input);\n      this.input.safes = this.input.safes.map(safe => {\n        return {\n          ...safe,\n          hash: getSafeHash(safe)\n        };\n      });\n      this.$emit('update:modelValue', this.input);\n    }\n  }\n};\n</script>\n\n<template>\n  <div\n    v-if=\"!preview || input.safes.length > 0\"\n    class=\"bg-skin-block-bg rounded-xl border\"\n  >\n    <div\n      class=\"block px-3 pt-3\"\n      style=\"\n        padding-bottom: 12px;\n        display: flex;\n        justify-content: space-between;\n      \"\n    >\n      <h4>\n        {{ $t('safeSnap.transactions') }}\n      </h4>\n      <BaseLink v-if=\"ipfs\" :link=\"ipfs\"> View Details </BaseLink>\n    </div>\n\n    <div\n      v-for=\"(safe, index) in input.safes\"\n      :key=\"index\"\n      class=\"border-b last:border-b-0\"\n    >\n      <SafeTransactions\n        v-if=\"!preview || safe.txs.length > 0\"\n        :preview=\"preview\"\n        :proposal=\"proposal\"\n        :space=\"space\"\n        :results=\"results\"\n        :hash=\"safe.hash\"\n        :network=\"safe.network\"\n        :reality-address=\"safe.realityAddress\"\n        :uma-address=\"safe.umaAddress\"\n        :multi-send-address=\"safe.multiSendAddress\"\n        :model-value=\"safe.txs\"\n        @update:model-value=\"updateSafeTransactions(index, $event)\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Form/ContractInteraction.vue",
    "content": "<script>\nimport Plugin, {\n  contractInteractionToModuleTransaction,\n  getABIWriteFunctions,\n  getContractABI,\n  getContractTransactionData,\n  InterfaceDecoder\n} from '../../index';\nimport { isBigNumberish } from '@ethersproject/bignumber/lib/bignumber';\nimport { isAddress } from '@ethersproject/address';\nimport { parseAmount } from '@/helpers/utils';\nimport SafeSnapInputAddress from '../Input/Address.vue';\nimport SafeSnapInputMethodParameter from '../Input/MethodParameter.vue';\n\nexport default {\n  components: { SafeSnapInputAddress, SafeSnapInputMethodParameter },\n  props: ['modelValue', 'nonce', 'config'],\n  emits: ['update:modelValue'],\n  data() {\n    let to = '';\n    let abi = '';\n    let value = '0';\n    let selectedMethod = undefined;\n    let methods = [];\n    let parameters = [];\n\n    if (this.modelValue) {\n      try {\n        const {\n          to: _to = '',\n          abi: _abi = '',\n          value: _value = '0',\n          data\n        } = this.modelValue;\n\n        to = _to;\n        abi = typeof _abi === 'object' ? JSON.stringify(_abi) : _abi;\n        value = _value;\n\n        const transactionDecoder = new InterfaceDecoder(abi);\n        selectedMethod = transactionDecoder.getMethodFragment(data);\n        parameters = transactionDecoder.decodeFunction(data, selectedMethod);\n        methods = [selectedMethod];\n      } catch (err) {\n        console.error('error decoding contract interaction tx', err);\n      }\n    }\n\n    return {\n      plugin: new Plugin(),\n\n      to,\n      abi,\n      value,\n\n      validAbi: true,\n      validValue: true,\n      methodIndex: 0,\n      selectedMethod,\n      methods,\n      parameters\n    };\n  },\n  watch: {\n    to() {\n      this.updateTransaction();\n    },\n    abi() {\n      this.updateTransaction();\n    },\n    value() {\n      this.updateTransaction();\n    },\n    selectedMethod() {\n      this.updateTransaction();\n    },\n    parameters() {\n      this.updateTransaction();\n    },\n    nonce() {\n      this.updateTransaction();\n    }\n  },\n  mounted() {\n    if (this.modelValue) {\n      const { to = '', abi = '', value = '0', data } = this.modelValue;\n      this.to = to;\n\n      if (this.config.preview) {\n        const transactionDecoder = new InterfaceDecoder(abi);\n        this.selectedMethod = transactionDecoder.getMethodFragment(data);\n        this.parameters = transactionDecoder.decodeFunction(\n          data,\n          this.selectedMethod\n        );\n\n        this.methods = [this.selectedMethod];\n        this.handleValueChange(value);\n        this.handleABIChanged(\n          typeof abi === 'object' ? JSON.stringify(abi) : abi\n        );\n      } else {\n        setTimeout(() => this.updateTransaction(), 1000);\n      }\n    }\n  },\n  methods: {\n    updateTransaction() {\n      if (this.config.preview) return;\n      try {\n        if (isBigNumberish(this.value) && isAddress(this.to)) {\n          const data = getContractTransactionData(\n            this.abi,\n            this.selectedMethod,\n            this.parameters\n          );\n\n          const transaction = contractInteractionToModuleTransaction(\n            {\n              data,\n              to: this.to,\n              value: this.value,\n              nonce: this.nonce,\n              method: this.selectedMethod\n            },\n            this.config.multiSendAddress\n          );\n\n          if (this.plugin.validateTransaction(transaction)) {\n            this.$emit('update:modelValue', transaction);\n            return;\n          }\n        }\n      } catch (error) {\n        console.warn('invalid transaction');\n      }\n      this.$emit('update:modelValue', undefined);\n    },\n    async handleAddressChanged() {\n      const result = await getContractABI(this.config.network, this.to);\n      if (result && result !== this.abi) {\n        this.abi = result;\n        this.handleABIChanged(result);\n      }\n    },\n    handleValueChange(value) {\n      this.value = value;\n      try {\n        parseAmount(value);\n        this.validValue = true;\n      } catch (error) {\n        this.validValue = false;\n      }\n    },\n    handleABIChanged(value) {\n      this.abi = value;\n      this.methodIndex = 0;\n      this.methods = [];\n\n      try {\n        this.methods = getABIWriteFunctions(this.abi);\n        this.validAbi = true;\n        this.handleMethodChanged();\n      } catch (error) {\n        this.validAbi = false;\n        console.warn('error extracting useful methods', error);\n      }\n    },\n    handleMethodChanged() {\n      this.parameters = [];\n      this.selectedMethod = this.methods[this.methodIndex];\n      this.updateTransaction();\n    },\n    handleParameterChanged(index, value) {\n      this.parameters[index] = value;\n      this.updateTransaction();\n    }\n  }\n};\n</script>\n\n<template>\n  <div class=\"space-y-2\">\n    <SafeSnapInputAddress\n      v-model=\"to\"\n      :disabled=\"config.preview\"\n      :input-props=\"{\n        required: true\n      }\"\n      :label=\"$t('safeSnap.to')\"\n      @validAddress=\"handleAddressChanged()\"\n    />\n\n    <UiInput\n      :disabled=\"config.preview\"\n      :error=\"!validValue && $t('safeSnap.invalidValue')\"\n      :model-value=\"value\"\n      @update:model-value=\"handleValueChange($event)\"\n    >\n      <template #label>{{ $t('safeSnap.value') }}</template>\n    </UiInput>\n\n    <UiInput\n      :disabled=\"config.preview\"\n      :error=\"!validAbi && $t('safeSnap.invalidAbi')\"\n      :model-value=\"abi\"\n      @update:model-value=\"handleABIChanged($event)\"\n    >\n      <template #label>ABI</template>\n    </UiInput>\n\n    <div v-if=\"methods.length\">\n      <UiSelect\n        v-model=\"methodIndex\"\n        :disabled=\"config.preview\"\n        @change=\"handleMethodChanged()\"\n      >\n        <template #label>function</template>\n        <option v-for=\"(method, i) in methods\" :key=\"i\" :value=\"i\">\n          {{ method.name }}()\n        </option>\n      </UiSelect>\n\n      <div v-if=\"selectedMethod && selectedMethod.inputs.length\">\n        <div class=\"divider\"></div>\n\n        <SafeSnapInputMethodParameter\n          v-for=\"(input, index) in selectedMethod.inputs\"\n          :key=\"input.name\"\n          :disabled=\"config.preview\"\n          :model-value=\"parameters[index]\"\n          :parameter=\"input\"\n          @update:model-value=\"handleParameterChanged(index, $event)\"\n        />\n      </div>\n    </div>\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n.textarea {\n  border: 1px solid var(--border-color);\n  background-color: transparent;\n  color: var(--link-color);\n  border-radius: 23px;\n  padding: 0 24px;\n  outline: none;\n  font-size: 14px;\n\n  &:hover {\n    color: var(--link-color);\n    border-color: var(--link-color);\n  }\n}\n\n.divider {\n  border-top: 1px solid #cacaca;\n  margin-top: 16px;\n  margin-bottom: 24px;\n}\n</style>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Form/ImportTransactionsButton.vue",
    "content": "<script>\nimport { getNativeAsset } from '../../index';\nimport { parseEther } from '@ethersproject/units';\nimport { isAddress } from '@ethersproject/address';\nimport { FunctionFragment, Interface } from '@ethersproject/abi';\n\nexport default {\n  name: 'ImportTransactionsButton',\n  props: ['network'],\n  emits: ['import', 'remove'],\n  data() {\n    return {\n      json: '',\n      error: '',\n      open: false,\n      dropping: false\n    };\n  },\n  methods: {\n    async handleFileUpload(event) {\n      const [file] = event.target?.files || [];\n      if (!file) {\n        this.error = 'Uploading file';\n        return;\n      }\n      event.target.value = '';\n      return this.importFromFile(file);\n    },\n    async importFromFile(file) {\n      let json;\n      try {\n        json = await this.readFile(file);\n      } catch (err) {\n        console.warn('err', err);\n        this.error = 'Uploading file';\n        return;\n      }\n      this.importTxs(json);\n    },\n    importFromText() {\n      let json;\n      try {\n        json = JSON.parse(this.json);\n      } catch (err) {\n        console.warn('err', err);\n        this.error = 'JSON is not valid';\n        return;\n      }\n      const successful = this.importTxs(json);\n      if (successful) {\n        // Clear textarea\n        this.json = '';\n      }\n    },\n    importTxs(json) {\n      this.error = '';\n      if (!Array.isArray(json)) {\n        this.error = 'JSON must be an array';\n        return false;\n      }\n      try {\n        const txs = json.map(this.importTx);\n        this.$emit('import', txs);\n        return true;\n      } catch (err) {\n        console.warn('err', err);\n        this.error = err.message;\n        return false;\n      }\n    },\n    toggleDropping() {\n      this.dropping = !this.dropping;\n    },\n    drop(event) {\n      this.toggleDropping();\n      const file = event.dataTransfer.files[0];\n      this.importFromFile(file);\n    },\n    importTx(tx, index) {\n      if (tx.to && tx.value && isAddress(tx.to)) {\n        let value, data;\n        try {\n          value = parseEther(tx.value).toString();\n          data = tx.data?.toString().trim();\n        } catch (err) {\n          throw new Error(`transaction #${index + 1} is invalid`);\n        }\n        const base = {\n          to: tx.to,\n          value,\n          nonce: index,\n          operation: tx.operation || '0'\n        };\n\n        if (tx.method && tx.params) {\n          const method = FunctionFragment.from(tx.method);\n          const contractInterface = new Interface([method]);\n          try {\n            const data = contractInterface.encodeFunctionData(\n              method,\n              tx.params\n            );\n            return {\n              type: 'contractInteraction',\n              data,\n              abi: contractInterface.fragments.map(frag => frag.format('full')),\n              ...base\n            };\n          } catch (err) {\n            throw new Error(\n              `invalid function params for transaction #${index + 1}`\n            );\n          }\n        } else if (data && data !== '0x') {\n          return {\n            type: 'raw',\n            data,\n            ...base\n          };\n        }\n\n        return {\n          type: 'transferFunds',\n          data: '0x',\n          token: getNativeAsset(this.network),\n          recipient: tx.to,\n          amount: value,\n          ...base\n        };\n      }\n      throw new Error(`transaction #${index + 1} is invalid`);\n    },\n    readFile(file) {\n      return new Promise((resolve, reject) => {\n        const reader = new FileReader();\n        reader.addEventListener('load', e => {\n          try {\n            resolve(JSON.parse(e.target.result));\n          } catch (e) {\n            reject(new Error('JSON is not valid'));\n          }\n        });\n        reader.addEventListener('error', reject);\n        reader.addEventListener('abort', reject);\n        reader.readAsBinaryString(file);\n      });\n    }\n  }\n};\n</script>\n\n<template>\n  <div class=\"root\">\n    <UiCollapsibleContent\n      title=\"Add Transaction Batch with JSON\"\n      class=\"import-transactions\"\n      :hide-remove=\"true\"\n      :show-arrow=\"true\"\n      :open=\"open\"\n      @toggle=\"open = !open\"\n      @remove=\"$emit('remove')\"\n    >\n      <div style=\"padding: 8px 16px\">\n        <label\n          for=\"files\"\n          :class=\"{\n            box: true,\n            'file-import': true,\n            'active-dropzone': dropping\n          }\"\n          class=\"box file-import\"\n          @dragenter.prevent=\"toggleDropping\"\n          @dragleave.prevent=\"toggleDropping\"\n          @drop.prevent=\"drop\"\n          @dragover.prevent\n        >\n          Click to select file <br />\n          or drag and drop\n        </label>\n        <input\n          id=\"files\"\n          ref=\"files\"\n          accept=\"application/json, text/plain\"\n          style=\"display: none\"\n          type=\"file\"\n          @change=\"handleFileUpload\"\n        />\n        <div v-if=\"error\" class=\"error mt-3\">Error: {{ error }}.</div>\n        <div class=\"box tx-content mt-3\">\n          <label for=\"tx_json\"><h5>Transaction JSON</h5></label>\n          <textarea\n            id=\"tx_json\"\n            v-model=\"json\"\n            placeholder=\"You can also paste in JSON here.\"\n            class=\"tx-textarea outline-none\"\n          ></textarea>\n        </div>\n        <div class=\"mt-3 flex flex-col items-center justify-center\">\n          <TuneButton @click=\"importFromText\">Parse JSON</TuneButton>\n          <a\n            class=\"mb-1 mt-2\"\n            style=\"text-decoration: underline; font-size: 14px\"\n            target=\"_blank\"\n            rel=\"noreferrer\"\n            href=\"https://github.com/gnosis/safe-tasks/blob/c9c52f76d16d43b0682950f3411ef283871acb5f/tx_input.sample.json\"\n          >\n            Transaction JSON example\n          </a>\n        </div>\n      </div>\n    </UiCollapsibleContent>\n  </div>\n</template>\n\n<style scoped>\n.root {\n  padding: 0 16px 16px 16px;\n}\n.import-transactions {\n  border-radius: 23px;\n}\n.box {\n  min-height: 100px;\n  border-radius: 23px;\n  border: 1px solid #cacaca;\n}\n.file-import {\n  display: flex;\n  padding: 20px;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n  background-color: rgba(244, 246, 246, 0.3);\n  cursor: pointer;\n\n  font-size: 16px;\n  font-weight: 400;\n}\n.tx-content {\n  padding: 16px 16px 0 16px;\n}\n.tx-textarea {\n  width: 100%;\n  padding: 0;\n  min-height: 80px;\n  font-size: 14px;\n  background: transparent;\n}\n.error {\n  background: rgba(255, 0, 0, 0.1);\n  color: rgba(255, 48, 48, 1);\n  border-radius: 24px;\n  padding: 16px;\n  font-size: 14px;\n  font-weight: 100;\n}\n.active-dropzone {\n  background: green;\n}\n</style>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Form/RawTransaction.vue",
    "content": "<script>\nimport Plugin, {\n  decodeTransactionData,\n  rawToModuleTransaction\n} from '../../index';\nimport { isHexString } from '@ethersproject/bytes';\nimport { parseAmount } from '@/helpers/utils';\nimport SafeSnapInputAddress from '../Input/Address.vue';\n\nexport default {\n  components: { SafeSnapInputAddress },\n  props: ['modelValue', 'nonce', 'config'],\n  emits: ['update:modelValue'],\n  data() {\n    return {\n      plugin: new Plugin(),\n\n      to: '',\n      value: '0',\n      data: ''\n    };\n  },\n  computed: {\n    isValidValue() {\n      if (!this.value.length) return true;\n      try {\n        parseAmount(this.value);\n        return true;\n      } catch (error) {\n        return false;\n      }\n    },\n    isValidData() {\n      return !this.data.length || isHexString(this.data);\n    }\n  },\n  watch: {\n    to() {\n      this.updateTransaction();\n    },\n    value() {\n      this.updateTransaction();\n    },\n    data() {\n      this.updateTransaction();\n    }\n  },\n  async mounted() {\n    if (this.modelValue) {\n      const { to = '', value = '0', data = '' } = this.modelValue;\n      this.to = to;\n      this.value = value;\n      this.data = data;\n\n      if (this.config.preview) {\n        try {\n          const transaction = await decodeTransactionData(\n            this.config.network,\n            this.modelValue,\n            this.config.multiSendAddress\n          );\n          if (this.plugin.validateTransaction(transaction)) {\n            this.$emit('update:modelValue', transaction);\n          }\n        } catch (e) {\n          console.warn('raw-transaction: failed to decode transaction');\n        }\n      }\n    }\n  },\n  methods: {\n    updateTransaction() {\n      if (this.config.preview) return;\n\n      const transaction = rawToModuleTransaction({\n        value: this.value,\n        to: this.to,\n        data: this.data,\n        nonce: this.nonce\n      });\n\n      if (this.plugin.validateTransaction(transaction)) {\n        this.$emit('update:modelValue', transaction);\n        return;\n      }\n      this.$emit('update:modelValue', undefined);\n    }\n  }\n};\n</script>\n\n<template>\n  <div class=\"space-y-2\">\n    <SafeSnapInputAddress\n      v-model=\"to\"\n      :disabled=\"config.preview\"\n      :input-props=\"{ required: false }\"\n      :label=\"$t('safeSnap.to')\"\n    />\n\n    <UiInput\n      v-model=\"value\"\n      :disabled=\"config.preview\"\n      :error=\"!isValidValue && $t('safeSnap.invalidValue')\"\n    >\n      <template #label>{{ $t('safeSnap.value') }}</template>\n    </UiInput>\n\n    <UiInput\n      v-model=\"data\"\n      :disabled=\"config.preview\"\n      :error=\"!isValidData && $t('safeSnap.invalidData')\"\n    >\n      <template #label>{{ $t('safeSnap.data') }}</template>\n    </UiInput>\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n.textarea {\n  border: 1px solid var(--border-color);\n  background-color: transparent;\n  color: var(--link-color);\n  border-radius: 23px;\n  padding: 0 24px;\n  outline: none;\n  font-size: 14px;\n\n  &:hover {\n    color: var(--link-color);\n    border-color: var(--link-color);\n  }\n}\n\n.divider {\n  border-top: 1px solid #cacaca;\n  margin-top: 16px;\n  margin-bottom: 24px;\n}\n</style>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Form/SendAsset.vue",
    "content": "<script>\nimport Plugin, {\n  getERC721TokenTransferTransactionData,\n  sendAssetToModuleTransaction\n} from '../../index';\nimport { isAddress } from '@ethersproject/address';\nimport { shorten } from '@/helpers/utils';\nimport SafeSnapInputAddress from '../Input/Address.vue';\n\nexport default {\n  components: { SafeSnapInputAddress },\n  props: ['modelValue', 'nonce', 'config'],\n  emits: ['update:modelValue'],\n  setup() {\n    return { shorten };\n  },\n  data() {\n    return {\n      plugin: new Plugin(),\n      collectables: [],\n      loading: false,\n\n      to: '',\n      collectableAddress: ''\n    };\n  },\n  computed: {\n    selectedCollectable() {\n      if (!this.collectableAddress) return null;\n      return this.collectables.find(\n        collectable => collectable.address === this.collectableAddress\n      );\n    }\n  },\n  watch: {\n    to() {\n      this.updateTransaction();\n    },\n    collectableAddress() {\n      this.updateTransaction();\n    },\n    config() {\n      this.setCollectables();\n    }\n  },\n  mounted() {\n    this.setCollectables();\n    if (this.modelValue) {\n      const { recipient = '', collectable } = this.modelValue;\n      this.to = recipient;\n      if (collectable) {\n        this.collectableAddress = collectable.address;\n        this.collectables = [collectable];\n      }\n    }\n  },\n  methods: {\n    updateTransaction() {\n      if (this.config.preview) return;\n      try {\n        if (isAddress(this.to)) {\n          const data = getERC721TokenTransferTransactionData(\n            this.config.gnosisSafeAddress,\n            this.to,\n            this.selectedCollectable.id\n          );\n\n          const transaction = sendAssetToModuleTransaction({\n            data,\n            nonce: this.nonce,\n            recipient: this.to,\n            collectable: this.selectedCollectable\n          });\n\n          if (this.plugin.validateTransaction(transaction)) {\n            this.$emit('update:modelValue', transaction);\n            return;\n          }\n        }\n      } catch (error) {\n        console.warn('invalid transaction');\n      }\n      this.$emit('update:modelValue', undefined);\n    },\n    setCollectables() {\n      if (!this.config.preview && this.config.collectables?.results) {\n        this.collectables = this.config.collectables.results;\n        if (!this.selectedCollectable && this.collectables.length) {\n          this.collectableAddress = this.collectables[0].address;\n        }\n      }\n    }\n  }\n};\n</script>\n\n<template>\n  <UiSelect v-model=\"collectableAddress\" :disabled=\"config.preview\">\n    <template #label>{{ $t('safeSnap.asset') }}</template>\n    <template\n      v-if=\"\n        selectedCollectable &&\n        (selectedCollectable.imageUri || selectedCollectable.logoUri)\n      \"\n      #image\n    >\n      <img\n        :src=\"selectedCollectable.imageUri || selectedCollectable.logoUri\"\n        alt=\"\"\n        class=\"tokenImage\"\n      />\n    </template>\n    <option v-if=\"!collectables.length\" disabled selected>\n      - {{ $t('safeSnap.noCollectibles') }} -\n    </option>\n    <option\n      v-for=\"(collectable, index) in collectables\"\n      :key=\"index\"\n      :value=\"collectable.address\"\n    >\n      {{ collectable.name }} #{{ shorten(collectable.id, 10) }}\n    </option>\n  </UiSelect>\n\n  <SafeSnapInputAddress\n    v-model=\"to\"\n    :disabled=\"config.preview\"\n    :input-props=\"{\n      required: true\n    }\"\n    :label=\"$t('safeSnap.to')\"\n  />\n</template>\n\n<style scoped>\n.tokenImage {\n  width: 24px;\n  margin-left: 8px;\n  vertical-align: middle;\n}\n</style>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Form/TokensModal.vue",
    "content": "<script setup lang=\"ts\">\nimport { useConfirmDialog } from '@vueuse/core';\nimport { TokenAsset } from '@/helpers/interfaces';\nimport TokensModalItem from './TokensModalItem.vue';\n\nconst props = defineProps<{\n  open: boolean;\n  tokenAddress: string;\n  tokens: TokenAsset[];\n  network: string;\n}>();\n\nconst emit = defineEmits(['close', 'tokenAddress']);\n\nconst searchInput = ref('');\nconst showUnverifiedTokens = ref(false);\n\nconst confirmDialogOpen = ref(false);\nconst confirmDialogData = ref(null);\nconst confirmDialog = useConfirmDialog(confirmDialogOpen);\nconfirmDialog.onConfirm(token => {\n  emit('tokenAddress', token.address);\n  emit('close');\n});\n\nconst tokensFiltered = computed(() => {\n  const filterTokens = (token: TokenAsset) => {\n    const tokenProperties = [token.symbol, token.name, token.address].map(\n      property => property.toLowerCase()\n    );\n\n    const searchQuery = searchInput.value.toLowerCase();\n\n    const searchMatch = tokenProperties.some(property =>\n      property.includes(searchQuery)\n    );\n    const isVerified = token.address === 'main' || token.verified;\n\n    return (\n      (searchMatch || !searchInput.value) &&\n      (showUnverifiedTokens.value || isVerified)\n    );\n  };\n\n  return props.tokens.filter(filterTokens);\n});\n\nfunction handleTokenClick(token) {\n  const isVerified = token.address === 'main' || token.verified;\n\n  if (!isVerified) {\n    confirmDialogData.value = token;\n    return confirmDialog.reveal();\n  }\n\n  emit('tokenAddress', token.address);\n  emit('close');\n}\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <div\n        class=\"flex flex-col content-center items-center justify-center gap-x-4\"\n      >\n        <h3>Assets</h3>\n        <BaseSearch\n          v-model=\"searchInput\"\n          :placeholder=\"$t('searchPlaceholderTokens')\"\n          modal\n          focus-on-mount\n          class=\"min-h-[60px] w-full flex-auto !px-3 pb-3 sm:!px-4\"\n        >\n          <template #after>\n            <BasePopover :focus=\"false\">\n              <template #button>\n                <BaseButtonIcon>\n                  <i-ho-adjustments class=\"text-skin-link\" />\n                </BaseButtonIcon>\n              </template>\n              <template #content>\n                <h3 class=\"-mb-2 mt-3 text-center text-skin-heading\">\n                  Filters\n                </h3>\n                <div class=\"m-4 space-y-3\">\n                  <div class=\"space-y-2\">\n                    <div class=\"space-y-2\">\n                      <TuneCheckbox\n                        v-model=\"showUnverifiedTokens\"\n                        hint=\"Show unverified tokens\"\n                        name=\"searchOnlyWithReason\"\n                      />\n                    </div>\n                  </div>\n                </div>\n              </template>\n            </BasePopover>\n          </template>\n        </BaseSearch>\n      </div>\n    </template>\n\n    <template #default=\"{ maxHeight }\">\n      <div\n        class=\"flex w-full flex-col overflow-auto\"\n        :style=\"{ minHeight: maxHeight }\"\n      >\n        <TokensModalItem\n          v-for=\"token in tokensFiltered\"\n          :key=\"token.address\"\n          :token=\"token\"\n          :is-selected=\"token.address === tokenAddress\"\n          :network=\"network\"\n          @select=\"handleTokenClick\"\n        />\n\n        <div\n          v-if=\"searchInput.length && tokensFiltered.length === 0\"\n          class=\"flex flex-row content-start items-start justify-center py-4\"\n        >\n          <span>{{ $t('noResultsFound') }}</span>\n        </div>\n      </div>\n    </template>\n  </BaseModal>\n\n  <teleport to=\"#modal\">\n    <ModalConfirmAction\n      :open=\"confirmDialogOpen\"\n      show-cancel\n      @close=\"confirmDialog.cancel\"\n      @confirm=\"confirmDialog.confirm(confirmDialogData)\"\n    >\n      <BaseMessageBlock level=\"warning-red\" class=\"m-4\">\n        This token isn't known to us. Please make sure it is the correct address\n        before proceeding.\n        <BaseLink\n          link=\"https://docs.snapshot.org/user-guides/token-verification\"\n        >\n          {{ $t('learnMore') }}</BaseLink\n        >\n      </BaseMessageBlock>\n    </ModalConfirmAction>\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Form/TokensModalItem.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten, explorerUrl } from '@/helpers/utils';\nimport { TokenAsset } from '@/helpers/interfaces';\nimport { ETH_CONTRACT } from '@/helpers/constants';\n\nconst props = defineProps<{\n  token: TokenAsset;\n  isSelected: boolean;\n  network: string;\n}>();\n\nconst emit = defineEmits(['select']);\n\nconst { formatNumber, getNumberFormatter } = useIntl();\nconst { copyToClipboard } = useCopy();\n\nconst exploreUrl = computed(() => {\n  return explorerUrl(props.network, props.token.address);\n});\n</script>\n\n<template>\n  <button\n    class=\"flex h-[64px] w-full cursor-pointer items-center justify-between border-b border-skin-border px-3 py-2 hover:bg-skin-border sm:px-4\"\n    :class=\"{\n      '!bg-skin-border': isSelected\n    }\"\n    @click=\"emit('select', token)\"\n  >\n    <div class=\"flex items-center\">\n      <div class=\"mr-3 flex\">\n        <AvatarToken\n          :address=\"token.address === 'main' ? ETH_CONTRACT : token.address\"\n          size=\"38\"\n        />\n      </div>\n\n      <div class=\"pr-4\">\n        <div class=\"flex w-full items-center text-skin-link\">\n          <div class=\"flex items-center gap-1\">\n            {{ token.symbol }}\n            <i-ho-check-badge\n              v-if=\"token.verified || token.address === 'main'\"\n              v-tippy=\"{ content: $t('verified') }\"\n              class=\"text-sm text-green\"\n            />\n          </div>\n        </div>\n        <span class=\"line-clamp-1 text-left text-skin-text\">\n          {{ token.name }}\n        </span>\n      </div>\n    </div>\n\n    <div class=\"h-full text-right\">\n      <span v-if=\"token.address !== 'main'\" class=\"text-skin-link\">\n        {{\n          formatNumber(\n            Number(token.balance),\n            getNumberFormatter({ maximumFractionDigits: 6 }).value\n          )\n        }}\n      </span>\n      <div>\n        <BaseLink\n          v-if=\"token.address !== 'main' && exploreUrl\"\n          :link=\"exploreUrl\"\n          @click.stop\n        >\n          {{ shorten(token.address) }}</BaseLink\n        >\n      </div>\n    </div>\n  </button>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Form/Transaction.vue",
    "content": "<script>\nimport { formatUnits } from '@ethersproject/units';\nimport { getAbiFirstFunctionName } from '../../index';\nimport { shorten } from '@/helpers/utils';\nimport SafeSnapFormContractInteraction from './ContractInteraction.vue';\nimport SafeSnapFormTransferFunds from './TransferFunds.vue';\nimport SafeSnapFormSendAsset from './SendAsset.vue';\nimport SafeSnapFormRawTransaction from './RawTransaction.vue';\n\nconst labels = {\n  contractInteraction: 'Contract Interaction',\n  transferFunds: 'Transfer Funds',\n  transferNFT: 'Transfer NFT',\n  raw: 'Raw Transaction'\n};\n\nexport default {\n  components: {\n    SafeSnapFormContractInteraction,\n    SafeSnapFormTransferFunds,\n    SafeSnapFormSendAsset,\n    SafeSnapFormRawTransaction\n  },\n  props: ['modelValue', 'nonce', 'config'],\n  emits: ['update:modelValue', 'remove'],\n  data() {\n    let type = 'transferFunds';\n    if (this.modelValue) {\n      type = this.modelValue.type ? this.modelValue.type : 'raw';\n    }\n\n    return {\n      open: !this.config.preview,\n      type\n    };\n  },\n  computed: {\n    title() {\n      if (this.open) {\n        return '';\n      }\n\n      if (this.modelValue) {\n        try {\n          const recipientAddr = shorten(this.modelValue.recipient);\n          const toAddr = shorten(this.modelValue.to);\n          const type = this.modelValue.type || this.type;\n          switch (type) {\n            case 'contractInteraction':\n              return this.$t('safeSnap.transactionLabels.contractInteraction', {\n                functionName: getAbiFirstFunctionName(this.modelValue.abi),\n                amount: this.modelValue.value,\n                address: toAddr\n              });\n            case 'transferFunds':\n              return this.$t('safeSnap.transactionLabels.transferFunds', {\n                amount: formatUnits(\n                  this.modelValue.amount,\n                  this.modelValue.token.decimals\n                ),\n                tokenSymbol: this.modelValue.token.symbol,\n                address: recipientAddr\n              });\n            case 'transferNFT':\n              return this.$t('safeSnap.transactionLabels.transferNFT', {\n                name: this.modelValue.collectable.name,\n                id: shorten(this.modelValue.collectable.id, 10),\n                address: recipientAddr\n              });\n            case 'raw':\n              return this.$t('safeSnap.transactionLabels.raw', {\n                amount: this.modelValue.value,\n                address: recipientAddr\n              });\n          }\n        } catch (error) {\n          console.log('could not determine title', error);\n        }\n      }\n      return this.getLabel(this.type);\n    }\n  },\n  watch: {\n    modelValue() {\n      if (this.modelValue?.type) {\n        this.type = this.modelValue.type;\n      }\n    }\n  },\n  mounted() {\n    if (!this.config.preview) this.$emit('update:modelValue', undefined);\n    if (this.config.preview && !this.modelValue.type) {\n      this.type = 'raw';\n    }\n  },\n  methods: {\n    getLabel(type) {\n      return labels[type];\n    },\n    handleTypeChange(type) {\n      this.type = type;\n      this.$emit('update:modelValue', undefined);\n    }\n  }\n};\n</script>\n\n<template>\n  <UiCollapsible\n    :hide-remove=\"config.preview\"\n    :number=\"nonce + 1\"\n    :open=\"open\"\n    :title=\"title\"\n    @remove=\"$emit('remove')\"\n    @toggle=\"open = !open\"\n  >\n    <UiSelect\n      :disabled=\"config.preview\"\n      :model-value=\"type\"\n      @update:model-value=\"handleTypeChange($event)\"\n    >\n      <template #label>{{ $t('safeSnap.type') }}</template>\n      <option value=\"transferFunds\">{{ $t('safeSnap.transferFunds') }}</option>\n      <option value=\"transferNFT\">{{ $t('safeSnap.transferNFT') }}</option>\n      <option value=\"contractInteraction\">\n        {{ $t('safeSnap.contractInteraction') }}\n      </option>\n      <option value=\"raw\">{{ $t('safeSnap.rawTransaction') }}</option>\n    </UiSelect>\n\n    <SafeSnapFormContractInteraction\n      v-if=\"type === 'contractInteraction'\"\n      :config=\"config\"\n      :model-value=\"modelValue\"\n      :nonce=\"nonce\"\n      @update:model-value=\"$emit('update:modelValue', $event)\"\n    />\n\n    <SafeSnapFormTransferFunds\n      v-if=\"type === 'transferFunds'\"\n      :config=\"config\"\n      :model-value=\"modelValue\"\n      :nonce=\"nonce\"\n      @update:model-value=\"$emit('update:modelValue', $event)\"\n    />\n\n    <SafeSnapFormSendAsset\n      v-if=\"type === 'transferNFT'\"\n      :config=\"config\"\n      :model-value=\"modelValue\"\n      :nonce=\"nonce\"\n      @update:model-value=\"$emit('update:modelValue', $event)\"\n    />\n\n    <SafeSnapFormRawTransaction\n      v-if=\"type === 'raw'\"\n      :model-value=\"modelValue\"\n      :nonce=\"nonce\"\n      :config=\"config\"\n      @update:model-value=\"$emit('update:modelValue', $event)\"\n    />\n  </UiCollapsible>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Form/TransactionBatch.vue",
    "content": "<script>\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { createBatch, ERC20_ABI, ERC721_ABI } from '../../index';\nimport { formatEther } from '@ethersproject/units';\nimport { FunctionFragment, Interface } from '@ethersproject/abi';\nimport SafeSnapFormTransaction from './Transaction.vue';\n\nexport default {\n  components: { SafeSnapFormTransaction },\n  props: ['modelValue', 'nonce', 'config'],\n  emits: ['update:modelValue', 'remove'],\n  setup() {\n    const { safesnap } = useSafe();\n    return { safesnap };\n  },\n  data() {\n    return {\n      open: true,\n      hashHidden: true,\n      jsonHidden: true,\n      batch: this.modelValue,\n      transactions: this.modelValue ? clone(this.modelValue.transactions) : []\n    };\n  },\n  async mounted() {\n    if (!this.config.preview && !this.transactions.length) {\n      // Add transaction if batch is empty\n      this.addTransaction();\n    }\n  },\n  methods: {\n    addTransaction() {\n      this.transactions.push(undefined);\n    },\n    updateTransaction(index, transaction) {\n      if (this.config.preview) return;\n      this.transactions[index] = transaction;\n      this.updateBatch(this.transactions);\n    },\n    removeTransaction(index) {\n      this.transactions.splice(index, 1);\n      this.updateBatch(this.transactions);\n      if (!this.transactions.length) {\n        this.$emit('remove');\n      }\n    },\n    updateBatch(txs) {\n      const batch = this.createBatch(this.nonce, txs);\n      this.$emit('update:modelValue', batch);\n    },\n    createBatch(nonce, txs) {\n      const chainId = parseInt(this.config.network);\n      return createBatch(\n        this.config.realityAddress,\n        chainId,\n        nonce,\n        txs,\n        this.config.multiSendAddress\n      );\n    },\n    formatBatchJson(txs) {\n      const valid = txs.every(tx => tx);\n      if (!valid) {\n        return null;\n      }\n      return txs.map(tx => {\n        const base = {\n          to: tx.to,\n          operation: tx.operation,\n          value: formatEther(tx.value)\n        };\n\n        let abi = tx.abi;\n        if (tx.data.length > 2) {\n          switch (tx.type) {\n            case 'transferFunds':\n              abi = ERC20_ABI;\n              break;\n            case 'transferNFT':\n              abi = ERC721_ABI;\n              break;\n            default:\n              base.data = tx.data;\n              break;\n          }\n        }\n\n        if (abi) {\n          const signHash = tx.data.substr(0, 10);\n          const contractInterface = new Interface(abi);\n          const functionFragment = contractInterface.fragments\n            .filter(frag => FunctionFragment.isFunctionFragment(frag))\n            .find(frag => contractInterface.getSighash(frag) === signHash);\n          const func = FunctionFragment.from(functionFragment);\n          const params = contractInterface.decodeFunctionData(func, tx.data);\n          return {\n            ...base,\n            method: func.format(),\n            params: params.map(param => param.toString())\n          };\n        }\n        return base;\n      });\n    }\n  }\n};\n</script>\n\n<template>\n  <UiCollapsible\n    borderless\n    :hide-remove=\"config.preview\"\n    :number=\"nonce + 1\"\n    :open=\"open\"\n    :title=\"`${$t('safeSnap.batch')} (${transactions.length})`\"\n    @remove=\"$emit('remove')\"\n    @toggle=\"open = !open\"\n  >\n    <div v-for=\"(transaction, index) in transactions\" :key=\"index\" class=\"mb-2\">\n      <SafeSnapFormTransaction\n        :model-value=\"transaction\"\n        :config=\"config\"\n        :nonce=\"index\"\n        @remove=\"removeTransaction(index)\"\n        @update:model-value=\"updateTransaction(index, $event)\"\n      />\n    </div>\n    <UiCollapsibleText\n      v-if=\"modelValue.hash\"\n      :show-arrow=\"true\"\n      :open=\"!hashHidden\"\n      :text=\"modelValue.hash\"\n      class=\"collapsible-text mt-2\"\n      title=\"Batch Transaction Hash\"\n      @toggle=\"hashHidden = !hashHidden\"\n    />\n    <UiCollapsibleText\n      v-if=\"modelValue.hash\"\n      :show-arrow=\"true\"\n      :open=\"!jsonHidden\"\n      :text=\"\n        JSON.stringify(formatBatchJson(modelValue.transactions), null, '\\t')\n      \"\n      class=\"collapsible-text mt-2\"\n      title=\"Batch Transaction JSON\"\n      pre\n      @toggle=\"jsonHidden = !jsonHidden\"\n    />\n    <BaseBlock\n      v-if=\"\n        safesnap.batchError &&\n        safesnap.batchError.message &&\n        nonce === safesnap.batchError.num\n      \"\n      class=\"mt-4\"\n      style=\"border-color: red !important\"\n    >\n      <BaseIcon name=\"warning\" class=\"mr-2 !text-red\" />\n      <span class=\"!text-red\"> Error: {{ safesnap.batchError.message }}</span>\n    </BaseBlock>\n\n    <TuneButton v-if=\"!config.preview\" class=\"mt-2\" @click=\"addTransaction\">\n      {{ $t('safeSnap.addTransaction') }}\n    </TuneButton>\n  </UiCollapsible>\n</template>\n\n<style scoped>\n.collapsible-text {\n  border-radius: 23px;\n}\n</style>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Form/TransferFunds.vue",
    "content": "<script>\nimport Plugin, {\n  getERC20TokenTransferTransactionData,\n  getNativeAsset,\n  transferFundsToModuleTransaction\n} from '../../index';\nimport { isBigNumberish } from '@ethersproject/bignumber/lib/bignumber';\nimport { isAddress } from '@ethersproject/address';\nimport SafeSnapInputAddress from '../Input/Address.vue';\nimport SafeSnapInputAmount from '../Input/Amount.vue';\nimport SafeSnapTokensModal from './TokensModal.vue';\nimport { ETH_CONTRACT } from '@/helpers/constants';\nimport { shorten } from '@/helpers/utils';\n\nexport default {\n  components: {\n    SafeSnapInputAddress,\n    SafeSnapInputAmount,\n    SafeSnapTokensModal\n  },\n  props: ['modelValue', 'nonce', 'config'],\n  emits: ['update:modelValue'],\n  data() {\n    const { amount = '0' } = this.modelValue || {};\n    const nativeAsset = getNativeAsset(this.config.network);\n    return {\n      plugin: new Plugin(),\n      tokens: [nativeAsset],\n\n      to: '',\n      value: amount,\n      tokenAddress: 'main',\n\n      validValue: true,\n      modalTokensOpen: false,\n      ETH_CONTRACT: ETH_CONTRACT\n    };\n  },\n  computed: {\n    selectedToken() {\n      return this.tokens.find(token => token.address === this.tokenAddress);\n    }\n  },\n  watch: {\n    to() {\n      this.updateTransaction();\n    },\n    tokenAddress() {\n      this.updateTransaction();\n    },\n    value() {\n      this.updateTransaction();\n    },\n    config() {\n      this.setTokens();\n    }\n  },\n  mounted() {\n    this.setTokens();\n    if (this.modelValue) {\n      const { recipient = '', token, amount = '0' } = this.modelValue;\n      this.to = recipient;\n      this.value = amount;\n      if (token) {\n        this.tokenAddress = token.address;\n        this.tokens = [token];\n      }\n    }\n  },\n  methods: {\n    updateTransaction() {\n      if (this.config.preview) return;\n      try {\n        if (isBigNumberish(this.value) && isAddress(this.to)) {\n          const data =\n            this.selectedToken.address === 'main'\n              ? '0x'\n              : getERC20TokenTransferTransactionData(this.to, this.value);\n\n          const transaction = transferFundsToModuleTransaction({\n            data,\n            nonce: this.nonce,\n            amount: this.value,\n            recipient: this.to,\n            token: this.selectedToken\n          });\n\n          if (this.plugin.validateTransaction(transaction)) {\n            this.$emit('update:modelValue', transaction);\n            return;\n          }\n        }\n      } catch (error) {\n        console.warn('invalid transaction', error);\n      }\n      this.$emit('update:modelValue', undefined);\n    },\n    setTokens() {\n      if (!this.config.preview && this.config.tokens) {\n        this.tokens = [\n          getNativeAsset(this.config.network),\n          ...this.config.tokens\n        ];\n      }\n    },\n    openModal() {\n      if (!this.config.tokens.length) return;\n      this.modalTokensOpen = true;\n    },\n    shorten: shorten\n  }\n};\n</script>\n\n<template>\n  <TuneButton\n    class=\"mb-2 flex w-full flex-row items-center justify-between !px-3\"\n    @click=\"openModal()\"\n  >\n    <div class=\"flex flex-row space-x-2\">\n      <span class=\"text-skin-text\">{{ $t('safeSnap.asset') }}</span>\n      <AvatarToken\n        :address=\"\n          selectedToken.address === 'main'\n            ? ETH_CONTRACT\n            : selectedToken.address\n        \"\n        class=\"ml-2\"\n      />\n      <span v-if=\"selectedToken\">{{ selectedToken.symbol }}</span>\n      <span>\n        {{\n          selectedToken.address === 'main'\n            ? ''\n            : `(${shorten(selectedToken.address)})`\n        }}\n      </span>\n    </div>\n    <i-ho-chevron-down class=\"text-xs text-skin-link\" />\n  </TuneButton>\n\n  <div class=\"space-y-2\">\n    <SafeSnapInputAddress\n      v-model=\"to\"\n      :disabled=\"config.preview\"\n      :input-props=\"{\n        required: true\n      }\"\n      :label=\"$t('safeSnap.to')\"\n    />\n    <SafeSnapInputAmount\n      :key=\"selectedToken?.decimals\"\n      v-model=\"value\"\n      :label=\"$t('safeSnap.amount')\"\n      :decimals=\"selectedToken?.decimals\"\n      :disabled=\"config.preview\"\n    />\n  </div>\n\n  <teleport to=\"#modal\">\n    <SafeSnapTokensModal\n      :tokens=\"tokens\"\n      :token-address=\"tokenAddress\"\n      :open=\"modalTokensOpen\"\n      :network=\"config.network\"\n      @token-address=\"tokenAddress = $event\"\n      @close=\"modalTokensOpen = false\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/HandleOutcome.vue",
    "content": "<script setup>\nimport Plugin from '../index';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { sleep } from '@snapshot-labs/snapshot.js/src/utils';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { formatUnits } from '@ethersproject/units';\nimport { ensureRightNetwork } from './SafeTransactions.vue';\n\nimport SafeSnapModalOptionApproval from './Modal/OptionApproval.vue';\n\nconst { formatRelativeTime } = useIntl();\nconst { t } = useI18n();\n\nconst { clearBatchError, setBatchError } = useSafe();\nconst { web3 } = useWeb3();\nconst {\n  createPendingTransaction,\n  updatePendingTransaction,\n  removePendingTransaction\n} = useTxStatus();\nconst { notify } = useFlashNotification();\n\nconst props = defineProps([\n  'batches',\n  'proposal',\n  'network',\n  'realityAddress',\n  'multiSendAddress'\n]);\n\nconst plugin = new Plugin();\n\nconst QuestionStates = {\n  error: -1,\n  noWalletConnection: 0,\n  loading: 1,\n  waitingForQuestion: 2,\n  questionNotSet: 3,\n  questionNotResolved: 4,\n  waitingForCooldown: 5,\n  proposalApproved: 6,\n  proposalRejected: 7,\n  completelyExecuted: 8,\n  timeExpired: 9\n};\nObject.freeze(QuestionStates);\n\nconst loading = ref(true);\nconst questionStates = ref(QuestionStates);\nconst actionInProgress = ref(false);\nconst action2InProgress = ref(false);\nconst questionDetails = ref(undefined);\nconst modalApproveDecisionOpen = ref(false);\nconst bondData = ref({\n  tokenSymbol: 'ETH',\n  canClaim: undefined,\n  data: undefined\n});\n\nconst getTxHashes = () => {\n  return props.batches.map(batch => batch.hash);\n};\n\nconst updateDetails = async () => {\n  loading.value = true;\n  try {\n    questionDetails.value = await plugin.getExecutionDetailsWithHashes(\n      props.network,\n      props.realityAddress,\n      props.proposal.id,\n      getTxHashes()\n    );\n    if (questionDetails.value.questionId && getInstance().web3) {\n      bondData.value = await plugin.loadClaimBondData(\n        getInstance().web3,\n        props.network,\n        questionDetails.value.questionId,\n        questionDetails.value.oracle,\n        props.proposal.snapshot\n      );\n    }\n  } catch (e) {\n    console.error(e);\n  } finally {\n    loading.value = false;\n  }\n};\n\nconst claimBond = async () => {\n  if (!questionDetails.value.oracle) return;\n  const txPendingId = createPendingTransaction();\n  try {\n    actionInProgress.value = 'claim-bond';\n\n    const params = Object.keys(bondData.value.data).map(\n      key => new Array(...bondData.value.data[key])\n    );\n\n    await ensureRightNetwork(props.network);\n    const clamingBond = plugin.claimBond(\n      getInstance().web3,\n      questionDetails.value.oracle,\n      questionDetails.value.questionId,\n      params\n    );\n    const step = await clamingBond.next();\n    if (step.value)\n      updatePendingTransaction(txPendingId, { hash: step.value.hash });\n    actionInProgress.value = null;\n    await clamingBond.next();\n    notify(t('notify.youDidIt'));\n    await sleep(3e3);\n    await updateDetails();\n  } catch (e) {\n    console.error(e);\n    actionInProgress.value = null;\n  } finally {\n    removePendingTransaction(txPendingId);\n  }\n};\n\nconst submitProposal = async () => {\n  if (!getInstance().isAuthenticated.value) return;\n  actionInProgress.value = 'submit-proposal';\n  const txPendingId = createPendingTransaction();\n  try {\n    await ensureRightNetwork(props.network);\n    const proposalSubmission = plugin.submitProposalWithHashes(\n      getInstance().web3,\n      props.realityAddress,\n      questionDetails.value.proposalId,\n      getTxHashes()\n    );\n    const step = await proposalSubmission.next();\n    if (step.value)\n      updatePendingTransaction(txPendingId, { hash: step.value.hash });\n    actionInProgress.value = null;\n    await proposalSubmission.next();\n    notify(t('notify.youDidIt'));\n    await sleep(3e3);\n    await updateDetails();\n  } catch (e) {\n    console.error(e);\n  } finally {\n    actionInProgress.value = null;\n    removePendingTransaction(txPendingId);\n  }\n};\n\nconst voteOnQuestion = async option => {\n  if (!getInstance().isAuthenticated.value) return;\n  const txPendingId = createPendingTransaction();\n  try {\n    await ensureRightNetwork(props.network);\n    const voting = plugin.voteForQuestion(\n      props.network,\n      getInstance().web3,\n      questionDetails.value.oracle,\n      questionDetails.value.questionId,\n      questionDetails.value.minimumBond,\n      option\n    );\n    const step = await voting.next();\n    if (step.value === 'erc20-approval') {\n      actionInProgress.value = null;\n      await voting.next();\n    }\n    actionInProgress.value = null;\n    const stepTx = await voting.next();\n    if (stepTx.value)\n      updatePendingTransaction(txPendingId, { hash: stepTx.value.hash });\n    await sleep(3e3);\n    await updateDetails();\n  } catch (e) {\n    console.error(e);\n    actionInProgress.value = null;\n  } finally {\n    removePendingTransaction(txPendingId);\n  }\n};\n\nconst executeProposal = async () => {\n  if (!getInstance().isAuthenticated.value) return;\n  action2InProgress.value = 'execute-proposal';\n  try {\n    await ensureRightNetwork(props.network);\n  } catch (e) {\n    console.error(e);\n    action2InProgress.value = null;\n    return;\n  }\n  const txPendingId = createPendingTransaction();\n  try {\n    clearBatchError();\n    const transaction =\n      props.batches[questionDetails.value.nextTxIndex].mainTransaction;\n    const executingProposal = plugin.executeProposalWithHashes(\n      getInstance().web3,\n      props.realityAddress,\n      questionDetails.value.proposalId,\n      getTxHashes(),\n      transaction,\n      questionDetails.value.nextTxIndex\n    );\n    const step = await executingProposal.next();\n    if (step.value)\n      updatePendingTransaction(txPendingId, { hash: step.value.hash });\n    action2InProgress.value = null;\n    await executingProposal.next();\n    notify(t('notify.youDidIt'));\n    await sleep(3e3);\n    await updateDetails();\n  } catch (err) {\n    action2InProgress.value = null;\n    setBatchError(questionDetails.value.nextTxIndex, err.reason);\n  } finally {\n    removePendingTransaction(txPendingId);\n  }\n};\n\nconst usingMetaMask = computed(() => {\n  return window.ethereum && getInstance().provider.value?.isMetaMask;\n});\n\nconst connectedToRightChain = computed(() => {\n  return getInstance().provider.value?.chainId === parseInt(props.network);\n});\n\nconst networkName = computed(() => {\n  return networks[props.network].name;\n});\n\nconst questionState = computed(() => {\n  if (!web3.value.account) return QuestionStates.noWalletConnection;\n\n  if (loading.value) return QuestionStates.loading;\n\n  if (!questionDetails.value) return QuestionStates.error;\n\n  if (!questionDetails.value.questionId)\n    return QuestionStates.waitingForQuestion;\n\n  if (questionDetails.value.currentBond.isZero())\n    return QuestionStates.questionNotSet;\n\n  const ts = (Date.now() / 1e3).toFixed();\n  const { finalizedAt, cooldown, expiration, executionApproved, nextTxIndex } =\n    questionDetails.value;\n\n  const isExpired = finalizedAt + expiration < ts;\n\n  if (!finalizedAt) return QuestionStates.questionNotResolved;\n  if (executionApproved) {\n    if (finalizedAt + cooldown > ts) return QuestionStates.waitingForCooldown;\n\n    if (!Number.isInteger(nextTxIndex))\n      return QuestionStates.completelyExecuted;\n    else if (isExpired) return QuestionStates.timeExpired;\n\n    return QuestionStates.proposalApproved;\n  }\n  if (isExpired) return QuestionStates.proposalRejected;\n\n  return QuestionStates.error;\n});\n\nconst showOracleInfo = computed(() => {\n  return (\n    questionState.value === questionStates.value.questionNotSet ||\n    questionState.value === questionStates.value.questionNotResolved ||\n    questionState.value === questionStates.value.waitingForCooldown\n  );\n});\n\nconst approvalData = computed(() => {\n  if (questionDetails.value) {\n    const { currentBond, finalizedAt, isApproved, endTime } =\n      questionDetails.value;\n\n    if (currentBond === undefined || BigNumber.from(currentBond).eq(0)) {\n      return {\n        decision: '--',\n        timeLeft: '--',\n        currentBond: '--'\n      };\n    }\n\n    if (finalizedAt) {\n      if (isApproved) {\n        return {\n          decision: 'Yes',\n          timeLeft: t('safeSnap.executableIn', [\n            formatRelativeTime(endTime + questionDetails.value.cooldown)\n          ])\n        };\n      }\n\n      return {\n        decision: 'No'\n      };\n    }\n\n    return {\n      decision: isApproved ? 'Yes' : 'No',\n      timeLeft: t('safeSnap.finalizedIn', [formatRelativeTime(endTime)]),\n      currentBond: `${formatUnits(currentBond, bondData.value.tokenDecimals)} ${\n        bondData.value.tokenSymbol\n      }`\n    };\n  }\n  return {\n    decision: '--',\n    timeLeft: '--',\n    currentBond: '--'\n  };\n});\n\nonMounted(async () => {\n  await updateDetails();\n});\n</script>\n\n<template>\n  <div v-if=\"questionState === questionStates.error\" class=\"my-4\">\n    {{ $t('safeSnap.labels.error') }}\n  </div>\n\n  <div v-if=\"questionState === questionStates.noWalletConnection\" class=\"my-4\">\n    {{ $t('safeSnap.labels.connectWallet') }}\n  </div>\n\n  <div v-if=\"questionState === questionStates.loading\" class=\"my-4\">\n    <LoadingSpinner />\n  </div>\n\n  <div v-if=\"connectedToRightChain || usingMetaMask\">\n    <div\n      v-if=\"questionState === questionStates.waitingForQuestion\"\n      class=\"my-4\"\n    >\n      <TuneButton\n        :loading=\"actionInProgress === 'submit-proposal'\"\n        @click=\"submitProposal\"\n      >\n        {{ $t('safeSnap.labels.request') }}\n      </TuneButton>\n    </div>\n\n    <div\n      v-if=\"\n        (showOracleInfo || bondData.canClaim) &&\n        questionState !== questionStates.loading\n      \"\n      class=\"my-4\"\n    >\n      <div class=\"inline-block text-base\">\n        <h4 class=\"text-center text-skin-link\">\n          Reality oracle\n          <a class=\"ml-2 text-skin-text\" @click=\"updateDetails\">\n            <BaseIcon name=\"refresh\" size=\"22\" />\n          </a>\n        </h4>\n        <div\n          v-if=\"questionState !== questionStates.questionNotSet\"\n          class=\"my-3 flex items-center space-x-3\"\n          style=\"text-align: left\"\n        >\n          <div class=\"self-stretch rounded-lg border p-3\">\n            <div>\n              <strong class=\"pr-3\"\n                >{{\n                  questionDetails?.finalizedAt\n                    ? $t('safeSnap.finalOutcome')\n                    : $t('safeSnap.currentOutcome')\n                }}:</strong\n              >\n              <span class=\"float-right text-skin-link\">\n                {{ approvalData?.decision }}\n              </span>\n            </div>\n            <div v-if=\"!questionDetails?.finalizedAt\" mt-3>\n              <strong class=\"pr-3\">{{ $t('safeSnap.currentBond') }}:</strong>\n              <span class=\"float-right text-skin-link\">\n                {{ approvalData?.currentBond }}\n              </span>\n            </div>\n          </div>\n\n          <div\n            v-if=\"approvalData?.timeLeft\"\n            class=\"flex items-center justify-center self-stretch rounded-lg border p-3 text-skin-link\"\n          >\n            <strong>{{ approvalData?.timeLeft }}</strong>\n          </div>\n        </div>\n\n        <div v-if=\"questionState === questionStates.questionNotSet\">\n          <TuneButton\n            class=\"mb-1 mt-3 w-full\"\n            :loading=\"actionInProgress === 'set-outcome'\"\n            @click=\"\n              modalApproveDecisionOpen = true;\n              actionInProgress = 'set-outcome';\n            \"\n          >\n            {{ $t('safeSnap.labels.setOutcome') }}\n          </TuneButton>\n        </div>\n        <div v-if=\"questionState === questionStates.questionNotResolved\">\n          <TuneButton\n            class=\"my-1 w-full\"\n            :loading=\"actionInProgress === 'set-outcome'\"\n            @click=\"\n              modalApproveDecisionOpen = true;\n              actionInProgress = 'set-outcome';\n            \"\n          >\n            {{ $t('safeSnap.labels.changeOutcome') }}\n          </TuneButton>\n        </div>\n        <div v-if=\"bondData.canClaim\">\n          <TuneButton\n            class=\"my-1 w-full\"\n            :loading=\"actionInProgress === 'claim-bond'\"\n            @click=\"claimBond\"\n          >\n            {{ $t('safeSnap.claimBond') }}\n          </TuneButton>\n        </div>\n      </div>\n    </div>\n\n    <div v-if=\"questionState === questionStates.proposalApproved\" class=\"my-4\">\n      <TuneButton\n        :loading=\"action2InProgress === 'execute-proposal'\"\n        @click=\"executeProposal\"\n      >\n        {{\n          $t('safeSnap.labels.executeTxs', [\n            questionDetails.nextTxIndex + 1,\n            batches.length\n          ])\n        }}\n      </TuneButton>\n    </div>\n  </div>\n  <div\n    v-else-if=\"\n      questionState !== questionStates.loading &&\n      questionState !== questionStates.noWalletConnection\n    \"\n    class=\"my-4\"\n  >\n    {{ $t('safeSnap.labels.switchChain', [networkName]) }}\n  </div>\n\n  <div v-if=\"questionState === questionStates.completelyExecuted\" class=\"my-4\">\n    {{ $t('safeSnap.labels.executed') }}\n  </div>\n\n  <div v-if=\"questionState === questionStates.proposalRejected\" class=\"my-4\">\n    {{ $t('safeSnap.labels.rejected') }}\n  </div>\n  <div v-if=\"questionState === questionStates.timeExpired\" class=\"my-4\">\n    {{ $t('safeSnap.labels.expired') }}\n  </div>\n\n  <teleport to=\"#modal\">\n    <SafeSnapModalOptionApproval\n      :space-id=\"proposal.space.id\"\n      :minimum-bond=\"questionDetails?.minimumBond\"\n      :open=\"modalApproveDecisionOpen\"\n      :is-approved=\"questionDetails?.isApproved\"\n      :bond=\"questionDetails?.currentBond\"\n      :question-id=\"questionDetails?.questionId\"\n      :token-symbol=\"bondData?.tokenSymbol\"\n      :token-decimals=\"bondData?.tokenDecimals\"\n      :oracle=\"questionDetails?.oracle\"\n      @setApproval=\"voteOnQuestion\"\n      @close=\"modalApproveDecisionOpen = actionInProgress = false\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/HandleOutcomeUma.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace, Proposal, Results } from '@/helpers/interfaces';\nimport { formatUnits } from '@ethersproject/units';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { sleep } from '@snapshot-labs/snapshot.js/src/utils';\nimport Plugin from '../index';\nimport type { Network } from '../types';\nimport { ensureRightNetwork } from './SafeTransactions.vue';\n\ntype Transaction = {\n  to: string;\n  operation: string;\n  value: string;\n  data: string;\n};\n\ntype Batch = {\n  transactions: Transaction[];\n};\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n  proposal: Proposal;\n  results: Results;\n  batches: Batch[];\n  network: Network;\n  umaAddress: string;\n  multiSendAddress: string;\n}>();\n\nconst { formatDuration } = useIntl();\nconst { t } = useI18n();\n\nconst { clearBatchError } = useSafe();\nconst { web3 } = useWeb3();\nconst {\n  createPendingTransaction,\n  updatePendingTransaction,\n  removePendingTransaction\n} = useTxStatus();\nconst { notify } = useFlashNotification();\nconst { quorum } = useQuorum(props);\n\nconst plugin = new Plugin();\n\ntype QuestionState =\n  | 'error'\n  | 'no-wallet-connection'\n  | 'loading'\n  | 'waiting-for-vote-confirmation'\n  | 'no-transactions'\n  | 'completely-executed'\n  | 'waiting-for-proposal'\n  | 'waiting-for-liveness'\n  | 'proposal-approved'\n  | 'waiting-for-vote-finalize'\n  | 'proposal-denied';\n\ntype Action1State = 'idle' | 'approve-bond' | 'submit-proposal';\ntype Action2State = 'idle' | 'execute-proposal';\n\nconst loading = ref(true);\nconst action1State = ref<Action1State>('idle');\nconst action2State = ref<Action2State>('idle');\nconst voteResultsConfirmed = ref(false);\nconst questionDetails =\n  ref<Awaited<ReturnType<typeof plugin.getExecutionDetailsUma>>>();\nconst closeModal = ref(false);\n\nfunction closeEvent() {\n  closeModal.value = false;\n  voteResultsConfirmed.value = false;\n}\n\nfunction showProposeModal() {\n  closeModal.value = true;\n  voteResultsConfirmed.value = true;\n}\n\nconst getTransactionsUma = () =>\n  props.batches.map(batch => {\n    const mainTx = batch.mainTransaction;\n    return [mainTx.to, Number(mainTx.operation), mainTx.value, mainTx.data];\n  });\n\nconst updateDetails = async () => {\n  loading.value = true;\n  try {\n    questionDetails.value = await plugin.getExecutionDetailsUma(\n      props.network,\n      props.umaAddress,\n      props.proposal.id,\n      props.proposal.ipfs,\n      getTransactionsUma()\n    );\n  } catch (e) {\n    console.error('Error loading uma execution details', e);\n  } finally {\n    loading.value = false;\n  }\n};\n\nconst approveBondUma = async () => {\n  if (!questionDetails.value?.oracle) return;\n  const txPendingId = createPendingTransaction();\n  try {\n    action1State.value = 'approve-bond';\n\n    await ensureRightNetwork(props.network);\n\n    const approveBond = plugin.approveBondUma(\n      props.network,\n      getInstance().web3,\n      props.umaAddress\n    );\n    const step = await approveBond.next();\n    if (step.value)\n      updatePendingTransaction(txPendingId, { hash: step.value.hash });\n    action1State.value = 'idle';\n    await approveBond.next();\n    notify(t('notify.youDidIt'));\n    await sleep(3e3);\n    await updateDetails();\n  } catch (e) {\n    console.error(e);\n    action1State.value = 'idle';\n  } finally {\n    removePendingTransaction(txPendingId);\n  }\n};\n\nconst getProposalUrl = (chain: string, txHash: string, logIndex: number) => {\n  if (Number(chain) !== 5 && Number(chain) !== 80001) {\n    return `https://oracle.uma.xyz?transactionHash=${txHash}&eventIndex=${logIndex}`;\n  }\n  return `https://testnet.oracle.uma.xyz?transactionHash=${txHash}&eventIndex=${logIndex}`;\n};\n\nconst submitProposalUma = async () => {\n  if (!getInstance().isAuthenticated.value) return;\n  action1State.value = 'submit-proposal';\n  const txPendingId = createPendingTransaction();\n  try {\n    await ensureRightNetwork(props.network);\n    const proposalSubmission = plugin.submitProposalUma(\n      getInstance().web3,\n      props.umaAddress,\n      props.proposal.ipfs,\n      getTransactionsUma()\n    );\n    const step = await proposalSubmission.next();\n    if (step.value)\n      updatePendingTransaction(txPendingId, { hash: step.value.hash });\n    action1State.value = 'idle';\n    await proposalSubmission.next();\n    notify(t('notify.youDidIt'));\n    await sleep(3e3);\n    await updateDetails();\n  } catch (e) {\n    console.error(e);\n  } finally {\n    action1State.value = 'idle';\n    removePendingTransaction(txPendingId);\n  }\n};\n\nconst executeProposalUma = async () => {\n  if (!getInstance().isAuthenticated.value) return;\n  action2State.value = 'execute-proposal';\n  const txPendingId = createPendingTransaction();\n  try {\n    await ensureRightNetwork(props.network);\n  } catch (e) {\n    console.error(e);\n    action2State.value = 'idle';\n    return;\n  }\n\n  try {\n    clearBatchError();\n    const executingProposal = plugin.executeProposalUma(\n      getInstance().web3,\n      props.umaAddress,\n      getTransactionsUma()\n    );\n    const step = await executingProposal.next();\n    if (step.value)\n      updatePendingTransaction(txPendingId, { hash: step.value.hash });\n    action2State.value = 'idle';\n    await executingProposal.next();\n    notify(t('notify.youDidIt'));\n    await sleep(3e3);\n    await updateDetails();\n  } catch (err) {\n    action2State.value = 'idle';\n  } finally {\n    removePendingTransaction(txPendingId);\n  }\n};\n\nconst usingMetaMask = computed(() => {\n  return (\n    // @ts-expect-error window.ethereum is not in the types\n    window.ethereum && getInstance().provider.value?.isMetaMask\n  );\n});\n\nconst connectedToRightChain = computed(() => {\n  return getInstance().provider.value?.chainId === parseInt(props.network);\n});\n\nconst networkName = computed(() => {\n  return networks[props.network].name;\n});\n\ntype Proposal = {\n  choices: string[];\n  scores: number[];\n  scores_total: number;\n  quorum: number;\n  created: number;\n  end: number;\n  state: string;\n};\nfunction didProposalPass(proposal: Proposal) {\n  // ensure the vote has ended\n  if (proposal.state !== 'closed') return false;\n  // ensure total votes are more than quorum\n  if (proposal.scores_total && proposal.scores_total < proposal.quorum)\n    return false;\n  const votes = Object.fromEntries(\n    proposal.choices.map((choice, i) => {\n      return [choice.toLowerCase(), proposal.scores[i] ?? 0];\n    })\n  );\n  // ensure there are more \"for\" votes than \"against\" votes\n  // abstain votes should not count against the proposal\n  return votes['for'] > (votes['against'] ?? 0);\n}\n\nfunction wasProposalFinalized(proposal: Proposal) {\n  return proposal.scores_state === 'final';\n}\n\nconst questionState = computed<QuestionState>(() => {\n  if (!web3.value.account) return 'no-wallet-connection';\n\n  if (loading.value) return 'loading';\n\n  if (!questionDetails.value) return 'error';\n\n  const { assertionEvent, proposalExecuted, activeProposal, noTransactions } =\n    questionDetails.value;\n\n  if (noTransactions) return 'no-transactions';\n\n  // check if proposal passed snapshot rules, ie votes for, and quorum\n  const proposalPassed = didProposalPass(props.proposal);\n\n  // vote may not be finalized, its possible for vote to pass, but require a waiting period till votes completely tally\n  const proposalFinalized = wasProposalFinalized(props.proposal);\n\n  // ordering of this is deliberate. it will prevent you from executing proposals that did not pass,\n  // but if for some reason the proposal did get executed elsewhere, it will still show that it was.\n  // If proposal has already been executed, prevents user from proposing again.\n  if (proposalExecuted) return 'completely-executed';\n\n  if (!proposalFinalized) {\n    return 'waiting-for-vote-finalize';\n  }\n\n  // User can confirm vote results if not done already and there is no proposal yet.\n  if (!activeProposal && !voteResultsConfirmed.value && proposalPassed)\n    return 'waiting-for-vote-confirmation';\n\n  // Proposal can be made if it has not been made already and user confirmed vote results.\n  if (!activeProposal && voteResultsConfirmed.value && proposalPassed)\n    return 'waiting-for-proposal';\n\n  // Proposal has been made and is waiting for liveness period to complete.\n\n  if (assertionEvent && !assertionEvent.isExpired)\n    return 'waiting-for-liveness';\n\n  // this is  above proposal-approved stated because we dont want to ever execute on proposals that did not pass vote\n  if (!proposalPassed) return 'proposal-denied';\n\n  // Proposal is approved if it expires without a dispute and hasn't been settled.\n  if (assertionEvent && assertionEvent.isExpired && !assertionEvent.isSettled)\n    return 'proposal-approved';\n\n  // Proposal is approved if it has been settled without a disputer and hasn't been executed.\n  if (assertionEvent && assertionEvent.isSettled && !proposalExecuted)\n    return 'proposal-approved';\n\n  return 'error';\n});\n\nonMounted(async () => {\n  await updateDetails();\n});\n</script>\n\n<template>\n  <div v-if=\"questionState === 'error'\" class=\"my-4\">\n    {{ $t('safeSnap.labels.error') }}\n  </div>\n  <template v-else>\n    <div v-if=\"questionState === 'no-wallet-connection'\" class=\"my-4\">\n      {{ $t('safeSnap.labels.connectWallet') }}\n    </div>\n\n    <div v-if=\"questionState === 'loading'\" class=\"my-4\">\n      <LoadingSpinner />\n    </div>\n    <div v-if=\"questionState === 'waiting-for-vote-finalize'\" class=\"my-4\">\n      Waiting on vote to be finalized\n    </div>\n\n    <div v-if=\"connectedToRightChain || usingMetaMask\">\n      <div\n        v-if=\"questionState === 'waiting-for-vote-confirmation'\"\n        class=\"my-4 inline-block\"\n      >\n        <BaseContainer class=\"flex items-center\">\n          <TuneButton @click=\"showProposeModal\" class=\"mr-2\">\n            {{ $t('safeSnap.labels.confirmVoteResults') }}\n          </TuneButton>\n        </BaseContainer>\n      </div>\n\n      <div v-if=\"questionState === 'no-transactions'\" class=\"my-4\">\n        {{ $t('safeSnap.labels.noTransactions') }}\n      </div>\n\n      <div\n        v-if=\"\n          questionState === 'waiting-for-proposal' &&\n          questionDetails.needsBondApproval === true\n        \"\n        class=\"my-4 inline-block\"\n      >\n        <BaseContainer class=\"flex items-center\">\n          <TuneButton\n            :loading=\"action1State === 'approve-bond'\"\n            @click=\"approveBondUma\"\n            class=\"mr-2\"\n          >\n            {{ $t('safeSnap.labels.approveBond') }}\n          </TuneButton>\n          <BasePopoverHover placement=\"top\">\n            <template #button>\n              <i-ho-information-circle />\n            </template>\n            <template #content>\n              <div\n                class=\"border bg-skin-bg p-3 text-md shadow-lg md:rounded-lg\"\n              >\n                {{ $t('safeSnap.labels.approveBondToolTip') }}\n              </div>\n            </template>\n          </BasePopoverHover>\n        </BaseContainer>\n      </div>\n      <div\n        v-if=\"\n          questionState === 'waiting-for-proposal' &&\n          questionDetails.needsBondApproval === false\n        \"\n        class=\"my-4 inline-block\"\n      >\n        <BaseContainer class=\"flex items-center\">\n          <BaseModal :open=\"closeModal\" @close=\"closeEvent\">\n            <template #header>\n              <h3 class=\"title\">{{ $t('safeSnap.labels.request') }}</h3>\n            </template>\n            <div class=\"my-3 p-3\">\n              <div class=\"pl-3 pr-3\">\n                <p>{{ $t('safeSnap.labels.confirmVoteResultsToolTip') }}</p>\n              </div>\n              <div class=\"my-3 rounded-lg border p-3\">\n                <div>\n                  <strong class=\"pr-3\">{{\n                    $t('safeSnap.labels.requiredBond')\n                  }}</strong>\n                  <span class=\"float-right text-skin-link\">\n                    {{\n                      formatUnits(\n                        questionDetails.minimumBond ?? 0,\n                        questionDetails.decimals\n                      ) +\n                      ' ' +\n                      questionDetails.symbol\n                    }}\n                  </span>\n                </div>\n                <div>\n                  <strong class=\"pr-3\">{{\n                    $t('safeSnap.labels.challengePeriod')\n                  }}</strong>\n                  <span class=\"float-right text-skin-link\">\n                    {{ formatDuration(Number(questionDetails.livenessPeriod)) }}\n                  </span>\n                </div>\n              </div>\n              <div>\n                <BaseMessage\n                  v-if=\"Number(props.proposal.scores_total) < Number(quorum)\"\n                  level=\"warning-red\"\n                >\n                  {{ $t('safeSnap.labels.quorumWarning') }}\n                </BaseMessage>\n                <BaseMessage\n                  v-if=\"\n                    Number(questionDetails.minimumBond.toString()) >\n                    Number(questionDetails.userBalance.toString())\n                  \"\n                  level=\"warning-red\"\n                >\n                  {{ $t('safeSnap.labels.bondWarning') }}\n                </BaseMessage>\n              </div>\n\n              <TuneButton\n                :loading=\"action1State === 'submit-proposal'\"\n                @click=\"submitProposalUma\"\n                class=\"my-1 w-full\"\n                :disabled=\"\n                  Number(questionDetails.minimumBond.toString()) >\n                    Number(questionDetails.userBalance.toString()) ||\n                  Number(props.proposal.scores_total) < Number(quorum)\n                \"\n              >\n                {{ $t('safeSnap.labels.request') }}\n              </TuneButton>\n            </div>\n          </BaseModal>\n        </BaseContainer>\n      </div>\n\n      <div\n        v-if=\"\n          questionState === 'waiting-for-liveness' &&\n          questionDetails.assertionEvent !== undefined\n        \"\n        class=\"flex items-center justify-center self-stretch p-3 text-skin-link\"\n      >\n        <BaseContainer class=\"my-1 inline-block\">\n          <div>\n            <strong>{{\n              'Proposal can be executed at ' +\n              new Date(\n                questionDetails.assertionEvent.expirationTimestamp.toNumber() *\n                  1000\n              ).toLocaleString()\n            }}</strong>\n          </div>\n\n          <div style=\"text-align: center\" class=\"mt-3\">\n            <a\n              :href=\"\n                getProposalUrl(\n                  props.network,\n                  questionDetails.assertionEvent.proposalTxHash,\n                  questionDetails.assertionEvent.logIndex\n                )\n              \"\n              class=\"rounded-lg border p-2 text-skin-text\"\n              rel=\"noreferrer noopener\"\n              target=\"_blank\"\n              style=\"font-size: 16px\"\n            >\n              {{ $t('safeSnap.labels.disputeProposal') }}\n              <em style=\"font-size: 14px\" class=\"iconfont iconexternal-link\" />\n            </a>\n          </div>\n        </BaseContainer>\n      </div>\n\n      <div\n        v-if=\"questionState === 'proposal-approved'\"\n        class=\"my-4 inline-block\"\n      >\n        <BaseContainer class=\"flex items-center\">\n          <TuneButton\n            :loading=\"action2State === 'execute-proposal'\"\n            @click=\"executeProposalUma\"\n            class=\"mr-2\"\n          >\n            {{ $t('safeSnap.labels.executeTxsUma', [batches.length]) }}\n          </TuneButton>\n          <BasePopoverHover placement=\"top\">\n            <template #button>\n              <i-ho-information-circle />\n            </template>\n            <template #content>\n              <div\n                class=\"border bg-skin-bg p-3 text-md shadow-lg md:rounded-lg\"\n              >\n                {{ $t('safeSnap.labels.executeToolTip') }}\n              </div>\n            </template>\n          </BasePopoverHover>\n        </BaseContainer>\n      </div>\n    </div>\n    <div\n      v-else-if=\"\n        questionState !== 'loading' && questionState !== 'no-wallet-connection'\n      \"\n      class=\"my-4\"\n    >\n      {{ $t('safeSnap.labels.switchChain', [networkName]) }}\n    </div>\n\n    <div v-if=\"questionState === 'completely-executed'\" class=\"my-4\">\n      {{ $t('safeSnap.labels.executed') }}\n    </div>\n    <div v-if=\"questionState === 'proposal-denied'\" class=\"my-4\">\n      {{ $t('safeSnap.labels.rejected') }}\n    </div>\n  </template>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Input/Address.vue",
    "content": "<script setup lang=\"ts\">\nimport { mustBeEthereumAddress } from '../../index';\n\nconst props = defineProps(['modelValue', 'inputProps', 'label', 'disabled']);\nconst emit = defineEmits(['update:modelValue', 'validAddress']);\n\nconst input = ref('');\nconst isValid = ref(false);\nconst dirty = ref(false);\n\nwatch(\n  () => props.modelValue,\n  value => {\n    input.value = value;\n  }\n);\n\nonMounted(() => {\n  if (props.modelValue) {\n    input.value = props.modelValue;\n  }\n});\n\nconst handleInput = () => {\n  dirty.value = input.value !== '';\n  emit('update:modelValue', input.value);\n  isValid.value = mustBeEthereumAddress(input.value);\n  if (isValid.value) {\n    emit('validAddress', input.value);\n  }\n};\n</script>\n\n<template>\n  <UiInput\n    v-model=\"input\"\n    v-bind=\"inputProps\"\n    :disabled=\"disabled\"\n    :error=\"dirty && !isValid && $t('safeSnap.invalidAddress')\"\n    @input=\"handleInput()\"\n  >\n    <template v-if=\"label\" #label>{{ label }}</template>\n  </UiInput>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Input/Amount.vue",
    "content": "<script setup lang=\"ts\">\nimport { parseUnits, formatUnits } from '@ethersproject/units';\n\nconst props = defineProps([\n  'modelValue',\n  'inputProps',\n  'label',\n  'disabled',\n  'decimals'\n]);\nconst emit = defineEmits(['update:modelValue', 'valid']);\n\nconst input = ref('0');\nconst isValid = ref(true);\nconst dirty = ref(false);\n\nconst format = (amount: string) => {\n  try {\n    return parseUnits(amount, props.decimals).toString();\n  } catch (error) {\n    return undefined;\n  }\n};\n\nconst handleInput = () => {\n  dirty.value = true;\n  const value = format(input.value);\n  isValid.value = !!value;\n  emit('update:modelValue', value);\n};\n\nonMounted(() => {\n  if (props.modelValue) {\n    input.value = formatUnits(props.modelValue, props.decimals);\n  }\n});\n\nwatch(\n  () => props.modelValue,\n  value => {\n    if (value && props.disabled) {\n      input.value = formatUnits(value, props.decimals);\n    }\n  }\n);\n\nwatch(\n  () => props.decimals,\n  () => {\n    handleInput();\n  }\n);\n</script>\n\n<template>\n  <UiInput\n    v-model=\"input\"\n    v-bind=\"inputProps\"\n    :disabled=\"disabled\"\n    :error=\"dirty && !isValid && $t('safeSnap.invalidAmount')\"\n    @input=\"handleInput()\"\n  >\n    <template v-if=\"label\" #label>{{ label }}</template>\n  </UiInput>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Input/ArrayType.vue",
    "content": "<script>\nimport {\n  isAddress,\n  isBoolean,\n  isByte,\n  isInt,\n  isStringArray,\n  isUint\n} from '../../utils/validator';\n\nconst getPlaceholder = (name, type) => {\n  if (isAddress(type)) {\n    return 'E.g.: [\"0xACa9...DA6E\",\"0x1dF6...006e\"]';\n  }\n\n  if (isBoolean(type)) {\n    return 'E.g.: [true, false, false, true]';\n  }\n\n  if (isUint(type)) {\n    return 'E.g.: [1000, 212, 320000022, 23]';\n  }\n\n  if (isInt(type)) {\n    return 'E.g.: [1000, -212, 1232, -1]';\n  }\n\n  if (isByte(type)) {\n    return 'E.g.: [\"0xc00000000000000000000000000000000000\", \"0xc00000000000000000000000000000000001\"]';\n  }\n\n  return 'E.g.: [\"first value\", \"second value\", \"third value\"]';\n};\n\nfunction getLabel(parameter) {\n  let type = parameter.type;\n  if (parameter.baseType === 'tuple') {\n    const components = parameter.components.map(param => param.type).join(', ');\n    type = `${type}(${components})`;\n  }\n  if (parameter.name) return `${parameter.name} (${type})`;\n  return type;\n}\n\nexport default {\n  props: ['parameter', 'disabled'],\n  emits: [],\n  data() {\n    const label = getLabel(this.parameter);\n    const placeholder = getPlaceholder(\n      this.parameter.name,\n      this.parameter.type\n    );\n    return {\n      input: '',\n      dirty: false,\n      placeholder,\n      label\n    };\n  },\n  computed: {\n    isValid() {\n      return isStringArray(this.input);\n    }\n  },\n  methods: {\n    handleInput(value) {\n      this.input = value;\n      this.dirty = true;\n    }\n  }\n};\n</script>\n\n<template>\n  <UiInput\n    :disabled=\"disabled\"\n    :error=\"dirty && !isValid && `Invalid ${type}`\"\n    :model-value=\"value\"\n    :placeholder=\"placeholder\"\n    @update:model-value=\"handleInput($event)\"\n  >\n    <template #label>{{ label }}</template>\n  </UiInput>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Input/MethodParameter.vue",
    "content": "<script>\nimport { isParameterValue } from '../../utils/validator';\nimport { isArrayParameter } from '../../index';\nimport SafeSnapInputAddress from './Address.vue';\nimport SafeSnapInputArrayType from './ArrayType.vue';\n\nexport default {\n  components: { SafeSnapInputAddress, SafeSnapInputArrayType },\n  props: ['modelValue', 'disabled', 'parameter'],\n  emits: ['update:modelValue', 'isValid'],\n  data() {\n    const placeholder = this.parameter.name\n      ? `${this.parameter.name} (${this.parameter.type})`\n      : this.parameter.type;\n\n    let value;\n    if (this.parameter.type === 'bool') value = false;\n\n    return {\n      placeholder,\n      value,\n      dirty: false\n    };\n  },\n  computed: {\n    isValid() {\n      return isParameterValue(this.parameter.baseType, this.value);\n    }\n  },\n  watch: {\n    modelValue(value) {\n      this.input = value;\n    }\n  },\n  mounted() {\n    if (this.modelValue) this.value = this.modelValue;\n  },\n  created() {\n    if (this.modelValue) this.input = this.modelValue;\n  },\n  methods: {\n    handleInput(value) {\n      this.value = value;\n      this.dirty = true;\n      this.$emit('update:modelValue', value);\n      this.$emit('isValid', this.isValid);\n    },\n    isArrayType() {\n      return isArrayParameter(this.parameter.baseType);\n    }\n  }\n};\n</script>\n\n<template>\n  <UiSelect\n    v-if=\"parameter.type === 'bool'\"\n    :disabled=\"disabled\"\n    :model-value=\"value\"\n    @update:model-value=\"handleInput($event)\"\n  >\n    <template #label>{{ placeholder }}</template>\n    <option :value=\"true\">true</option>\n    <option :value=\"false\">false</option>\n  </UiSelect>\n\n  <!-- ADDRESS -->\n  <SafeSnapInputAddress\n    v-else-if=\"parameter.type === 'address'\"\n    :disabled=\"disabled\"\n    :input-props=\"{ required: true }\"\n    :label=\"placeholder\"\n    :model-value=\"value\"\n    @update:model-value=\"handleInput($event)\"\n  />\n  <!-- Array of X type -->\n  <SafeSnapInputArrayType\n    v-else-if=\"isArrayType()\"\n    :disabled=\"disabled\"\n    :model-value=\"value\"\n    :parameter=\"parameter\"\n    @update:model-value=\"handleInput($event)\"\n  />\n  <!-- Text input -->\n  <UiInput\n    v-else\n    :disabled=\"disabled\"\n    :error=\"dirty && !isValid && `Invalid ${parameter.type}`\"\n    :model-value=\"value\"\n    @update:model-value=\"handleInput($event)\"\n  >\n    <template #label>{{ placeholder }}</template>\n  </UiInput>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Modal/OptionApproval.vue",
    "content": "<script>\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { formatUnits } from '@ethersproject/units';\nimport { getEnsTextRecord } from '@snapshot-labs/snapshot.js/src/utils';\n\nexport default {\n  props: [\n    'spaceId',\n    'open',\n    'isApproved',\n    'bond',\n    'questionId',\n    'minimumBond',\n    'tokenSymbol',\n    'tokenDecimals',\n    'oracle'\n  ],\n  emits: ['close', 'setApproval'],\n  setup() {\n    const { safesnap } = useSafe();\n    const { isValidEnsDomain } = useEns();\n    return { safesnap, isValidEnsDomain };\n  },\n  data() {\n    return { criteriaLink: '' };\n  },\n  computed: {\n    answer() {\n      return this.isApproved ? 'Yes' : 'No';\n    },\n    bondData() {\n      const bondNotSet = BigNumber.from(this.bond).eq(0);\n      const minimumBond = BigNumber.from(this.minimumBond).eq(0)\n        ? BigNumber.from(10).pow(this.tokenDecimals)\n        : this.minimumBond;\n      const toSet = bondNotSet ? minimumBond : BigNumber.from(this.bond).mul(2);\n      return {\n        toSet: formatUnits(toSet, this.tokenDecimals),\n        current: bondNotSet ? '--' : formatUnits(this.bond, this.tokenDecimals),\n        tokenSymbol: this.tokenSymbol\n      };\n    },\n    questionLink() {\n      if (this.tokenSymbol && this.tokenSymbol !== 'ETH') {\n        return `https://reality.eth.link/app/#!/token/${this.tokenSymbol}/question/${this.oracle}-${this.questionId}`;\n      }\n      return `https://reality.eth.link/app/#!/question/${this.oracle}-${this.questionId}`;\n    }\n  },\n  mounted() {\n    setTimeout(this.getCriteriaLink, 800);\n  },\n  methods: {\n    async handleSetApproval(option) {\n      await this.$emit('setApproval', option);\n      this.$emit('close');\n    },\n    async getCriteriaLink() {\n      if (this.isValidEnsDomain(this.spaceId)) {\n        try {\n          this.criteriaLink = await getEnsTextRecord(\n            this.spaceId,\n            'daorequirements',\n            '1',\n            { broviderUrl: import.meta.env.VITE_BROVIDER_URL }\n          );\n        } catch (err) {\n          console.warn(\n            '[safesnap] failed to get the \"daorequirements\" text record'\n          );\n        }\n      }\n    }\n  }\n};\n</script>\n\n<template>\n  <BaseModal :open=\"open\" @close=\"$emit('close')\">\n    <template #header>\n      <h3 class=\"title\">SafeSnap</h3>\n    </template>\n    <div class=\"m-4 mb-5\">\n      <p v-if=\"criteriaLink\">\n        {{ $t('safeSnap.labels.question') }}\n        <a\n          class=\"question-link\"\n          rel=\"noreferrer noopener\"\n          target=\"_blank\"\n          :href=\"criteriaLink\"\n        >\n          {{ $t('safeSnap.labels.criteria') }}\n        </a>\n      </p>\n      <p v-if=\"!criteriaLink\">{{ $t('safeSnap.labels.proposalPassed') }}</p>\n      <div style=\"text-align: right\">\n        <a\n          :href=\"questionLink\"\n          class=\"text-skin-text\"\n          rel=\"noreferrer noopener\"\n          target=\"_blank\"\n          style=\"font-size: 16px\"\n        >\n          Question\n          <em style=\"font-size: 14px\" class=\"iconfont iconexternal-link\" />\n        </a>\n      </div>\n\n      <div class=\"my-3 rounded-lg border p-3\">\n        <div>\n          <strong class=\"pr-3\">{{ $t('safeSnap.currentOutcome') }}:</strong>\n          <span class=\"float-right text-skin-link\">\n            {{ answer }}\n          </span>\n        </div>\n        <div>\n          <strong class=\"pr-3\">{{ $t('safeSnap.currentBond') }}:</strong>\n          <span class=\"float-right text-skin-link\">\n            {{ bondData.current + ' ' + tokenSymbol }}\n          </span>\n        </div>\n        <div>\n          <strong class=\"pr-3\">{{ $t('safeSnap.nextBond') }}:</strong>\n          <span class=\"float-right text-skin-link\">\n            {{ bondData.toSet + ' ' + bondData.tokenSymbol }}\n          </span>\n        </div>\n      </div>\n\n      <div class=\"mt-5\">\n        <h4 class=\"text-center\">\n          {{ $t('safeSnap.setOutcomeTo') }}\n        </h4>\n        <div class=\"vote-button-row\">\n          <TuneButton class=\"button vote-button\" @click=\"handleSetApproval(0)\">\n            No\n          </TuneButton>\n          <TuneButton class=\"vote-button\" primary @click=\"handleSetApproval(1)\">\n            Yes\n          </TuneButton>\n        </div>\n      </div>\n    </div>\n  </BaseModal>\n</template>\n\n<style>\n.vote-button {\n  width: 187px;\n}\n.vote-button-row {\n  padding-top: 15px;\n  display: flex;\n  justify-content: space-between;\n}\n.title {\n  text-align: left;\n  padding-left: 20px;\n}\n.question-link {\n  color: var(--link-color);\n  text-decoration: underline;\n}\n</style>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/SafeTransactions.vue",
    "content": "<script>\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { getIpfsUrl, shorten } from '@/helpers/utils';\nimport SafeSnapTooltip from './Tooltip.vue';\nimport SafeSnapHandleOutcome from './HandleOutcome.vue';\nimport SafeSnapHandleOutcomeUma from './HandleOutcomeUma.vue';\nimport SafeSnapFormImportTransactionsButton from './Form/ImportTransactionsButton.vue';\nimport SafeSnapFormTransactionBatch from './Form/TransactionBatch.vue';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { sleep } from '@snapshot-labs/snapshot.js/src/utils';\nimport { formatUnits } from '@ethersproject/units';\nimport Plugin, {\n  createBatch,\n  EIP3770_PREFIXES,\n  getGnosisSafeBalances,\n  getGnosisSafeCollectibles\n} from '../index';\nimport { getSafeAppLink } from '@/plugins/oSnap/utils';\n\nconst plugin = new Plugin();\n\nexport const ensureRightNetwork = async chainId => {\n  const chainIdInt = parseInt(chainId);\n  const connectedToChainId = getInstance().provider.value?.chainId;\n  if (connectedToChainId === chainIdInt) return; // already on right chain\n\n  if (!window.ethereum || !getInstance().provider.value?.isMetaMask) {\n    // we cannot switch automatically\n    throw new Error(\n      `Connected to wrong chain #${connectedToChainId}, required: #${chainId}`\n    );\n  }\n\n  const network = networks[chainId];\n  const chainIdHex = `0x${chainIdInt.toString(16)}`;\n\n  try {\n    // check if the chain to connect to is installed\n    await window.ethereum.request({\n      method: 'wallet_switchEthereumChain',\n      params: [{ chainId: chainIdHex }] // chainId must be in hexadecimal numbers\n    });\n  } catch (error) {\n    // This error code indicates that the chain has not been added to MetaMask. Let's add it.\n    if (error.code === 4902) {\n      try {\n        await window.ethereum.request({\n          method: 'wallet_addEthereumChain',\n          params: [\n            {\n              chainId: chainIdHex,\n              chainName: network.name,\n              rpcUrls: network.rpc,\n              blockExplorerUrls: [network.explorer.url]\n            }\n          ]\n        });\n      } catch (addError) {\n        console.error(addError);\n      }\n    }\n    console.error(error);\n  }\n\n  await sleep(1e3); // somehow the switch does not take immediate effect :/\n  if (window.ethereum.chainId !== chainIdHex) {\n    throw new Error(\n      `Could not switch to the right chain on MetaMask (required: ${chainIdHex}, active: ${window.ethereum.chainId})`\n    );\n  }\n};\n\nasync function fetchBalances(network, safeAddress) {\n  if (!safeAddress) {\n    return [];\n  }\n\n  try {\n    const balances = await getGnosisSafeBalances(network, safeAddress);\n\n    const uniswapTokensPromise = fetchTokens(\n      'https://gateway.ipfs.io/ipns/tokens.uniswap.org'\n    );\n    const snapshotTokensPromise = fetchTokens(\n      `${import.meta.env.VITE_SIDEKICK_URL}/api/moderation?list=verifiedTokens`\n    );\n\n    const tokensLists = await Promise.all([\n      uniswapTokensPromise,\n      snapshotTokensPromise\n    ]);\n    const tokens = tokensLists.flat();\n\n    return sortBalances(enhanceBalances(balances, tokens));\n  } catch (e) {\n    console.warn('Error fetching balances');\n    return [];\n  }\n}\n\nfunction fetchTokens(url) {\n  return fetch(url)\n    .then(response => response.json())\n    .then(data => {\n      return data.verifiedTokens?.tokens || data.tokens || [];\n    })\n    .catch(() => []);\n}\n\nfunction enhanceBalances(balances, tokens) {\n  return balances\n    .filter(balance => balance.token)\n    .map(balance => enhanceBalance(balance, tokens));\n}\n\nfunction enhanceBalance(balance, tokens) {\n  const verifiedToken = getVerifiedToken(balance.tokenAddress, tokens);\n  return {\n    ...balance.token,\n    address: balance.tokenAddress,\n    balance: balance.balance\n      ? formatUnits(balance.balance, balance.token.decimals)\n      : 0,\n    verified: !!verifiedToken,\n    chainId: verifiedToken ? verifiedToken.chainId : undefined\n  };\n}\n\nfunction getVerifiedToken(tokenAddress, tokens) {\n  return tokens.find(\n    token => token.address.toLowerCase() === tokenAddress.toLowerCase()\n  );\n}\n\nfunction sortBalances(balances) {\n  return balances.sort((a, b) => b.verified - a.verified);\n}\n\nasync function fetchCollectibles(network, gnosisSafeAddress) {\n  if (gnosisSafeAddress) {\n    try {\n      return await getGnosisSafeCollectibles(network, gnosisSafeAddress);\n    } catch (error) {\n      console.warn('Error fetching collectables');\n    }\n  }\n  return [];\n}\n\nfunction formatBatches(network, module, batches, multiSend) {\n  if (batches.length) {\n    const batchSample = batches[0];\n    if (Array.isArray(batchSample)) {\n      const chainId = parseInt(network);\n      return batches.map((txs, index) =>\n        createBatch(module, chainId, index, txs, multiSend)\n      );\n    }\n  }\n  return batches;\n}\n\nexport default {\n  components: {\n    SafeSnapTooltip,\n    SafeSnapFormImportTransactionsButton,\n    SafeSnapHandleOutcome,\n    SafeSnapHandleOutcomeUma,\n    SafeSnapFormTransactionBatch\n  },\n  props: [\n    'modelValue',\n    'proposal',\n    'space',\n    'results',\n    'network',\n    'realityAddress',\n    'umaAddress',\n    'multiSendAddress',\n    'preview',\n    'hash'\n  ],\n  emits: ['update:modelValue'],\n  setup() {\n    return { shorten };\n  },\n  data() {\n    return {\n      input: formatBatches(\n        this.network,\n        this.realityAddress,\n        this.modelValue,\n        this.multiSendAddress\n      ),\n      gnosisSafeAddress: undefined,\n      moduleType: undefined,\n      moduleAddress: undefined,\n      moduleTypeReady: false,\n      showHash: false,\n      transactionConfig: {\n        preview: this.preview,\n        gnosisSafeAddress: undefined,\n        realityAddress: this.realityAddress,\n        umaAddress: this.umaAddress,\n        network: this.network,\n        multiSendAddress: this.multiSendAddress,\n        tokens: [],\n        collectables: []\n      }\n    };\n  },\n  computed: {\n    safeLink() {\n      return getSafeAppLink(this.network, this.gnosisSafeAddress);\n    },\n    networkName() {\n      if (this.network === '1') return 'Mainnet';\n      const { shortName, name } = networks[this.network] || {};\n      return shortName || name || `#${this.network}`;\n    },\n    networkIcon() {\n      const { logo } = networks[this.network];\n      return getIpfsUrl(logo);\n    },\n    proposalResolved() {\n      const ts = (Date.now() / 1e3).toFixed();\n      return ts > this.proposal.end;\n    }\n  },\n  async mounted() {\n    try {\n      const moduleType = await plugin.validateUmaModule(\n        this.network,\n        this.umaAddress\n      );\n\n      const { dao } =\n        moduleType === 'reality'\n          ? await plugin.getModuleDetailsReality(\n              this.network,\n              this.realityAddress\n            )\n          : await plugin.getModuleDetailsUma(this.network, this.umaAddress);\n\n      const moduleAddress =\n        moduleType === 'reality' ? this.realityAddress : this.umaAddress;\n\n      this.moduleType = moduleType;\n      this.moduleAddress = moduleAddress;\n      this.gnosisSafeAddress = dao;\n      this.transactionConfig = {\n        ...this.transactionConfig,\n        gnosisSafeAddress: this.gnosisSafeAddress,\n        tokens: await fetchBalances(this.network, this.gnosisSafeAddress),\n        collectables: await fetchCollectibles(\n          this.network,\n          this.gnosisSafeAddress\n        )\n      };\n      this.moduleTypeReady = true;\n    } catch (e) {\n      console.error(e);\n    }\n  },\n  methods: {\n    addTransactionBatch() {\n      this.input.push(\n        createBatch(\n          this.moduleAddress,\n          parseInt(this.network),\n          this.input.length,\n          [],\n          this.multiSendAddress\n        )\n      );\n      this.$emit('update:modelValue', this.input);\n    },\n    removeBatch(index) {\n      this.input.splice(index, 1);\n      this.$emit('update:modelValue', this.input);\n    },\n    updateTransactionBatch(index, batch) {\n      this.input[index] = batch;\n      this.$emit('update:modelValue', this.input);\n    },\n    handleImport(txs) {\n      this.input.push(\n        createBatch(\n          this.moduleAddress,\n          parseInt(this.network),\n          this.input.length,\n          txs,\n          this.multiSendAddress\n        )\n      );\n      this.$emit('update:modelValue', this.input);\n    }\n  }\n};\n</script>\n\n<template>\n  <div>\n    <h4\n      class=\"flex rounded-t-none border-b px-3 pb-[12px] pt-3 md:rounded-t-md\"\n    >\n      <BaseAvatar class=\"float-left mr-2\" :src=\"networkIcon\" size=\"28\" />\n      {{ networkName }} Safe\n      <a\n        v-if=\"gnosisSafeAddress\"\n        :href=\"safeLink\"\n        class=\"ml-2 flex font-normal text-skin-text\"\n        target=\"_blank\"\n      >\n        {{ shorten(gnosisSafeAddress) }}\n        <i-ho-external-link class=\"ml-1\" />\n      </a>\n      <div class=\"flex-grow\"></div>\n      <SafeSnapTooltip\n        v-if=\"moduleTypeReady\"\n        :module-address=\"moduleAddress\"\n        :multi-send-address=\"multiSendAddress\"\n        :module-type=\"moduleType\"\n      />\n      <LoadingSpinner v-else />\n    </h4>\n    <UiCollapsibleText\n      v-if=\"hash\"\n      :show-arrow=\"true\"\n      :open=\"showHash\"\n      :text=\"hash\"\n      class=\"border-b\"\n      style=\"border-width: 0 0 1px 0 !important\"\n      title=\"Complete Transaction Hash\"\n      @toggle=\"showHash = !showHash\"\n    />\n    <div class=\"text-center\">\n      <div\n        v-for=\"(batch, index) in input\"\n        :key=\"index\"\n        class=\"border-b last:border-b-0\"\n      >\n        <SafeSnapFormTransactionBatch\n          :config=\"transactionConfig\"\n          :model-value=\"batch\"\n          :nonce=\"index\"\n          @remove=\"removeBatch(index)\"\n          @update:model-value=\"updateTransactionBatch(index, $event)\"\n        />\n      </div>\n\n      <div v-if=\"!preview || proposalResolved\">\n        <TuneButton v-if=\"!preview\" class=\"my-3\" @click=\"addTransactionBatch\">\n          {{ $t('safeSnap.addBatch') }}\n        </TuneButton>\n\n        <SafeSnapFormImportTransactionsButton\n          v-if=\"!preview\"\n          :network=\"network\"\n          @import=\"handleImport($event)\"\n        />\n\n        <SafeSnapHandleOutcome\n          v-if=\"\n            preview &&\n            proposalResolved &&\n            moduleType === 'reality' &&\n            moduleTypeReady\n          \"\n          :batches=\"input\"\n          :proposal=\"proposal\"\n          :reality-address=\"transactionConfig.realityAddress\"\n          :multi-send-address=\"transactionConfig.multiSendAddress\"\n          :network=\"transactionConfig.network\"\n        />\n\n        <SafeSnapHandleOutcomeUma\n          v-if=\"\n            preview &&\n            proposalResolved &&\n            moduleType === 'uma' &&\n            moduleTypeReady\n          \"\n          :batches=\"input\"\n          :proposal=\"proposal\"\n          :space=\"space\"\n          :results=\"results\"\n          :uma-address=\"transactionConfig.umaAddress\"\n          :multi-send-address=\"transactionConfig.multiSendAddress\"\n          :network=\"transactionConfig.network\"\n        />\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/components/Tooltip.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\n\ndefineProps<{\n  moduleAddress: string;\n  multiSendAddress: string;\n  moduleType: string;\n}>();\n\nconst { copyToClipboard } = useCopy();\n</script>\n\n<template>\n  <BasePopoverHover placement=\"left\">\n    <template #button>\n      <i-ho-information-circle />\n    </template>\n    <template #content>\n      <div class=\"border bg-skin-bg p-3 text-md shadow-lg md:rounded-lg\">\n        <div>Multisend address</div>\n        <TuneButton\n          class=\"flex w-full items-center justify-between\"\n          @click=\"copyToClipboard(multiSendAddress)\"\n        >\n          {{ shorten(multiSendAddress) }}\n          <i-ho-duplicate class=\"ml-1\" />\n        </TuneButton>\n        <div class=\"mt-3\">\n          {{ moduleType === 'reality' ? 'Reality' : 'UMA' }} Module address\n        </div>\n        <TuneButton\n          class=\"flex w-full items-center justify-between\"\n          @click=\"copyToClipboard(moduleAddress)\"\n        >\n          {{ shorten(moduleAddress) }}\n          <i-ho-duplicate class=\"ml-1\" />\n        </TuneButton>\n      </div>\n    </template>\n  </BasePopoverHover>\n</template>\n"
  },
  {
    "path": "src/plugins/safeSnap/constants.ts",
    "content": "import { MULTI_SEND_VERSION } from './utils/multiSend';\n\nexport const EIP712_TYPES = {\n  Transaction: [\n    { name: 'to', type: 'address' },\n    { name: 'value', type: 'uint256' },\n    { name: 'data', type: 'bytes' },\n    { name: 'operation', type: 'uint8' },\n    { name: 'nonce', type: 'uint256' }\n  ]\n};\n\nexport const EIP3770_PREFIXES = {\n  1: 'eth',\n  5: 'gor',\n  56: 'bnb',\n  100: 'gno',\n  246: 'ewt',\n  73799: 'vt',\n  42161: 'arb1',\n  137: 'matic',\n  1116: 'core',\n  11155111: 'sep'\n};\n\nexport const EXPLORER_API_URLS = {\n  '1': 'https://api.etherscan.io/api',\n  '5': 'https://api-goerli.etherscan.io/api',\n  '100': 'https://gnosis.blockscout.com/api',\n  '73799': 'https://volta-explorer.energyweb.org/api',\n  '246': 'https://explorer.energyweb.org/api',\n  '137': 'https://api.polygonscan.com/api',\n  '56': 'https://api.bscscan.com/api',\n  '42161': 'https://api.arbiscan.io/api',\n  // '1116': Add 'https://openapi.coredao.org/api' if API key requirement is removed\n  '11155111': 'https://api-sepolia.etherscan.io/api'\n};\n\nexport const GNOSIS_SAFE_TRANSACTION_API_URLS = {\n  '1': 'https://safe-transaction-mainnet.safe.global/api',\n  '5': 'https://safe-transaction-goerli.safe.global/api',\n  '100': 'https://safe-transaction-gnosis-chain.safe.global/api',\n  '73799': 'https://safe-transaction-volta.safe.global/api',\n  '246': 'https://safe-transaction-ewc.safe.global/api',\n  '137': 'https://safe-transaction-polygon.safe.global/api',\n  '56': 'https://safe-transaction-bsc.safe.global/api',\n  '42161': 'https://safe-transaction-arbitrum.safe.global/api',\n  '1116': 'https://safetx.coredao.org/api',\n  '11155111': 'https://safe-transaction-sepolia.safe.global/api'\n};\n\n// ABIs\n\nexport const REALITY_MODULE_ABI = [\n  // Events\n  'event ProposalQuestionCreated(bytes32 indexed questionId, string indexed proposalId)',\n\n  // Read functions\n  'function avatar() view returns (address)', // Reality Module\n  'function executor() view returns (address)', // Dao Module\n  'function oracle() view returns (address)',\n  'function questionCooldown() view returns (uint32)',\n  'function answerExpiration() view returns (uint32)',\n  'function buildQuestion(string proposalId, bytes32[] txHashes) view returns (string)',\n  'function executedProposalTransactions(bytes32 questionHash, bytes32 txHash) view returns (bool)',\n  'function questionIds(bytes32 questionHash) view returns (bytes32)',\n  'function minimumBond() view returns (uint256)',\n\n  // Write functions\n  'function addProposal(string proposalId, bytes32[] txHashes)',\n  'function executeProposalWithIndex(string proposalId, bytes32[] txHashes, address to, uint256 value, bytes data, uint8 operation, uint256 txIndex)'\n];\n\nexport const ORACLE_ABI = [\n  // Events\n  `event LogNewAnswer(\n    bytes32 answer,\n    bytes32 indexed question_id,\n    bytes32 history_hash,\n    address indexed user,\n    uint256 bond,\n    uint256 ts,\n    bool is_commitment\n  )`,\n\n  // Read functions\n  'function resultFor(bytes32 question_id) view returns (bytes32)',\n  'function getFinalizeTS(bytes32 question_id) view returns (uint32)',\n  'function getBond(bytes32 question_id) view returns (uint256)',\n  'function getBestAnswer(bytes32 question_id) view returns (uint32)',\n  'function balanceOf(address) view returns (uint256)',\n  'function getHistoryHash(bytes32 question_id) view returns (bytes32)',\n  'function isFinalized(bytes32 question_id) view returns (bool)',\n  'function token() view returns (address)',\n\n  // Write functions\n  'function submitAnswer(bytes32 question_id, bytes32 answer, uint256 max_previous) external payable',\n  'function submitAnswerERC20(bytes32 question_id, bytes32 answer, uint256 max_previous, uint256 tokens) external',\n  `function claimMultipleAndWithdrawBalance(\n    bytes32[] question_ids,\n    uint256[] lengths,\n    bytes32[] hist_hashes,\n    address[] addrs,\n    uint256[] bonds,\n    bytes32[] answers\n  ) public`,\n  'function withdraw() public'\n];\n\nexport const UMA_MODULE_ABI = [\n  'constructor(address _finder, address _owner, address _collateral, uint256 _bondAmount, string _rules, bytes32 _identifier, uint64 _liveness)',\n  'error NotIERC165Compliant(address guard_)',\n  'event AvatarSet(address indexed previousAvatar, address indexed newAvatar)',\n  'event ChangedGuard(address guard)',\n  'event Initialized(uint8 version)',\n  'event OptimisticGovernorDeployed(address indexed owner, address indexed avatar, address target)',\n  'event OptimisticOracleChanged(address indexed newOptimisticOracleV3)',\n  'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)',\n  'event ProposalDeleted(bytes32 indexed proposalHash, bytes32 indexed assertionId)',\n  'event ProposalExecuted(bytes32 indexed proposalHash, bytes32 indexed assertionId)',\n  'event SetCollateralAndBond(address indexed collateral, uint256 indexed bondAmount)',\n  'event SetEscalationManager(address indexed escalationManager)',\n  'event SetIdentifier(bytes32 indexed identifier)',\n  'event SetLiveness(uint64 indexed liveness)',\n  'event SetRules(string rules)',\n  'event TargetSet(address indexed previousTarget, address indexed newTarget)',\n  'event TransactionExecuted(bytes32 indexed proposalHash, bytes32 indexed assertionId, uint256 indexed transactionIndex)',\n  'event TransactionsProposed(address indexed proposer, uint256 indexed proposalTime, bytes32 indexed assertionId, tuple(tuple(address to, uint8 operation, uint256 value, bytes data)[] transactions, uint256 requestTime) proposal, bytes32 proposalHash, bytes explanation, string rules, uint256 challengeWindowEnds)',\n  'function EXPLANATION_KEY() view returns (bytes)',\n  'function PROPOSAL_HASH_KEY() view returns (bytes)',\n  'function RULES_KEY() view returns (bytes)',\n  'function assertionDisputedCallback(bytes32 assertionId)',\n  'function assertionIds(bytes32) view returns (bytes32)',\n  'function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully)',\n  'function avatar() view returns (address)',\n  'function bondAmount() view returns (uint256)',\n  'function collateral() view returns (address)',\n  'function deleteProposalOnUpgrade(bytes32 proposalHash)',\n  'function escalationManager() view returns (address)',\n  'function executeProposal(tuple(address to, uint8 operation, uint256 value, bytes data)[] transactions)',\n  'function finder() view returns (address)',\n  'function getCurrentTime() view returns (uint256)',\n  'function getGuard() view returns (address _guard)',\n  'function getProposalBond() view returns (uint256)',\n  'function guard() view returns (address)',\n  'function identifier() view returns (bytes32)',\n  'function liveness() view returns (uint64)',\n  'function optimisticOracleV3() view returns (address)',\n  'function owner() view returns (address)',\n  'function proposalHashes(bytes32) view returns (bytes32)',\n  'function proposeTransactions(tuple(address to, uint8 operation, uint256 value, bytes data)[] transactions, bytes explanation)',\n  'function renounceOwnership()',\n  'function rules() view returns (string)',\n  'function setAvatar(address _avatar)',\n  'function setCollateralAndBond(address _collateral, uint256 _bondAmount)',\n  'function setEscalationManager(address _escalationManager)',\n  'function setGuard(address _guard)',\n  'function setIdentifier(bytes32 _identifier)',\n  'function setLiveness(uint64 _liveness)',\n  'function setRules(string _rules)',\n  'function setTarget(address _target)',\n  'function setUp(bytes initializeParams)',\n  'function sync()',\n  'function target() view returns (address)',\n  'function transferOwnership(address newOwner)'\n];\n\nexport const UMA_ORACLE_ABI = [\n  'constructor(address _finder, address _defaultCurrency, uint64 _defaultLiveness)',\n  'event AdminPropertiesSet(address defaultCurrency, uint64 defaultLiveness, uint256 burnedBondPercentage)',\n  'event AssertionDisputed(bytes32 indexed assertionId, address indexed caller, address indexed disputer)',\n  'event AssertionMade(bytes32 indexed assertionId, bytes32 domainId, bytes claim, address indexed asserter, address callbackRecipient, address escalationManager, address caller, uint64 expirationTime, address currency, uint256 bond, bytes32 indexed identifier)',\n  'event AssertionSettled(bytes32 indexed assertionId, address indexed bondRecipient, bool disputed, bool settlementResolution, address settleCaller)',\n  'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)',\n  'function assertTruth(bytes claim, address asserter, address callbackRecipient, address escalationManager, uint64 liveness, address currency, uint256 bond, bytes32 identifier, bytes32 domainId) returns (bytes32 assertionId)',\n  'function assertTruthWithDefaults(bytes claim, address asserter) returns (bytes32)',\n  'function assertions(bytes32) view returns (tuple(bool arbitrateViaEscalationManager, bool discardOracle, bool validateDisputers, address assertingCaller, address escalationManager) escalationManagerSettings, address asserter, uint64 assertionTime, bool settled, address currency, uint64 expirationTime, bool settlementResolution, bytes32 domainId, bytes32 identifier, uint256 bond, address callbackRecipient, address disputer)',\n  'function burnedBondPercentage() view returns (uint256)',\n  'function cachedCurrencies(address) view returns (bool isWhitelisted, uint256 finalFee)',\n  'function cachedIdentifiers(bytes32) view returns (bool)',\n  'function cachedOracle() view returns (address)',\n  'function defaultCurrency() view returns (address)',\n  'function defaultIdentifier() view returns (bytes32)',\n  'function defaultLiveness() view returns (uint64)',\n  'function disputeAssertion(bytes32 assertionId, address disputer)',\n  'function finder() view returns (address)',\n  'function getAssertion(bytes32 assertionId) view returns (tuple(tuple(bool arbitrateViaEscalationManager, bool discardOracle, bool validateDisputers, address assertingCaller, address escalationManager) escalationManagerSettings, address asserter, uint64 assertionTime, bool settled, address currency, uint64 expirationTime, bool settlementResolution, bytes32 domainId, bytes32 identifier, uint256 bond, address callbackRecipient, address disputer))',\n  'function getAssertionResult(bytes32 assertionId) view returns (bool)',\n  'function getCurrentTime() view returns (uint256)',\n  'function getMinimumBond(address currency) view returns (uint256)',\n  'function multicall(bytes[] data) returns (bytes[] results)',\n  'function numericalTrue() view returns (int256)',\n  'function owner() view returns (address)',\n  'function renounceOwnership()',\n  'function setAdminProperties(address _defaultCurrency, uint64 _defaultLiveness, uint256 _burnedBondPercentage)',\n  'function settleAndGetAssertionResult(bytes32 assertionId) returns (bool)',\n  'function settleAssertion(bytes32 assertionId)',\n  'function stampAssertion(bytes32 assertionId) view returns (bytes)',\n  'function syncUmaParams(bytes32 identifier, address currency)',\n  'function transferOwnership(address newOwner)'\n];\n\nexport const UMA_VOTING_ABI = [\n  'constructor(uint128 _emissionRate, uint64 _unstakeCoolDown, uint64 _phaseLength, uint32 _maxRolls, uint32 _maxRequestsPerRound, uint128 _gat, uint64 _spat, address _votingToken, address _finder, address _slashingLibrary, address _previousVotingContract)',\n  'event DelegateSet(address indexed delegator, address indexed delegate)',\n  'event DelegatorSet(address indexed delegate, address indexed delegator)',\n  'event EncryptedVote(address indexed caller, uint32 indexed roundId, bytes32 indexed identifier, uint256 time, bytes ancillaryData, bytes encryptedVote)',\n  'event ExecutedUnstake(address indexed voter, uint128 tokensSent, uint128 voterStake)',\n  'event GatAndSpatChanged(uint128 newGat, uint64 newSpat)',\n  'event MaxRequestsPerRoundChanged(uint32 newMaxRequestsPerRound)',\n  'event MaxRollsChanged(uint32 newMaxRolls)',\n  'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)',\n  'event RequestAdded(address indexed requester, uint32 indexed roundId, bytes32 indexed identifier, uint256 time, bytes ancillaryData, bool isGovernance)',\n  'event RequestDeleted(bytes32 indexed identifier, uint256 indexed time, bytes ancillaryData, uint32 rollCount)',\n  'event RequestResolved(uint32 indexed roundId, uint256 indexed resolvedPriceRequestIndex, bytes32 indexed identifier, uint256 time, bytes ancillaryData, int256 price)',\n  'event RequestRolled(bytes32 indexed identifier, uint256 indexed time, bytes ancillaryData, uint32 rollCount)',\n  'event RequestedUnstake(address indexed voter, uint128 amount, uint64 unstakeTime, uint128 voterStake)',\n  'event SetNewEmissionRate(uint128 newEmissionRate)',\n  'event SetNewUnstakeCoolDown(uint64 newUnstakeCoolDown)',\n  'event SlashingLibraryChanged(address newAddress)',\n  'event Staked(address indexed voter, address indexed from, uint128 amount, uint128 voterStake, uint128 voterPendingUnstake, uint128 cumulativeStake)',\n  'event UpdatedReward(address indexed voter, uint128 newReward, uint64 lastUpdateTime)',\n  'event VoteCommitted(address indexed voter, address indexed caller, uint32 roundId, bytes32 indexed identifier, uint256 time, bytes ancillaryData)',\n  'event VoteRevealed(address indexed voter, address indexed caller, uint32 roundId, bytes32 indexed identifier, uint256 time, bytes ancillaryData, int256 price, uint128 numTokens)',\n  'event VoterSlashApplied(address indexed voter, int128 slashedTokens, uint128 postStake)',\n  'event VoterSlashed(address indexed voter, uint256 indexed requestIndex, int128 slashedTokens)',\n  'event VotingContractMigrated(address newAddress)',\n  'event WithdrawnRewards(address indexed voter, address indexed delegate, uint128 tokensWithdrawn)',\n  'function ANCILLARY_BYTES_LIMIT() view returns (uint256)',\n  'function UINT64_MAX() view returns (uint64)',\n  'function commitAndEmitEncryptedVote(bytes32 identifier, uint256 time, bytes ancillaryData, bytes32 hash, bytes encryptedVote)',\n  'function commitVote(bytes32 identifier, uint256 time, bytes ancillaryData, bytes32 hash)',\n  'function cumulativeStake() view returns (uint128)',\n  'function currentActiveRequests() view returns (bool)',\n  'function delegateToStaker(address) view returns (address)',\n  'function emissionRate() view returns (uint128)',\n  'function executeUnstake()',\n  'function finder() view returns (address)',\n  'function gat() view returns (uint128)',\n  'function getCurrentRoundId() view returns (uint32)',\n  'function getCurrentTime() view returns (uint256)',\n  'function getNumberOfPriceRequests() view returns (uint256 numberPendingPriceRequests, uint256 numberResolvedPriceRequests)',\n  'function getNumberOfPriceRequestsPostUpdate() returns (uint256 numberPendingPriceRequests, uint256 numberResolvedPriceRequests)',\n  'function getPendingRequests() view returns (tuple(uint32 lastVotingRound, bool isGovernance, uint64 time, uint32 rollCount, bytes32 identifier, bytes ancillaryData)[])',\n  'function getPrice(bytes32 identifier, uint256 time, bytes ancillaryData) view returns (int256)',\n  'function getPrice(bytes32 identifier, uint256 time) view returns (int256)',\n  'function getPriceRequestStatuses(tuple(bytes32 identifier, uint256 time, bytes ancillaryData)[] requests) view returns (tuple(uint8 status, uint32 lastVotingRound)[])',\n  'function getRoundEndTime(uint256 roundId) view returns (uint256)',\n  'function getRoundIdToVoteOnRequest(uint32 targetRoundId) view returns (uint32)',\n  'function getVotePhase() view returns (uint8)',\n  'function getVoterFromDelegate(address caller) view returns (address)',\n  'function getVoterParticipation(uint256 requestIndex, uint32 lastVotingRound, address voter) view returns (uint8)',\n  'function getVoterPendingStake(address voter, uint32 roundId) view returns (uint128)',\n  'function getVoterStakePostUpdate(address voter) returns (uint128)',\n  'function hasPrice(bytes32 identifier, uint256 time) view returns (bool)',\n  'function hasPrice(bytes32 identifier, uint256 time, bytes ancillaryData) view returns (bool)',\n  'function lastRoundIdProcessed() view returns (uint32)',\n  'function lastUpdateTime() view returns (uint64)',\n  'function maxRequestsPerRound() view returns (uint32)',\n  'function maxRolls() view returns (uint32)',\n  'function migratedAddress() view returns (address)',\n  'function multicall(bytes[] data) returns (bytes[] results)',\n  'function nextPendingIndexToProcess() view returns (uint64)',\n  'function outstandingRewards(address voter) view returns (uint256)',\n  'function owner() view returns (address)',\n  'function pendingPriceRequestsIds(uint256) view returns (bytes32)',\n  'function previousVotingContract() view returns (address)',\n  'function priceRequests(bytes32) view returns (uint32 lastVotingRound, bool isGovernance, uint64 time, uint32 rollCount, bytes32 identifier, bytes ancillaryData)',\n  'function processResolvablePriceRequests()',\n  'function processResolvablePriceRequestsRange(uint64 maxTraversals)',\n  'function renounceOwnership()',\n  'function requestGovernanceAction(bytes32 identifier, uint256 time, bytes ancillaryData)',\n  'function requestPrice(bytes32 identifier, uint256 time, bytes ancillaryData)',\n  'function requestPrice(bytes32 identifier, uint256 time)',\n  'function requestSlashingTrackers(uint256 requestIndex) view returns (tuple(uint256 wrongVoteSlashPerToken, uint256 noVoteSlashPerToken, uint256 totalSlashed, uint256 totalCorrectVotes, uint32 lastVotingRound))',\n  'function requestUnstake(uint128 amount)',\n  'function resolvedPriceRequestIds(uint256) view returns (bytes32)',\n  'function retrieveRewardsOnMigratedVotingContract(address voter, uint256 roundId, tuple(bytes32 identifier, uint256 time, bytes ancillaryData)[] toRetrieve) returns (uint256)',\n  'function revealVote(bytes32 identifier, uint256 time, int256 price, bytes ancillaryData, int256 salt)',\n  'function rewardPerToken() view returns (uint256)',\n  'function rewardPerTokenStored() view returns (uint128)',\n  'function rounds(uint256) view returns (address slashingLibrary, uint128 minParticipationRequirement, uint128 minAgreementRequirement, uint128 cumulativeStakeAtRound, uint32 numberOfRequestsToVote)',\n  'function setDelegate(address delegate)',\n  'function setDelegator(address delegator)',\n  'function setEmissionRate(uint128 newEmissionRate)',\n  'function setGatAndSpat(uint128 newGat, uint64 newSpat)',\n  'function setMaxRequestPerRound(uint32 newMaxRequestsPerRound)',\n  'function setMaxRolls(uint32 newMaxRolls)',\n  'function setMigrated(address newVotingAddress)',\n  'function setSlashingLibrary(address _newSlashingLibrary)',\n  'function setUnstakeCoolDown(uint64 newUnstakeCoolDown)',\n  'function slashingLibrary() view returns (address)',\n  'function spat() view returns (uint64)',\n  'function stake(uint128 amount)',\n  'function stakeTo(address recipient, uint128 amount)',\n  'function transferOwnership(address newOwner)',\n  'function unstakeCoolDown() view returns (uint64)',\n  'function updateTrackers(address voter)',\n  'function updateTrackersRange(address voter, uint64 maxTraversals)',\n  'function voteTiming() view returns (uint256 phaseLength)',\n  'function voterStakes(address) view returns (uint128 stake, uint128 pendingUnstake, uint128 rewardsPaidPerToken, uint128 outstandingRewards, int128 unappliedSlash, uint64 nextIndexToProcess, uint64 unstakeTime, address delegate)',\n  'function votingToken() view returns (address)',\n  'function withdrawAndRestake() returns (uint128)',\n  'function withdrawRewards() returns (uint128)'\n];\n\nexport const UMA_FINDER_ABI = [\n  'event InterfaceImplementationChanged(bytes32 indexed interfaceName, address indexed newImplementationAddress)',\n  'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)',\n  'function changeImplementationAddress(bytes32 interfaceName, address implementationAddress)',\n  'function getImplementationAddress(bytes32 interfaceName) view returns (address)',\n  'function interfacesImplemented(bytes32) view returns (address)',\n  'function owner() view returns (address)',\n  'function renounceOwnership()',\n  'function transferOwnership(address newOwner)'\n];\n\nexport const ERC20_ABI = [\n  //Read functions\n  'function balanceOf(address account) view returns (uint256)',\n  'function decimals() view returns (uint32)',\n  'function symbol() view returns (string)',\n  'function allowance(address owner, address spender) external view returns (uint256)',\n\n  // Write functions\n  'function approve(address spender, uint256 value) external returns (bool)',\n  'function transfer(address recipient, uint256 amount) public virtual override returns (bool)'\n];\n\nexport const ERC721_ABI = [\n  'function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable'\n];\n\nexport const MULTI_SEND_ABI = [\n  'function multiSend(bytes transactions) payable'\n];\n\n// MULTI SEND CONSTANTS\n\nexport const MULTI_SEND_V1_3_0 = {\n  '1': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '3': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '10': '0x998739BFdAAdde7C933B942a68053933098f9EDa',\n  '28': '0x998739BFdAAdde7C933B942a68053933098f9EDa',\n  '42': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '5': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '56': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '69': '0x998739BFdAAdde7C933B942a68053933098f9EDa',\n  '100': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '122': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '123': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '137': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '246': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '288': '0x998739BFdAAdde7C933B942a68053933098f9EDa',\n  '588': '0x998739BFdAAdde7C933B942a68053933098f9EDa',\n  '1088': '0x998739BFdAAdde7C933B942a68053933098f9EDa',\n  '1116': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '1285': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '1287': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '4002': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '8453': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '42161': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '42220': '0x998739BFdAAdde7C933B942a68053933098f9EDa',\n  '43114': '0x998739BFdAAdde7C933B942a68053933098f9EDa',\n  '73799': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '80001': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '333999': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '11155111': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '1313161554': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',\n  '1313161555': '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761'\n};\nexport const MULTI_SEND_V1_2_0 = {\n  '1': '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',\n  '42': '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',\n  '5': '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',\n  '88': '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',\n  '100': '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',\n  '246': '0x6851D6fDFAfD08c0295C392436245E5bc78B0185',\n  '73799': '0x6851D6fDFAfD08c0295C392436245E5bc78B0185'\n};\nexport const MULTI_SEND_V1_1_1 = {\n  '1': '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',\n  '5': '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',\n  '42': '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',\n  '88': '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',\n  '100': '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',\n  '246': '0x8D29bE29923b68abfDD21e541b9374737B49cdAD',\n  '73799': '0x8D29bE29923b68abfDD21e541b9374737B49cdAD'\n};\nexport const MULTI_SEND_VERSIONS: Record<\n  MULTI_SEND_VERSION,\n  Record<string, string>\n> = {\n  [MULTI_SEND_VERSION.V1_1_1]: MULTI_SEND_V1_1_1,\n  [MULTI_SEND_VERSION.V1_2_0]: MULTI_SEND_V1_2_0,\n  [MULTI_SEND_VERSION.V1_3_0]: MULTI_SEND_V1_3_0\n};\n\n// to potentially cut down on event ranges we query, hard code some deploy blocks for contracts\nexport type ContractData = {\n  network: string;\n  name: string;\n  address?: string;\n  deployBlock?: number;\n  subgraph?: string;\n};\n// contract addresses pulled from https://github.com/UMAprotocol/protocol/tree/master/packages/core/networks\nexport const contractData: ContractData[] = [\n  {\n    // mainnet\n    network: '1',\n    name: 'OptimisticOracleV3',\n    address: '0xfb55F43fB9F48F63f9269DB7Dde3BbBe1ebDC0dE',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/Bm3ytsa1YvcyFJahdfQQgscFQVCcMvoXujzkd3Cz6aof',\n    deployBlock: 16636058\n  },\n  {\n    // goerli\n    network: '5',\n    name: 'OptimisticOracleV3',\n    address: '0x9923D42eF695B5dd9911D05Ac944d4cAca3c4EAB',\n    subgraph:\n      'https://api.thegraph.com/subgraphs/name/md0x/goerli-optimistic-oracle-v3',\n    deployBlock: 8497481\n  },\n  {\n    // optimism\n    network: '10',\n    name: 'OptimisticOracleV3',\n    address: '0x072819Bb43B50E7A251c64411e7aA362ce82803B',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/FyJQyV5TLNeowZrL6kLUTB9JNPyWQNCNXJoxJWGEtBcn',\n    deployBlock: 74537234\n  },\n  {\n    // gnosis\n    network: '100',\n    name: 'OptimisticOracleV3',\n    address: '0x22A9AaAC9c3184f68C7B7C95b1300C4B1D2fB95C',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/9K2nctaB2rAh7Cgzx3wKtdHwWoEeEQ9AThGATak6Ngm9',\n    deployBlock: 27087150\n  },\n  {\n    // polygon\n    network: '137',\n    name: 'OptimisticOracleV3',\n    address: '0x5953f2538F613E05bAED8A5AeFa8e6622467AD3D',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/7KWbDhUE5Eqcfn3LXQtLbCfJLkNucnhzJLpi2jKhqNuf',\n    deployBlock: 39331673\n  },\n  {\n    //arbitrum\n    network: '42161',\n    name: 'OptimisticOracleV3',\n    address: '0xa6147867264374F324524E30C02C331cF28aa879',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/9wpkM5tHgJBHYTzKEKk4tK8a7q6MimfS9QnW7Japa8hW',\n    deployBlock: 61236565\n  },\n  {\n    // avalanche\n    network: '43114',\n    name: 'OptimisticOracleV3',\n    address: '0xa4199d73ae206d49c966cF16c58436851f87d47F',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/3k8gzGzTMV2vDZAGBFM2q642SUyVbE31bAUL8SjFQkre',\n    deployBlock: 27816737\n  },\n  {\n    // core\n    network: '43114',\n    name: 'OptimisticOracleV3',\n    address: '0xD84ACa67d683aF7702705141b3C7E57e4e5e7726',\n    subgraph:\n      'https://thegraph.coredao.org/subgraphs/name/umaprotocol/core-optimistic-oracle-v3',\n    deployBlock: 11341063\n  },\n  {\n    // base\n    network: '8453',\n    name: 'OptimisticOracleV3',\n    address: '0x2aBf1Bd76655de80eDB3086114315Eec75AF500c',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/2Q4i8YgVZd6bAmLyDxXgrKPL2B6QwySiEUqbTyQ4vm4C',\n    deployBlock: 12066343\n  },\n  {\n    // sepolia\n    network: '11155111',\n    name: 'OptimisticOracleV3',\n    address: '0xFd9e2642a170aDD10F53Ee14a93FcF2F31924944',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/78JbrMhcC9CVDZHDADvNcyhRrrccTJG4vCVBztyer1Xa',\n    deployBlock: 5421195\n  },\n  {\n    // mainnet\n    network: '1',\n    name: 'OptimisticGovernor',\n    address: '0x28CeBFE94a03DbCA9d17143e9d2Bd1155DC26D5d',\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/DQpwhiRSPQJEuc8y6ZBGsFfNpfwFQ8NjmjLmfv8kBkLu',\n    deployBlock: 16890621\n  },\n  // Keep in mind, OG addresses are not the module addresses for each individual space, these addresses typically\n  // are not used, but are here for reference.\n  {\n    //goerli\n    network: '5',\n    name: 'OptimisticGovernor',\n    address: '0x07a7Be7AA4AaD42696A17e974486cb64A4daC47b',\n    deployBlock: 8700589,\n    subgraph:\n      'https://api.thegraph.com/subgraphs/name/md0x/goerli-optimistic-governor'\n  },\n  {\n    // optimism\n    network: '10',\n    name: 'OptimisticGovernor',\n    address: '0x357fe84E438B3150d2F68AB9167bdb8f881f3b9A',\n    deployBlock: 83168480,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/Fd5RvSfkajAJ8Mi9sPxFSMVPFf56SDivDQW3ocqTAW5'\n  },\n  {\n    // gnosis\n    network: '100',\n    name: 'OptimisticGovernor',\n    deployBlock: 27102135,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/RrkjZ6wTgLJkcjX68auzrEZHMRYwDx8kR5sFQQy4Phz'\n  },\n  {\n    // polygon\n    network: '137',\n    name: 'OptimisticGovernor',\n    address: '0x3Cc4b597E9c3f51288c6Cd0c087DC14c3FfdD966',\n    deployBlock: 40677035,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/7L2JM14PnZgxGnRX7xaz54zWS6KVK6ZqVRCxEKJrJTDG'\n  },\n  {\n    // arbitrum\n    network: '42161',\n    name: 'OptimisticGovernor',\n    address: '0x30679ca4ea452d3df8a6c255a806e08810321763',\n    deployBlock: 72850751,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/BfK867bnkQhnx1LspA99ypqiqxbAReQ92aZz66Ubv4tz'\n  },\n  {\n    // avalanche\n    network: '43114',\n    name: 'OptimisticGovernor',\n    address: '0xEF8b46765ae805537053C59f826C3aD61924Db45',\n    deployBlock: 28050250,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/5F8875fmvtnv8Vv4aeedUcwNWjuxUg54aTHdapFuMJi3'\n  },\n  {\n    // core\n    network: '1116',\n    name: 'OptimisticGovernor',\n    address: '0x596Fd6A5A185c67aBD1c845b39f593fBA9C233aa',\n    deployBlock: 11341122,\n    subgraph:\n      'https://thegraph.coredao.org/subgraphs/name/umaprotocol/core-optimistic-governor'\n  },\n  {\n    // base\n    network: '8453',\n    name: 'OptimisticGovernor',\n    address: '0x80bCA2E1c272239AdFDCdc87779BC8Af6E12e633',\n    deployBlock: 13062540,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/H1WyWZqh5pHebWRDCXs7GhvGj7XznSP7arPY6pYcCqLn'\n  },\n  {\n    // sepolia\n    network: '11155111',\n    name: 'OptimisticGovernor',\n    address: '0x40153DdFAd90C49dbE3F5c9F96f2a5B25ec67461',\n    deployBlock: 5421242,\n    subgraph:\n      'https://subgrapher.snapshot.org/subgraph/arbitrum/5pwrjCkpcpCd79k9MBS5yVgnsHQiw6afvXUfzqHjdRFw'\n  }\n];\n"
  },
  {
    "path": "src/plugins/safeSnap/index.ts",
    "content": "import { Result } from '@ethersproject/abi';\nimport { isAddress } from '@ethersproject/address';\nimport { isHexString } from '@ethersproject/bytes';\nimport { toUtf8Bytes } from '@ethersproject/strings';\nimport { Contract } from '@ethersproject/contracts';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { _TypedDataEncoder } from '@ethersproject/hash';\nimport { StaticJsonRpcProvider } from '@ethersproject/providers';\nimport { keccak256 as solidityKeccak256 } from '@ethersproject/solidity';\nimport { isBigNumberish } from '@ethersproject/bignumber/lib/bignumber';\n\nimport snapshot from '@snapshot-labs/snapshot.js';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\nimport {\n  SafeTransaction,\n  RealityOracleProposal,\n  UmaOracleProposal\n} from '@/helpers/interfaces';\nimport {\n  EIP712_TYPES,\n  REALITY_MODULE_ABI,\n  UMA_MODULE_ABI,\n  ORACLE_ABI,\n  ERC20_ABI\n} from './constants';\nimport {\n  buildQuestion,\n  checkPossibleExecution,\n  getModuleDetailsReality,\n  getProposalDetails\n} from './utils/realityModule';\nimport { getModuleDetailsUma, getModuleDetailsUmaGql } from './utils/umaModule';\nimport { retrieveInfoFromOracle } from './utils/realityETH';\nimport { getNativeAsset } from '@/plugins/safeSnap/utils/coins';\nimport { Network } from './types';\n\nexport * from './constants';\n\nexport * from './utils/abi';\nexport * from './utils/safe';\nexport * from './utils/coins';\nexport * from './utils/index';\nexport * from './utils/decoder';\nexport * from './utils/multiSend';\nexport * from './utils/realityETH';\nexport * from './utils/transactions';\nexport * from './utils/realityModule';\n\nconst broviderUrl = import.meta.env.VITE_BROVIDER_URL;\nexport default class Plugin {\n  validateTransaction(transaction: SafeTransaction) {\n    const addressEmptyOrValidate =\n      transaction.to === '' || isAddress(transaction.to);\n    return (\n      isBigNumberish(transaction.value) &&\n      addressEmptyOrValidate &&\n      (!transaction.data || isHexString(transaction.data)) &&\n      ['0', '1'].includes(transaction.operation) &&\n      isBigNumberish(transaction.nonce)\n    );\n  }\n\n  calcTransactionHash(\n    network: string,\n    moduleAddress: string,\n    transaction: SafeTransaction\n  ) {\n    const chainId = parseInt(network);\n    const domain = {\n      chainId,\n      verifyingContract: moduleAddress\n    };\n    return _TypedDataEncoder.hash(domain, EIP712_TYPES, transaction);\n  }\n\n  calcTransactionHashes(\n    chainId: number,\n    moduleAddress: string,\n    transactions: SafeTransaction[]\n  ) {\n    const domain = {\n      chainId: chainId,\n      verifyingContract: moduleAddress\n    };\n    return transactions.map(tx => {\n      return _TypedDataEncoder.hash(domain, EIP712_TYPES, {\n        ...tx,\n        nonce: tx.nonce || '0',\n        data: tx.data || '0x'\n      });\n    });\n  }\n\n  async getExecutionDetailsWithHashes(\n    network: string,\n    moduleAddress: string,\n    proposalId: string,\n    txHashes: string[]\n  ): Promise<Omit<RealityOracleProposal, 'transactions'>> {\n    const provider: StaticJsonRpcProvider = getProvider(network, {\n      broviderUrl\n    });\n    const question = await buildQuestion(proposalId, txHashes);\n    const questionHash = solidityKeccak256(['string'], [question]);\n\n    const proposalDetails = await getProposalDetails(\n      provider,\n      network,\n      moduleAddress,\n      questionHash,\n      txHashes\n    );\n    const moduleDetails = await getModuleDetailsReality(\n      provider,\n      network,\n      moduleAddress\n    );\n    const questionState = await checkPossibleExecution(\n      provider,\n      network,\n      moduleDetails.oracle,\n      proposalDetails.questionId\n    );\n    const infoFromOracle = await retrieveInfoFromOracle(\n      provider,\n      network,\n      moduleDetails.oracle,\n      proposalDetails.questionId\n    );\n    return {\n      ...moduleDetails,\n      proposalId,\n      ...questionState,\n      ...proposalDetails,\n      txHashes,\n      ...infoFromOracle\n    };\n  }\n\n  async getModuleDetailsReality(network: string, moduleAddress: string) {\n    const provider: StaticJsonRpcProvider = getProvider(network, {\n      broviderUrl\n    });\n    return getModuleDetailsReality(provider, network, moduleAddress);\n  }\n\n  async validateUmaModule(network: string, umaAddress: string) {\n    if (!isAddress(umaAddress)) return 'reality';\n\n    const provider: StaticJsonRpcProvider = getProvider(network, {\n      broviderUrl\n    });\n    const moduleContract = new Contract(umaAddress, UMA_MODULE_ABI, provider);\n\n    return moduleContract\n      .rules()\n      .then(() => 'uma')\n      .catch(() => 'reality');\n  }\n\n  async getExecutionDetailsUma(\n    network: Network,\n    moduleAddress: string,\n    proposalId: string,\n    explanation: string,\n    transactions: any\n  ) {\n    const moduleDetails = await this.getModuleDetailsUma(\n      network,\n      moduleAddress,\n      explanation,\n      transactions\n    );\n\n    return {\n      ...moduleDetails,\n      proposalId,\n      explanation\n    };\n  }\n\n  async *approveBondUma(\n    network: Network,\n    web3: any,\n    moduleAddress: string,\n    transactions?: any\n  ) {\n    const moduleDetails = await this.getModuleDetailsUma(\n      network,\n      moduleAddress,\n      '',\n      transactions\n    );\n\n    const approveTx = await snapshot.utils.sendTransaction(\n      web3,\n      moduleDetails.collateral,\n      ERC20_ABI,\n      'approve',\n      [moduleAddress, moduleDetails.minimumBond],\n      {}\n    );\n    yield approveTx;\n    const approvalReceipt = await approveTx.wait();\n    console.log('[DAO module] token transfer approved:', approvalReceipt);\n    yield;\n  }\n\n  async getModuleDetailsUma(\n    network: Network,\n    moduleAddress: string,\n    explanation: string,\n    transactions: any\n  ) {\n    const provider: StaticJsonRpcProvider = getProvider(network, {\n      broviderUrl\n    });\n    try {\n      // try optimized calls, which use the graph over web3 event queries\n      return await getModuleDetailsUmaGql(\n        provider,\n        network,\n        moduleAddress,\n        explanation,\n        transactions\n      );\n    } catch (err) {\n      console.warn('Error querying module details from the graph:', err);\n      // fall back to web3 event queries.\n      return getModuleDetailsUma(\n        provider,\n        network,\n        moduleAddress,\n        explanation,\n        transactions\n      );\n    }\n  }\n\n  async *submitProposalWithHashes(\n    web3: any,\n    moduleAddress: string,\n    proposalId: string,\n    txHashes: string[]\n  ) {\n    const tx = await snapshot.utils.sendTransaction(\n      web3,\n      moduleAddress,\n      REALITY_MODULE_ABI,\n      'addProposal',\n      [proposalId, txHashes]\n    );\n    yield tx;\n    const receipt = await tx.wait();\n    console.log('[DAO module] submitted proposal:', receipt);\n  }\n\n  async *submitProposalUma(\n    web3: any,\n    moduleAddress: string,\n    explanation: string,\n    transactions: any\n  ) {\n    const explanationBytes = toUtf8Bytes(explanation);\n    const tx = await snapshot.utils.sendTransaction(\n      web3,\n      moduleAddress,\n      UMA_MODULE_ABI,\n      'proposeTransactions',\n      [transactions, explanationBytes]\n      // [[[\"0xB8034521BB1a343D556e5005680B3F17FFc74BeD\", 0, \"0\", \"0x\"]], '0x']\n    );\n    yield tx;\n    const receipt = await tx.wait();\n    console.log('[DAO module] submitted proposal:', receipt);\n  }\n\n  async loadClaimBondData(\n    web3: any,\n    network: Network,\n    questionId: string,\n    oracleAddress: string,\n    block: string\n  ) {\n    const contract = new Contract(oracleAddress, ORACLE_ABI, web3);\n    const provider: StaticJsonRpcProvider = getProvider(network, {\n      broviderUrl\n    });\n    const account = (await web3.listAccounts())[0];\n\n    const [[userBalance], [bestAnswer], [historyHash], [isFinalized]] =\n      await snapshot.utils.multicall(network, provider, ORACLE_ABI, [\n        [oracleAddress, 'balanceOf', [account]],\n        [oracleAddress, 'getBestAnswer', [questionId]],\n        [oracleAddress, 'getHistoryHash', [questionId]],\n        [oracleAddress, 'isFinalized', [questionId]]\n      ]);\n\n    const nativeToken = getNativeAsset(network);\n    let token = {\n      symbol: nativeToken.symbol,\n      decimals: nativeToken.decimals\n    };\n\n    try {\n      const tokenCall = await snapshot.utils.call(provider, ORACLE_ABI, [\n        oracleAddress,\n        'token',\n        []\n      ]);\n      const [[symbol], [decimals]] = await snapshot.utils.multicall(\n        network,\n        provider,\n        ERC20_ABI,\n        [\n          [tokenCall, 'symbol', []],\n          [tokenCall, 'decimals', []]\n        ]\n      );\n\n      token = {\n        symbol,\n        decimals\n      };\n    } catch (e) {\n      console.log('[Realitio] Info: Oracle is not ERC20 based.');\n    }\n\n    const answersFilter = contract.filters.LogNewAnswer(null, questionId);\n    const events = await contract.queryFilter(answersFilter, parseInt(block));\n\n    const users: Result[] = [];\n    const historyHashes: Result[] = [];\n    const bonds: Result[] = [];\n    const answers: Result[] = [];\n\n    // We need to send the information from last to first\n    events.reverse();\n    events.forEach(({ args }) => {\n      users.push(args?.user.toLowerCase());\n      historyHashes.push(args?.history_hash);\n      bonds.push(args?.bond);\n      answers.push(args?.answer);\n    });\n\n    const alreadyClaimed = BigNumber.from(historyHash).eq(0);\n    const address = account.toLowerCase();\n\n    // Check if current user has submitted an answer\n    const currentUserAnswers = users.map((user, i) => {\n      if (user === address) return answers[i];\n    });\n\n    // If the user has answers, check if one of them is the winner\n    const votedForCorrectQuestion =\n      currentUserAnswers.some(answer => {\n        if (answer) {\n          return BigNumber.from(answer).eq(bestAnswer);\n        }\n      }) && isFinalized;\n\n    // If user has balance in the contract, he should be able to withdraw\n    const hasBalance = !userBalance.eq(0) && isFinalized;\n\n    // Remove the first history and add an empty one\n    // More info: https://github.com/realitio/realitio-contracts/blob/master/truffle/contracts/Realitio.sol#L502\n    historyHashes.shift();\n    const firstHash =\n      '0x0000000000000000000000000000000000000000000000000000000000000000' as unknown;\n    historyHashes.push(firstHash as Result);\n\n    return {\n      tokenSymbol: token.symbol,\n      tokenDecimals: token.decimals,\n      canClaim: (!alreadyClaimed && votedForCorrectQuestion) || hasBalance,\n      data: {\n        length: [bonds.length.toString()],\n        historyHashes,\n        users,\n        bonds,\n        answers\n      }\n    };\n  }\n\n  async *claimBond(\n    web3: any,\n    oracleAddress: string,\n    questionId: string,\n    claimParams: [string[], string[], number[], string[]]\n  ) {\n    const currentHistoryHash = await snapshot.utils.call(web3, ORACLE_ABI, [\n      oracleAddress,\n      'getHistoryHash',\n      [questionId]\n    ]);\n\n    if (BigNumber.from(currentHistoryHash).eq(0)) {\n      const withdrawTx = await snapshot.utils.sendTransaction(\n        web3,\n        oracleAddress,\n        ORACLE_ABI,\n        'withdraw',\n        []\n      );\n      yield withdrawTx;\n      const withdrawReceipt = await withdrawTx.wait();\n      console.log('[Realitio] executed withdraw:', withdrawReceipt);\n      return;\n    }\n\n    const tx = await snapshot.utils.sendTransaction(\n      web3,\n      oracleAddress,\n      ORACLE_ABI,\n      'claimMultipleAndWithdrawBalance',\n      [[questionId], ...claimParams]\n    );\n    yield tx;\n    const receipt = await tx.wait();\n    console.log(\n      '[Realitio] executed claimMultipleAndWithdrawBalance:',\n      receipt\n    );\n  }\n\n  async *executeProposalWithHashes(\n    web3: any,\n    moduleAddress: string,\n    proposalId: string,\n    txHashes: string[],\n    moduleTx: SafeTransaction,\n    transactionIndex: number\n  ) {\n    const tx = await snapshot.utils.sendTransaction(\n      web3,\n      moduleAddress,\n      REALITY_MODULE_ABI,\n      'executeProposalWithIndex',\n      [\n        proposalId,\n        txHashes,\n        moduleTx.to,\n        moduleTx.value,\n        moduleTx.data || '0x',\n        moduleTx.operation,\n        transactionIndex\n      ]\n    );\n    yield tx;\n    const receipt = await tx.wait();\n    console.log('[DAO module] executed proposal:', receipt);\n  }\n\n  async *executeProposalUma(\n    web3: any,\n    moduleAddress: string,\n    transactions: any\n  ) {\n    const tx = await snapshot.utils.sendTransaction(\n      web3,\n      moduleAddress,\n      UMA_MODULE_ABI,\n      'executeProposal',\n      [transactions]\n    );\n    yield tx;\n    const receipt = await tx.wait();\n    console.log('[DAO module] executed proposal:', receipt);\n  }\n\n  async *voteForQuestion(\n    network: string,\n    web3: any,\n    oracleAddress: string,\n    questionId: string,\n    minimumBondInDaoModule: string,\n    answer: '1' | '0'\n  ) {\n    const currentBond = await snapshot.utils.call(web3, ORACLE_ABI, [\n      oracleAddress,\n      'getBond',\n      [questionId]\n    ]);\n\n    let bond;\n    let methodName;\n    const txOverrides = {};\n    let parameters = [\n      questionId,\n      `0x000000000000000000000000000000000000000000000000000000000000000${answer}`\n    ];\n\n    const currentBondIsZero = currentBond.eq(BigNumber.from(0));\n    if (currentBondIsZero) {\n      // DaoModules can have 0 minimumBond, if it happens, the initial bond will be 1 token\n      const daoBondIsZero = BigNumber.from(minimumBondInDaoModule).eq(0);\n      bond = daoBondIsZero ? BigNumber.from(10) : minimumBondInDaoModule;\n    } else {\n      bond = currentBond.mul(2);\n    }\n\n    // fetch token attribute from Realitio contract, if it works, it means it is\n    // a RealitioERC20, otherwise the catch will handle the currency as ETH\n    try {\n      const account = (await web3.listAccounts())[0];\n      const token = await snapshot.utils.call(web3, ORACLE_ABI, [\n        oracleAddress,\n        'token',\n        []\n      ]);\n      const [[tokenDecimals], [allowance]] = await snapshot.utils.multicall(\n        network,\n        web3,\n        ERC20_ABI,\n        [\n          [token, 'decimals', []],\n          [token, 'allowance', [account, oracleAddress]]\n        ]\n      );\n\n      if (bond.eq(10)) {\n        bond = bond.pow(tokenDecimals);\n      }\n\n      // Check if contract has allowance on user tokens,\n      // if not, trigger approve method\n      if (allowance.lt(bond)) {\n        const approveTx = await snapshot.utils.sendTransaction(\n          web3,\n          token,\n          ERC20_ABI,\n          'approve',\n          [oracleAddress, bond],\n          {}\n        );\n        yield 'erc20-approval';\n        const approvalReceipt = await approveTx.wait();\n        console.log('[DAO module] token transfer approved:', approvalReceipt);\n        yield;\n      }\n      parameters = [...parameters, bond, bond];\n      methodName = 'submitAnswerERC20';\n    } catch (e) {\n      if (bond.eq(10)) {\n        bond = bond.pow(18);\n      }\n      parameters = [...parameters, bond];\n      txOverrides['value'] = bond.toString();\n      methodName = 'submitAnswer';\n    }\n\n    const tx = await snapshot.utils.sendTransaction(\n      web3,\n      oracleAddress,\n      ORACLE_ABI,\n      methodName,\n      parameters,\n      txOverrides\n    );\n    yield tx;\n    const receipt = await tx.wait();\n    console.log('[DAO module] executed vote on oracle:', receipt);\n  }\n}\n"
  },
  {
    "path": "src/plugins/safeSnap/plugin.json",
    "content": "{\n  \"name\": \"Gnosis SafeSnap\",\n  \"version\": \"1.0.0\",\n  \"author\": \"Gnosis\",\n  \"website\": \"https://safe.global\",\n  \"icon\": \"ipfs://QmQjZaheMTLRrk22mrGCkGk2LNoNqqYMR8Bxtwa8mBHyc9\",\n  \"defaults\": {\n    \"space\": {\n      \"safes\": [\n        {\n          \"network\": \"CHAIN_ID\",\n          \"realityAddress\": \"0xSWITCH_WITH_REALITY_MODULE_ADDRESS\",\n          \"umaAddress\": \"0xSWITCH_WITH_UMA_MODULE_ADDRESS\"\n        }\n      ]\n    },\n    \"proposal\": {\n      \"safes\": [\n        {\n          \"network\": \"CHAIN_ID\",\n          \"realityAddress\": \"0xSWITCH_WITH_REALITY_MODULE_ADDRESS\",\n          \"umaAddress\": \"0xSWITCH_WITH_UMA_MODULE_ADDRESS\"\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "src/plugins/safeSnap/types/index.ts",
    "content": "import networks from '@snapshot-labs/snapshot.js/src/networks.json';\n\ntype Networks = typeof networks;\n\nexport type Network = keyof Networks;\n"
  },
  {
    "path": "src/plugins/safeSnap/utils/abi.ts",
    "content": "import {\n  FormatTypes,\n  Fragment,\n  FunctionFragment,\n  Interface,\n  ParamType\n} from '@ethersproject/abi';\nimport { BigNumberish } from '@ethersproject/bignumber';\nimport memoize from 'lodash/memoize';\nimport { ERC20_ABI, ERC721_ABI, EXPLORER_API_URLS } from '../constants';\nimport { ABI } from '@/helpers/interfaces';\nimport { mustBeEthereumAddress, mustBeEthereumContractAddress } from './index';\n\nexport function isArrayParameter(parameter: string): boolean {\n  return ['tuple', 'array'].includes(parameter);\n}\n\nconst fetchContractABI = memoize(\n  async (url: string, contractAddress: string) => {\n    const params = new URLSearchParams({\n      module: 'contract',\n      action: 'getAbi',\n      address: contractAddress\n    });\n\n    const response = await fetch(`${url}?${params}`);\n\n    if (!response.ok) {\n      return { status: 0, result: '' };\n    }\n\n    return response.json();\n  },\n  (url, contractAddress) => `${url}_${contractAddress}`\n);\n\nexport function parseMethodToABI(method: FunctionFragment) {\n  return [method.format(FormatTypes.full)];\n}\n\nexport async function getContractABI(\n  network: string,\n  contractAddress: string\n): Promise<string> {\n  const apiUrl = EXPLORER_API_URLS[network as keyof typeof EXPLORER_API_URLS];\n\n  if (!apiUrl) {\n    return '';\n  }\n\n  const isEthereumAddress = mustBeEthereumAddress(contractAddress);\n  const isEthereumContractAddress = await mustBeEthereumContractAddress(\n    network,\n    contractAddress\n  );\n\n  if (!isEthereumAddress || !isEthereumContractAddress) {\n    return '';\n  }\n\n  try {\n    const { result, status } = await fetchContractABI(apiUrl, contractAddress);\n\n    if (status === '0') {\n      return '';\n    }\n\n    return result;\n  } catch (e) {\n    console.error('Failed to retrieve ABI', e);\n    return '';\n  }\n}\n\nexport function isWriteFunction(method: FunctionFragment) {\n  if (!method.stateMutability) return true;\n  return !['view', 'pure'].includes(method.stateMutability);\n}\n\nexport function getABIWriteFunctions(abi: Fragment[]) {\n  const abiInterface = new Interface(abi);\n  return (\n    abiInterface.fragments\n      // Return only contract's functions\n      .filter(FunctionFragment.isFunctionFragment)\n      .map(FunctionFragment.fromObject)\n      // Return only write functions\n      .filter(isWriteFunction)\n      // Sort by name\n      .sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1))\n  );\n}\n\nfunction extractMethodArgs(values: string[]) {\n  return (param: ParamType, index: number) => {\n    const value = values[index];\n    if (isArrayParameter(param.baseType)) {\n      return JSON.parse(value);\n    }\n    return value;\n  };\n}\n\nexport function getContractTransactionData(\n  abi: string,\n  method: FunctionFragment,\n  values: string[]\n) {\n  const contractInterface = new Interface(abi);\n  const parameterValues = method.inputs.map(extractMethodArgs(values));\n  return contractInterface.encodeFunctionData(method, parameterValues);\n}\n\nexport function getAbiFirstFunctionName(abi: ABI): string {\n  const abiInterface = new Interface(abi);\n  return abiInterface.fragments[0].name;\n}\n\nexport function getERC20TokenTransferTransactionData(\n  recipientAddress: string,\n  amount: BigNumberish\n): string {\n  const contractInterface = new Interface(ERC20_ABI);\n  return contractInterface.encodeFunctionData('transfer', [\n    recipientAddress,\n    amount\n  ]);\n}\n\nexport function getERC721TokenTransferTransactionData(\n  fromAddress: string,\n  recipientAddress: string,\n  id: BigNumberish\n): string {\n  const contractInterface = new Interface(ERC721_ABI);\n  return contractInterface.encodeFunctionData('safeTransferFrom', [\n    fromAddress,\n    recipientAddress,\n    id\n  ]);\n}\n"
  },
  {
    "path": "src/plugins/safeSnap/utils/coins.ts",
    "content": "import { TokenAsset } from '@/helpers/interfaces';\nimport { Network } from '../types';\n\nexport const ETHEREUM_COIN: TokenAsset = {\n  name: 'Ether',\n  decimals: 18,\n  symbol: 'ETH',\n  logoUri:\n    'https://safe-transaction-assets.safe.global/chains/1/currency_logo.png',\n  address: 'main'\n};\nexport const MATIC_COIN: TokenAsset = {\n  name: 'MATIC',\n  decimals: 18,\n  symbol: 'MATIC',\n  address: 'main',\n  logoUri:\n    'https://safe-transaction-assets.safe.global/chains/137/currency_logo.png'\n};\nconst EWC_COIN: TokenAsset = {\n  name: 'Energy Web Token',\n  symbol: 'EWT',\n  address: 'main',\n  decimals: 18,\n  logoUri:\n    'https://safe-transaction-assets.safe.global/chains/246/currency_logo.png'\n};\nconst XDAI_COIN: TokenAsset = {\n  name: 'XDAI',\n  symbol: 'XDAI',\n  address: 'main',\n  decimals: 18,\n  logoUri:\n    'https://safe-transaction-assets.safe.global/chains/100/currency_logo.png'\n};\nconst BNB_COIN: TokenAsset = {\n  name: 'BNB',\n  symbol: 'BNB',\n  address: 'main',\n  decimals: 18,\n  logoUri:\n    'https://safe-transaction-assets.safe.global/chains/56/currency_logo.png'\n};\nconst CORE_COIN: TokenAsset = {\n  name: 'Core',\n  symbol: 'CORE',\n  address: 'main',\n  decimals: 18,\n  logoUri:\n    'https://cloudflare-ipfs.com/ipfs/bafkreigjv5yb7uhlrryzib7j2f73nnwqan2tmfnwjdu26vkk365fyesoiu'\n};\n\nexport function getNativeAsset(network: Network) {\n  switch (parseInt(network)) {\n    case 137:\n    case 80001:\n      return MATIC_COIN;\n    case 100:\n      return XDAI_COIN;\n    case 246:\n      return EWC_COIN;\n    case 56:\n      return BNB_COIN;\n    case 1116:\n      return CORE_COIN;\n    }\n\n  return ETHEREUM_COIN;\n}\n"
  },
  {
    "path": "src/plugins/safeSnap/utils/decoder.ts",
    "content": "import {\n  FunctionFragment,\n  Interface,\n  Fragment,\n  JsonFragment\n} from '@ethersproject/abi';\nimport { isArrayParameter } from './abi';\n\nexport class InterfaceDecoder extends Interface {\n  public decodeFunction(\n    data: string,\n    fragmentOrName?: string | Fragment | JsonFragment\n  ) {\n    const fragment = this.getMethodFragment(data, fragmentOrName);\n    if (!FunctionFragment.isFunctionFragment(fragment)) {\n      throw new Error(\n        `could not resolved to a function fragment fragmentOrName: ${fragmentOrName}`\n      );\n    }\n    const functionFragment = FunctionFragment.fromObject(fragment);\n    const decodedValues = this.decodeFunctionData(functionFragment.name, data);\n\n    return functionFragment.inputs.reduce((acc, parameter, index) => {\n      const value = decodedValues[index];\n      const formattedValue = this.formatParameter(parameter, value);\n      acc.push(formattedValue);\n      if (parameter.name) {\n        acc[parameter.name] = formattedValue;\n      }\n      return acc;\n    }, [] as string[]);\n  }\n\n  public getMethodFragment(\n    data: string,\n    fragmentOrName?: string | Fragment | JsonFragment\n  ): Fragment | JsonFragment {\n    if (typeof fragmentOrName === 'string') {\n      return this.getFunction(fragmentOrName);\n    } else if (!fragmentOrName) {\n      const signature = data.slice(0, 10);\n      return this.getFunction(signature);\n    }\n    return fragmentOrName;\n  }\n\n  private formatParameter(parameter, value, deep = 0): string {\n    if (isArrayParameter(parameter.baseType)) {\n      return this.formatArrayValue(parameter.arrayChildren, value, deep);\n    }\n    return value.toString();\n  }\n\n  private formatArrayValue(paramType, value, deep = 0) {\n    const formattedValues = value.map(paramValue =>\n      this.formatParameter(paramType, paramValue, deep + 1)\n    );\n    if (deep) return formattedValues;\n    return JSON.stringify(formattedValues);\n  }\n}\n"
  },
  {
    "path": "src/plugins/safeSnap/utils/events.ts",
    "content": "import bb from 'bluebird';\n// This state is meant for adjusting a start/end block when querying events. Some apis will fail if the range\n// is too big, so the following functions will adjust range dynamically.\nexport type RangeState = {\n  startBlock: number;\n  endBlock: number;\n  maxRange: number;\n  currentRange: number;\n  currentStart: number; // This is the start value you want for your query.\n  currentEnd: number; // this is the end value you want for your query.\n  done: boolean; // Signals we successfully queried the entire range.\n  multiplier?: number; // Multiplier increases or decreases range by this value, depending on success or failure\n};\n\n/**\n * rangeStart. This starts a new range query and sets defaults for state.  Use this as the first call before starting your queries\n *\n * @param {Pick} state\n * @returns {RangeState}\n */\nexport function rangeStart(\n  state: Pick<RangeState, 'startBlock' | 'endBlock' | 'multiplier'> & {\n    maxRange?: number;\n  }\n): RangeState {\n  const { startBlock, endBlock, multiplier = 2 } = state;\n  if (state.maxRange && state.maxRange > 0) {\n    const range = endBlock - startBlock;\n    if (!(range >= 0)) {\n      throw new Error('End block must be higher than start block');\n    }\n    const currentRange = Math.min(state.maxRange, range);\n    const currentStart = endBlock - currentRange;\n    const currentEnd = endBlock;\n    return {\n      done: false,\n      startBlock,\n      endBlock,\n      maxRange: state.maxRange,\n      currentRange,\n      currentStart,\n      currentEnd,\n      multiplier\n    };\n  } else {\n    // the largest range we can have, since this is the users query for start and end\n    const maxRange = endBlock - startBlock;\n    if (!(maxRange > 0)) {\n      throw new Error('End block must be higher than start block');\n    }\n    const currentStart = startBlock;\n    const currentEnd = endBlock;\n    const currentRange = maxRange;\n\n    return {\n      done: false,\n      startBlock,\n      endBlock,\n      maxRange,\n      currentRange,\n      currentStart,\n      currentEnd,\n      multiplier\n    };\n  }\n}\n/**\n * rangeSuccessDescending. We have 2 ways of querying events, from oldest to newest, or newest to oldest. Typically we want them in order, from\n * oldest to newest, but for this particular case we want them newest to oldest, ie descending ( larger timestamp to smaller timestamp).\n * This function will increase the range between start/end block and return a new start/end to use since by calling this you are signalling\n * that the last range ended in a successful query.\n *\n * @param {RangeState} state\n * @returns {RangeState}\n */\nexport function rangeSuccessDescending(state: RangeState): RangeState {\n  const {\n    startBlock,\n    currentStart,\n    maxRange,\n    currentRange,\n    multiplier = 2\n  } = state;\n  // we are done if we succeeded querying where the currentStart matches are initial start block\n  const done = currentStart <= startBlock;\n  // increase range up to max range for every successful query\n  const nextRange = Math.min(Math.ceil(currentRange * multiplier), maxRange);\n  // move our end point to the previously successful start, ie moving from newest to oldest\n  const nextEnd = currentStart;\n  // move our start block to the next range down\n  const nextStart = Math.max(nextEnd - nextRange, startBlock);\n  return {\n    ...state,\n    currentStart: nextStart,\n    currentEnd: nextEnd,\n    currentRange: nextRange,\n    done\n  };\n}\n/**\n * rangeFailureDescending. Like the previous function, this will decrease the range between start/end for your query, because you are signalling\n * that the last query failed. It will also keep the end of your range the same, while moving the start range up. This is why\n * its considered descending, it will attempt to move from end to start, rather than start to end.\n *\n * @param {RangeState} state\n * @returns {RangeState}\n */\nexport function rangeFailureDescending(state: RangeState): RangeState {\n  const { startBlock, currentEnd, currentRange, multiplier = 2 } = state;\n  const nextRange = Math.floor(currentRange / multiplier);\n  // this will eventually throw an error if you keep calling this function, which protects us against re-querying a broken api in a loop\n  if (currentRange <= 0) throw new Error('Current range must be above 0');\n  if (!(nextRange > 0)) throw new Error('Range must be above 0');\n  // we stay at the same end block\n  const nextEnd = currentEnd;\n  // move our start block closer to the end block, shrinking the range\n  const nextStart = Math.max(nextEnd - nextRange, startBlock);\n  return {\n    ...state,\n    currentStart: nextStart,\n    currentEnd: nextEnd,\n    currentRange: nextRange\n  };\n}\n\n// The main interface to wrap the above pure functions up. requires you to pass in a generic function\n// which returns the events based on a start/end block query.\nexport async function pageEvents<E>(\n  startBlock: number,\n  endBlock: number,\n  maxRange: number,\n  //start and end block range to query\n  fetchEvents: (query: { start: number; end: number }) => Promise<E[]>,\n  concurrency: number = 5\n): Promise<E[]> {\n  let state = rangeStart({ startBlock, endBlock, maxRange });\n  const ranges: { start: number; end: number }[] = [];\n  let index = 0;\n  do {\n    ranges.push({\n      start: state.currentStart,\n      end: state.currentEnd,\n      index: index++\n    });\n    state = rangeSuccessDescending(state);\n  } while (!state.done);\n\n  return (await bb.map(ranges, fetchEvents, { concurrency })).flat();\n}\n"
  },
  {
    "path": "src/plugins/safeSnap/utils/index.ts",
    "content": "import { isAddress } from '@ethersproject/address';\nimport { JsonRpcProvider } from '@ethersproject/providers';\nimport { keccak256 } from '@ethersproject/solidity';\nimport memoize from 'lodash/memoize';\n\nimport { SafeExecutionData, SafeTransaction } from '@/helpers/interfaces';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\nimport SafeSnapPlugin, { MULTI_SEND_VERSION } from '../index';\nimport { createMultiSendTx, getMultiSend } from './multiSend';\n\nexport const mustBeEthereumAddress = memoize((address: string) => {\n  const startsWith0x = address?.startsWith('0x');\n  const isValidAddress = isAddress(address);\n  return startsWith0x && isValidAddress;\n});\n\nexport const mustBeEthereumContractAddress = memoize(\n  async (network: string, address: string) => {\n    const broviderUrl = import.meta.env.VITE_BROVIDER_URL;\n    const provider = getProvider(network, { broviderUrl }) as JsonRpcProvider;\n    const contractCode = await provider.getCode(address);\n\n    return (\n      contractCode && contractCode.replace(/^0x/, '').replace(/0/g, '') !== ''\n    );\n  },\n  (url, contractAddress) => `${url}_${contractAddress}`\n);\n\nexport function formatBatchTransaction(\n  batch: SafeTransaction[],\n  nonce: number,\n  multiSendAddress: string\n): SafeTransaction | null {\n  if (!batch.every(x => x)) return null;\n  if (batch.length === 1) {\n    return { ...batch[0], nonce: nonce.toString() };\n  }\n  return createMultiSendTx(batch, nonce, multiSendAddress);\n}\n\nexport function createBatch(\n  module: string,\n  chainId: number,\n  nonce: number,\n  txs: SafeTransaction[],\n  multiSendAddress: string\n) {\n  const mainTransaction = formatBatchTransaction(txs, nonce, multiSendAddress);\n  const hash = mainTransaction\n    ? getBatchHash(module, chainId, mainTransaction)\n    : null;\n  return {\n    hash,\n    nonce,\n    mainTransaction,\n    transactions: txs\n  };\n}\n\nexport function getBatchHash(\n  module: string,\n  chainId: number,\n  transaction: SafeTransaction\n) {\n  try {\n    const safeSnap = new SafeSnapPlugin();\n    const hashes = safeSnap.calcTransactionHashes(chainId, module, [\n      transaction\n    ]);\n    return hashes[0];\n  } catch (err) {\n    console.warn('invalid batch hash', err);\n    return null;\n  }\n}\n\nexport function getSafeHash(safe: SafeExecutionData) {\n  const hashes = safe.txs.map(batch => batch.hash);\n  const valid = hashes.every(hash => hash);\n  if (!valid || !hashes.length) return null;\n  return keccak256(['bytes32[]'], [hashes]);\n}\n\nexport function validateSafeData(safe: SafeExecutionData) {\n  return (\n    safe.txs.length === 0 ||\n    safe.txs\n      .map(batch => batch.transactions)\n      .flat()\n      .every(tx => tx)\n  );\n}\n\nexport function isValidInput<Input extends { safes: SafeExecutionData[] }>(\n  input: Input\n) {\n  return input.safes.every(validateSafeData);\n}\n\nexport function coerceConfig(config, network) {\n  if (config.safes) {\n    return {\n      ...config,\n      safes: config.safes.map(safe => {\n        const _network = safe.network || network;\n        const multiSendAddress =\n          safe.multiSendAddress || getMultiSend(_network);\n        const txs = (safe.txs || []).map((batch, nonce) => {\n          const oldMultiSendAddress =\n            safe.multiSendAddress ||\n            getMultiSend(_network, MULTI_SEND_VERSION.V1_1_1) ||\n            getMultiSend(_network, MULTI_SEND_VERSION.V1_3_0);\n          if (Array.isArray(batch)) {\n            // Assume old config\n            return createBatch(\n              safe.realityAddress,\n              _network,\n              nonce,\n              batch,\n              oldMultiSendAddress\n            );\n          }\n\n          if (!batch.mainTransaction) {\n            return {\n              ...batch,\n              mainTransaction: formatBatchTransaction(\n                batch.transactions,\n                batch.nonce,\n                oldMultiSendAddress\n              )\n            };\n          }\n          return batch;\n        });\n        const sanitizedSafe = {\n          ...safe,\n          txs,\n          multiSendAddress\n        };\n        return {\n          ...sanitizedSafe,\n          hash: sanitizedSafe.hash ?? getSafeHash(sanitizedSafe)\n        };\n      })\n    };\n  }\n\n  // map legacy config to new format\n  return {\n    safes: [\n      {\n        network,\n        realityAddress: config.address,\n        multiSendAddress:\n          getMultiSend(network, MULTI_SEND_VERSION.V1_1_1) ||\n          getMultiSend(network, MULTI_SEND_VERSION.V1_3_0)\n      }\n    ]\n  };\n}\n\nexport async function fetchTextSignatures(\n  methodSignature: string\n): Promise<string[]> {\n  const url = new URL('/api/v1/signatures', 'https://www.4byte.directory');\n  url.searchParams.set('hex_signature', methodSignature);\n  url.searchParams.set('ordering', 'created_at');\n  const response = await fetch(url.toString());\n  const { results } = await response.json();\n  return results.map(signature => signature.text_signature);\n}\n"
  },
  {
    "path": "src/plugins/safeSnap/utils/multiSend.ts",
    "content": "import { pack } from '@ethersproject/solidity';\nimport { Interface } from '@ethersproject/abi';\nimport { hexDataLength } from '@ethersproject/bytes';\n\nimport { SafeTransaction } from '@/helpers/interfaces';\nimport { MULTI_SEND_ABI, MULTI_SEND_VERSIONS } from '../constants';\n\nexport enum MULTI_SEND_VERSION {\n  V1_3_0 = '1.3.0',\n  V1_2_0 = '1.2.0',\n  V1_1_1 = '1.1.1'\n}\n\nexport function getMultiSend(\n  network: number | string,\n  version: MULTI_SEND_VERSION = MULTI_SEND_VERSION.V1_3_0\n) {\n  return MULTI_SEND_VERSIONS[version][network.toString()];\n}\n\nexport function encodeTransactions(transactions: SafeTransaction[]) {\n  const values = transactions.map(tx => [\n    tx.operation,\n    tx.to,\n    tx.value,\n    hexDataLength(tx.data || '0x'),\n    tx.data || '0x'\n  ]);\n\n  const types = transactions.map(() => [\n    'uint8',\n    'address',\n    'uint256',\n    'uint256',\n    'bytes'\n  ]);\n\n  return pack(types.flat(1), values.flat(1));\n}\n\nexport function createMultiSendTx(\n  txs: SafeTransaction[],\n  nonce: number,\n  multiSendAddress: string\n) {\n  const multiSendContract = new Interface(MULTI_SEND_ABI);\n  const transactionsEncoded = encodeTransactions(txs);\n  const data = multiSendContract.encodeFunctionData('multiSend', [\n    transactionsEncoded\n  ]);\n  return {\n    to: multiSendAddress,\n    operation: '1',\n    value: '0',\n    nonce: nonce.toString(),\n    data\n  };\n}\n"
  },
  {
    "path": "src/plugins/safeSnap/utils/realityETH.ts",
    "content": "import { StaticJsonRpcProvider } from '@ethersproject/providers';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport snapshot from '@snapshot-labs/snapshot.js';\nimport { ORACLE_ABI } from '../constants';\n\nexport const retrieveInfoFromOracle = async (\n  provider: StaticJsonRpcProvider,\n  network: string,\n  oracleAddress: string,\n  questionId: string | undefined\n): Promise<{\n  currentBond: BigNumber | undefined;\n  isApproved: boolean;\n  endTime: number | undefined;\n}> => {\n  if (questionId) {\n    const result = await snapshot.utils.multicall(network, provider, ORACLE_ABI, [\n      [oracleAddress, 'getFinalizeTS', [questionId]],\n      [oracleAddress, 'getBond', [questionId]],\n      [oracleAddress, 'getBestAnswer', [questionId]]\n    ]);\n\n    const currentBond = BigNumber.from(result[1][0]);\n    const answer = BigNumber.from(result[2][0]);\n\n    return {\n      currentBond,\n      isApproved: answer.eq(BigNumber.from(1)),\n      endTime: BigNumber.from(result[0][0]).toNumber()\n    };\n  }\n  return {\n    currentBond: undefined,\n    isApproved: false,\n    endTime: undefined\n  };\n};\n"
  },
  {
    "path": "src/plugins/safeSnap/utils/realityModule.ts",
    "content": "import { StaticJsonRpcProvider } from '@ethersproject/providers';\nimport snapshot from '@snapshot-labs/snapshot.js';\nimport { REALITY_MODULE_ABI, ORACLE_ABI } from '../constants';\nimport { HashZero } from '@ethersproject/constants';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { keccak256 as solidityKeccak256 } from '@ethersproject/solidity';\n\nexport const buildQuestion = async (proposalId: string, txHashes: string[]) => {\n  const hashesHash = solidityKeccak256(['bytes32[]'], [txHashes]).slice(2);\n  return `${proposalId}␟${hashesHash}`;\n};\n\nexport const getProposalDetails = async (\n  provider: StaticJsonRpcProvider,\n  network: string,\n  moduleAddress: string,\n  questionHash: string,\n  txHashes: string[]\n): Promise<{ questionId: string; nextTxIndex: number | undefined }> => {\n  const proposalInfo = (\n    await snapshot.utils.multicall(\n      network,\n      provider,\n      REALITY_MODULE_ABI,\n      [[moduleAddress, 'questionIds', [questionHash]]].concat(\n        txHashes.map(txHash => [\n          moduleAddress,\n          'executedProposalTransactions',\n          [questionHash, txHash]\n        ])\n      )\n    )\n  ).map(res => res[0]);\n  const questionId = proposalInfo[0];\n  // We need to offset the index by -1 the first element is the questionId\n  const nextIndexToExecute = proposalInfo.indexOf(false, 1) - 1;\n  return {\n    questionId: questionId !== HashZero ? questionId : undefined,\n    nextTxIndex:\n      nextIndexToExecute < 0 || nextIndexToExecute >= txHashes.length\n        ? undefined\n        : nextIndexToExecute\n  };\n};\n\nexport const getModuleDetailsReality = async (\n  provider: StaticJsonRpcProvider,\n  network: string,\n  moduleAddress: string\n): Promise<{\n  dao: string;\n  oracle: string;\n  cooldown: number;\n  minimumBond: number;\n  expiration: number;\n}> => {\n  let moduleDetails;\n  try {\n    // Assume module is Reality Module\n    moduleDetails = await snapshot.utils.multicall(network, provider, REALITY_MODULE_ABI, [\n      [moduleAddress, 'avatar'],\n      [moduleAddress, 'oracle'],\n      [moduleAddress, 'questionCooldown'],\n      [moduleAddress, 'minimumBond'],\n      [moduleAddress, 'answerExpiration']\n    ]);\n  } catch (err) {\n    // The Reality Module doesn't have an avatar field, causing tx to fails.\n    // Assume module is Dao Module (old version)\n    moduleDetails = await snapshot.utils.multicall(network, provider, REALITY_MODULE_ABI, [\n      [moduleAddress, 'executor'],\n      [moduleAddress, 'oracle'],\n      [moduleAddress, 'questionCooldown'],\n      [moduleAddress, 'minimumBond'],\n      [moduleAddress, 'answerExpiration']\n    ]);\n  }\n\n  return {\n    dao: moduleDetails[0][0],\n    oracle: moduleDetails[1][0],\n    cooldown: moduleDetails[2][0],\n    minimumBond: moduleDetails[3][0],\n    expiration: moduleDetails[4][0]\n  };\n};\n\nexport const checkPossibleExecution = async (\n  provider: StaticJsonRpcProvider,\n  network: string,\n  oracleAddress: string,\n  questionId: string | undefined\n): Promise<{\n  executionApproved: boolean;\n  finalizedAt: number | undefined;\n}> => {\n  if (questionId) {\n    try {\n      const result = await snapshot.utils.multicall(network, provider, ORACLE_ABI, [\n        [oracleAddress, 'resultFor', [questionId]],\n        [oracleAddress, 'getFinalizeTS', [questionId]]\n      ]);\n\n      return {\n        executionApproved: BigNumber.from(result[0][0]).eq(BigNumber.from(1)),\n        finalizedAt: BigNumber.from(result[1][0]).toNumber()\n      };\n    } catch (e) {\n      // We expect an error while the question is not answered yet\n    }\n  }\n  return {\n    executionApproved: false,\n    finalizedAt: undefined\n  };\n};\n"
  },
  {
    "path": "src/plugins/safeSnap/utils/safe.ts",
    "content": "import { GNOSIS_SAFE_TRANSACTION_API_URLS } from '../constants';\nimport { TokenAsset } from '@/helpers/interfaces';\nimport memoize from 'lodash/memoize';\n\nasync function callGnosisSafeTransactionApi(\n  network: keyof typeof GNOSIS_SAFE_TRANSACTION_API_URLS,\n  url: string\n) {\n  const apiUrl = GNOSIS_SAFE_TRANSACTION_API_URLS[network];\n  const response = await fetch(apiUrl + url);\n  return response.json();\n}\n\nexport const getGnosisSafeBalances = memoize(\n  (network, safeAddress) => {\n    const endpointPath = `/v1/safes/${safeAddress}/balances/`;\n    return callGnosisSafeTransactionApi(network, endpointPath);\n  },\n  (safeAddress, network) => `${safeAddress}_${network}`\n);\n\nexport const getGnosisSafeCollectibles = memoize(\n  (network, safeAddress) => {\n    const endpointPath = `/v2/safes/${safeAddress}/collectibles/`;\n    return callGnosisSafeTransactionApi(network, endpointPath);\n  },\n  (safeAddress, network) => `${safeAddress}_${network}`\n);\n\nexport const getGnosisSafeToken = memoize(\n  async (network, tokenAddress): Promise<TokenAsset> => {\n    const endpointPath = `/v1/tokens/${tokenAddress}`;\n    return callGnosisSafeTransactionApi(network, endpointPath);\n  },\n  (tokenAddress, network) => `${tokenAddress}_${network}`\n);\n"
  },
  {
    "path": "src/plugins/safeSnap/utils/transactions.ts",
    "content": "import {\n  CollectableAsset,\n  CollectableAssetTransaction,\n  CustomContractTransaction,\n  SafeTransaction,\n  TokenAsset,\n  TokenAssetTransaction\n} from '@/helpers/interfaces';\nimport { Fragment, FunctionFragment, JsonFragment } from '@ethersproject/abi';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { isHexString } from '@ethersproject/bytes';\nimport { ERC20_ABI, ERC721_ABI } from '../constants';\nimport { getContractABI, parseMethodToABI } from './abi';\nimport { getNativeAsset } from './coins';\nimport { InterfaceDecoder } from './decoder';\nimport { fetchTextSignatures } from './index';\nimport { getGnosisSafeToken } from './safe';\nimport { Network } from '../types';\n\nexport function rawToModuleTransaction({\n  to,\n  value,\n  data,\n  nonce\n}: {\n  to: string;\n  value: string;\n  data: string;\n  nonce: string;\n}): SafeTransaction {\n  return {\n    to,\n    value,\n    data,\n    nonce,\n    operation: '0'\n  };\n}\n\nexport function sendAssetToModuleTransaction({\n  recipient,\n  collectable,\n  data,\n  nonce\n}: {\n  recipient: string;\n  collectable: CollectableAsset;\n  data: string;\n  nonce: string;\n}): CollectableAssetTransaction {\n  return {\n    data,\n    nonce,\n    recipient,\n    value: '0',\n    operation: '0',\n    type: 'transferNFT',\n    to: collectable.address,\n    collectable\n  };\n}\n\nexport function transferFundsToModuleTransaction({\n  recipient,\n  amount,\n  token,\n  data,\n  nonce\n}: {\n  recipient: string;\n  amount: string;\n  token: TokenAsset;\n  data: string;\n  nonce: string;\n}): TokenAssetTransaction {\n  const base = {\n    operation: '0',\n    nonce,\n    token,\n    recipient\n  };\n  if (token.address === 'main') {\n    return {\n      ...base,\n      type: 'transferFunds',\n      data: '0x',\n      to: recipient,\n      amount: parseAmount(amount),\n      value: parseAmount(amount)\n    };\n  }\n  return {\n    ...base,\n    data,\n    type: 'transferFunds',\n    to: token.address,\n    amount: parseAmount(amount),\n    value: '0'\n  };\n}\n\nexport function contractInteractionToModuleTransaction(\n  {\n    to,\n    value,\n    data,\n    nonce,\n    method\n  }: {\n    to: string;\n    value: string;\n    data: string;\n    nonce: string;\n    method: FunctionFragment;\n  },\n  multiSendAddress: string\n): CustomContractTransaction {\n  const operation = to === multiSendAddress ? '1' : '0';\n  return {\n    to,\n    data,\n    nonce,\n    operation,\n    type: 'contractInteraction',\n    value: parseValueInput(value),\n    abi: parseMethodToABI(method)\n  };\n}\n\nexport async function decodeContractTransaction(\n  network: string,\n  transaction: SafeTransaction,\n  multiSendAddress: string\n): Promise<CustomContractTransaction> {\n  const decode = (abi: string | FunctionFragment[]) => {\n    const contractInterface = new InterfaceDecoder(abi);\n    const method = contractInterface.getMethodFragment(transaction.data);\n    contractInterface.decodeFunction(transaction.data, method); // Validate data can be decode by method.\n    return contractInteractionToModuleTransaction(\n      {\n        data: transaction.data,\n        nonce: '0',\n        to: transaction.to,\n        value: transaction.value,\n        method: method as FunctionFragment\n      },\n      multiSendAddress\n    );\n  };\n\n  const contractAbi = await getContractABI(network, transaction.to);\n  if (contractAbi) return decode(contractAbi);\n\n  const methodSignature = getMethodSignature(transaction.data);\n  if (methodSignature) {\n    const textSignatures = await fetchTextSignatures(methodSignature);\n    for (const signature of textSignatures) {\n      try {\n        return decode([FunctionFragment.fromString(signature)]);\n      } catch (e) {\n        console.warn('invalid abi for transaction');\n      }\n    }\n  }\n\n  throw new Error(`we were not able to decode this transaction`);\n}\n\nfunction getMethodSignature(data: string) {\n  const methodSignature = data.slice(0, 10);\n  if (isHexString(methodSignature) && methodSignature.length === 10) {\n    return methodSignature;\n  }\n  return null;\n}\n\nexport function isERC20TransferTransaction(transaction: SafeTransaction) {\n  // 0xa9059cbb == transfer(address to, uint256 amount)\n  return getMethodSignature(transaction.data) === '0xa9059cbb';\n}\n\nfunction decodeERC721TransferTransaction(transaction: SafeTransaction) {\n  const erc721ContractInterface = new InterfaceDecoder(ERC721_ABI);\n  try {\n    return erc721ContractInterface.decodeFunction(transaction.data);\n  } catch (e) {\n    return null;\n  }\n}\n\nexport async function decodeTransactionData(\n  network: Network,\n  transaction: SafeTransaction,\n  multiSendAddress: string\n) {\n  if (!transaction.data || transaction.data === '0x') {\n    return transferFundsToModuleTransaction({\n      recipient: transaction.to,\n      amount: transaction.value,\n      data: '0x',\n      token: getNativeAsset(network),\n      nonce: '0'\n    });\n  }\n\n  if (isERC20TransferTransaction(transaction)) {\n    try {\n      const erc20ContractInterface = new InterfaceDecoder(ERC20_ABI);\n      const params = erc20ContractInterface.decodeFunction(transaction.data);\n      const token = await getGnosisSafeToken(network, transaction.to);\n      return transferFundsToModuleTransaction({\n        recipient: params[0],\n        amount: params[1],\n        data: transaction.data,\n        nonce: '0',\n        token\n      });\n    } catch (e) {\n      console.warn('invalid ERC20 transfer transaction');\n    }\n  }\n\n  const erc721DecodedParams = decodeERC721TransferTransaction(transaction);\n  if (erc721DecodedParams) {\n    const collectable: CollectableAsset = {\n      id: erc721DecodedParams[2],\n      address: transaction.to,\n      name: 'Unknown'\n    };\n    return sendAssetToModuleTransaction({\n      collectable,\n      nonce: '0',\n      data: transaction.data,\n      recipient: erc721DecodedParams[1]\n    });\n  }\n\n  return decodeContractTransaction(network, transaction, multiSendAddress);\n}\n\nexport function parseAmount(input: string) {\n  return BigNumber.from(input).toString();\n}\n\nexport function parseValueInput(input: string) {\n  try {\n    return parseAmount(input);\n  } catch (e) {\n    return input;\n  }\n}\n"
  },
  {
    "path": "src/plugins/safeSnap/utils/umaModule.ts",
    "content": "import gql from 'graphql-tag';\nimport { defaultAbiCoder } from '@ethersproject/abi';\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { Contract } from '@ethersproject/contracts';\nimport { keccak256 } from '@ethersproject/keccak256';\nimport { StaticJsonRpcProvider } from '@ethersproject/providers';\nimport { pack } from '@ethersproject/solidity';\nimport { toUtf8Bytes, toUtf8String } from '@ethersproject/strings';\nimport snapshot from '@snapshot-labs/snapshot.js';\nimport {\n  ERC20_ABI,\n  UMA_MODULE_ABI,\n  UMA_ORACLE_ABI,\n  contractData\n} from '../constants';\nimport { pageEvents } from './events';\nimport filter from 'lodash/filter';\n\nfunction getDeployBlock(network: string, name: string): number {\n  const data = filter(contractData, { network, name });\n  if (data.length === 1) return data[0].deployBlock ?? 0;\n  return 0;\n}\nfunction getContractSubgraph(search: {\n  network: string;\n  name: string;\n}): string {\n  const results = filter(contractData, search);\n  if (results.length > 1)\n    throw new Error(\n      `Too many results finding ${search.name} subgraph on network ${search.network}`\n    );\n  if (results.length < 1)\n    throw new Error(\n      `No results finding ${search.name} subgraph on network ${search.network}`\n    );\n  if (!results[0].subgraph)\n    throw new Error(\n      `No subgraph url defined for ${search.name} on network ${search.network}`\n    );\n  return results[0].subgraph;\n}\nfunction getOptimisticGovernorSubgraph(network: string): string {\n  return getContractSubgraph({ network, name: 'OptimisticGovernor' });\n}\nfunction getOracleV3Subgraph(network: string): string {\n  return getContractSubgraph({ network, name: 'OptimisticOracleV3' });\n}\n\nexport const queryGql = async (url: string, query: string) => {\n  try {\n    const response = await fetch(url, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Accept: 'application/json'\n      },\n      body: JSON.stringify({ query: query })\n    });\n    if (!response.ok) {\n      const errorData = await response.json();\n      throw new Error(\n        `Network Error: ${response.status}, message: ${errorData.message}`\n      );\n    }\n    const data = await response.json();\n    // Throw an error if there are errors in the GraphQL response\n    if (data.errors) {\n      throw new Error(\n        `GraphQL Error: ${data.errors.map(error => error.message).join(', ')}`\n      );\n    }\n    return data.data;\n  } catch (error) {\n    throw new Error(`Network error: ${error.message}`);\n  }\n};\n\nconst findAssertionGql = async (\n  network: string,\n  params: { assertionId: string }\n) => {\n  const oracleUrl = getOracleV3Subgraph(network);\n  const request = `\n  {\n    assertion(id:\"${params.assertionId}\"){\n      assertionId\n      expirationTime\n      assertionHash\n      assertionLogIndex\n      settlementHash\n    }\n  }\n  `;\n  const result = await queryGql(oracleUrl, request);\n  return result?.assertion;\n};\n// Search optimistic governor for individual proposal\nconst findProposalGql = async (\n  network: string,\n  params: { proposalHash; explanation; ogAddress }\n) => {\n  const subgraph = getOptimisticGovernorSubgraph(network);\n  const request = `\n  {\n    proposals(where:{proposalHash:\"${params.proposalHash}\",explanation:\"${\n      params.explanation\n    }\",optimisticGovernor:\"${params.ogAddress.toLowerCase()}\"}){\n      id\n      executed\n      assertionId\n    }\n  }\n  `;\n  const result = await queryGql(subgraph, request);\n  return result?.proposals;\n};\n\nconst getBondDetailsUma = async (\n  provider: StaticJsonRpcProvider,\n  moduleAddress: string\n) => {\n  const { web3Account } = useWeb3();\n\n  const moduleContract = new Contract(moduleAddress, UMA_MODULE_ABI, provider);\n\n  const erc20Contract = new Contract(\n    await moduleContract.collateral(),\n    ERC20_ABI,\n    provider\n  );\n\n  const bondInfo = ref({\n    collateral: erc20Contract.address,\n    symbol: await erc20Contract.symbol(),\n    decimals: await erc20Contract.decimals(),\n    currentUserBondAllowance: BigNumber.from(0),\n    currentUserBalance: BigNumber.from(0)\n  });\n\n  async function updateCurrentUserBondInfo() {\n    bondInfo.value.currentUserBondAllowance = BigNumber.from(\n      web3Account.value\n        ? await erc20Contract.allowance(web3Account.value, moduleAddress)\n        : 0\n    );\n    bondInfo.value.currentUserBalance = BigNumber.from(\n      web3Account.value ? await erc20Contract.balanceOf(web3Account.value) : 0\n    );\n  }\n  await updateCurrentUserBondInfo();\n\n  return bondInfo.value;\n};\n\nexport const getModuleDetailsUma = async (\n  provider: StaticJsonRpcProvider,\n  network: string,\n  moduleAddress: string,\n  explanation: string,\n  transactions: any\n) => {\n  const moduleContract = new Contract(moduleAddress, UMA_MODULE_ABI, provider);\n  const moduleDetails = await snapshot.utils.multicall(network, provider, UMA_MODULE_ABI, [\n    [moduleAddress, 'avatar'],\n    [moduleAddress, 'optimisticOracleV3'],\n    [moduleAddress, 'rules'],\n    [moduleAddress, 'bondAmount'],\n    [moduleAddress, 'liveness']\n  ]);\n  let needsApproval = false;\n  const optimisticOracle = moduleDetails[1][0];\n  const rules = moduleDetails[2][0];\n  const minimumBond = moduleDetails[3][0];\n  const livenessPeriod = moduleDetails[4][0];\n  const bondDetails = await getBondDetailsUma(provider, moduleAddress);\n\n  if (\n    Number(minimumBond) > 0 &&\n    Number(minimumBond) > Number(bondDetails.currentUserBondAllowance)\n  ) {\n    needsApproval = true;\n  }\n\n  // Create ancillary data for proposal hash\n  let ancillaryData = '';\n  let proposalHash: string;\n  if (transactions !== undefined) {\n    proposalHash = keccak256(\n      defaultAbiCoder.encode(\n        ['(address to, uint8 operation, uint256 value, bytes data)[]'],\n        [transactions]\n      )\n    );\n\n    ancillaryData = pack(\n      ['string', 'bytes', 'bytes', 'bytes', 'bytes', 'bytes', 'bytes', 'bytes'],\n      [\n        '',\n        pack(['string', 'string'], ['proposalHash', ':']),\n        toUtf8Bytes(proposalHash.replace(/^0x/, '')),\n        pack(\n          ['string', 'string', 'string', 'string'],\n          [',', 'explanation', ':', '\"']\n        ),\n        toUtf8Bytes(explanation.replace(/^0x/, '')),\n        pack(\n          ['string', 'string', 'string', 'string', 'string'],\n          ['\"', ',', 'rules', ':', '\"']\n        ),\n        toUtf8Bytes(rules.replace(/^0x/, '')),\n        pack(['string'], ['\"'])\n      ]\n    );\n  } else {\n    return {\n      dao: moduleDetails[0][0],\n      oracle: moduleDetails[1][0],\n      rules: moduleDetails[2][0],\n      minimumBond: minimumBond,\n      expiration: moduleDetails[4][0],\n      allowance: bondDetails.currentUserBondAllowance,\n      collateral: bondDetails.collateral,\n      decimals: bondDetails.decimals,\n      symbol: bondDetails.symbol,\n      userBalance: bondDetails.currentUserBalance,\n      needsBondApproval: needsApproval,\n      noTransactions: true,\n      activeProposal: false,\n      assertionEvent: undefined,\n      proposalExecuted: false,\n      livenessPeriod: livenessPeriod\n    };\n  }\n  // Check for active proposals. proposal hash can be identical across assertions\n  // but the explanation field should be unique. we will filter this out later.\n  const assertionId = await moduleContract.assertionIds(proposalHash);\n\n  const activeProposal =\n    assertionId !==\n    '0x0000000000000000000000000000000000000000000000000000000000000000';\n\n  // Search for requests with matching ancillary data\n  const oracleContract = new Contract(\n    optimisticOracle,\n    UMA_ORACLE_ABI,\n    provider\n  );\n  const latestBlock = await provider.getBlock('latest');\n  // modify this per chain. this should be updated with constants for all chains. start block is og deploy block.\n  // this needs to be optimized to reduce loading time, currently takes a long time to parse 3k blocks at a time.\n  const oGstartBlock = getDeployBlock(network, 'OptimisticGovernor');\n  const oOStartBlock = getDeployBlock(network, 'OptimisticOracleV3');\n  const maxRange = 3000;\n\n  const [assertionEvents, transactionsProposedEvents, executionEvents] =\n    await Promise.all([\n      pageEvents(\n        oOStartBlock,\n        latestBlock.number,\n        maxRange,\n        ({ start, end }: { start: number; end: number }) => {\n          return oracleContract.queryFilter(\n            oracleContract.filters.AssertionMade(),\n            start,\n            end\n          );\n        }\n      ),\n      pageEvents(\n        oGstartBlock,\n        latestBlock.number,\n        maxRange,\n        ({ start, end }: { start: number; end: number }) => {\n          return moduleContract.queryFilter(\n            moduleContract.filters.TransactionsProposed(),\n            start,\n            end\n          );\n        }\n      ),\n      pageEvents(\n        oGstartBlock,\n        latestBlock.number,\n        maxRange,\n        ({ start, end }: { start: number; end: number }) => {\n          return moduleContract.queryFilter(\n            moduleContract.filters.ProposalExecuted(proposalHash),\n            start,\n            end\n          );\n        }\n      )\n    ]);\n\n  const thisModuleAssertionEvent = assertionEvents.filter(event => {\n    return (\n      event.args?.claim === ancillaryData &&\n      event.args?.callbackRecipient === moduleAddress\n    );\n  });\n\n  // Get the full proposal events (with state).\n  const fullAssertionEvent = await Promise.all(\n    thisModuleAssertionEvent.map(async event => {\n      const assertion = await oracleContract.getAssertion(\n        event.args?.assertionId\n      );\n\n      const isExpired =\n        Math.floor(Date.now() / 1000) >= assertion.expirationTime.toNumber();\n\n      return {\n        assertionId: event?.args?.assertionId,\n        expirationTimestamp: assertion.expirationTime,\n        isExpired: isExpired,\n        isSettled: assertion.settled,\n        proposalHash: proposalHash,\n        proposalTxHash: event.transactionHash,\n        logIndex: event.logIndex\n      };\n    })\n  );\n\n  const thisProposalTransactionsProposedEvents =\n    transactionsProposedEvents.filter(\n      event => toUtf8String(event.args?.explanation) === explanation\n    );\n\n  const assertion = thisProposalTransactionsProposedEvents.map(\n    tx => tx.args?.assertionId\n  );\n\n  const assertionIds = executionEvents.map(tx => tx.args?.assertionId);\n\n  const proposalExecuted = assertion.some(assertionId =>\n    assertionIds.includes(assertionId)\n  );\n\n  return {\n    dao: moduleDetails[0][0],\n    oracle: moduleDetails[1][0],\n    rules: moduleDetails[2][0],\n    minimumBond: minimumBond,\n    expiration: moduleDetails[4][0],\n    allowance: bondDetails.currentUserBondAllowance,\n    collateral: bondDetails.collateral,\n    decimals: bondDetails.decimals,\n    symbol: bondDetails.symbol,\n    userBalance: bondDetails.currentUserBalance,\n    needsBondApproval: needsApproval,\n    noTransactions: false,\n    activeProposal: activeProposal,\n    assertionEvent: fullAssertionEvent[0],\n    proposalExecuted: proposalExecuted,\n    livenessPeriod: livenessPeriod.toString()\n  };\n};\n\n// This is intended to function identically to getModuleDetailsUma but use subgraphs rather than web3 events.\n// This has a lot of duplicate code on purpose. Reducing code duplication will require a risky refactor,\n// and we also want a fallback function in case the graph is down, so we will leave the original untouched for now.\nexport const getModuleDetailsUmaGql = async (\n  provider: StaticJsonRpcProvider,\n  network: string,\n  moduleAddress: string,\n  explanation: string,\n  transactions: any\n) => {\n  const moduleContract = new Contract(moduleAddress, UMA_MODULE_ABI, provider);\n  const moduleDetails = await snapshot.utils.multicall(network, provider, UMA_MODULE_ABI, [\n    [moduleAddress, 'avatar'],\n    [moduleAddress, 'optimisticOracleV3'],\n    [moduleAddress, 'rules'],\n    [moduleAddress, 'bondAmount'],\n    [moduleAddress, 'liveness']\n  ]);\n  let needsApproval = false;\n  const optimisticOracle = moduleDetails[1][0];\n  const rules = moduleDetails[2][0];\n  const minimumBond = moduleDetails[3][0];\n  const livenessPeriod = moduleDetails[4][0];\n  const bondDetails = await getBondDetailsUma(provider, moduleAddress);\n\n  if (\n    Number(minimumBond) > 0 &&\n    Number(minimumBond) > Number(bondDetails.currentUserBondAllowance)\n  ) {\n    needsApproval = true;\n  }\n\n  let proposalHash: string;\n  let encodedExplanation: string;\n  if (transactions !== undefined && explanation !== undefined) {\n    proposalHash = keccak256(\n      defaultAbiCoder.encode(\n        ['(address to, uint8 operation, uint256 value, bytes data)[]'],\n        [transactions]\n      )\n    );\n    encodedExplanation = pack(\n      ['bytes'],\n      [toUtf8Bytes(explanation.replace(/^0x/, ''))]\n    );\n  } else {\n    return {\n      dao: moduleDetails[0][0],\n      oracle: moduleDetails[1][0],\n      rules: moduleDetails[2][0],\n      minimumBond: minimumBond,\n      expiration: moduleDetails[4][0],\n      allowance: bondDetails.currentUserBondAllowance,\n      collateral: bondDetails.collateral,\n      decimals: bondDetails.decimals,\n      symbol: bondDetails.symbol,\n      userBalance: bondDetails.currentUserBalance,\n      needsBondApproval: needsApproval,\n      noTransactions: true,\n      activeProposal: false,\n      assertionEvent: undefined,\n      proposalExecuted: false,\n      livenessPeriod: livenessPeriod\n    };\n  }\n  // Check for active proposals. proposal hash can be identical across assertions\n  // but the explanation field should be unique. we will filter this out later.\n  const assertionId = await moduleContract.assertionIds(proposalHash);\n\n  const activeProposal =\n    assertionId !==\n    '0x0000000000000000000000000000000000000000000000000000000000000000';\n\n  const [proposal] = await findProposalGql(network, {\n    proposalHash,\n    explanation: encodedExplanation,\n    ogAddress: moduleAddress\n  });\n  const proposalExecuted = proposal?.executed ? true : false;\n  const assertion = proposal?.assertionId\n    ? await findAssertionGql(network, { assertionId: proposal.assertionId })\n    : undefined;\n\n  const assertionEvent = assertion\n    ? {\n        assertionId: assertion.assertionId,\n        expirationTimestamp: BigNumber.from(assertion.expirationTime),\n        isExpired:\n          Math.floor(Date.now() / 1000) >= Number(assertion.expirationTime),\n        isSettled: assertion.settlementHash ? true : false,\n        proposalHash,\n        proposalTxHash: assertion.assertionHash,\n        logIndex: assertion.assertionLogIndex\n      }\n    : undefined;\n\n  return {\n    dao: moduleDetails[0][0],\n    oracle: moduleDetails[1][0],\n    rules: moduleDetails[2][0],\n    minimumBond: minimumBond,\n    expiration: moduleDetails[4][0],\n    allowance: bondDetails.currentUserBondAllowance,\n    collateral: bondDetails.collateral,\n    decimals: bondDetails.decimals,\n    symbol: bondDetails.symbol,\n    userBalance: bondDetails.currentUserBalance,\n    needsBondApproval: needsApproval,\n    noTransactions: false,\n    activeProposal,\n    assertionEvent,\n    proposalExecuted,\n    livenessPeriod: livenessPeriod.toString()\n  };\n};\n"
  },
  {
    "path": "src/plugins/safeSnap/utils/validator.ts",
    "content": "import { mustBeEthereumAddress, isArrayParameter } from '../index';\n\nexport const isAddress = (type: string): boolean =>\n  type.indexOf('address') === 0;\nexport const isBoolean = (type: string): boolean => type.indexOf('bool') === 0;\nexport const isString = (type: string): boolean => type.indexOf('string') === 0;\nexport const isUint = (type: string): boolean => type.indexOf('uint') === 0;\nexport const isInt = (type: string): boolean => type.indexOf('int') === 0;\nexport const isByte = (type: string): boolean => type.indexOf('byte') === 0;\n\nexport const isStringArray = (text: string): boolean => {\n  try {\n    const values = JSON.parse(text);\n    return Array.isArray(values);\n  } catch (e) {\n    return false;\n  }\n};\n\nexport const isParameterValue = (type: string, value: string) => {\n  if (type === 'address') {\n    return mustBeEthereumAddress(value);\n  } else if (isArrayParameter(type)) {\n    return isStringArray(value);\n  }\n  return !!value;\n};\n"
  },
  {
    "path": "src/router/index.ts",
    "content": "import { createRouter, createWebHashHistory, RouteLocation } from 'vue-router';\n\nimport DelegateView from '@/views/DelegateView.vue';\nimport ExploreView from '@/views/ExploreView.vue';\nimport PlaygroundView from '@/views/PlaygroundView.vue';\nimport SetupView from '@/views/SetupView.vue';\nimport StrategyView from '@/views/StrategyView.vue';\nimport TimelineView from '@/views/TimelineView.vue';\nimport RankingView from '@/views/RankingView.vue';\n\nimport ProfileView from '@/views/ProfileView.vue';\nimport ProfileAbout from '@/views/ProfileAbout.vue';\nimport ProfileActivity from '@/views/ProfileActivity.vue';\n\nimport SpaceView from '@/views/SpaceView.vue';\nimport SpaceProposals from '@/views/SpaceProposals.vue';\nimport SpaceProposal from '@/views/SpaceProposal.vue';\nimport SpaceCreate from '@/views/SpaceCreate.vue';\nimport SpaceSettings from '@/views/SpaceSettings.vue';\nimport SpaceAbout from '@/views/SpaceAbout.vue';\nimport SpaceTreasury from '@/views/SpaceTreasury.vue';\nimport SpaceDelegates from '@/views/SpaceDelegates.vue';\nimport SpaceDelegate from '@/views/SpaceDelegate.vue';\nimport SpaceBoost from '@/views/SpaceBoost.vue';\nimport TermsView from '@/views/TermsView.vue';\n\n// The frontend shows all spaces or just a single one, when being accessed\n// through that space's custom domain.\nconst { domain, domainAlias } = useApp();\n\nconst routes: any[] = [];\n\n// These routes get prefixed with the respective space's ENS domain (/:key)\n// or they get mounted at \"/\" in the single space scenario.\nconst spaceRoutes = [\n  {\n    path: '',\n    name: 'spaceProposals',\n    component: SpaceProposals\n  },\n  {\n    path: 'proposal/:id',\n    name: 'spaceProposal',\n    component: SpaceProposal\n  },\n  {\n    path: 'create/:sourceProposal?',\n    name: 'spaceCreate',\n    component: SpaceCreate\n  },\n  {\n    path: 'about',\n    name: 'spaceAbout',\n    component: SpaceAbout\n  },\n  {\n    path: 'settings',\n    name: 'spaceSettings',\n    component: SpaceSettings\n  },\n  {\n    path: 'treasury/:wallet?',\n    name: 'spaceTreasury',\n    component: SpaceTreasury\n  },\n  {\n    path: 'delegates',\n    name: 'spaceDelegates',\n    component: SpaceDelegates\n  },\n  {\n    path: 'delegate/:address?',\n    name: 'spaceDelegate',\n    component: SpaceDelegate\n  },\n  {\n    path: 'boost/:proposalId',\n    name: 'spaceBoost',\n    component: SpaceBoost\n  }\n];\n\nconst profileRoutes = [\n  {\n    path: '',\n    name: 'profileActivity',\n    component: ProfileActivity\n  },\n  {\n    path: 'about/',\n    name: 'profileAbout',\n    component: ProfileAbout\n  }\n];\n\n// If accessed through custom domain, mount space routes under /.\n// Requests starting with /:key will be redirected.\n// E.g. /balancer/proposal/:proposalId becomes /proposal/:proposalId\nif (domain) {\n  routes.push(\n    { path: '/', name: 'home', component: SpaceView, children: spaceRoutes },\n    {\n      path: `/${domain}`,\n      alias: `/${domainAlias ?? domain}`,\n      name: 'home-redirect',\n      redirect: '/'\n    },\n    {\n      path: `/${domain}/:path(.*)`,\n      alias: `/${domainAlias ?? domain}/:path(.*)`,\n      name: 'space-redirect',\n      redirect: (to: RouteLocation) => ({ path: `/${to.params.path}` })\n    }\n  );\n} else {\n  // If accessed through localhost or snapshot.org, add all routes and\n  // prefix space routes with space domain (/:key).\n  routes.push(\n    { path: '/', name: 'home', component: ExploreView },\n    {\n      path: '/setup/:ens?',\n      name: 'setup',\n      component: SetupView\n    },\n    { path: '/timeline', name: 'timeline', component: TimelineView },\n    { path: '/ranking', name: 'ranking', component: RankingView },\n    {\n      path: '/playground/:name',\n      name: 'playground',\n      component: PlaygroundView\n    },\n    { path: '/strategy/:name', name: 'strategy', component: StrategyView },\n    {\n      path: '/profile/:address',\n      name: 'profile',\n      component: ProfileView,\n      children: profileRoutes\n    },\n    { path: '/delegate/:key?/:to?', name: 'delegate', component: DelegateView },\n    {\n      path: '/:key',\n      name: 'space',\n      component: SpaceView,\n      children: spaceRoutes,\n      beforeEnter: to => {\n        // Make sure key is lowercase\n        if (to.params.key) {\n          to.params.key = to.params.key.toLowerCase();\n        }\n      }\n    },\n    {\n      path: '/network',\n      component: {\n        // To redirect existing links to snapshot.box\n        beforeRouteEnter() {\n          window.location.href = 'https://snapshot.box/#/network';\n        }\n      }\n    },\n    {\n      path: '/terms-and-conditions',\n      name: 'terms-and-conditions',\n      component: TermsView\n    }\n  );\n}\n\n// catch all\nroutes.push({\n  path: '/:pathMatch(.*)*',\n  name: 'error-404',\n  redirect: '/'\n});\n\nconst router = createRouter({\n  history: createWebHashHistory(),\n  routes,\n  scrollBehavior(to, _from, savedPosition) {\n    if (savedPosition) {\n      return savedPosition;\n    }\n\n    if (to.params.retainScrollPosition) {\n      return {};\n    }\n\n    if (to.hash) {\n      const position = { selector: to.hash };\n      return { el: position };\n    }\n\n    return { top: 0 };\n  }\n});\n\nexport { routes };\n\nexport default router;\n"
  },
  {
    "path": "src/sentry.ts",
    "content": "import * as Sentry from '@sentry/vue';\n\nexport const initSentry = (app, router) => {\n  const dsn = import.meta.env.VITE_SENTRY_DSN;\n\n  if (!dsn) {\n    return;\n  }\n\n  Sentry.init({\n    app,\n    tunnel: `${import.meta.env.VITE_SIDEKICK_URL}/sentry`,\n    dsn,\n    integrations: [\n      new Sentry.BrowserTracing({\n        routingInstrumentation: Sentry.vueRouterInstrumentation(router)\n      }),\n      new Sentry.Replay()\n    ],\n    sampleRate: 0,\n    maxBreadcrumbs: 50,\n    tracingOptions: {\n      trackComponents: true\n    },\n    replaysSessionSampleRate: 0,\n    replaysOnErrorSampleRate: 0,\n    denyUrls: [/extensions\\//i, /^chrome:\\/\\//i, /^chrome-extension:\\/\\//i]\n  });\n};\n\nexport const setUser = user => {\n  Sentry.setUser(user);\n};\n"
  },
  {
    "path": "src/views/DelegateView.vue",
    "content": "<script setup lang=\"ts\">\nimport { debouncedWatch } from '@vueuse/core';\nimport { getAddress, isAddress } from '@ethersproject/address';\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport {\n  getScores,\n  getDelegatesBySpace\n} from '@snapshot-labs/snapshot.js/src/utils';\nimport { getDelegates, getDelegators } from '@/helpers/delegation';\nimport { shorten } from '@/helpers/utils';\nimport { SPACE_DELEGATE_QUERY } from '@/helpers/queries';\n\nconst route = useRoute();\nconst { t } = useI18n();\nconst { web3Account } = useWeb3();\nconst { formatCompactNumber } = useIntl();\nconst { modalAccountOpen } = useModal();\nconst { delegateTo, delegationLoading, networkSupportsDelegate, networkKey } =\n  useDelegate();\n\nconst modalOpen = ref(false);\nconst currentId = ref('');\nconst currentDelegate = ref('');\nconst loaded = ref(false);\nconst delegatesLoading = ref(false);\nconst delegates = ref<any[]>([]);\nconst delegatesWithScore = ref<any[]>([]);\nconst delegators = ref<any[]>([]);\nconst specifySpaceChecked = ref(false);\nconst space = ref();\nconst form = ref({\n  address: (route.params.to as string) || '',\n  id: (route.params.key as string) || ''\n});\nconst delegationLoadingError = ref(false);\n\nconst { profiles, loadProfiles } = useProfiles();\n\nconst { loadOwnedEnsDomains, ownedEnsDomains, validEnsTlds, isValidEnsDomain } =\n  useEns();\n\nconst isEnsOwnedByWeb3Account = computed(() =>\n  ownedEnsDomains.value.map(d => d.name).includes(form.value.address)\n);\n\nconst validateSpaceInput = computed(() => {\n  if (space.value === null) return t('delegate.noValidSpaceId');\n  return '';\n});\n\nconst validateToInput = computed(() => {\n  if (form.value.address === '') return '';\n  const address = form.value.address;\n  if (!isValidEnsDomain(address) && !isAddress(address)) {\n    if (address.includes('.'))\n      return `${t('delegate.noValidEns')} ${t(\n        'setup.supportedEnsTLDs'\n      )}: ${validEnsTlds.join(', ')}`;\n    else return t('delegate.noValidAddress');\n  }\n  if (address.toLowerCase() === web3Account.value.toLowerCase())\n    return t('delegate.delegateToSelf');\n  if (isEnsOwnedByWeb3Account.value) return t('delegate.delegateToSelfAddress');\n  return '';\n});\n\nwatch(\n  web3Account,\n  () => {\n    loadOwnedEnsDomains(web3Account.value);\n  },\n  { immediate: true }\n);\n\nconst isValidForm = computed(() => {\n  const address = form.value.address;\n  return (\n    (isValidEnsDomain(address) || isAddress(address)) &&\n    address.toLowerCase() !== web3Account.value.toLowerCase() &&\n    (!specifySpaceChecked.value || space.value?.id === form.value.id) &&\n    !isEnsOwnedByWeb3Account.value\n  );\n});\n\nfunction revokeDelegate(id, delegate) {\n  currentId.value = id;\n  currentDelegate.value = delegate;\n  modalOpen.value = true;\n}\n\nwatch([networkKey, web3Account], ([valN, valA], [prevN, prevA]) => {\n  if (valN !== prevN || valA?.toLowerCase() !== prevA)\n    getDelegationsAndDelegates();\n});\n\nconst getDelegationsAndDelegatesLoading = ref(false);\n\nasync function getDelegationsAndDelegates() {\n  if (web3Account.value) {\n    try {\n      delegationLoadingError.value = false;\n      getDelegationsAndDelegatesLoading.value = true;\n      const [delegatesObj, delegatorsObj] = await Promise.all([\n        getDelegates(networkKey.value, web3Account.value),\n        getDelegators(networkKey.value, web3Account.value)\n      ]);\n      delegates.value = delegatesObj.delegations;\n      delegators.value = delegatorsObj.delegations;\n    } catch (error) {\n      delegates.value = [];\n      delegators.value = [];\n      console.log(error);\n      delegationLoadingError.value = true;\n    } finally {\n      getDelegationsAndDelegatesLoading.value = false;\n    }\n  } else {\n    // user logged out\n    delegates.value = [];\n    delegators.value = [];\n  }\n}\n\nconst delegationStrategies = [\n  'delegation',\n  'erc20-balance-of-delegation',\n  'delegation-with-cap',\n  'delegation-with-overrides',\n  'with-delegation',\n  'erc20-balance-of-delegation-with-delegation'\n];\n\nasync function getDelegatesWithScore() {\n  const delegationStrategy = space.value.strategies.filter(strategy =>\n    delegationStrategies.includes(strategy.name)\n  );\n  if (delegationStrategy.length === 0) return;\n\n  delegationLoadingError.value = false;\n  delegatesLoading.value = true;\n  try {\n    const delegations: any = await getDelegatesBySpace(\n      space.value.network,\n      space.value.id,\n      'latest'\n    );\n\n    const uniqueDelegators = Array.from(\n      new Set(delegations.map(d => d.delegate))\n    )\n      .map(delegate => {\n        return delegations.find(a => a.delegate === delegate);\n      })\n      .map(delegation => {\n        return {\n          ...delegation,\n          delegate: getAddress(delegation.delegate),\n          delegator: getAddress(delegation.delegator)\n        };\n      });\n\n    const delegatesAddresses = uniqueDelegators.map(d => d.delegate);\n\n    const scores = await getScores(\n      space.value.id,\n      delegationStrategy,\n      space.value.network,\n      delegatesAddresses,\n      'latest',\n      `${import.meta.env.VITE_SCORES_URL}/api/scores`\n    );\n\n    uniqueDelegators.forEach(delegate => {\n      delegate.score = 0;\n      scores.forEach(delegationScore => {\n        Object.entries(delegationScore).forEach(([address, score]) => {\n          if (address === delegate.delegate) {\n            delegate.score += score;\n          }\n        });\n      });\n    });\n\n    const sortedDelegates = uniqueDelegators\n      .filter(delegate => delegate.score > 0)\n      .sort((a, b) => b.score - a.score);\n\n    delegatesWithScore.value = sortedDelegates;\n    delegatesLoading.value = false;\n  } catch (e) {\n    delegatesLoading.value = false;\n    delegationLoadingError.value = true;\n    console.log(e);\n    return e;\n  }\n}\n\nasync function handleSubmit() {\n  await delegateTo(form.value.address, form.value.id);\n  getDelegationsAndDelegates();\n}\n\nwatch(\n  delegates,\n  () => {\n    loadProfiles(\n      delegates.value\n        .map(delegate => delegate.delegate)\n        .concat(delegators.value.map(delegator => delegator.delegator))\n        .concat(delegatesWithScore.value.map(delegate => delegate.delegate))\n    );\n  },\n  { immediate: true }\n);\n\nconst { apolloQuery, queryLoading: spaceLoading } = useApolloQuery();\n\ndebouncedWatch(\n  () => form.value.id,\n  async () => {\n    if (!form.value.id) return;\n    space.value = await apolloQuery(\n      {\n        query: SPACE_DELEGATE_QUERY,\n        variables: {\n          id: form.value.id\n        }\n      },\n      'space'\n    );\n    if (space.value?.id === form.value?.id) {\n      delegatesWithScore.value = [];\n      getDelegatesWithScore();\n    } else delegatesWithScore.value = [];\n  },\n  { immediate: true, debounce: 500 }\n);\n\nonMounted(async () => {\n  if (route.params.key) specifySpaceChecked.value = true;\n  await getDelegationsAndDelegates();\n  loaded.value = true;\n});\n</script>\n\n<template>\n  <TheLayout v-bind=\"$attrs\">\n    <template #content-left>\n      <div class=\"mb-3 px-4 md:px-0\">\n        <ButtonBack @click=\"$router.go(-1)\" />\n        <h1 v-if=\"loaded\" v-text=\"$t('delegate.header')\" />\n      </div>\n      <LoadingPage v-if=\"!loaded\" />\n      <BaseBlock v-else-if=\"!networkSupportsDelegate\">\n        <BaseIcon name=\"warning\" class=\"mr-1\" />\n        {{\n          $t('delegate.delegateNotSupported', {\n            network:\n              networks?.[networkKey]?.shortName ?? $t('theCurrentNetwork')\n          })\n        }}\n        <BaseLink\n          class=\"ml-1 whitespace-nowrap\"\n          :link=\"`https://docs.snapshot.org/guides/delegation#supported-networks`\"\n          @click.stop\n        >\n          {{ $t('learnMore') }}\n        </BaseLink>\n      </BaseBlock>\n      <div v-else class=\"space-y-3\">\n        <BaseBlock>\n          <div class=\"space-y-2\">\n            <BaseInput\n              v-model.trim=\"form.address\"\n              :title=\"$t('delegate.to')\"\n              :placeholder=\"$t('delegate.addressPlaceholder')\"\n              :error=\"{ message: validateToInput }\"\n            />\n            <div class=\"flex items-center space-x-2 px-2\">\n              <InputSwitch v-model=\"specifySpaceChecked\" />\n              <span>{{ $t('setDelegationToSpace') }}</span>\n            </div>\n            <BaseInput\n              v-show=\"specifySpaceChecked\"\n              v-model.trim=\"form.id\"\n              :title=\"$t('space')\"\n              :loading=\"spaceLoading\"\n              placeholder=\"e.g. balancer.eth\"\n              :error=\"{ message: validateSpaceInput }\"\n            />\n          </div>\n        </BaseBlock>\n        <BaseBlock\n          v-if=\"\n            delegates.length < 1 &&\n            delegators.length < 1 &&\n            !getDelegationsAndDelegatesLoading &&\n            web3Account\n          \"\n        >\n          <BaseIcon name=\"warning\" class=\"mr-1\" />\n          {{ $t('delegate.noDelegationsAndDelegates') }}\n        </BaseBlock>\n        <BaseBlock\n          v-if=\"delegates.length > 0\"\n          :slim=\"true\"\n          :title=\"$t('delegate.delegations')\"\n        >\n          <div\n            v-for=\"(delegate, i) in delegates\"\n            :key=\"i\"\n            class=\"flex border-t px-4 py-3\"\n            :class=\"{ '!border-0': i === 0 }\"\n          >\n            <BaseUser\n              :address=\"delegate.delegate\"\n              :space=\"{ network: networkKey }\"\n              :profile=\"profiles[delegate.delegate]\"\n            />\n            <div\n              class=\"flex-auto text-right text-skin-link\"\n              v-text=\"shorten(delegate.space || $t('allSpaces'), 'choice')\"\n            />\n            <a\n              class=\"-mr-2 ml-2 px-2\"\n              @click=\"revokeDelegate(delegate.space, delegate.delegate)\"\n            >\n              <BaseIcon name=\"close\" size=\"12\" class=\"mb-1\" />\n            </a>\n          </div>\n        </BaseBlock>\n        <BaseBlock\n          v-if=\"delegators.length > 0\"\n          :slim=\"true\"\n          :title=\"$t('delegate.delegated')\"\n        >\n          <div\n            v-for=\"(delegator, i) in delegators\"\n            :key=\"i\"\n            :class=\"{ '!border-0': i === 0 }\"\n            class=\"flex border-t px-4 py-3\"\n          >\n            <BaseUser\n              :address=\"delegator.delegator\"\n              :space=\"{ network: networkKey }\"\n              :profile=\"profiles[delegator.delegator]\"\n            />\n            <div\n              class=\"flex-auto text-right text-skin-link\"\n              v-text=\"shorten(delegator.space || '-', 'choice')\"\n            />\n          </div>\n        </BaseBlock>\n        <BaseBlock\n          v-if=\"space?.id && specifySpaceChecked\"\n          :title=\"$tc('delegate.topDelegates')\"\n          :loading=\"delegatesLoading\"\n          slim\n        >\n          <div\n            v-for=\"(delegate, i) in delegatesWithScore\"\n            :key=\"i\"\n            :class=\"{ '!border-0': i === 0 }\"\n            class=\"flex border-t px-4 py-3\"\n          >\n            <BaseUser\n              :profile=\"profiles[delegate.delegate]\"\n              :space=\"{ network: networkKey }\"\n              :address=\"delegate.delegate\"\n              class=\"w-[160px]\"\n            />\n            <div\n              class=\"w-[160px] flex-auto text-right text-skin-link\"\n              v-text=\"\n                `${\n                  delegate.score >= 0.005\n                    ? formatCompactNumber(delegate.score)\n                    : '< 0.01'\n                } ${space.symbol}`\n              \"\n            />\n          </div>\n\n          <div\n            v-if=\"delegationLoadingError\"\n            class=\"mx-4 flex items-center py-3 text-red\"\n          >\n            <BaseIcon name=\"warning\" class=\"mr-1\" /> Error while retrieving the\n            delegates list\n          </div>\n          <div\n            v-else-if=\"!delegatesLoading && delegatesWithScore.length < 1\"\n            class=\"mx-4 flex items-center py-3\"\n          >\n            {{ $tc('delegate.noDelegatesFoundFor', [space.id]) }}\n          </div>\n        </BaseBlock>\n      </div>\n    </template>\n    <template v-if=\"networkSupportsDelegate\" #sidebar-right>\n      <BaseBlock class=\"mt-4 lg:mt-0\">\n        <TuneButton\n          :disabled=\"!isValidForm && !!web3Account\"\n          :loading=\"delegationLoading\"\n          class=\"block w-full\"\n          primary\n          @click=\"web3Account ? handleSubmit() : (modalAccountOpen = true)\"\n        >\n          {{ $t('confirm') }}\n        </TuneButton>\n      </BaseBlock>\n    </template>\n  </TheLayout>\n  <teleport\n    v-if=\"networkSupportsDelegate && profiles[currentDelegate]\"\n    to=\"#modal\"\n  >\n    <ModalRevokeDelegate\n      v-if=\"loaded\"\n      :id=\"currentId\"\n      :open=\"modalOpen\"\n      :delegate=\"currentDelegate\"\n      :profile=\"profiles[currentDelegate]\"\n      @close=\"modalOpen = false\"\n      @reload=\"getDelegationsAndDelegates\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/views/ExploreView.vue",
    "content": "<script setup lang=\"ts\">\nimport { useInfiniteScroll } from '@vueuse/core';\n\nuseMeta({\n  title: {\n    key: 'metaInfo.home.title'\n  },\n  description: {\n    key: 'metaInfo.home.description'\n  }\n});\n\nconst { t } = useI18n();\nconst { formatCompactNumber } = useIntl();\nconst route = useRoute();\n\nconst isSpaces = computed(\n  () => !route.query.filter || route.query.filter === 'spaces'\n);\nconst isStrategies = computed(() => route.query.filter === 'strategies');\nconst isNetworks = computed(() => route.query.filter === 'networks');\nconst isPlugins = computed(() => route.query.filter === 'plugins');\n\nconst buttonStr = computed(() => {\n  if (isStrategies.value) return t('explore.createStrategy');\n  if (isNetworks.value) return t('explore.addNetwork');\n  if (isPlugins.value) return t('explore.createPlugin');\n  return '';\n});\n\nconst resultsStr = computed(() => {\n  if (isStrategies.value) return t('explore.strategies');\n  if (isNetworks.value) return t('explore.networks');\n  if (isPlugins.value) return t('explore.plugins');\n  return t('explore.results');\n});\n\nconst createLink = computed(() => {\n  if (isStrategies.value) return 'https://docs.snapshot.org/strategies/create';\n  if (isNetworks.value) return 'https://snapshot.box/#/network';\n  if (isPlugins.value) return 'https://docs.snapshot.org/plugins/create';\n  return 'https://docs.snapshot.org/strategies/create';\n});\n\nconst { filterNetworks, getNetworksSpacesCount, loadingNetworksSpacesCount } =\n  useNetworksFilter();\n\nconst { filterPlugins, getPluginsSpacesCount, loadingPluginsSpacesCount } =\n  usePlugins();\n\nconst { filterStrategies, getStrategies, isLoadingStrategies } =\n  useStrategies();\nconst { env } = useApp();\n\nconst onlyMainnetNetworks = n => (env === 'production' ? !n.testnet : true);\n\nconst items = computed(() => {\n  const q = (route.query.q as string) || '';\n  if (isStrategies.value) return filterStrategies(q);\n  if (isNetworks.value) return filterNetworks(q).filter(onlyMainnetNetworks);\n  if (isPlugins.value) return filterPlugins(q);\n  return [];\n});\n\nwatch(\n  () => route.query.filter,\n  () => {\n    if (isStrategies.value) getStrategies();\n    if (isNetworks.value) getNetworksSpacesCount();\n    if (isPlugins.value) getPluginsSpacesCount();\n  },\n  { immediate: true }\n);\n\nconst loading = computed(() => {\n  if (isStrategies.value) return isLoadingStrategies.value;\n  if (isNetworks.value) return loadingNetworksSpacesCount.value;\n  if (isPlugins.value) return loadingPluginsSpacesCount.value;\n  return false;\n});\n\nconst loadBy = 15;\nconst limit = ref(loadBy);\n\nuseInfiniteScroll(\n  document,\n  () => {\n    limit.value += loadBy;\n  },\n  { distance: 400 }\n);\n</script>\n\n<template>\n  <div v-if=\"isSpaces\">\n    <ExploreSpaces />\n  </div>\n  <div v-else>\n    <BaseContainer class=\"mb-4 flex items-center\">\n      <div tabindex=\"-1\" class=\"mr-auto w-full max-w-[420px]\">\n        <TheSearchBar />\n      </div>\n      <div\n        class=\"ml-3 hidden items-center whitespace-nowrap text-right sm:flex\"\n      >\n        <div v-if=\"items.length\" class=\"flex flex-col\">\n          {{ formatCompactNumber(items.length) }} {{ resultsStr }}\n        </div>\n\n        <BaseLink\n          v-if=\"buttonStr\"\n          :link=\"createLink\"\n          class=\"ml-3 hidden md:block\"\n          hide-external-icon\n        >\n          <TuneButton tabindex=\"-1\">\n            {{ buttonStr }}\n          </TuneButton>\n        </BaseLink>\n      </div>\n    </BaseContainer>\n    <BaseContainer :slim=\"true\">\n      <div class=\"overflow-hidden\">\n        <ExploreSkeletonLoading\n          v-if=\"\n            isLoadingStrategies ||\n            loadingNetworksSpacesCount ||\n            loadingPluginsSpacesCount\n          \"\n        />\n        <template v-else-if=\"isStrategies\">\n          <div class=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n            <router-link\n              v-for=\"item in items.slice(0, limit)\"\n              :key=\"item.key\"\n              :to=\"`/strategy/${item.id}`\"\n            >\n              <BaseStrategyItem :strategy=\"item\" />\n            </router-link>\n          </div>\n        </template>\n        <template v-else-if=\"isNetworks\">\n          <div class=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n            <div v-for=\"item in items.slice(0, limit)\" :key=\"item.key\">\n              <BaseNetworkItem :network=\"item\" />\n            </div>\n          </div>\n        </template>\n        <template v-else-if=\"isPlugins\">\n          <div class=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n            <div v-for=\"item in items.slice(0, limit)\" :key=\"item.key\">\n              <BasePluginItem :plugin=\"item\" />\n            </div>\n          </div>\n        </template>\n        <BaseNoResults v-if=\"items.length < 1 && !loading\" use-block />\n      </div>\n    </BaseContainer>\n  </div>\n</template>\n"
  },
  {
    "path": "src/views/PlaygroundView.vue",
    "content": "<script setup lang=\"ts\">\nimport mapKeys from 'lodash/fp/mapKeys';\nimport { getAddress } from '@ethersproject/address';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\nimport { getBlockNumber } from '@snapshot-labs/snapshot.js/src/utils/web3';\nimport { getScores } from '@snapshot-labs/snapshot.js/src/utils';\nimport { decodeJson, encodeJson } from '@/helpers/b64';\n\nconst defaultParams = {\n  symbol: 'BAL',\n  address: '0xba100000625a3754423978a60c9317c58a424e3D',\n  decimals: 18\n};\n\nconst router = useRouter();\nconst route = useRoute();\nconst { query: queryParams } = useRoute();\nconst { copyToClipboard } = useCopy();\nconst { t } = useI18n();\nconst { formatCompactNumber } = useIntl();\nconst {\n  getExtendedStrategy,\n  extendedStrategy,\n  strategyDefinition,\n  getStrategies,\n  isLoadingStrategies,\n  filterStrategies\n} = useStrategies();\n\nconst loading = ref(false);\nconst strategyError = ref(null);\nconst networkError = ref(false);\nconst scores = ref(null);\nconst searchInput = ref('');\nconst form = ref<{\n  params: Record<string, any>;\n  space: string;\n  network: string;\n  snapshot: string;\n  addresses: string[];\n}>({\n  params: {},\n  space: '',\n  network: '1',\n  snapshot: '',\n  addresses: []\n});\n\nconst scoresWithZeroBalanceAddresses = computed(() => {\n  if (!scores.value) {\n    return null;\n  }\n  // If an address is not present inside the scoresObject, add it with a zero balance\n  const addressesArray = (form.value.addresses ?? []).map(getAddress);\n  const scoresObject = mapKeys(getAddress, scores.value[0] ?? {});\n  const scoresObjectWithZeroBalances: Record<string, number> =\n    addressesArray.reduce((acc, address) => {\n      acc[address] = scoresObject[address] || 0;\n      return acc;\n    }, {});\n  // Order scoreObjectWithZeroBalances by score\n  return Object.fromEntries(\n    Object.entries(scoresObjectWithZeroBalances).sort(\n      (a: any, b: any) => b[1] - a[1]\n    )\n  );\n});\n\nconst strategyExample = computed(() => {\n  if (queryParams.query) {\n    try {\n      const { params, network, snapshot, addresses, space } = decodeJson(\n        queryParams.query\n      );\n      return {\n        ...extendedStrategy.value?.examples?.[0],\n        addresses: addresses || extendedStrategy.value?.examples?.[0].addresses,\n        network,\n        snapshot,\n        space,\n        strategy: { params }\n      };\n    } catch (e) {\n      return extendedStrategy.value?.examples?.[0];\n    }\n  }\n  return extendedStrategy.value?.examples?.[0];\n});\n\nasync function loadScores() {\n  if (!extendedStrategy.value) return;\n\n  scores.value = null;\n  strategyError.value = null;\n  loading.value = true;\n\n  try {\n    const strategyParams = {\n      name: extendedStrategy.value.id,\n      params: form.value.params\n    };\n    scores.value = await getScores(\n      form.value.space,\n      [strategyParams],\n      form.value.network,\n      form.value.addresses,\n      parseInt(form.value.snapshot),\n      `${import.meta.env.VITE_SCORES_URL}/api/scores`\n    );\n    loading.value = false;\n  } catch (e: any) {\n    loading.value = false;\n    console.log(e);\n    strategyError.value = e;\n  }\n}\n\nfunction selectStrategy(strategy) {\n  router.push({ name: 'playground', params: { name: strategy.id } });\n}\n\nasync function loadSnapshotBlockNumber() {\n  try {\n    loading.value = true;\n    scores.value = null;\n    networkError.value = false;\n    const broviderUrl = import.meta.env.VITE_BROVIDER_URL;\n    const provider = await getProvider(form.value.network, { broviderUrl });\n    const blockNumber = await getBlockNumber(provider);\n    form.value.snapshot = blockNumber.toString();\n    loading.value = false;\n  } catch (e) {\n    loading.value = false;\n    networkError.value = true;\n    console.log(e);\n  }\n}\n\nasync function handleURLUpdate(_, paramName) {\n  router.replace({\n    query: { query: encodeJson(form.value) },\n    params: { retainScrollPosition: 'yes' }\n  });\n\n  if (paramName === 'networkUpdate') {\n    loadSnapshotBlockNumber();\n  }\n}\n\nfunction copyURL() {\n  copyToClipboard(\n    `${window.location.origin}/#${route.path}?query=${encodeJson(form.value)}`\n  );\n}\n\nwatch(\n  strategyExample,\n  () => {\n    form.value.params = strategyExample.value?.strategy.params ?? defaultParams;\n    form.value.network = strategyExample.value?.network ?? '1';\n    form.value.space = strategyExample.value?.space ?? '';\n    form.value.addresses = strategyExample.value?.addresses ?? [];\n  },\n  { immediate: true }\n);\n\nwatch(\n  () => route.params.name,\n  async () => {\n    await getStrategies();\n    await getExtendedStrategy(route.params.name as string);\n\n    if (queryParams.query && strategyExample.value?.snapshot) {\n      form.value.snapshot = strategyExample.value.snapshot;\n    } else {\n      loadSnapshotBlockNumber();\n    }\n  },\n  { immediate: true }\n);\n\nfunction handleNetworkSelect(value) {\n  form.value.network = value;\n  handleURLUpdate(null, 'networkUpdate');\n}\n</script>\n\n<template>\n  <TheLayout v-bind=\"$attrs\">\n    <template #content-left>\n      <div class=\"mb-3 px-4 md:px-0\">\n        <ButtonBack\n          :name=\"$t('strategyDetails')\"\n          @click=\"\n            router.push({\n              name: 'strategy',\n              params: { name: route.params.name }\n            })\n          \"\n        />\n      </div>\n      <LoadingPage v-if=\"isLoadingStrategies\" />\n      <template v-else>\n        <BaseBlock title=\"Strategy\" class=\"mb-3\">\n          <BaseCombobox\n            :label=\"''\"\n            :items=\"\n              filterStrategies(searchInput).map(s => ({ id: s.id, name: s.id }))\n            \"\n            :selected-id=\"route.params.name as string\"\n            @select=\"selectStrategy\"\n            @search=\"value => (searchInput = value)\"\n          />\n        </BaseBlock>\n        <LoadingPage v-if=\"!extendedStrategy\" />\n        <div v-else class=\"space-y-3\">\n          <BaseBlock :title=\"$t('settings.header')\">\n            <div class=\"space-y-2\">\n              <ComboboxNetwork\n                :network=\"form.network.toString()\"\n                @select=\"handleNetworkSelect\"\n              />\n              <BaseInput\n                v-model=\"form.snapshot\"\n                :title=\"$t('snapshot')\"\n                @update:model-value=\"handleURLUpdate\"\n              />\n              <BaseInput\n                v-model=\"form.space\"\n                title=\"Space\"\n                @update:model-value=\"handleURLUpdate\"\n              />\n            </div>\n            <BaseBlock\n              v-if=\"networkError\"\n              class=\"mt-4\"\n              style=\"border-color: red !important\"\n            >\n              <BaseIcon name=\"warning\" class=\"mr-2 !text-red\" />\n              <span class=\"!text-red\">{{ $t('networkErrorPlayground') }}</span>\n            </BaseBlock>\n          </BaseBlock>\n          <BaseBlock :title=\"$t('strategyParams')\">\n            <FormObjectStrategyParams\n              v-if=\"strategyDefinition\"\n              v-model=\"form.params\"\n              :strategy-name=\"route.params.name as string\"\n            />\n            <TextareaJson\n              v-else\n              v-model=\"form.params\"\n              is-valid\n              :placeholder=\"$t('strategyParameters')\"\n              class=\"input text-left\"\n              @update:model-value=\"handleURLUpdate\"\n            />\n            <BaseBlock\n              v-if=\"strategyError\"\n              class=\"mt-3 overflow-x-auto\"\n              style=\"border-color: red !important\"\n            >\n              <pre class=\"whitespace-pre-wrap !text-red\">\n {{ strategyError }}</pre\n              >\n            </BaseBlock>\n          </BaseBlock>\n          <BaseBlock :title=\"$t('addresses')\">\n            <TextareaArray\n              v-model=\"form.addresses\"\n              :placeholder=\"`0x8C28Cf33d9Fd3D0293f963b1cd27e3FF422B425c\\n0xeF8305E140ac520225DAf050e2f71d5fBcC543e7`\"\n              @update:model-value=\"handleURLUpdate\"\n            />\n          </BaseBlock>\n        </div>\n      </template>\n    </template>\n    <template #sidebar-right>\n      <div class=\"space-y-3\">\n        <BaseBlock :title=\"$t('actions')\">\n          <TuneButton\n            :loading=\"loading\"\n            :disabled=\"loading || !extendedStrategy\"\n            class=\"flex w-full items-center justify-center\"\n            primary\n            @click=\"loadScores\"\n          >\n            <i-ho-play class=\"text-lg\" />\n          </TuneButton>\n          <TuneButton class=\"mt-2 w-full\" @click=\"copyURL\">\n            <BaseIcon\n              name=\"insertlink\"\n              size=\"18\"\n              class=\"mr-1 align-text-bottom\"\n            />\n            {{ t('copyLink') }}\n          </TuneButton>\n        </BaseBlock>\n        <BaseBlock v-if=\"scores\" :title=\"$t('results')\">\n          <div\n            v-for=\"(score, address) in scoresWithZeroBalanceAddresses\"\n            :key=\"address\"\n            class=\"flex justify-between\"\n          >\n            <BaseUser :address=\"address as string\" />\n            <span>\n              {{ formatCompactNumber(score) }}\n              {{ form.params.symbol }}\n            </span>\n          </div>\n        </BaseBlock>\n      </div>\n    </template>\n  </TheLayout>\n</template>\n"
  },
  {
    "path": "src/views/ProfileAbout.vue",
    "content": "<script setup lang=\"ts\">\nimport { FOLLOWS_QUERY } from '@/helpers/queries';\n\nconst props = defineProps<{\n  userAddress: string;\n  profile?: { about?: string };\n}>();\n\nconst { apolloQuery } = useApolloQuery();\nconst { loadSpaces, spaces } = useSpaces();\nconst { loadOwnedEnsDomains, ownedEnsDomains } = useEns();\n\nconst followingSpaceIds = ref<string[]>([]);\nconst isLoading = ref(false);\n\nasync function loadSpacesFollowed() {\n  if (!props.userAddress) return [];\n  try {\n    const response = await apolloQuery(\n      {\n        query: FOLLOWS_QUERY,\n        variables: {\n          follower_in: props.userAddress\n        }\n      },\n      'follows'\n    );\n\n    followingSpaceIds.value = response.map(f => f.space.id);\n  } catch (e) {\n    console.error(e);\n  }\n}\n\nconst domainsWithExistingSpace = computed(() => {\n  const ownedEnsNames = ownedEnsDomains.value.map(d => d.name);\n  return spaces.value.filter(d => ownedEnsNames.includes(d.id));\n});\n\nconst followingSpaces = computed(() => {\n  return spaces.value.filter(d => followingSpaceIds.value.includes(d.id));\n});\n\nonMounted(async () => {\n  isLoading.value = true;\n  await loadSpacesFollowed();\n  await loadOwnedEnsDomains(props.userAddress);\n  await loadSpaces([\n    ...ownedEnsDomains.value.map(d => d.name),\n    ...followingSpaceIds.value\n  ]);\n  isLoading.value = false;\n});\n</script>\n\n<template>\n  <div>\n    <div class=\"space-y-4\">\n      <ProfileAboutBiography v-if=\"profile?.about\" :about=\"profile.about\" />\n      <BlockSpacesList\n        :spaces=\"domainsWithExistingSpace\"\n        :title=\"$t('profile.about.createdSpaces')\"\n        :message=\"$t('profile.about.notCreatedSpacesYet')\"\n        :loading=\"isLoading\"\n      />\n\n      <BlockSpacesList\n        :spaces=\"followingSpaces\"\n        :title=\"$t('profile.about.joinedSpaces')\"\n        :message=\"$t('profile.about.notJoinSpacesYet')\"\n        :loading=\"isLoading\"\n      />\n\n      <ProfileAboutDelegate\n        :user-address=\"userAddress\"\n        :following-spaces=\"followingSpaces\"\n        :loading=\"isLoading\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/views/ProfileActivity.vue",
    "content": "<script setup lang=\"ts\">\nimport { ACTIVITY_VOTES_QUERY } from '@/helpers/queries';\nimport { ProfileActivity } from '@/helpers/interfaces';\nimport { useInfiniteScroll } from '@vueuse/core';\n\nconst props = defineProps<{\n  userAddress: string;\n}>();\n\nconst { apolloQuery } = useApolloQuery();\n\nconst activities = ref<ProfileActivity[]>([]);\nconst loadingActivities = ref(false);\n\nconst activityToday = computed(() => {\n  const oneDaySeconds = 24 * 60 * 60;\n  return activities.value.filter(\n    activity => activity.created > Math.floor(Date.now() / 1000) - oneDaySeconds\n  );\n});\n\nconst activityOneWeek = computed(() => {\n  const oneDaySeconds = 24 * 60 * 60;\n  const oneWeekSeconds = 7 * 24 * 60 * 60;\n  return activities.value.filter(\n    activity =>\n      activity.created > Math.floor(Date.now() / 1000) - oneWeekSeconds &&\n      activity.created < Math.floor(Date.now() / 1000) - oneDaySeconds\n  );\n});\n\nconst activityOlder = computed(() => {\n  const oneWeekSeconds = 7 * 24 * 60 * 60;\n  return activities.value.filter(\n    activity =>\n      activity.created < Math.floor(Date.now() / 1000) - oneWeekSeconds\n  );\n});\n\nconst { loadBy, loadingMore, stopLoadingMore, loadMore } =\n  useInfiniteLoader(20);\n\nasync function loadVotes(skip = 0) {\n  const votes = await apolloQuery(\n    {\n      query: ACTIVITY_VOTES_QUERY,\n      variables: {\n        first: loadBy,\n        skip,\n        voter: props.userAddress\n      }\n    },\n    'votes'\n  );\n\n  stopLoadingMore.value = votes?.length < loadBy;\n\n  votes.forEach(vote => {\n    const isVisibleChoice = ['basic', 'single-choice'].includes(\n      vote.proposal?.type ?? ''\n    );\n\n    activities.value.push({\n      id: vote.id,\n      created: vote.created,\n      type: 'vote',\n      title: vote.proposal.title,\n      space: {\n        id: vote.space.id,\n        avatar: vote.space.avatar\n      },\n      vote: {\n        proposalId: vote.proposal.id,\n        choice: isVisibleChoice\n          ? vote.proposal.choices?.[vote.choice - 1] ?? ''\n          : '',\n        type: vote.proposal.type\n      }\n    });\n  });\n\n  return votes;\n}\n\nuseInfiniteScroll(\n  document,\n  () => {\n    if (activities.value.length === 0) return;\n    loadMore(() => loadVotes(activities.value.length));\n  },\n  { distance: 400 }\n);\n\nonMounted(async () => {\n  loadingActivities.value = true;\n  await loadVotes();\n  loadingActivities.value = false;\n});\n</script>\n\n<template>\n  <div>\n    <LoadingRow v-if=\"loadingActivities\" block />\n\n    <BaseBlock v-else-if=\"!activities.length\" class=\"text-center\">\n      {{ $t('profile.activity.noActivity') }}\n    </BaseBlock>\n\n    <div v-else class=\"space-y-3\">\n      <ProfileActivityList\n        v-if=\"activityToday.length\"\n        :title=\"$t('profile.activity.today')\"\n      >\n        <ProfileActivityListItem\n          v-for=\"activity in activityToday\"\n          :key=\"activity.id\"\n          :activity=\"activity\"\n        />\n      </ProfileActivityList>\n\n      <ProfileActivityList\n        v-if=\"activityOneWeek.length\"\n        :title=\"$t('profile.activity.thisWeek')\"\n      >\n        <ProfileActivityListItem\n          v-for=\"activity in activityOneWeek\"\n          :key=\"activity.id\"\n          :activity=\"activity\"\n        />\n      </ProfileActivityList>\n\n      <ProfileActivityList\n        v-if=\"activityOlder.length\"\n        :title=\"$t('profile.activity.olderThanWeek')\"\n      >\n        <ProfileActivityListItem\n          v-for=\"activity in activityOlder\"\n          :key=\"activity.id\"\n          :activity=\"activity\"\n        />\n      </ProfileActivityList>\n\n      <LoadingRow v-if=\"loadingMore\" block />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "src/views/ProfileView.vue",
    "content": "<script setup lang=\"ts\">\nimport { getAddress } from '@ethersproject/address';\nconst route = useRoute();\n\nconst modalProfileFormOpen = ref(false);\n\nlet userAddress = computed(() => {\n  const address = route.params.address as string;\n  try {\n    return getAddress(address);\n  } catch (error) {\n    console.error(error);\n    return '';\n  }\n});\n\nconst { profiles, loadProfiles } = useProfiles();\n\nonMounted(() => loadProfiles([userAddress.value]));\n</script>\n\n<template>\n  <BaseBlock v-if=\"!userAddress\" class=\"text-center m-4\">\n    Invalid address\n  </BaseBlock>\n  <TheLayout v-else>\n    <template #sidebar-left>\n      <ProfileSidebar\n        :profiles=\"profiles\"\n        :user-address=\"userAddress\"\n        class=\"mb-4 lg:mb-0\"\n        @edit=\"modalProfileFormOpen = true\"\n      />\n    </template>\n    <template #content-right>\n      <router-view\n        :user-address=\"userAddress\"\n        :profile=\"profiles[userAddress]\"\n      />\n    </template>\n  </TheLayout>\n  <teleport to=\"#modal\">\n    <ModalProfileForm\n      :open=\"modalProfileFormOpen\"\n      :address=\"userAddress\"\n      :profile=\"profiles[userAddress]\"\n      @close=\"modalProfileFormOpen = false\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/views/RankingView.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\nimport { useInfiniteScroll } from '@vueuse/core';\n\nconst { formatCompactNumber } = useIntl();\n\nconst {\n  spacesRanking,\n  loadSpacesRanking,\n  loadMoreSpacesRanking,\n  loadingSpacesRanking,\n  loadingMoreSpacesRanking\n} = useSpaces();\n\nonMounted(() => {\n  loadSpacesRanking();\n});\n\nuseInfiniteScroll(\n  document,\n  () => {\n    loadMoreSpacesRanking();\n  },\n  { distance: 500 }\n);\n</script>\n\n<template>\n  <div>\n    <BaseContainer slim>\n      <BaseBlock slim>\n        <div class=\"flex border-b p-3 text-right\">\n          <div class=\"mr-2 w-[40px] text-left\" v-text=\"'Rank'\" />\n          <div class=\"flex-auto text-left\" v-text=\"'Space'\" />\n          <div class=\"w-[120px]\" v-text=\"'Proposals'\" />\n          <div class=\"w-[120px]\" v-text=\"'Votes'\" />\n          <div class=\"w-[120px]\" v-text=\"'Members'\" />\n        </div>\n\n        <router-link\n          v-for=\"(space, i) in spacesRanking\"\n          :key=\"space.id\"\n          :to=\"{ name: 'spaceProposals', params: { key: space.id } }\"\n          class=\"flex border-b p-3 text-right last:border-b-0\"\n        >\n          <div class=\"mr-2 mt-2 w-[40px] pt-1 text-center\" v-text=\"i + 1\" />\n          <div class=\"flex flex-auto space-x-3 text-left\">\n            <AvatarSpace :space=\"space\" size=\"32\" />\n            <div>\n              <div v-text=\"shorten(space.name, 32)\" />\n              <div class=\"text-skin-text\" v-text=\"shorten(space.id, 32)\" />\n            </div>\n          </div>\n          <div class=\"w-[120px]\">\n            <div v-text=\"formatCompactNumber(space.proposalsCount)\" />\n            <div\n              v-if=\"space.proposalsCount7d\"\n              class=\"text-green\"\n              v-text=\"`+${formatCompactNumber(space.proposalsCount7d)}`\"\n            />\n          </div>\n          <div class=\"w-[120px]\">\n            <div v-text=\"formatCompactNumber(space.votesCount)\" />\n            <div\n              v-if=\"space.votesCount7d\"\n              class=\"text-green\"\n              v-text=\"`+${formatCompactNumber(space.votesCount7d)}`\"\n            />\n          </div>\n          <div class=\"w-[120px]\">\n            <div v-text=\"formatCompactNumber(space.followersCount)\" />\n            <div\n              v-if=\"space.followersCount7d\"\n              class=\"text-green\"\n              v-text=\"`+${formatCompactNumber(space.followersCount7d)}`\"\n            />\n          </div>\n        </router-link>\n        <div\n          v-if=\"loadingSpacesRanking || loadingMoreSpacesRanking\"\n          class=\"flex\"\n        >\n          <LoadingSpinner class=\"mx-auto py-3\" big />\n        </div>\n      </BaseBlock>\n    </BaseContainer>\n  </div>\n</template>\n"
  },
  {
    "path": "src/views/SetupView.vue",
    "content": "<script setup lang=\"ts\">\nimport { sleep } from '@snapshot-labs/snapshot.js/src/utils';\nimport { useStorage } from '@vueuse/core';\nimport { clearStampCache } from '@/helpers/utils';\n\nuseMeta({\n  title: {\n    key: 'metaInfo.setup.title'\n  },\n  description: {\n    key: 'metaInfo.setup.description'\n  }\n});\n\nenum Step {\n  GETTING_STARTED,\n  ENS,\n  PROFILE,\n  STRATEGY,\n  EXTRAS\n}\n\nconst route = useRoute();\nconst router = useRouter();\nconst { web3Account } = useWeb3();\nconst { notify } = useFlashNotification();\nconst { prunedForm, isValid, resetForm } = useFormSpaceSettings('setup');\nconst { t } = useI18n();\nconst { send } = useClient();\nconst { loadSpaces, spaces } = useSpaces();\n\nconst creatingSpace = ref(false);\n\nconst currentStep = computed(() => Number(route.query.step));\n\nasync function checkIfSpaceExists() {\n  await loadSpaces([route.params.ens as string]);\n  if (spaces.value?.some(space => space.id === route.params.ens)) {\n    return;\n  } else {\n    await sleep(5000);\n    await checkIfSpaceExists();\n  }\n}\n\nasync function handleSubmit() {\n  if (!isValid.value) {\n    return;\n  }\n  creatingSpace.value = true;\n\n  // Create the space\n  const result = await send(\n    { id: route.params.ens as string },\n    'settings',\n    prunedForm.value\n  );\n  if (result.id) {\n    // Wait for the space to be available on the HUB\n    await checkIfSpaceExists();\n    await clearStampCache(route.params.ens as string);\n    creatingSpace.value = false;\n    console.log('Result', result);\n\n    // Save created space to local storage\n    const createdSpaces = useStorage(\n      `snapshot.createdSpaces.${web3Account.value.slice(0, 8).toLowerCase()}`,\n      {}\n    );\n    createdSpaces.value[route.params.ens as string] = {\n      showMessage: true\n    };\n\n    resetForm();\n\n    // Redirect to the new space page\n    notify(['green', t('notify.saved')]);\n    router.push({\n      name: 'spaceProposals',\n      params: {\n        key: route.params.ens\n      }\n    });\n  }\n  creatingSpace.value = false;\n}\n\nfunction nextStep(ensKey = '') {\n  router.push({\n    params: ensKey ? { ens: ensKey } : {},\n    query: { step: currentStep.value + 1 }\n  });\n}\n\nfunction previousStep() {\n  router.push({ query: { step: currentStep.value - 1 } });\n}\n\nfunction pushStepOne() {\n  router.push({ query: { step: Step.GETTING_STARTED } });\n}\n\nonMounted(() => {\n  if (!route.query.step || !web3Account.value) pushStepOne();\n});\n</script>\n\n<template>\n  <TheLayout>\n    <template #sidebar-left>\n      <SetupSidebarStepper\n        class=\"fixed hidden lg:block\"\n        :current-step=\"currentStep\"\n        @change-step=\"value => router.push({ query: { step: value } })\"\n      />\n    </template>\n    <template #content-right>\n      <div class=\"px-4 md:px-0\">\n        <h1 class=\"mb-4\" v-text=\"$t('setup.createASpace')\" />\n      </div>\n      <template v-if=\"web3Account || currentStep === Step.GETTING_STARTED\">\n        <SetupIntro\n          v-if=\"currentStep === Step.GETTING_STARTED\"\n          @next=\"nextStep\"\n        />\n\n        <SetupDomain v-if=\"currentStep === Step.ENS\" @next=\"nextStep\" />\n\n        <SetupProfile\n          v-if=\"currentStep === Step.PROFILE && route.params.ens\"\n          @next=\"nextStep\"\n          @back=\"previousStep\"\n        />\n\n        <SetupStrategy\n          v-show=\"currentStep === Step.STRATEGY && route.params.ens\"\n          @next=\"nextStep\"\n          @back=\"previousStep\"\n        />\n\n        <SetupExtras\n          v-if=\"currentStep === Step.EXTRAS && route.params.ens\"\n          :creating-space=\"creatingSpace\"\n          @back=\"previousStep\"\n          @submit=\"handleSubmit\"\n        />\n      </template>\n    </template>\n  </TheLayout>\n</template>\n"
  },
  {
    "path": "src/views/SpaceAbout.vue",
    "content": "<script setup lang=\"ts\">\nimport { getUrl } from '@snapshot-labs/snapshot.js/src/utils';\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n}>();\n\nuseMeta({\n  title: {\n    key: 'metaInfo.space.about.title',\n    params: {\n      space: props.space.name\n    }\n  },\n  description: {\n    key: 'metaInfo.space.about.description',\n    params: {\n      about: props.space.about.slice(0, 160)\n    }\n  }\n});\n\nconst { profiles, loadProfiles } = useProfiles();\n\ntype Moderator = {\n  id: string;\n  roles: string[];\n};\n\nconst isModalStrategiesOpen = ref(false);\n\nconst spaceMembers = computed(() => {\n  const authors = props.space.members.map(member => {\n    return {\n      id: member,\n      roles: ['author']\n    };\n  });\n\n  const moderators = props.space.moderators.map(moderator => {\n    return {\n      id: moderator,\n      roles: ['moderator']\n    };\n  });\n\n  const admins = props.space.admins.map(admin => {\n    return {\n      id: admin,\n      roles: ['admin']\n    };\n  });\n\n  return authors\n    .concat(moderators)\n    .concat(admins)\n    .reduce<Moderator[]>((acc, curr) => {\n      const existing = acc.find(member => member.id === curr.id);\n      if (existing) {\n        if (curr.roles[0] === 'admin') {\n          existing.roles = curr.roles;\n        }\n      } else {\n        acc.push(curr);\n      }\n      return acc;\n    }, [] as Moderator[]);\n});\n\nonMounted(() => {\n  loadProfiles(spaceMembers.value.map(member => member.id));\n});\n</script>\n\n<template>\n  <TheLayout v-bind=\"$attrs\">\n    <template #sidebar-left>\n      <SpaceSidebar :space=\"space\" />\n    </template>\n    <template #content-right>\n      <h1 class=\"hidden lg:mb-3 lg:block\">\n        {{ $t('about') }}\n      </h1>\n\n      <div class=\"space-y-3\">\n        <BaseBlock v-if=\"space.about || space.terms\">\n          <div class=\"space-y-3\">\n            <div v-if=\"space.about\">\n              <TextAutolinker :text=\"space.about\" />\n            </div>\n            <div v-if=\"space.terms\">\n              <h4 class=\"mb-1 text-skin-link\">\n                {{ $t('settings.terms.label') }}\n              </h4>\n\n              <BaseLink\n                :link=\"getUrl(space.terms)\"\n                class=\"flex items-center text-skin-text hover:text-skin-link\"\n              >\n                <div class=\"max-w-[300px] truncate\">\n                  {{ space.terms }}\n                </div>\n              </BaseLink>\n            </div>\n          </div>\n        </BaseBlock>\n\n        <BaseBlock\n          v-if=\"space.strategies\"\n          :title=\"$t('settings.strategies.label')\"\n          :counter=\"space.strategies.length\"\n          :show-more-button=\"space.strategies.length > 2\"\n          show-more-button-label=\"seeAll\"\n          slim\n          @show-more=\"isModalStrategiesOpen = true\"\n        >\n          <div class=\"p-4 space-y-3\">\n            <StrategiesListItem\n              v-for=\"(strategy, i) in space.strategies.slice(0, 2)\"\n              :key=\"i\"\n              :strategy=\"strategy\"\n            />\n          </div>\n        </BaseBlock>\n\n        <BaseBlock\n          v-if=\"spaceMembers.length\"\n          :title=\"$t('spaceMembers')\"\n          :counter=\"spaceMembers.length\"\n          slim\n        >\n          <AboutMembersListItem v-for=\"(mod, i) in spaceMembers\" :key=\"i\">\n            <BaseUser :address=\"mod.id\" :profile=\"profiles[mod.id]\" />\n            <div class=\"space-x-2\">\n              <BasePill\n                v-if=\"mod.roles.includes('admin')\"\n                v-tippy=\"{ content: $t('settings.members.admin.description') }\"\n                class=\"cursor-help py-1\"\n              >\n                admin\n              </BasePill>\n              <BasePill\n                v-if=\"mod.roles.includes('moderator')\"\n                v-tippy=\"{\n                  content: $t('settings.members.moderator.description')\n                }\"\n                class=\"cursor-help py-1\"\n              >\n                moderator\n              </BasePill>\n              <BasePill\n                v-if=\"mod.roles.includes('author')\"\n                v-tippy=\"{ content: $t('settings.members.author.description') }\"\n                class=\"cursor-help py-1\"\n              >\n                author\n              </BasePill>\n            </div>\n          </AboutMembersListItem>\n        </BaseBlock>\n      </div>\n    </template>\n  </TheLayout>\n  <teleport to=\"#modal\">\n    <ModalStrategies\n      :open=\"isModalStrategiesOpen\"\n      :strategies=\"space.strategies\"\n      @close=\"isModalStrategiesOpen = false\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/views/SpaceBoost.vue",
    "content": "<script setup lang=\"ts\">\nimport { BigNumber } from '@ethersproject/bignumber';\nimport { parseUnits, formatEther, formatUnits } from '@ethersproject/units';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport {\n  createBoost,\n  SNAPSHOT_GUARD_ADDRESS,\n  BOOST_CONTRACTS,\n  BOOST_VERSION,\n  getFees\n} from '@/helpers/boost';\nimport { TWO_WEEKS, ONE_DAY } from '@/helpers/constants';\nimport getProvider from '@snapshot-labs/snapshot.js/src/utils/provider';\nimport { getProposal } from '@/helpers/snapshot';\nimport { Token } from '@/helpers/alchemy';\nimport { sendApprovalTransaction } from '@/helpers/transaction';\nimport { pinGraph } from '@/helpers/pin';\nimport { BoostStrategy } from '@/helpers/boost/types';\nimport { isExcludedToken } from '@/helpers/boost/tokens';\n\nconst DISTRIBUTION_TYPE_ITEMS = [\n  {\n    value: 'lottery' as const,\n    name: 'Lottery',\n    description: 'Randomly selected based on voting power.'\n  },\n  {\n    value: 'weighted' as const,\n    name: 'Proportional',\n    description: 'Distributed to voters based on voting power.'\n  }\n];\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n}>();\n\ntype Form = {\n  eligibility: {\n    choice: 'any' | 'prediction' | number;\n  };\n  distribution: {\n    type: 'lottery' | 'weighted';\n    hasWeightedLimit: boolean;\n    weightedLimit: string;\n    hasLotteryLimit: boolean;\n    lotteryLimit: string;\n    numWinners: string | undefined;\n  };\n  network: string;\n  token?: Token;\n  amount: string;\n};\n\nconst route = useRoute();\nconst router = useRouter();\nconst auth = getInstance();\nconst { web3Account, web3 } = useWeb3();\nconst { account, updatingAccount, updateAccount } = useAccount();\nconst { modalAccountOpen } = useModal();\nconst { getRelativeProposalPeriod } = useIntl();\nconst { env } = useApp();\nconst { formatNumber, getNumberFormatter } = useIntl();\nconst { loadBalances, tokens, loading: loadingBalances } = useBalances();\n\nconst proposal = ref();\nconst createStatus = ref('');\nconst createTx = ref();\nconst approveTx = ref();\nconst modalWrongNetworkOpen = ref(false);\nconst showFormErrors = ref(false);\nconst ethFee = ref('');\nconst tokenFeePercent = ref('');\nconst loadingFees = ref(false);\nconst form = ref<Form>({\n  eligibility: {\n    choice: 'any'\n  },\n  distribution: {\n    type: 'lottery',\n    hasWeightedLimit: false,\n    weightedLimit: '',\n    hasLotteryLimit: false,\n    lotteryLimit: '',\n    numWinners: '1'\n  },\n  network: '1',\n  token: undefined,\n  amount: ''\n});\n\nconst formToken = computed(() => form.value.token);\n\nconst isWrongNetwork = computed(() => {\n  return form.value.network !== web3.value.network.key.toString();\n});\n\nconst bribeEnabled = computed(() => {\n  return (\n    proposal.value.privacy !== 'shutter' &&\n    props.space.boost.bribeEnabled &&\n    ['basic', 'single-choice'].includes(proposal.value.type)\n  );\n});\n\nconst eligibilityOptions = computed(() => {\n  const proposalChoices = proposal.value?.choices.map(\n    (choice: string, index: number) => {\n      return {\n        value: index + 1,\n        label: `Who votes '${choice}'`,\n        extras: {\n          disabled: !bribeEnabled.value\n        }\n      };\n    }\n  );\n\n  return [\n    {\n      value: 'any',\n      label: 'Anyone who votes'\n    },\n    {\n      value: 'prediction',\n      label: 'Anyone who votes for the winning choice'\n    },\n    ...proposalChoices\n  ];\n});\n\nconst isLoading = computed(() => {\n  return loadingBalances.value || updatingAccount.value || loadingFees.value;\n});\n\nconst isValidForm = computed(() => {\n  if (Object.keys(formValidation.value).length > 0) return false;\n  return true;\n});\n\nconst createStatusModalConfig = computed(() => {\n  switch (createStatus.value) {\n    case 'approve':\n      return {\n        title: 'Approve spending token',\n        subtitle: 'Please approve on your wallet.',\n        variant: 'loading' as const\n      };\n    case 'confirm':\n      return {\n        title: 'Confirm token deposit',\n        subtitle: 'Please confirm deposit on your wallet.',\n        variant: 'loading' as const\n      };\n    case 'pending':\n      return {\n        title: 'Transaction pending',\n        subtitle: createTx.value?.hash || approveTx.value?.hash || '',\n        variant: 'loading' as const\n      };\n    case 'success':\n      return {\n        title: 'Well done! 🥳',\n        subtitle: 'Your boost was successfully created.',\n        variant: 'success' as const\n      };\n    case 'error':\n      return {\n        title: 'Transaction failed',\n        subtitle:\n          createTx.value?.hash ||\n          approveTx.value?.hash ||\n          'Oops... Your boost creation failed!',\n        variant: 'error' as const\n      };\n    case 'pinning':\n      return {\n        title: 'Pinning boost',\n        subtitle: 'Please wait while we pin the boost on IPFS.',\n        variant: 'loading' as const\n      };\n    default:\n      return undefined;\n  }\n});\n\nconst strategyDistributionLimit = computed(() => {\n  if (\n    form.value.distribution.type === 'lottery' &&\n    form.value.distribution.lotteryLimit\n  ) {\n    const limitWithTwoDecimals = Number(\n      Number(form.value.distribution.lotteryLimit).toFixed(2)\n    );\n\n    return ((limitWithTwoDecimals || 0) * 100).toString();\n  }\n  if (\n    form.value.distribution.type === 'weighted' &&\n    form.value.distribution.weightedLimit\n  ) {\n    return parseUnits(\n      form.value.distribution.weightedLimit || '0',\n      formToken.value?.decimals ?? '18'\n    ).toString();\n  }\n  return undefined;\n});\n\nconst strategy = computed<BoostStrategy>(() => {\n  let choice;\n  let eligibilityType;\n\n  switch (form.value.eligibility.choice) {\n    case 'any':\n      eligibilityType = 'incentive';\n      break;\n    case 'prediction':\n      eligibilityType = 'prediction';\n      break;\n    default:\n      choice = form.value.eligibility.choice.toString();\n      eligibilityType = 'bribe';\n  }\n\n  const numWinners =\n    form.value.distribution.type === 'lottery'\n      ? form.value.distribution.numWinners\n      : undefined;\n\n  return {\n    name: 'Boost',\n    description: 'Snapshot.org proposal boost',\n    image: 'https://snapshot.org/boost.png',\n    external_url: `https://snapshot.org/#/${props.space.id}/proposal/${proposal.value.id}`,\n    strategyName: 'proposal',\n    params: {\n      version: BOOST_VERSION,\n      env: env === 'demo' ? 'snapshot-testnet' : 'snapshot',\n      proposal: proposal.value.id,\n      eligibility: {\n        type: eligibilityType,\n        choice\n      },\n      distribution: {\n        type: form.value.distribution.type,\n        limit: strategyDistributionLimit.value,\n        numWinners\n      }\n    }\n  };\n});\n\nconst isEndingSoon = computed(() => {\n  if (!proposal.value) return false;\n  const now = Math.floor(Date.now() / 1000);\n  return proposal.value.end - now < ONE_DAY;\n});\n\nconst amountParsed = computed(() => {\n  try {\n    return parseUnits(\n      form.value.amount || '0',\n      formToken.value?.decimals ?? '18'\n    );\n  } catch (e) {\n    return BigNumber.from('0');\n  }\n});\n\nconst amountPerWinner = computed(() => {\n  if (!amountParsed.value || !form.value.distribution.numWinners) return '0';\n  const amountPer = amountParsed.value.div(\n    Number(form.value.distribution.numWinners)\n  );\n  const formattedAmount = formatNumber(\n    Number(formatUnits(amountPer, formToken.value?.decimals ?? '18')),\n    getNumberFormatter({ maximumFractionDigits: 8 }).value\n  );\n\n  return formattedAmount;\n});\n\nconst tokenFeeParsed = computed(() => {\n  if (!tokenFeePercent.value || !amountParsed.value) return BigNumber.from('0');\n  const feeAmount = amountParsed.value.div(100).mul(tokenFeePercent.value);\n  return feeAmount.lte(0) ? BigNumber.from('0') : feeAmount;\n});\n\nconst formValidation = computed(() => {\n  const errors: Record<string, string> = {};\n\n  if (form.value.distribution.hasWeightedLimit) {\n    if (!form.value.distribution.weightedLimit) {\n      errors.weightedLimit = 'Please enter a number or disable';\n    } else if (Number(form.value.distribution.weightedLimit) <= 0) {\n      errors.weightedLimit = 'Please enter a value greater than 0 or disable';\n    }\n  }\n\n  if (form.value.distribution.hasLotteryLimit) {\n    if (!form.value.distribution.lotteryLimit) {\n      errors.lotteryLimit = 'Please enter a number or disable';\n    } else if (Number(form.value.distribution.lotteryLimit) <= 0) {\n      errors.lotteryLimit = 'Please enter a value greater than 0 or disable';\n    } else if (Number(form.value.distribution.lotteryLimit) >= 100) {\n      errors.lotteryLimit = 'Please enter a value less than 100%';\n    } else if (Number(form.value.distribution.lotteryLimit) < 0.01) {\n      errors.lotteryLimit = 'Please enter a value greater than 0.01%';\n    }\n  }\n\n  if (!formToken.value) {\n    errors.token = 'Please select a token';\n  } else if (\n    isExcludedToken(form.value.network, formToken.value.contractAddress)\n  ) {\n    errors.token = 'This token is not allowed';\n  }\n\n  if (!form.value.amount) {\n    errors.amount = 'Please enter a value';\n  } else if (amountParsed.value.lte(0)) {\n    errors.amount = 'Please enter a value greater than 0';\n  }\n\n  if (formToken.value) {\n    const balance = BigNumber.from(account.value.balance || '0');\n\n    const amount = amountWithTokenFeeParsed.value;\n\n    if (balance.lt(amount) && !isLoading.value) {\n      errors.balance = 'Insufficient balance';\n    }\n  }\n\n  if (form.value.distribution.type === 'lottery') {\n    if (!form.value.distribution.numWinners) {\n      errors.numWinners = 'Please enter a value';\n    } else if (Number(form.value.distribution.numWinners) <= 0) {\n      errors.numWinners = 'Please enter a value greater than 0';\n    } else if (Number(form.value.distribution.numWinners) > 1e7) {\n      errors.numWinners = 'Please enter a value less than 10,000,000';\n    } else if (!Number.isInteger(Number(form.value.distribution.numWinners))) {\n      errors.numWinners = 'Please enter a whole number without any decimals';\n    }\n  }\n\n  return errors;\n});\n\nconst formErrorMessages = computed(() => {\n  return showFormErrors.value ? formValidation.value : {};\n});\n\nfunction retryCreation() {\n  createStatus.value = '';\n  handleCreate();\n}\n\nconst amountWithTokenFeeParsed = computed(() => {\n  return amountParsed.value.add(tokenFeeParsed.value).toString();\n});\n\nconst requireApproval = computed(() =>\n  BigNumber.from(account.value.allowance || '0').lt(\n    amountWithTokenFeeParsed.value\n  )\n);\n\nfunction setErrorStatus(error: string) {\n  if (error?.includes('user rejected transaction')) {\n    createStatus.value = '';\n  } else {\n    createStatus.value = 'error';\n  }\n}\n\nasync function loadFees() {\n  try {\n    loadingFees.value = true;\n    const provider = getProvider(form.value.network);\n    const response = await getFees(provider, form.value.network);\n    ethFee.value = formatEther(response.ethFee);\n    tokenFeePercent.value = (Number(response.tokenFeePercent) / 100).toString();\n  } catch (e: any) {\n    console.error('Error loading fees:', e);\n  } finally {\n    loadingFees.value = false;\n  }\n}\n\nasync function handleApproval() {\n  createStatus.value = 'approve';\n\n  try {\n    approveTx.value = await sendApprovalTransaction(\n      auth.web3,\n      formToken.value!.contractAddress,\n      BOOST_CONTRACTS[form.value.network],\n      amountWithTokenFeeParsed.value\n    );\n\n    createStatus.value = 'pending';\n\n    await approveTx.value.wait();\n\n    await updateAccount(\n      formToken.value!.contractAddress,\n      form.value.network,\n      BOOST_CONTRACTS[form.value.network]\n    );\n    handleCreate();\n  } catch (e: any) {\n    console.error('Approval error:', e);\n    setErrorStatus(e.message);\n  } finally {\n    approveTx.value = undefined;\n  }\n}\n\nasync function handleCreate() {\n  if (!web3Account.value) {\n    modalAccountOpen.value = true;\n    return;\n  }\n  if (!isValidForm.value) {\n    showFormErrors.value = true;\n    return;\n  }\n  if (isWrongNetwork.value) {\n    modalWrongNetworkOpen.value = true;\n    return;\n  }\n  if (requireApproval.value) {\n    handleApproval();\n    return;\n  }\n\n  try {\n    createStatus.value = 'pinning';\n\n    const { cid: ipfsHash } = await pinGraph(strategy.value);\n    if (!ipfsHash) throw new Error('Error pinning the strategy');\n\n    createStatus.value = 'confirm';\n    createTx.value = await createBoost(\n      auth.web3,\n      form.value.network,\n      ethFee.value,\n      {\n        strategyURI: `ipfs://${ipfsHash}`,\n        token: formToken.value!.contractAddress,\n        amount: amountWithTokenFeeParsed.value,\n        guard: SNAPSHOT_GUARD_ADDRESS,\n        start: proposal.value.end,\n        end: proposal.value.end + TWO_WEEKS,\n        owner: web3Account.value\n      }\n    );\n\n    createStatus.value = 'pending';\n\n    await createTx.value.wait();\n    createStatus.value = 'success';\n  } catch (e: any) {\n    console.error('Create boost error:', e);\n    setErrorStatus(e.message);\n  } finally {\n    createTx.value = undefined;\n  }\n}\n\nfunction closeStatusModal() {\n  if (createStatus.value === 'success')\n    router.push({ name: 'spaceProposal', params: { id: proposal.value.id } });\n  createStatus.value = '';\n}\n\nwatchEffect(async () => {\n  const id = route.params.proposalId;\n\n  proposal.value = await getProposal(id);\n  if (proposal.value) return;\n\n  router.push({ name: 'spaceProposals', params: { key: props.space.id } });\n});\n\nwatch(\n  [() => form.value.distribution.type],\n  () => {\n    if (!form.value.distribution.hasWeightedLimit) {\n      form.value.distribution.weightedLimit = '';\n    }\n    if (!form.value.distribution.hasLotteryLimit) {\n      form.value.distribution.lotteryLimit = '';\n    }\n    if (form.value.distribution.type === 'lottery') {\n      form.value.distribution.hasWeightedLimit = false;\n      form.value.distribution.weightedLimit = '';\n      form.value.distribution.numWinners ??= '1';\n    }\n    if (form.value.distribution.type === 'weighted') {\n      form.value.distribution.hasLotteryLimit = false;\n      form.value.distribution.lotteryLimit = '';\n      form.value.distribution.numWinners = undefined;\n    }\n  },\n  { deep: true }\n);\n\nwatch(\n  [web3Account, formToken, () => form.value.network],\n  () => {\n    if (!web3Account.value || !formToken.value) return;\n    updateAccount(\n      formToken.value.contractAddress,\n      form.value.network,\n      BOOST_CONTRACTS[form.value.network]\n    );\n  },\n  { immediate: true }\n);\n\nwatch(\n  () => form.value.network,\n  async () => {\n    loadFees();\n  },\n  { immediate: true }\n);\n\nwatch(\n  [web3Account, () => form.value.network],\n  () => {\n    form.value.token = undefined;\n    loadBalances(web3Account.value, form.value.network);\n  },\n  { immediate: true }\n);\n</script>\n\n<template>\n  <LoadingSpinner v-if=\"!proposal\" class=\"overlay big\" />\n\n  <div v-else>\n    <SpaceBreadcrumbs :space=\"space\" :proposal=\"proposal\" />\n    <TheLayout class=\"pt-[12px] px-[20px] mb-[124px] md:mb-0\">\n      <template #content-left>\n        <h1 class=\"leading-[44px]\">Create a new boost</h1>\n        <p class=\"text-md leading-5\">\n          Reward voters on this proposal and drive engagement\n        </p>\n\n        <div class=\"space-y-3 mt-[20px] md:mt-4\">\n          <SpaceBoostCardProposal :proposal=\"proposal\" :space=\"space\" />\n\n          <SpaceBoostDeposit\n            v-model:formToken=\"form.token\"\n            v-model:formNetwork=\"form.network\"\n            v-model:formAmount=\"form.amount\"\n            :tokens=\"tokens\"\n            :loading-balances=\"loadingBalances\"\n            :amount-with-token-fee-parsed=\"amountWithTokenFeeParsed\"\n            :form-error-messages=\"formErrorMessages\"\n            :token-fee-percent=\"tokenFeePercent\"\n            :token-fee-parsed=\"tokenFeeParsed\"\n            :eth-fee=\"ethFee\"\n          />\n\n          <TuneBlock>\n            <template #header>\n              <TuneBlockHeader\n                title=\"Distribution\"\n                sub-title=\"Define how the reward pool is distributed to eligible voters.\"\n              />\n            </template>\n            <div class=\"space-y-3\">\n              <div class=\"flex flex-col sm:flex-row gap-[12px]\">\n                <button\n                  v-for=\"item in DISTRIBUTION_TYPE_ITEMS\"\n                  :key=\"item.value\"\n                  type=\"button\"\n                  class=\"rounded-xl p-3 cursor-pointer border sm:w-1/2 text-left\"\n                  :class=\"{\n                    'border-skin-link': form.distribution.type === item.value\n                  }\"\n                  @click=\"form.distribution.type = item.value\"\n                >\n                  <div\n                    class=\"flex justify-between font-semibold text-skin-heading\"\n                  >\n                    {{ item.name }}\n                    <i-ho-check v-if=\"form.distribution.type === item.value\" />\n                  </div>\n                  <div class=\"leading-[18px] pr-4\">\n                    {{ item.description }}\n                  </div>\n                </button>\n              </div>\n              <template v-if=\"form.distribution.type === 'weighted'\">\n                <TuneSwitch\n                  v-model=\"form.distribution.hasWeightedLimit\"\n                  label=\"Define a maximum reward per voter\"\n                />\n\n                <TuneInput\n                  v-if=\"form.distribution.hasWeightedLimit\"\n                  v-model=\"form.distribution.weightedLimit\"\n                  label=\"Max reward\"\n                  type=\"number\"\n                  placeholder=\"100\"\n                  always-show-error\n                  :error=\"formErrorMessages?.weightedLimit\"\n                  @click=\"form.distribution.hasWeightedLimit = true\"\n                >\n                  <template #after>\n                    <div class=\"-mr-[8px]\">{{ formToken?.symbol }}</div>\n                  </template>\n                </TuneInput>\n              </template>\n\n              <template v-if=\"form.distribution.type === 'lottery'\">\n                <TuneInput\n                  v-model=\"form.distribution.numWinners\"\n                  label=\"Number of winners\"\n                  type=\"number\"\n                  placeholder=\"5\"\n                  always-show-error\n                  :error=\"formErrorMessages?.numWinners\"\n                >\n                  <template #after>\n                    <div class=\"-mr-2\">Winners</div>\n                  </template>\n                </TuneInput>\n\n                <TuneSwitch\n                  v-model=\"form.distribution.hasLotteryLimit\"\n                  label=\"Define a maximum chance to win\"\n                  hint=\"As the chance to win is based on voting power, setting a limit will ensure a more fair lottery. If set to 1% and the voter has 5% of the voted voting power, the voter will only have a 1% chance to win. If there are not enough voters, the chance to win will be increased to reach a total of 100%.\"\n                />\n\n                <TuneInput\n                  v-if=\"form.distribution.hasLotteryLimit\"\n                  v-model=\"form.distribution.lotteryLimit\"\n                  label=\"Max chance to win\"\n                  type=\"number\"\n                  placeholder=\"1\"\n                  always-show-error\n                  :error=\"formErrorMessages?.lotteryLimit\"\n                  @click=\"form.distribution.hasLotteryLimit = true\"\n                >\n                  <template #after>\n                    <div class=\"-mr-[8px]\">%</div>\n                  </template>\n                </TuneInput>\n              </template>\n            </div>\n            <TuneBlockFooter v-if=\"form.distribution.type === 'lottery'\">\n              <div class=\"flex justify-between\">\n                Reward per winner\n                <div class=\"text-skin-heading\">\n                  {{ amountPerWinner }}\n                  {{ formToken?.symbol }}\n                </div>\n              </div>\n            </TuneBlockFooter>\n          </TuneBlock>\n\n          <TuneBlock>\n            <template #header>\n              <TuneBlockHeader\n                title=\"Eligibility\"\n                sub-title=\"Choose an option that best incentivises meaningful participation.\"\n              />\n            </template>\n\n            <TuneSelect\n              v-model=\"form.eligibility.choice\"\n              :items=\"eligibilityOptions\"\n              label=\"Eligible to\"\n            />\n            <TuneBlockFooter v-if=\"!bribeEnabled\">\n              <BaseMessage level=\"info\">\n                <template v-if=\"!space.boost.bribeEnabled\">\n                  Selecting a specific choice is disabled for the\n                  <span class=\"font-semibold\">\n                    {{ space.name }}\n                  </span>\n                  space. Please enable strategic incentivization in the space\n                  settings to enable this feature.\n                </template>\n                <template v-else-if=\"proposal.privacy === 'shutter'\">\n                  Strategic incentivization is disabled for proposal with\n                  shutter on.\n                </template>\n                <template\n                  v-else-if=\"\n                    !['basic', 'single-choice'].includes(proposal.type)\n                  \"\n                >\n                  Strategic incentivization is available only for basic and\n                  single choice voting type.\n                </template>\n              </BaseMessage>\n            </TuneBlockFooter>\n          </TuneBlock>\n        </div>\n      </template>\n\n      <template #sidebar-right>\n        <TheActionbar break-point=\"md\">\n          <div\n            class=\"md:border rounded-xl p-3 md:mt-3 lg:mt-0 lg:fixed lg:w-[320px]\"\n          >\n            <h4 class=\"leading-5 mb-1 hidden md:block\">Create boost</h4>\n            <p class=\"text-md leading-5 hidden md:block\">\n              Once a boost is created, it can no longer be modified.\n            </p>\n            <div class=\"flex justify-left md:mt-3 leading-5\">\n              <i-ho-exclamation-circle class=\"inline-block text-sm mr-1\" />\n\n              The boost contract is not audited.\n            </div>\n            <TuneButton\n              :loading=\"isLoading\"\n              :disabled=\"!space.boost.enabled || proposal.state === 'closed'\"\n              primary\n              class=\"w-full mt-3\"\n              @click=\"handleCreate\"\n            >\n              Create\n            </TuneButton>\n            <div\n              v-if=\"isEndingSoon || proposal.state === 'closed'\"\n              class=\"text-boost flex items-center gap-1 justify-center mt-[6px]\"\n            >\n              <template v-if=\"proposal.state === 'closed'\">\n                <i-ho-exclamation-circle />\n                This proposal is closed\n              </template>\n              <template v-else-if=\"!space.boost.enabled\">\n                <i-ho-exclamation-circle />\n                Boost is not enabled in this space\n              </template>\n              <template v-else-if=\"isEndingSoon\">\n                <i-ho-clock />\n                Proposal\n                {{\n                  getRelativeProposalPeriod(\n                    'active',\n                    proposal.start,\n                    proposal.end\n                  )\n                }}\n              </template>\n            </div>\n          </div>\n        </TheActionbar>\n      </template>\n    </TheLayout>\n    <ModalTransactionStatus\n      v-if=\"createStatusModalConfig\"\n      open\n      :variant=\"createStatusModalConfig.variant\"\n      :title=\"createStatusModalConfig.title\"\n      :subtitle=\"createStatusModalConfig.subtitle\"\n      :network=\"form.network\"\n      @close=\"closeStatusModal\"\n      @try-again=\"retryCreation\"\n    />\n\n    <ModalWrongNetwork\n      :open=\"modalWrongNetworkOpen\"\n      :network=\"form.network\"\n      @network-changed=\"handleCreate\"\n      @close=\"modalWrongNetworkOpen = false\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/views/SpaceCreate.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport { PROPOSAL_QUERY } from '@/helpers/queries';\nimport { proposalValidation } from '@/helpers/snapshot';\nimport Plugin from '@/plugins/safeSnap';\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { PROPOSAL_BODY_LIMITS } from '@/helpers/constants';\n\nconst safeSnapPlugin = new Plugin();\n\nenum Step {\n  CONTENT,\n  VOTING,\n  PLUGINS\n}\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n}>();\n\nconst spaceType = computed(() => (props.space.turbo ? 'turbo' : 'default'));\nconst bodyCharactersLimit = computed(\n  () => PROPOSAL_BODY_LIMITS[spaceType.value]\n);\n\nuseMeta({\n  title: {\n    key: 'metaInfo.space.create.title',\n    params: {\n      space: props.space.name\n    }\n  },\n  description: {\n    key: 'metaInfo.space.create.description'\n  }\n});\n\nconst { notify } = useFlashNotification();\nconst router = useRouter();\nconst route = useRoute();\nconst { t } = useI18n();\nconst auth = getInstance();\nconst { domain } = useApp();\nconst { web3, web3Account } = useWeb3();\nconst { send, isSending } = useClient();\nconst { pluginIndex } = usePlugins();\nconst { modalAccountOpen } = useModal();\nconst { modalTermsOpen, termsAccepted, acceptTerms } = useTerms(props.space.id);\nconst { isGnosisAndNotSpaceNetwork } = useGnosis(props.space);\nconst { isSnapshotLoading } = useSnapshot();\nconst { apolloQuery, queryLoading } = useApolloQuery();\nconst { containsShortUrl } = useShortUrls();\n\nconst { isValid: isValidSpaceSettings, populateForm } = useFormSpaceSettings(\n  'settings',\n  {\n    spaceType: spaceType.value\n  }\n);\n\nconst {\n  form,\n  formDraft,\n  userSelectedDateEnd,\n  sourceProposalLoaded,\n  sourceProposal,\n  validationErrors,\n  resetForm\n} = useFormSpaceProposal({\n  spaceType: spaceType.value\n});\n\nconst isValidAuthor = ref(false);\nconst validationLoading = ref(false);\nconst preview = ref(false);\nconst hasAuthorValidationFailed = ref(false);\nconst timeSeconds = ref(Number((Date.now() / 1e3).toFixed()));\nconst currentStep = ref(Step.CONTENT);\n\nconst proposal = computed(() =>\n  Object.assign(form.value, { choices: form.value.choices })\n);\n\nconst isEditing = computed(\n  () => !!(sourceProposal.value && route.query.editing)\n);\n\ntype DateRange = {\n  dateStart: number;\n  dateEnd?: number;\n};\n\nfunction sanitizeDateRange({ dateStart, dateEnd }: DateRange): DateRange {\n  const { delay = 0, period = 0 } = props.space?.voting ?? {};\n  const threeDays = 259200;\n  const currentTimestamp = Math.floor(Date.now() / 1000);\n\n  const sanitizedDateStart = delay\n    ? timeSeconds.value + delay\n    : Math.max(dateStart, currentTimestamp);\n\n  if (typeof dateEnd === 'undefined') {\n    return { dateStart: sanitizedDateStart };\n  }\n\n  if (period) {\n    const sanitizedDateEnd = sanitizedDateStart + period;\n    return { dateStart: sanitizedDateStart, dateEnd: sanitizedDateEnd };\n  }\n\n  if (userSelectedDateEnd.value || sourceProposalLoaded.value) {\n    return { dateStart: sanitizedDateStart, dateEnd };\n  }\n\n  return {\n    dateStart: sanitizedDateStart,\n    dateEnd: sanitizedDateStart + threeDays\n  };\n}\n\nconst dateStart = computed(() => {\n  const { dateStart } = sanitizeDateRange({ dateStart: form.value.start });\n  return dateStart;\n});\n\nconst dateEnd = computed(() => {\n  const { dateEnd } = sanitizeDateRange({\n    dateStart: form.value.start,\n    dateEnd: form.value.end\n  });\n  return dateEnd;\n});\n\nconst isFormValid = computed(() => {\n  const isSafeSnapPluginValid = form.value.metadata.plugins?.safeSnap\n    ? form.value.metadata.plugins.safeSnap.valid\n    : true;\n\n  const isOsnapPluginValid = (() => {\n    const safes = form.value.metadata.plugins.oSnap?.safes;\n    if (!safes) {\n      //  not using osnap plugin\n      return true;\n    }\n    if (safes.length && safes.some(safe => !(safe.transactions.length > 0))) {\n      //  using osnap, but some have no transactions\n      return false;\n    }\n    if (\n      safes &&\n      !safes.every(safe => safe.transactions.every(tx => tx.isValid))\n    ) {\n      //  all transactions must be valid\n      return false;\n    }\n    return true;\n  })();\n\n  return (\n    !web3.value.authLoading &&\n    isOsnapPluginValid &&\n    !isSending.value &&\n    form.value.body.length <= bodyCharactersLimit.value &&\n    dateEnd.value &&\n    dateEnd.value > dateStart.value &&\n    form.value.snapshot &&\n    form.value.choices.length >= 1 &&\n    !form.value.choices.some((a, i) => a.text === '' && i === 0) &&\n    isValidAuthor.value &&\n    isSafeSnapPluginValid\n  );\n});\n\nconst formContainsShortUrl = computed(() => {\n  const { body, name, discussion } = form.value;\n\n  return (\n    containsShortUrl(body) ||\n    containsShortUrl(name) ||\n    containsShortUrl(discussion)\n  );\n});\n\nconst stepIsValid = computed(() => {\n  if (\n    currentStep.value === Step.CONTENT &&\n    form.value.name &&\n    form.value.body.length <= bodyCharactersLimit.value &&\n    isValidAuthor.value &&\n    !validationErrors.value.name &&\n    !validationErrors.value.body &&\n    !validationErrors.value.discussion &&\n    !formContainsShortUrl.value\n  )\n    return true;\n  else if (\n    currentStep.value === Step.VOTING &&\n    dateEnd.value &&\n    dateEnd.value > dateStart.value &&\n    form.value.snapshot &&\n    !form.value.choices.some((a, i) => a.text === '' && i === 0)\n  )\n    return true;\n  else return false;\n});\n\nconst isMember = computed(() => {\n  function findAccount(object: string[], account: string) {\n    return object.map(a => a.toLowerCase()).includes(account.toLowerCase());\n  }\n  return (\n    findAccount(props.space.members, web3Account.value) ||\n    findAccount(props.space.admins, web3Account.value) ||\n    findAccount(props.space.moderators, web3Account.value) ||\n    false\n  );\n});\n\nconst needsPluginConfigs = computed(() =>\n  Object.keys(props.space?.plugins ?? {}).some(\n    pluginKey => pluginIndex[pluginKey]?.defaults?.proposal\n  )\n);\n\nconst validationName = computed(() => props.space.validation?.name ?? 'basic');\n\nfunction getFormattedForm() {\n  const clonedForm = clone(form.value);\n  clonedForm.snapshot = Number(form.value.snapshot);\n  clonedForm.choices = form.value.choices\n    .map(choice => choice.text)\n    .filter(choiceText => choiceText.length > 0);\n  updateTime();\n  const { dateStart: sanitizedDateStart, dateEnd: sanitizedDateEnd } =\n    sanitizeDateRange({\n      dateStart: dateStart.value,\n      dateEnd: dateEnd.value\n    });\n  clonedForm.start = sanitizedDateStart;\n  clonedForm.end = sanitizedDateEnd;\n  clonedForm.privacy =\n    props.space.voting.privacy === 'any' ? '' : props.space.voting.privacy;\n  return clonedForm;\n}\n\nconst { resetSpaceProposals } = useProposals();\n\nfunction handleSubmit() {\n  if (!termsAccepted && props.space.terms) return (modalTermsOpen.value = true);\n  if (isEditing.value) return handleUpdate();\n  handleCreate();\n}\n\nasync function handleCreate() {\n  const formattedForm = getFormattedForm();\n  const result = await send(props.space, 'create-proposal', formattedForm);\n  if (result.id) {\n    resetSpaceProposals();\n    if (!result.ipfs) notify(['green', t('notify.waitingForOtherSigners')]);\n    else notify(['green', t('notify.proposalCreated')]);\n    resetForm();\n    router.push({\n      name: 'spaceProposal',\n      params: {\n        key: props.space.id,\n        id: result.id\n      }\n    });\n  }\n}\n\nasync function handleUpdate() {\n  const formattedForm = getFormattedForm();\n  formattedForm.id = sourceProposal.value;\n  const result = await send(props.space, 'update-proposal', formattedForm);\n  if (result.id) {\n    resetSpaceProposals();\n    if (!result.ipfs) notify(['green', t('notify.waitingForOtherSigners')]);\n    else notify(['green', t('notify.proposalUpdated')]);\n    resetForm();\n    router.push({\n      name: 'spaceProposal',\n      params: {\n        key: props.space.id,\n        id: sourceProposal.value\n      }\n    });\n  }\n}\n\nfunction setSourceProposal(proposal) {\n  const { plugins } = proposal;\n\n  form.value = {\n    name: proposal.title,\n    body: proposal.body,\n    discussion: proposal.discussion,\n    choices: proposal.choices,\n    labels: proposal.labels,\n    start: proposal.start,\n    end: proposal.end,\n    snapshot: proposal.snapshot,\n    type: proposal.type,\n    metadata: { plugins }\n  };\n\n  form.value.choices = proposal.choices.map((text, key) => ({\n    key,\n    text\n  }));\n}\n\nasync function loadSourceProposal() {\n  const proposal = await apolloQuery(\n    {\n      query: PROPOSAL_QUERY,\n      variables: {\n        id: sourceProposal.value\n      }\n    },\n    'proposal'\n  );\n\n  setSourceProposal(proposal);\n  sourceProposalLoaded.value = true;\n}\n\nfunction nextStep() {\n  if (formContainsShortUrl.value) return;\n  // skip transaction page if user has osnap, but chosen not to use it for this vote\n  if (shouldSkipTransactions()) return;\n  currentStep.value++;\n}\n\nfunction previousStep() {\n  currentStep.value--;\n}\n\nfunction updateTime() {\n  timeSeconds.value = Number((Date.now() / 1e3).toFixed());\n}\n\nasync function validateAuthor() {\n  isValidAuthor.value = false;\n  if (web3Account.value && auth.isAuthenticated.value) {\n    if (isMember.value) {\n      isValidAuthor.value = true;\n      return;\n    }\n\n    if (props.space.filters.onlyMembers) {\n      isValidAuthor.value = false;\n      return;\n    }\n\n    if (\n      props.space.validation.name === 'any' ||\n      (props.space.validation.name === 'basic' &&\n        !props.space.filters.minScore &&\n        !props.space.validation.params?.minScore)\n    ) {\n      isValidAuthor.value = true;\n      return;\n    }\n\n    try {\n      validationLoading.value = true;\n      const validationRes = await proposalValidation(\n        props.space,\n        web3Account.value\n      );\n\n      isValidAuthor.value = validationRes;\n    } catch (e) {\n      hasAuthorValidationFailed.value = true;\n      console.warn(e);\n    } finally {\n      validationLoading.value = false;\n    }\n  }\n}\n\nwatch(\n  () => web3Account.value,\n  () => {\n    validateAuthor();\n  },\n  { immediate: true }\n);\n\nconst hasOsnapPlugin = computed(() => {\n  return !!props.space?.plugins?.oSnap;\n});\nconst shouldUseOsnap = ref(false);\n\nfunction toggleShouldUseOsnap() {\n  shouldUseOsnap.value = !shouldUseOsnap.value;\n}\n\n// We need to know if the space is using osnap, this will change what types of voting we can do\n// We also need to know if the user plans to use osnap\nconst legacyOsnap = ref<{\n  enabled: boolean;\n  selection: boolean;\n  valid: boolean;\n}>({\n  selection: false,\n  enabled: false,\n  valid: false\n});\n\n// Skip transaction page if osnap is enabled, its not selected to be used, and we are on the voting page\nfunction shouldSkipTransactions() {\n  if (currentStep.value !== Step.VOTING) return false;\n  if (\n    legacyOsnap.value.enabled &&\n    legacyOsnap.value.valid &&\n    !legacyOsnap.value.selection\n  )\n    return true;\n  if (hasOsnapPlugin.value && !shouldUseOsnap.value) return true;\n  return false;\n}\n\nfunction handleLegacyOsnapToggle() {\n  legacyOsnap.value.selection = !legacyOsnap.value.selection;\n  shouldUseOsnap.value = !shouldUseOsnap.value;\n}\n\nonMounted(async () => {\n  const network = props?.space?.plugins?.safeSnap?.safes?.[0]?.network;\n  const umaAddress = props?.space?.plugins?.safeSnap?.safes?.[0]?.umaAddress;\n  if (network && umaAddress) {\n    // this is how we check if osnap is enabled and valid.\n    legacyOsnap.value.enabled = true;\n    legacyOsnap.value.valid =\n      (await safeSnapPlugin.validateUmaModule(network, umaAddress)) === 'uma';\n  }\n  if (sourceProposal.value && !sourceProposalLoaded.value)\n    await loadSourceProposal();\n\n  if (!sourceProposal.value) {\n    form.value.name = formDraft.value.name;\n    form.value.body = formDraft.value.body;\n  }\n\n  if (\n    !!props.space?.template &&\n    !sourceProposal.value &&\n    !formDraft.value.isBodySet\n  ) {\n    form.value.body = props.space.template;\n  }\n});\n\nonBeforeRouteLeave(async () => {\n  if (isEditing.value) {\n    resetForm();\n  }\n});\n\nonMounted(() => populateForm(props.space));\n</script>\n\n<template>\n  <TheLayout v-bind=\"$attrs\">\n    <template #content-left>\n      <div\n        v-if=\"currentStep === Step.CONTENT\"\n        class=\"mb-3 overflow-hidden px-4 md:px-0\"\n      >\n        <ButtonBack\n          @click=\"\n            router.push(domain ? { path: '/' } : { name: 'spaceProposals' })\n          \"\n        />\n      </div>\n      <SpaceCreateWarnings\n        v-if=\"!validationLoading\"\n        :space=\"space\"\n        :validation-failed=\"hasAuthorValidationFailed\"\n        :is-valid-author=\"isValidAuthor\"\n        :is-valid-space=\"isValidSpaceSettings\"\n        :validation-name=\"validationName\"\n        :contains-short-url=\"formContainsShortUrl\"\n        data-testid=\"create-proposal-connect-wallet-warning\"\n      />\n\n      <!-- Step 1 -->\n      <SpaceCreateContent\n        v-if=\"currentStep === Step.CONTENT\"\n        :space=\"space\"\n        :preview=\"preview\"\n        :body-limit=\"bodyCharactersLimit\"\n      />\n\n      <!-- Step 2 -->\n      <SpaceCreateVoting\n        v-else-if=\"currentStep === Step.VOTING\"\n        :space=\"space\"\n        :date-start=\"dateStart\"\n        :date-end=\"dateEnd\"\n        :has-osnap-plugin=\"hasOsnapPlugin\"\n        :should-use-osnap=\"shouldUseOsnap\"\n        :legacy-osnap=\"legacyOsnap\"\n        :is-editing=\"isEditing\"\n        @toggle-should-use-osnap=\"toggleShouldUseOsnap\"\n        @legacy-osnap-toggle=\"handleLegacyOsnapToggle\"\n      />\n\n      <!-- Step 3 (only when plugins) -->\n      <SpaceCreatePlugins\n        v-else\n        v-model=\"form.metadata.plugins\"\n        :proposal=\"proposal\"\n        :space=\"space\"\n      />\n    </template>\n    <template #sidebar-right>\n      <BaseBlock class=\"lg:fixed lg:w-[320px]\">\n        <TuneButton\n          v-if=\"currentStep === Step.CONTENT\"\n          class=\"mb-2 block w-full\"\n          @click=\"preview = !preview\"\n        >\n          {{ preview ? $t('create.edit') : $t('create.preview') }}\n        </TuneButton>\n        <TuneButton v-else class=\"mb-2 block w-full\" @click=\"previousStep\">\n          {{ $t('back') }}\n        </TuneButton>\n        <TuneButton\n          v-if=\"\n            currentStep === Step.PLUGINS ||\n            (!needsPluginConfigs && currentStep === Step.VOTING) ||\n            shouldSkipTransactions()\n          \"\n          :disabled=\"!isFormValid\"\n          :loading=\"isSending || queryLoading || isSnapshotLoading\"\n          class=\"block w-full\"\n          primary\n          data-testid=\"create-proposal-publish-button\"\n          @click=\"handleSubmit\"\n        >\n          {{ isEditing ? 'Save changes' : $t('create.publish') }}\n        </TuneButton>\n        <TuneButton\n          v-else\n          class=\"block w-full\"\n          :loading=\"validationLoading || isSnapshotLoading\"\n          :disabled=\"\n            (!stepIsValid && !!web3Account) ||\n            web3.authLoading ||\n            hasAuthorValidationFailed ||\n            validationLoading ||\n            isGnosisAndNotSpaceNetwork ||\n            space.hibernated ||\n            !isValidSpaceSettings\n          \"\n          primary\n          :data-testid=\"\n            web3Account\n              ? 'create-proposal-continue-button'\n              : 'create-proposal-connect-wallet-button'\n          \"\n          @click=\"web3Account ? nextStep() : (modalAccountOpen = true)\"\n        >\n          {{ web3Account ? $t('create.continue') : $t('connectWallet') }}\n        </TuneButton>\n      </BaseBlock>\n    </template>\n  </TheLayout>\n  <teleport to=\"#modal\">\n    <ModalTerms\n      :open=\"modalTermsOpen\"\n      :space=\"space\"\n      :action=\"$t('modalTerms.actionCreate')\"\n      @close=\"modalTermsOpen = false\"\n      @accept=\"acceptTerms(), handleSubmit()\"\n    />\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/views/SpaceDelegate.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport { useConfirmDialog } from '@vueuse/core';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { DelegatingTo } from '../helpers/delegationV2/types';\nimport { DelegationTypes } from '@/helpers/delegationV2';\nimport { getAddress } from '@ethersproject/address';\n\nconst INITIAL_STATEMENT = {\n  about: '',\n  statement: '',\n  discourse: '',\n  network: 'INACTIVE',\n  status: 's'\n};\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n}>();\n\nconst { web3Account } = useWeb3();\nconst { formatCompactNumber, formatNumber } = useIntl();\nconst { getProfile } = useProfiles();\nconst { saveStatement, savingStatement } = useStatement();\nconst route = useRoute();\n\nconst {\n  loadDelegate,\n  fetchDelegatingTo,\n  delegate,\n  delegatesStats,\n  isLoadingDelegate,\n  isLoadingDelegatingTo,\n  hasDelegationPortal\n} = useDelegates(props.space);\nconst {\n  reloadStatement,\n  getStatement,\n  formatPercentageNumber,\n  loadingStatements\n} = useStatement();\nconst { modalAccountOpen } = useModal();\n\nconst showEdit = ref(false);\nconst showDelegateModal = ref(false);\nconst web3AccountDelegatingTo = ref<DelegatingTo | undefined>();\nconst fetchedStatement = ref(INITIAL_STATEMENT);\nconst statementForm = ref(INITIAL_STATEMENT);\n\nconst address = computed(() => route.params.address as string);\n\nconst edited = computed(() => {\n  return (\n    fetchedStatement.value?.about !== statementForm.value?.about ||\n    fetchedStatement.value?.statement !== statementForm.value?.statement\n  );\n});\n\nconst isLoggedUser = computed(() => {\n  return web3Account.value?.toLowerCase() === address.value?.toLowerCase();\n});\n\nconst showUndelegate = computed(() => {\n  return (\n    web3AccountDelegatingTo.value?.[0]?.toLowerCase() ===\n    address.value?.toLowerCase()\n  );\n});\n\nconst delegateStats = computed(() => {\n  return delegatesStats.value?.[getAddress(address.value)];\n});\n\nconst delegatorItems = computed(() => {\n  return [\n    {\n      label: props.space.symbol,\n      value: formatCompactNumber(Number(delegate.value?.delegatedVotes || 0)),\n      tooltip: `${formatNumber(\n        Number(delegate.value?.delegatedVotes)\n      )} (${formatPercentageNumber(Number(delegate.value?.votesPercentage))})`\n    },\n    {\n      label: 'Delegators',\n      value: formatCompactNumber(\n        Number(delegate.value?.tokenHoldersRepresentedAmount || 0)\n      ),\n      tooltip: formatPercentageNumber(delegate.value?.delegatorsPercentage || 0)\n    },\n    {\n      label: 'Proposals',\n      value: formatCompactNumber(delegateStats.value?.proposals || 0),\n      tooltip: null\n    },\n    {\n      label: 'Votes',\n      value: formatCompactNumber(delegateStats.value?.votes || 0),\n      tooltip: null\n    }\n  ];\n});\n\nasync function saveStatementForm() {\n  if (!showEdit.value) showEdit.value = true;\n  try {\n    await saveStatement(props.space.id, statementForm.value);\n    reloadStatement(props.space.id, address.value);\n    fetchedStatement.value = clone(statementForm.value);\n    showEdit.value = false;\n  } catch (e) {\n    console.log(e);\n  }\n}\n\nasync function loadDelegatingTo() {\n  web3AccountDelegatingTo.value = await fetchDelegatingTo(web3Account.value);\n}\n\nasync function handleReload() {\n  loadDelegate(address.value);\n  loadDelegatingTo();\n}\n\nasync function init() {\n  loadDelegatingTo();\n  await loadDelegate(address.value);\n\n  await reloadStatement(props.space.id, address.value);\n  statementForm.value = getStatement(address.value);\n  fetchedStatement.value = getStatement(address.value);\n}\n\nfunction handleClickDelegate() {\n  if (!web3Account.value) {\n    modalAccountOpen.value = true;\n    return;\n  }\n\n  showDelegateModal.value = true;\n}\n\nwatch(\n  address,\n  addr => {\n    showEdit.value = false;\n\n    if (addr) init();\n  },\n  {\n    immediate: true\n  }\n);\n\nwatch(web3Account, async () => {\n  loadDelegatingTo();\n});\n\nconst {\n  isRevealed: isConfirmLeaveOpen,\n  reveal: openConfirmLeave,\n  confirm: confirmLeave,\n  cancel: cancelLeave\n} = useConfirmDialog();\n\nonBeforeRouteLeave(async () => {\n  if (edited.value) {\n    const { data } = await openConfirmLeave();\n    if (!data) return false;\n  }\n});\n</script>\n\n<template>\n  <div class=\"mb-[80px] md:mb-0\">\n    <SpaceBreadcrumbs :space=\"space\" />\n\n    <BaseContainer v-if=\"isLoggedUser\" class=\"pb-2 pt-3 lg:py-[20px]\">\n      <ButtonSwitch\n        v-model=\"showEdit\"\n        :state1=\"{\n          name: 'Preview',\n          value: false\n        }\"\n        :state2=\"{\n          name: 'Write',\n          value: true\n        }\"\n        class=\"w-full md:w-[180px]\"\n      />\n    </BaseContainer>\n\n    <div>\n      <SpaceDelegateEdit\n        v-if=\"showEdit\"\n        :space=\"space\"\n        :address=\"address\"\n        :statement=\"statementForm\"\n        :edited=\"edited\"\n        :saving=\"savingStatement\"\n        class=\"mt-[16px]\"\n        @save=\"saveStatementForm\"\n        @update:about=\"statementForm.about = $event\"\n        @update:statement=\"statementForm.statement = $event\"\n      />\n\n      <TheLayout v-else reverse class=\"pt-[12px]\">\n        <template #content-left>\n          <div class=\"px-4 md:px-0\">\n            <LoadingPage v-if=\"isLoadingDelegate || loadingStatements\" slim />\n            <div v-else class=\"space-y-[40px]\">\n              <div>\n                <h3 class=\"mb-2 mt-0\">About</h3>\n                <p\n                  v-if=\"statementForm.about\"\n                  class=\"text-[19px] text-skin-heading sm:text-[22px] sm:leading-7\"\n                >\n                  {{ statementForm.about }}\n                </p>\n                <div v-else>No about provided yet</div>\n              </div>\n\n              <div>\n                <h3 class=\"m-0 mb-2\">Statement</h3>\n                <BaseMarkdown\n                  v-if=\"statementForm.statement\"\n                  :body=\"statementForm.statement\"\n                  class=\"text-skin-heading\"\n                />\n                <div v-else>No statement provided yet</div>\n              </div>\n            </div>\n          </div>\n        </template>\n\n        <template #sidebar-right>\n          <BaseBlock\n            class=\"mb-5 lg:sticky lg:top-[110px] lg:mb-0 lg:mt-0 lg:w-[320px]\"\n          >\n            <div class=\"flex items-center\">\n              <div>\n                <AvatarUser :address=\"address\" size=\"40\" />\n              </div>\n              <div class=\"ml-2 truncate\">\n                <ProfileName\n                  :profile=\"getProfile(address)\"\n                  :address=\"address\"\n                  class=\"leading-6\"\n                />\n                <ProfileAddressCopy :user-address=\"address\" />\n              </div>\n            </div>\n            <div class=\"mt-3 space-y-2\">\n              <div\n                v-for=\"(item, i) in delegatorItems\"\n                :key=\"i\"\n                class=\"flex justify-between\"\n              >\n                <div>\n                  {{ item.label }}\n                </div>\n                <div\n                  v-tippy=\"{ content: item.tooltip }\"\n                  class=\"text-skin-heading\"\n                  :class=\"item.tooltip ? 'cursor-help' : ''\"\n                >\n                  {{ item.value }}\n                </div>\n              </div>\n            </div>\n            <TheActionbar\n              v-if=\"\n                space.delegationPortal.delegationType !==\n                DelegationTypes.SPLIT_DELEGATION\n              \"\n              break-point=\"md\"\n            >\n              <div\n                class=\"flex h-full items-center px-[20px] py-[16px] md:px-0 md:pb-0\"\n              >\n                <TuneButton\n                  v-if=\"!showUndelegate\"\n                  class=\"w-full\"\n                  primary\n                  :loading=\"isLoadingDelegatingTo\"\n                  @click=\"handleClickDelegate\"\n                >\n                  {{ isLoggedUser ? 'Delegate to yourself' : 'Delegate' }}\n                </TuneButton>\n\n                <div\n                  v-else\n                  v-tippy=\"{ content: 'You can not un-delegate from yourself' }\"\n                  class=\"w-full\"\n                >\n                  <TuneButton\n                    variant=\"danger\"\n                    class=\"w-full\"\n                    :disabled=\"isLoggedUser\"\n                    @click=\"showDelegateModal = true\"\n                  >\n                    Un-delegate\n                  </TuneButton>\n                </div>\n              </div>\n            </TheActionbar>\n          </BaseBlock>\n        </template>\n      </TheLayout>\n    </div>\n    <Teleport to=\"#modal\">\n      <SpaceDelegatesDelegateModal\n        v-if=\"hasDelegationPortal\"\n        :open=\"showDelegateModal\"\n        :space=\"space\"\n        :address=\"showUndelegate ? web3Account : address\"\n        @close=\"showDelegateModal = false\"\n        @reload=\"handleReload\"\n      />\n      <ModalConfirmLeave\n        :open=\"isConfirmLeaveOpen\"\n        show-cancel\n        @close=\"cancelLeave\"\n        @save=\"saveStatementForm\"\n        @leave=\"confirmLeave(true)\"\n      />\n    </Teleport>\n  </div>\n</template>\n"
  },
  {
    "path": "src/views/SpaceDelegates.vue",
    "content": "<script setup lang=\"ts\">\nimport { SNAPSHOT_HELP_LINK } from '@/helpers/constants';\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport { useInfiniteScroll, refDebounced } from '@vueuse/core';\nimport { DelegationTypes } from '@/helpers/delegationV2';\nimport SpaceDelegatesSplitDelegationModal from '@/components/SpaceDelegatesSplitDelegationModal.vue';\nimport SpaceDelegatesDelegateModal from '@/components/SpaceDelegatesDelegateModal.vue';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n}>();\n\nconst {\n  loadDelegate,\n  loadDelegates,\n  fetchMoreDelegates,\n  delegate,\n  delegates,\n  isLoadingDelegates,\n  isLoadingDelegate,\n  hasMoreDelegates,\n  delegatesStats,\n  hasDelegatesLoadFailed,\n  hasDelegationPortal\n} = useDelegates(props.space);\nconst { profiles, loadProfiles } = useProfiles();\nconst { modalAccountOpen } = useModal();\nconst route = useRoute();\nconst router = useRouter();\nconst { t } = useI18n();\nconst { isFollowing } = useFollowSpace(props.space.id);\nconst { web3Account } = useWeb3();\nconst { getStatement, loadStatements } = useStatement();\n\nconst searchInput = ref((route.query.search as string) || '');\nconst searchInputDebounced = refDebounced(searchInput, 300);\nconst selectedFilter = ref(route.query.filter || 'mostVotingPower');\n\nconst isSplitDelegation = computed(() => {\n  return (\n    props.space.delegationPortal.delegationType ===\n    DelegationTypes.SPLIT_DELEGATION\n  );\n});\n\nconst matchFilter = computed(() => {\n  switch (selectedFilter.value) {\n    case 'mostDelegators':\n      return 'tokenHoldersRepresentedAmount';\n    case 'mostProposals':\n      return 'proposalsCount';\n    case 'mostVotes':\n      return 'votesCount';\n    default:\n      return 'delegatedVotes';\n  }\n});\n\nconst filterItems = computed(() => {\n  return [\n    {\n      action: 'mostVotingPower',\n      text: t('delegates.filters.mostVotingPower')\n    },\n    {\n      action: 'mostDelegators',\n      text: t('delegates.filters.mostDelegators')\n    }\n  ];\n});\n\nfunction handleSearchInput(value: string) {\n  searchInput.value = value;\n  router.push({\n    query: {\n      ...route.query,\n      search: value || undefined\n    }\n  });\n}\n\nfunction handleSelectFilter(e: string) {\n  selectedFilter.value = e;\n  router.push({\n    query: {\n      ...route.query,\n      filter: e\n    }\n  });\n}\n\nfunction handleClickDelegate(id = '') {\n  if (!web3Account.value) {\n    modalAccountOpen.value = true;\n    return;\n  }\n\n  router.push({\n    query: {\n      ...route.query,\n      delegate: id\n    }\n  });\n}\n\nfunction handleCloseModalDelegate() {\n  router.push({\n    query: {\n      ...route.query,\n      delegate: undefined\n    }\n  });\n}\n\nfunction handleClickProfile(id: string) {\n  router.push({\n    name: 'spaceDelegate',\n    params: {\n      address: id.toLowerCase()\n    }\n  });\n}\n\nuseInfiniteScroll(\n  document,\n  () => {\n    if (hasMoreDelegates.value && !searchInput.value)\n      fetchMoreDelegates(matchFilter.value);\n  },\n  { distance: 500 }\n);\n\nwatch(searchInputDebounced, async () => {\n  await loadDelegate(searchInput.value);\n});\n\nwatch(matchFilter, () => {\n  loadDelegates(matchFilter.value);\n});\n\nwatch(delegates, delegates => {\n  const ids = delegates.map(d => d.id);\n  loadStatements(props.space.id, ids);\n  loadProfiles(ids);\n});\n\nonMounted(() => {\n  if (!hasDelegationPortal) return;\n\n  if (searchInput.value) loadDelegate(searchInput.value);\n  loadDelegates(matchFilter.value);\n});\n</script>\n\n<template>\n  <TheLayout>\n    <template #sidebar-left>\n      <SpaceSidebar :space=\"space\" />\n    </template>\n    <template #content-right>\n      <div class=\"mb-[80px] md:mb-0\">\n        <h1 class=\"hidden lg:mb-3 lg:block\">\n          {{ $t('delegates.header') }}\n        </h1>\n\n        <div class=\"mb-4\">\n          <div class=\"justify-between px-[20px] md:flex md:px-0\">\n            <div class=\"flex gap-2 sm:gap-[12px]\">\n              <div\n                class=\"flex w-full rounded-full border pl-3 pr-0 focus-within:border-skin-text md:w-[250px] lg:w-[280px]\"\n              >\n                <BaseSearch\n                  :model-value=\"searchInput\"\n                  :placeholder=\"$t('searchPlaceholderVotes')\"\n                  class=\"flex-auto pr-2\"\n                  :is-disabled=\"!hasDelegationPortal\"\n                  @update:model-value=\"handleSearchInput\"\n                />\n              </div>\n              <BaseMenu\n                :items=\"filterItems\"\n                :disabled=\"!hasDelegationPortal\"\n                @select=\"handleSelectFilter\"\n              >\n                <template #button>\n                  <div>\n                    <TuneButton\n                      class=\"hidden items-center sm:flex\"\n                      :disabled=\"!hasDelegationPortal\"\n                    >\n                      <div class=\"whitespace-nowrap\">\n                        {{\n                          filterItems.find(i => i.action === selectedFilter)\n                            ?.text\n                        }}\n                      </div>\n                      <i-ho-chevron-down\n                        class=\"-mr-1 ml-1 text-xs\"\n                        :class=\"{ 'text-skin-link': hasDelegationPortal }\"\n                      />\n                    </TuneButton>\n\n                    <BaseButtonRound class=\"sm:hidden\">\n                      <i-ho-sort-descending class=\"text-skin-text\" />\n                    </BaseButtonRound>\n                  </div>\n                </template>\n                <template #item=\"{ item }\">\n                  <div class=\"flex items-center\">\n                    {{ item.text }}\n                    <i-ho-check\n                      v-if=\"item.action === selectedFilter\"\n                      class=\"ml-2 shrink-0 !text-green text-skin-text\"\n                    />\n                  </div>\n                </template>\n              </BaseMenu>\n            </div>\n            <div class=\"flex justify-center gap-[12px]\">\n              <TheActionbar break-point=\"md\">\n                <div\n                  class=\"flex h-full items-center gap-[12px] px-[20px] py-[16px] md:py-0 md:px-0\"\n                >\n                  <SpaceDelegatesAccount\n                    v-if=\"web3Account\"\n                    @click=\"handleClickProfile(web3Account)\"\n                  />\n                  <TuneButton\n                    :disabled=\"!hasDelegationPortal\"\n                    :primary=\"isFollowing\"\n                    class=\"w-full md:w-auto\"\n                    @click=\"handleClickDelegate()\"\n                  >\n                    Delegate\n                  </TuneButton>\n                </div>\n              </TheActionbar>\n            </div>\n          </div>\n        </div>\n        <BaseMessageBlock v-if=\"!hasDelegationPortal\" level=\"warning-red\">\n          This space has misconfigured or not setup their delegation portal\n        </BaseMessageBlock>\n        <BaseMessageBlock\n          v-else-if=\"hasDelegatesLoadFailed\"\n          level=\"warning-red\"\n        >\n          An error occurred while loading delegates. Please try again later. If\n          the problem persists, consider contacting our support team on\n          <BaseLink :link=\"SNAPSHOT_HELP_LINK\">Help Center</BaseLink>\n        </BaseMessageBlock>\n        <template v-else-if=\"searchInputDebounced\">\n          <div\n            class=\"mx-[20px] grid grid-cols-1 gap-3 md:mx-0 lg:grid-cols-2\"\n            :class=\"{ 'opacity-40': isLoadingDelegate }\"\n          >\n            <SpaceDelegatesSkeleton v-if=\"isLoadingDelegate\" />\n            <SpaceDelegatesCard\n              v-else-if=\"delegate\"\n              :delegate=\"delegate\"\n              :profiles=\"profiles\"\n              :space=\"space\"\n              :about=\"getStatement(delegate.id).about\"\n              :stats=\"delegatesStats[delegate.id]\"\n              class=\"border-b\"\n              @click-delegate=\"handleClickDelegate(delegate.id)\"\n              @click-user=\"handleClickProfile(delegate.id)\"\n            />\n          </div>\n          <BaseNoResults v-if=\"!delegate\" use-block />\n        </template>\n        <template v-else>\n          <div\n            class=\"mx-[20px] grid grid-cols-1 gap-3 md:mx-0 lg:grid-cols-2\"\n            :class=\"{ 'opacity-40': isLoadingDelegates }\"\n          >\n            <SpaceDelegatesSkeleton v-if=\"isLoadingDelegates\" />\n            <template v-else>\n              <div\n                v-for=\"(d, i) in delegates\"\n                :key=\"i\"\n                class=\"last:border-b md:last:border-b-0\"\n              >\n                <SpaceDelegatesCard\n                  :delegate=\"d\"\n                  :profiles=\"profiles\"\n                  :space=\"space\"\n                  :about=\"getStatement(d.id).about\"\n                  :stats=\"delegatesStats[d.id]\"\n                  @click-delegate=\"handleClickDelegate(d.id)\"\n                  @click-user=\"handleClickProfile(d.id)\"\n                />\n              </div>\n            </template>\n          </div>\n          <div v-if=\"hasMoreDelegates\" class=\"mt-4 flex\">\n            <LoadingSpinner class=\"mx-auto\" big />\n          </div>\n          <BaseNoResults\n            v-else-if=\"!delegates.length && !isLoadingDelegates\"\n            use-block\n          />\n        </template>\n      </div>\n    </template>\n    <Teleport to=\"#modal\">\n      <component\n        :is=\"\n          isSplitDelegation\n            ? SpaceDelegatesSplitDelegationModal\n            : SpaceDelegatesDelegateModal\n        \"\n        v-if=\"hasDelegationPortal\"\n        :open=\"route.query.delegate !== undefined\"\n        :space=\"space\"\n        :address=\"(route.query.delegate as string) || ''\"\n        @close=\"handleCloseModalDelegate\"\n        @reload=\"loadDelegates(matchFilter)\"\n      />\n    </Teleport>\n  </TheLayout>\n</template>\n"
  },
  {
    "path": "src/views/SpaceProposal.vue",
    "content": "<script setup lang=\"ts\">\nimport { getProposal } from '@/helpers/snapshot';\nimport { ExtendedSpace, Proposal } from '@/helpers/interfaces';\n\nconst props = defineProps<{ spaceKey: string }>();\n\nconst loadingProposal = ref(false);\nconst proposal = ref<Proposal | null>(null);\nconst space = ref<ExtendedSpace | null>(null);\n\nconst route = useRoute();\nconst router = useRouter();\n\nconst { loadExtendedSpace, extendedSpaces } = useExtendedSpaces();\n\nconst proposalId = route.params.id as string;\n\nconst isSpaceRelatedProposal = computed(() => {\n  if (!space.value || !proposal.value) return false;\n  return (\n    props.spaceKey === proposal.value.space.id ||\n    space.value?.parent?.children\n      .map(c => c.id)\n      .includes(proposal.value.space.id)\n  );\n});\n\nasync function loadProposal() {\n  proposal.value = await getProposal(proposalId);\n  if (!proposal.value) return router.push({ name: 'error-404' });\n\n  await loadExtendedSpace(proposal.value.space.id);\n  space.value =\n    extendedSpaces.value.find(\n      space => space.id.toLowerCase() === proposal.value?.space.id.toLowerCase()\n    ) ?? null;\n\n  if (!isSpaceRelatedProposal.value) return router.push({ name: 'error-404' });\n}\n\nonMounted(async () => {\n  loadingProposal.value = true;\n  await loadProposal();\n  loadingProposal.value = false;\n});\n</script>\n\n<template>\n  <div>\n    <LoadingSpinner v-if=\"loadingProposal\" class=\"overlay big\" />\n\n    <SpaceProposalPage\n      v-else-if=\"proposal && space\"\n      :space=\"space\"\n      :proposal=\"proposal\"\n      @reload-proposal=\"loadProposal\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "src/views/SpaceProposals.vue",
    "content": "<script setup lang=\"ts\">\nimport { PROPOSALS_QUERY } from '@/helpers/queries';\nimport { ExtendedSpace, Proposal } from '@/helpers/interfaces';\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { useInfiniteScroll, watchDebounced } from '@vueuse/core';\nimport { getBoosts } from '@/helpers/boost/subgraph';\nimport { BoostSubgraph } from '@/helpers/boost/types';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n}>();\n\nuseMeta({\n  title: {\n    key: 'metaInfo.space.proposals.title',\n    params: {\n      space: props.space.name\n    }\n  },\n  description: {\n    key: 'metaInfo.space.proposals.description',\n    params: {\n      about: props.space.about.slice(0, 160)\n    }\n  }\n});\n\nconst loading = ref(false);\nconst boosts = ref<BoostSubgraph[]>([]);\n\nconst route = useRoute();\nconst router = useRouter();\nconst { loadBy, loadingMore, stopLoadingMore, loadMore } = useInfiniteLoader();\nconst { emitUpdateLastSeenProposal } = useUnseenProposals();\nconst { profiles, loadProfiles } = useProfiles();\nconst { apolloQuery } = useApolloQuery();\nconst { web3Account } = useWeb3();\nconst { isFollowing } = useFollowSpace(props.space.id);\nconst { sanitizeBoosts } = useBoost();\nconst {\n  store,\n  userVotedProposalIds,\n  addSpaceProposals,\n  resetSpaceProposals,\n  setSpaceProposals\n} = useProposals();\n\nconst spaceMembers = computed(() =>\n  props.space.members?.length < 1\n    ? ['none']\n    : [...props.space.members, ...props.space.moderators, ...props.space.admins]\n);\n\nconst subSpaces = computed(\n  () => props.space.children?.map(space => space.id.toLowerCase()) ?? []\n);\n\nconst spaceProposals = computed(() => {\n  return clone(store.space.proposals).filter(proposal =>\n    [props.space.id.toLowerCase(), ...subSpaces.value].includes(\n      proposal.space.id.toLowerCase()\n    )\n  );\n});\n\nconst stateFilter = computed(() => route.query.state || 'all');\nconst titleSearch = computed(() => route.query.q || '');\nconst showOnlyCore = computed(() => (route.query.onlyCore as string) || '0');\nconst showFlagged = computed(() => (route.query.showFlagged as string) || '0');\n\nasync function getProposals(skip = 0) {\n  return apolloQuery(\n    {\n      query: PROPOSALS_QUERY,\n      variables: {\n        first: loadBy,\n        skip,\n        space_in: [props.space.id, ...subSpaces.value],\n        state: stateFilter.value,\n        author_in: showOnlyCore.value === '1' ? spaceMembers.value : undefined,\n        title_contains: titleSearch.value,\n        flagged: showFlagged.value === '0' ? false : undefined\n      }\n    },\n    'proposals'\n  );\n}\n\nasync function loadBoosts(proposals: Proposal[]) {\n  if (!props.space.boost.enabled) return;\n\n  const alreadyLoadedProposals = boosts.value.map(\n    boost => boost.strategy.proposal\n  );\n  const proposalsToLoad = proposals.filter(\n    proposal => !alreadyLoadedProposals.includes(proposal.id)\n  );\n  try {\n    const response = await getBoosts(\n      proposalsToLoad.map(proposal => proposal.id)\n    );\n    const sanitizedBoosts = sanitizeBoosts(response, proposals, props.space);\n    boosts.value = boosts.value.concat(sanitizedBoosts);\n  } catch (e) {\n    console.error('Load boosts error:', e);\n  }\n}\n\nasync function loadMoreProposals(skip: number) {\n  if (skip === 0) return;\n  const proposals = await getProposals(skip);\n  stopLoadingMore.value = proposals?.length < loadBy;\n  addSpaceProposals(proposals);\n}\n\nuseInfiniteScroll(\n  document,\n  () => {\n    if (loadingMore.value || spaceProposals.value.length < 6) return;\n    loadMore(() => loadMoreProposals(spaceProposals.value.length));\n  },\n  { distance: 400 }\n);\n\nwatch(web3Account, () => emitUpdateLastSeenProposal(props.space.id));\n\nasync function loadProposals() {\n  if (!needToRefreshProposals()) return;\n  resetSpaceProposals();\n\n  loading.value = true;\n  const proposals = await getProposals();\n  stopLoadingMore.value = proposals?.length < loadBy;\n  emitUpdateLastSeenProposal(props.space.id);\n  setSpaceProposals(proposals);\n  loading.value = false;\n}\n\nfunction needToRefreshProposals() {\n  const preventRefreshWhenLeaving = route.fullPath.includes('proposal');\n  if (preventRefreshWhenLeaving) return false;\n\n  if (spaceProposals.value.length < 1) return true;\n\n  const lastVisitedPath = router.options.history.state.forward;\n  if (typeof lastVisitedPath !== 'string') return true;\n  const lastPathContainedSubSpace = subSpaces.value.some(space => {\n    return lastVisitedPath.toLowerCase().includes(space);\n  });\n\n  return !(\n    lastVisitedPath.toLowerCase().includes(props.space.id.toLowerCase()) ||\n    lastPathContainedSubSpace\n  );\n}\n\nwatch(\n  [stateFilter, showOnlyCore, showFlagged],\n  () => {\n    loadProposals();\n  },\n  { immediate: true }\n);\n\nwatchDebounced(\n  titleSearch,\n  () => {\n    loadProposals();\n  },\n  { debounce: 300 }\n);\n\nwatch(\n  spaceProposals,\n  value => {\n    loadProfiles(value.map((proposal: Proposal) => proposal.author));\n    loadBoosts(value);\n  },\n  {\n    immediate: true\n  }\n);\n</script>\n\n<template>\n  <TheLayout>\n    <template #sidebar-left>\n      <SpaceSidebar :space=\"space\" />\n    </template>\n    <template #content-right>\n      <div class=\"relative\">\n        <SpaceProposalsNotice\n          v-if=\"spaceProposals.length < 1 && !loading\"\n          :space-id=\"space.id\"\n        />\n      </div>\n      <div\n        class=\"mb-4 flex flex-col justify-between gap-x-3 gap-y-[10px] px-[20px] sm:flex-row md:px-0\"\n      >\n        <SpaceProposalsSearch />\n        <BaseLink\n          :link=\"{ name: 'spaceCreate' }\"\n          data-testid=\"create-proposal-button\"\n        >\n          <TuneButton :primary=\"isFollowing\" class=\"w-full sm:w-auto\">\n            New proposal\n          </TuneButton>\n        </BaseLink>\n      </div>\n\n      <LoadingRow v-if=\"loading\" block />\n\n      <BaseNoResults v-else-if=\"spaceProposals.length < 1\" />\n      <div v-else class=\"mb-3 space-y-3\">\n        <template v-for=\"(proposal, i) in spaceProposals\" :key=\"i\">\n          <BaseBlock slim class=\"transition-colors\">\n            <ProposalsItem\n              :proposal=\"proposal\"\n              :profiles=\"profiles\"\n              :space=\"space\"\n              :voted=\"userVotedProposalIds.includes(proposal.id)\"\n              :hide-space-avatar=\"proposal.space.id === space.id\"\n              :to=\"{\n                name: 'spaceProposal',\n                params: { id: proposal.id, key: proposal.space.id }\n              }\"\n              :boosts=\"boosts\"\n            />\n          </BaseBlock>\n        </template>\n      </div>\n      <LoadingRow v-if=\"loadingMore && !loading\" block />\n    </template>\n  </TheLayout>\n</template>\n"
  },
  {
    "path": "src/views/SpaceSettings.vue",
    "content": "<script setup lang=\"ts\">\nimport { shorten, clearStampCache } from '@/helpers/utils';\nimport { ExtendedSpace } from '@/helpers/interfaces';\nimport { useConfirmDialog, useStorage } from '@vueuse/core';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n}>();\nconst spaceType = computed(() => (props.space.turbo ? 'turbo' : 'default'));\n\nuseMeta({\n  title: {\n    key: 'metaInfo.space.settings.title',\n    params: {\n      space: props.space.name\n    }\n  },\n  description: {\n    key: 'metaInfo.space.settings.description'\n  }\n});\n\nconst { t } = useI18n();\nconst { domain } = useApp();\nconst router = useRouter();\nconst { web3Account } = useWeb3();\nconst { send, isSending } = useClient();\nconst { reloadSpace, deleteSpace } = useExtendedSpaces();\nconst { loadFollows } = useFollowSpace();\nconst {\n  validationErrors,\n  isValid,\n  isReadyToSubmit,\n  hasFormChanged,\n  prunedForm,\n  populateForm,\n  resetForm,\n  forceShowError\n} = useFormSpaceSettings('settings', {\n  spaceType: spaceType.value\n});\nconst { resetTreasuryAssets } = useTreasury();\nconst { notify } = useFlashNotification();\nconst { isGnosisAndNotDefaultNetwork } = useGnosis();\nconst {\n  settingENSRecord,\n  modalWrongNetworkOpen,\n  modalConfirmSetTextRecordOpen,\n  spaceControllerInput,\n  setRecord,\n  loadEnsOwner,\n  isEnsOwner,\n  loadSpaceController,\n  isSpaceController,\n  ensOwner\n} = useSpaceController();\n\nenum Page {\n  GENERAL,\n  STRATEGIES,\n  PROPOSAL,\n  VOTING,\n  DELEGATION,\n  MEMBERS,\n  ADVANCED\n}\n\nconst loaded = ref(false);\nconst modalControllerEditOpen = ref(false);\nconst currentPage = ref(Page.GENERAL);\nconst modalDeleteSpaceConfirmation = ref('');\nconst modalDeleteSpaceAcknowledge = ref(false);\nconst modalSettingsSavedOpen = ref(false);\nconst modalSettingsSavedIgnore = useStorage(\n  'snapshot.settings.saved.ignore',\n  false\n);\nconst showFormErrors = ref(false);\n\nconst isSpaceAdmin = computed(() => {\n  if (!props.space) return false;\n  const admins = (props.space?.admins || []).map(admin => admin.toLowerCase());\n  return admins.includes(web3Account.value?.toLowerCase());\n});\n\nconst settingsPages = computed(() => [\n  {\n    id: Page.GENERAL,\n    title: t('settings.navigation.general')\n  },\n  {\n    id: Page.STRATEGIES,\n    title: t('settings.navigation.strategies')\n  },\n  {\n    id: Page.PROPOSAL,\n    title: t('settings.navigation.proposal')\n  },\n  {\n    id: Page.VOTING,\n    title: t('settings.navigation.voting')\n  },\n  {\n    id: Page.DELEGATION,\n    title: t('settings.navigation.delegation')\n  },\n  {\n    id: Page.MEMBERS,\n    title: t('settings.navigation.members')\n  },\n  {\n    id: Page.ADVANCED,\n    title: t('settings.navigation.advanced')\n  }\n]);\n\nasync function handleDelete() {\n  modalDeleteSpaceConfirmation.value = '';\n  modalDeleteSpaceAcknowledge.value = false;\n\n  const result = await send(props.space, 'delete-space', {});\n  console.log(':handleDelete result', result);\n\n  if (result?.id) {\n    if (domain) {\n      return window.open(`https://snapshot.org/#/`, '_self');\n    } else {\n      deleteSpace(props.space.id);\n      loadFollows();\n      return router.push({ name: 'home' });\n    }\n  }\n}\n\nasync function handleSubmit() {\n  if (!isValid.value) {\n    showFormErrors.value = true;\n    forceShowError();\n    window.scrollTo(0, 0);\n    return console.log('Invalid form', validationErrors.value);\n  }\n\n  const result = await send(props.space, 'settings', prunedForm.value);\n  console.log('Result', result);\n  if (result.id) {\n    if (!result.ipfs) notify(['green', t('notify.waitingForOtherSigners')]);\n    else notify(['green', t('notify.saved')]);\n    if (!modalSettingsSavedIgnore.value) modalSettingsSavedOpen.value = true;\n    resetTreasuryAssets();\n    await clearStampCache(props.space.id);\n    await reloadSpace(props.space.id);\n    populateForm(props.space);\n  }\n}\n\nconst {\n  isRevealed: isConfirmLeaveOpen,\n  reveal: openConfirmLeave,\n  confirm: confirmLeave,\n  cancel: cancelLeave\n} = useConfirmDialog();\n\nconst {\n  isRevealed: isConfirmDeleteOpen,\n  reveal: openConfirmDelete,\n  cancel: cancelDelete\n} = useConfirmDialog();\n\nconst isViewOnly = computed(() => {\n  return !(isSpaceController.value || isSpaceAdmin.value);\n});\n\nonMounted(async () => {\n  populateForm(props.space);\n  await loadEnsOwner();\n  await loadSpaceController();\n  loaded.value = true;\n});\n\nonBeforeRouteLeave(async () => {\n  if (hasFormChanged.value && !isViewOnly.value) {\n    const { data } = await openConfirmLeave();\n    if (!data) return false;\n  }\n});\n</script>\n\n<template>\n  <TheLayout v-bind=\"$attrs\">\n    <div class=\"mb-3 px-4 md:px-0\">\n      <ButtonBack @click=\"router.push({ name: 'spaceProposals' })\" />\n    </div>\n    <template #content-right>\n      <LoadingRow v-if=\"!loaded\" block />\n\n      <template v-else>\n        <div class=\"mt-3 space-y-3 sm:mt-0\">\n          <SpaceSettingsMessageHibernated\n            v-if=\"space.hibernated && !isViewOnly\"\n            :space=\"space\"\n            :is-sending=\"isSending\"\n            :is-valid=\"isValid\"\n            @show-errors=\"showFormErrors = true\"\n            @reactivate-space=\"handleSubmit\"\n          />\n\n          <BaseMessageBlock\n            v-if=\"showFormErrors && Object.keys(validationErrors).length\"\n            level=\"warning-red\"\n          >\n            {{ $t('settings.validationErrorsMessage') }}\n            <span class=\"font-bold\">\n              {{\n                Object.keys(validationErrors)\n                  .map(key => key)\n                  .join(', ')\n              }}\n            </span>\n          </BaseMessageBlock>\n\n          <MessageWarningGnosisNetwork\n            v-else-if=\"isGnosisAndNotDefaultNetwork\"\n            :space=\"space\"\n            action=\"settings\"\n            is-responsive\n          />\n\n          <BaseMessageBlock\n            v-else-if=\"isViewOnly\"\n            class=\"md:mx-0\"\n            level=\"info\"\n            is-responsive\n          >\n            {{ $t('settings.connectWithSpaceOwner') }}\n          </BaseMessageBlock>\n\n          <template v-if=\"currentPage === Page.GENERAL\">\n            <SettingsProfileBlock\n              context=\"settings\"\n              :is-view-only=\"isViewOnly\"\n            />\n            <SettingsLinkBlock context=\"settings\" :is-view-only=\"isViewOnly\" />\n          </template>\n\n          <template v-if=\"currentPage === Page.STRATEGIES\">\n            <SettingsStrategiesBlock\n              context=\"settings\"\n              :is-view-only=\"isViewOnly\"\n              :show-errors=\"showFormErrors\"\n              :space-type=\"spaceType\"\n            />\n          </template>\n\n          <template v-if=\"currentPage === Page.PROPOSAL\">\n            <SettingsValidationBlock\n              context=\"settings\"\n              :is-view-only=\"isViewOnly\"\n              :show-errors=\"showFormErrors\"\n            />\n            <SettingsProposalBlock\n              context=\"settings\"\n              :is-view-only=\"isViewOnly\"\n            />\n          </template>\n\n          <template v-if=\"currentPage === Page.VOTING\">\n            <SettingsBoostBlock context=\"settings\" :is-view-only=\"isViewOnly\" />\n\n            <SettingsVotingBlock\n              context=\"settings\"\n              :is-view-only=\"isViewOnly\"\n            />\n          </template>\n\n          <template v-if=\"currentPage === Page.DELEGATION\">\n            <SettingsDelegationBlock\n              context=\"settings\"\n              :is-view-only=\"isViewOnly\"\n            />\n          </template>\n\n          <template v-if=\"currentPage === Page.MEMBERS\">\n            <SettingsMembersBlock\n              context=\"settings\"\n              :space=\"space\"\n              :is-space-controller=\"isSpaceController\"\n              :is-space-admin=\"isSpaceAdmin\"\n            />\n          </template>\n\n          <template v-if=\"currentPage === Page.ADVANCED\">\n            <SettingsPluginsBlock\n              context=\"settings\"\n              :is-view-only=\"isViewOnly\"\n            />\n            <SettingsTreasuriesBlock\n              context=\"settings\"\n              :space=\"space\"\n              :is-view-only=\"isViewOnly\"\n              :error=\"validationErrors.treasuries\"\n            />\n            <SettingsSubSpacesBlock\n              context=\"settings\"\n              :is-view-only=\"isViewOnly\"\n            />\n            <!-- <SettingsDomainBlock\n              context=\"settings\"\n              :is-view-only=\"isViewOnly\"\n            /> -->\n            <SettingsDangerzoneBlock\n              :is-controller=\"isSpaceController\"\n              :ens-owner=\"ensOwner\"\n              :is-owner=\"isEnsOwner\"\n              :is-setting-ens-record=\"settingENSRecord\"\n              :is-deleting=\"isConfirmDeleteOpen\"\n              @change-controller=\"modalControllerEditOpen = true\"\n              @delete-space=\"openConfirmDelete\"\n            />\n          </template>\n          <div\n            v-if=\"isSpaceAdmin || isSpaceController\"\n            class=\"flex gap-5 px-4 pt-2 md:px-0\"\n          >\n            <TuneButton class=\"mb-2 block w-full\" @click=\"resetForm\">\n              {{ $t('reset') }}\n            </TuneButton>\n            <TuneButton\n              :disabled=\"!isReadyToSubmit || isGnosisAndNotDefaultNetwork\"\n              :loading=\"isSending\"\n              class=\"block w-full\"\n              primary\n              @click=\"handleSubmit\"\n            >\n              {{ $t('save') }}\n            </TuneButton>\n          </div>\n        </div>\n      </template>\n    </template>\n\n    <template #sidebar-left>\n      <BaseBlock slim class=\"overflow-hidden !border-t-0 md:!border-t\">\n        <div class=\"lg:max-h-[calc(100vh-120px)] lg:overflow-y-auto\">\n          <div\n            class=\"no-scrollbar mt-0 flex overflow-y-auto md:mt-4 lg:my-3 lg:block\"\n          >\n            <a\n              v-for=\"page in settingsPages\"\n              :key=\"page.id\"\n              tabindex=\"0\"\n              @click=\"currentPage = page.id\"\n              @keypress=\"currentPage = page.id\"\n            >\n              <BaseSidebarNavigationItem :is-active=\"currentPage === page.id\">\n                {{ page.title }}\n              </BaseSidebarNavigationItem>\n            </a>\n          </div>\n        </div>\n      </BaseBlock>\n      <BaseBlock class=\"my-3\">\n        <div class=\"mb-2 text-skin-link\">\n          {{ $t('newsletter.join') }}\n        </div>\n        <InputNewsletter tag=\"6449077\" />\n      </BaseBlock>\n    </template>\n  </TheLayout>\n\n  <ModalWrongNetwork\n    :open=\"modalWrongNetworkOpen\"\n    show-demo-button\n    @close=\"modalWrongNetworkOpen = false\"\n    @network-changed=\"modalConfirmSetTextRecordOpen = true\"\n  />\n\n  <teleport to=\"#modal\">\n    <ModalControllerEdit\n      :open=\"modalControllerEditOpen\"\n      :ens-address=\"space.id\"\n      @close=\"modalControllerEditOpen = false\"\n    />\n\n    <ModalConfirmAction\n      :open=\"modalConfirmSetTextRecordOpen\"\n      @close=\"modalConfirmSetTextRecordOpen = false\"\n      @confirm=\"setRecord\"\n    >\n      <div class=\"m-4 space-y-4 text-skin-link\">\n        <p>\n          {{\n            $t('setup.confirmToSetAddress', {\n              address: shorten(spaceControllerInput)\n            })\n          }}\n          {{ $t('setup.controllerHasAuthority') + '.' }}\n        </p>\n      </div>\n    </ModalConfirmAction>\n    <ModalConfirmLeave\n      :open=\"isConfirmLeaveOpen\"\n      show-cancel\n      @close=\"cancelLeave\"\n      @save=\"handleSubmit\"\n      @leave=\"confirmLeave(true)\"\n    />\n    <ModalConfirmAction\n      :open=\"isConfirmDeleteOpen\"\n      :disabled=\"\n        modalDeleteSpaceConfirmation !== space.id ||\n        !modalDeleteSpaceAcknowledge\n      \"\n      show-cancel\n      @close=\"cancelDelete\"\n      @confirm=\"handleDelete\"\n    >\n      <BaseMessageBlock level=\"warning-red\" class=\"m-4\">\n        {{ $t('settings.confirmDeleteSpace', { name: space.id }) }}\n      </BaseMessageBlock>\n      <div class=\"px-4 pb-4\">\n        <BaseInput\n          v-model.trim=\"modalDeleteSpaceConfirmation\"\n          :title=\"$t('settings.confirmInputDeleteSpace', { space: space.id })\"\n          focus-on-mount\n        >\n        </BaseInput>\n        <TuneCheckbox\n          id=\"space-delete-acknowledge\"\n          v-model=\"modalDeleteSpaceAcknowledge\"\n          :hint=\"`I acknowledge that I will not be able to use ${space.id} again to create a new space.`\"\n          class=\"mt-3\"\n        />\n      </div>\n    </ModalConfirmAction>\n    <ModalNotice\n      :open=\"modalSettingsSavedOpen\"\n      :title=\"$t('settings.noticeSavedTitle')\"\n      @close=\"modalSettingsSavedOpen = false\"\n    >\n      <BaseMessageBlock level=\"info\" class=\"mb-5\">\n        <p class=\"text-left\">{{ $t('settings.noticeSavedText') }}</p>\n      </BaseMessageBlock>\n      <InputCheckbox\n        v-model=\"modalSettingsSavedIgnore\"\n        name=\"settings-saved-input-checkbox\"\n        :label=\"$t('settings.noticeSavedInputCheckboxLabel')\"\n        class=\"ml-4 mt-auto max-w-min cursor-pointer self-start whitespace-nowrap\"\n        @click=\"modalSettingsSavedIgnore = !modalSettingsSavedIgnore\"\n      />\n    </ModalNotice>\n  </teleport>\n</template>\n"
  },
  {
    "path": "src/views/SpaceTreasury.vue",
    "content": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n  space: ExtendedSpace;\n}>();\n\nconst route = useRoute();\n\nconst wallet = computed(() =>\n  props.space.treasuries.find(w => w.address === route.params.wallet)\n);\n</script>\n\n<template>\n  <TheLayout>\n    <template #sidebar-left>\n      <SpaceSidebar :space=\"space\" />\n    </template>\n    <template #content-right>\n      <TreasuryAssetsList v-if=\"wallet\" :wallet=\"wallet\" />\n      <TreasuryWalletsList\n        v-else\n        :wallets=\"space.treasuries\"\n        :admins=\"space.admins\"\n      />\n    </template>\n  </TheLayout>\n</template>\n"
  },
  {
    "path": "src/views/SpaceView.vue",
    "content": "<script setup lang=\"ts\">\nimport aliases from '@/../snapshot-spaces/spaces/aliases.json';\nimport { useFlaggedMessageStatus } from '@/composables/useFlaggedMessageStatus';\n\nconst route = useRoute();\nconst router = useRouter();\nconst { domain } = useApp();\nconst { loadExtendedSpace, extendedSpaces } = useExtendedSpaces();\n\nconst aliasedSpace = computed(\n  () => aliases[domain] || aliases[route.params.key as string]\n);\n\nconst spaceKey = computed(\n  () => aliasedSpace.value || domain || route.params.key\n);\n\nconst space = computed(\n  () => extendedSpaces.value?.find(s => s.id === spaceKey.value.toLowerCase())\n);\n\nconst { isMessageVisible, setMessageVisibility } =\n  useFlaggedMessageStatus(spaceKey);\n\nwatch(\n  spaceKey,\n  async () => {\n    if (!spaceKey.value || aliasedSpace.value) return;\n    await loadExtendedSpace(spaceKey.value.toLowerCase());\n    if (!space.value) {\n      router.push('/');\n    }\n    setMessageVisibility(space.value?.flagged || false);\n  },\n  { immediate: true }\n);\n\nonMounted(() => {\n  if (aliasedSpace.value) {\n    const updatedPath = route.path.replace(\n      domain || route.params.key,\n      aliasedSpace.value\n    );\n    router.replace({ path: updatedPath });\n  }\n});\n</script>\n\n<template>\n  <template v-if=\"space\">\n    <BaseContainer v-if=\"isMessageVisible\" slim>\n      <MessageWarningFlagged\n        type=\"space\"\n        responsive\n        @force-show=\"setMessageVisibility(false)\"\n      />\n    </BaseContainer>\n\n    <router-view v-else :space=\"space\" :space-key=\"spaceKey\" />\n  </template>\n  <LoadingSpinner v-else class=\"overlay big\" />\n</template>\n"
  },
  {
    "path": "src/views/StrategyView.vue",
    "content": "<script setup lang=\"ts\">\nconst route = useRoute();\n\nconst { getExtendedStrategy, extendedStrategy: strategy } = useStrategies();\n\nuseMeta({\n  title: {\n    key: 'metaInfo.strategy.title',\n    params: {\n      strategy: route.params.name as string\n    }\n  },\n  description: {\n    key: 'metaInfo.strategy.description'\n  }\n});\n\nonMounted(async () => {\n  await getExtendedStrategy(route.params.name as string);\n});\n</script>\n\n<template>\n  <TheLayout>\n    <template #content-left>\n      <div class=\"mb-3 px-4 md:px-0\">\n        <router-link\n          :to=\"{ path: '/', query: { type: 'strategies' } }\"\n          class=\"text-skin-text\"\n        >\n          <BaseIcon name=\"back\" size=\"22\" class=\"!align-middle\" />\n          {{ $t('strategiesPage') }}\n        </router-link>\n      </div>\n      <LoadingPage v-if=\"!strategy\" />\n      <div v-else class=\"px-4 md:px-0\">\n        <h1 class=\"mb-2\">\n          {{ strategy.id }}\n        </h1>\n        <span\n          class=\"text-skin-text\"\n          v-text=\"`In ${strategy.spacesCount} space(s)`\"\n        />\n        <BaseMarkdown\n          v-if=\"strategy.about\"\n          :body=\"strategy.about\"\n          class=\"mb-6 mt-4\"\n        />\n      </div>\n    </template>\n    <template #sidebar-right>\n      <BaseBlock v-if=\"strategy\" :title=\"$t('information')\">\n        <div class=\"mb-1\">\n          <b>{{ $t('author') }}</b>\n          <BaseLink\n            class=\"float-right\"\n            :link=\"`https://github.com/${strategy.author}`\"\n            hide-external-icon\n          >\n            <BaseIcon name=\"github\" class=\"ml-1\" />\n            {{ strategy.author }}\n          </BaseLink>\n        </div>\n        <div>\n          <div class=\"mb-1\">\n            <b>{{ $t('version') }}</b>\n            <BaseLink\n              class=\"float-right\"\n              :link=\"`https://github.com/snapshot-labs/score-api/tree/master/src/strategies/strategies/${strategy.id}`\"\n            >\n              {{ strategy.version }}\n            </BaseLink>\n          </div>\n        </div>\n        <router-link :to=\"`/playground/${$route.params.name}`\">\n          <TuneButton tabindex=\"-1\" class=\"mt-2 w-full\">{{\n            $t('playground')\n          }}</TuneButton>\n        </router-link>\n      </BaseBlock>\n    </template>\n  </TheLayout>\n</template>\n"
  },
  {
    "path": "src/views/TermsView.vue",
    "content": "<script setup lang=\"ts\">\nconst terms = `# Terms of Use for Snapshot\n\n**Last updated: 1 August 2024**\n\n## PREAMBLE\n\nThese Terms of Use for snapshot.org (\"**Terms**\") are entered into between the Prototype Stiftung, a non-profit foundation established under the laws of Switzerland with its registered office in Zug, Switzerland (\"**Foundation**\") and you as a user (\"you\", \"your\" or \"**User**\") of snapshot.org and/or the snapshot technology as further specified herein (\"**Snapshot**\").\n\nThe Terms, together with any documents incorporated by reference herein, govern your access and use of Snapshot, consisting of the snapshot user interface accessible on snapshot.org and all related tools, applications, smart contracts, and application programming interfaces (APIs) made available. By accessing, interacting with and/or using Snapshot in any form and capacity, you agree to be bound by these Terms.\n\nThe Foundation reserves the right to modify these Terms at any time at its sole discretion. In this case, the Foundation will provide notice by changing the “last updated” date above. By continuing to access or use Snapshot, you confirm that you accept the updated Terms and all documents incorporated therein by reference. If you do not agree with these Terms, please immediately cease all use of Snapshot.\n\n## 1. Eligibility and Prohibited Jurisdictions\n\nBy accessing or using Snapshot, you represent and warrant that you:\n\na) have the right, authority, and legal capacity to accept these Terms;\nb) will not use Snapshot if the laws of your countries of residency and/or citizenship prohibit you from doing so in accordance with these Terms;\nc) are not subject to personal sanctions issued by the UN, US, EU or Switzerland;\nd) only access or use Snapshot for your own personal use; and\ne) are not accessing Snapshot from one of the countries embargoed or restricted by the Swiss State Secretariat for Economic Affairs (SECO), including, but not limited to: Belarus, Burundi, Central African Republic, Congo, DPRK (North Korea), Guinea, Guinea-Bissau, Iran, Iraq, Lebanon, Libya, Mali, Myanmar (Burma), Republic of South Sudan, Russia, Somalia, Sudan, Syria, Ukraine, Venezuela, Yemen, or Zimbabwe (\"**Prohibited Jurisdictions**\").\n\nThe Foundation reserves the right to technically restrict access to Snapshot and the content provided thereon based on your geographical location.\n\n## 2. Interaction with Snapshot\n\n### 2.1 Connecting Wallet to Snapshot\n\nIn order to interact with Snapshot, you must first connect one of the non-custodial third-party wallet solutions listed under the “Connect” tab (\"**Wallet**\") to Snapshot. Your Wallet stores and manages the private keys (PIK) to your blockchain addresses that were created with the Wallet or manually imported into the Wallet. As these Wallets store your private keys, which are required to sign transactions on-chain, they can be used to execute transactions and publish them to the Ethereum blockchain.\n\nWhen you connect your Wallet, Snapshot will ask for permission to send Sign Requests (as defined in Section 2.2 below) to the Wallet. During this process, your Wallet will show the blockchain addresses managed by the Wallet that can be connected to Snapshot. You can modify these permissions at any time in the settings of the Wallet. Please note that you can only interact with blockchain addresses that are both managed by the Wallet and connected to Snapshot.\n\nThe use of the Wallets is subject to the terms and conditions of the respective provider. The Foundation has no control over the respective PIKs and/or the blockchain addresses that are managed by your Wallet and connected to Snapshot, and no ability to access any assets that are held thereon. You are solely responsible for the security of the Wallet as well as the corresponding private keys and passwords. The Foundation does not assume any responsibility for the connected Wallets, regardless of whether or not they are used to effectuate transactions, and shall not be liable for any damages arising out of or related to your use of the Wallets and/or your inability to connect and/or use the Wallets to execute transactions.\n\n### 2.2 Sign Requests\n\nOnce you have connected your Wallet to Snapshot, you can use Snapshot to initiate transactions from your blockchain address by generating standardized transaction messages (\"**Sign Requests**\"). Sign Requests that are generated by Snapshot are sent to the connected Wallet for approval. To complete the transaction, you must approve the Sign Request by signing the transaction with the connected Wallet. Your Wallet will then display whether the transaction was successful and inform you once it is finalized.\n\nExcept when you are voting on Proposals, transactions that are signed with a Wallet are executed on the Ethereum blockchain without any involvement of the Foundation. Snapshot does not execute transactions on your behalf and does not control the execution of transactions initiated by you. You are fully responsible for all inputs you make while using Snapshot.\n\n## 3. Features\n\nSnapshot offers the following features (together \"**Features**\"):\n\n- **Spaces** (see Section 4 below)\n- **Creation and Voting on Proposals** (see Section 5 below)\n- **Archiving on IPFS** (see Section 6)\n- **Boost Application (\"Boost App\";** see Section 8 below)\n\nThe Foundation reserves the right to modify, suspend, or discontinue, temporarily or permanently, Snapshot (or any Feature or part thereof) at any time in its sole discretion without liability.\n\n## 4. Spaces\n\nSnapshot is a platform that allows Users to create and manage spaces (\"**Spaces**\") for decentralized autonomous organizations and decentralized communities (together \"**DAOs**\"). The Space is a dedicated area where such DAO’s community (\"**Community**\") can participate in the decentralized governance of DAOs by creating, managing, and participating in gasless and off-chain proposal-based voting.\n\nAny User can create a new Space on the Snapshot platform tailored to their specific DAO's needs and branding. The creator of the Space becomes the administrator of such Space and can customize the Space with specific settings, roles, and permissions to allow the Community to engage in governance activities.\n\nWithin each Space, Community members can:\n\n- submit proposals for various decisions or actions they want the DAO to consider;\n- participate in proposals by voting.\n\n### 4.1 Creation, Modification, and Deletion of Spaces\n\nCreating a Space requires an Ethereum Name Service (\"**ENS**\") domain and a Wallet. When creating a Space, you will be required to define the profile of your Space (e.g., name, description, profile, etc.), specify the Permissions (as defined below), the Voting Strategies (see Section 17), and Validation Strategies (see Section 4.3).\n\nAs part of the creation of a Space, you will be able to assign a set of permissions (\"**Permissions**\") to Wallets in relation to the management of Spaces and Proposals. You will be able to choose between the following Permissions:\n\n- **Controller**: The Controller of your Space has full control over the Space settings, including managing the list of Admins. The Controller will be able to delete your Space.\n- **Admin(s)**: The Admins can edit the Space’s settings and details such as name, avatar, banner, description, and validation setting but not the list of Admins. Admins can hide the Proposals from the main view but do not have the right to delete it permanently.\n- **Moderator**: The Moderator can create, edit, archive, and manage Proposals within the Space.\n- **Author**: The Author can create Proposals regardless of their voting power and the Proposal validation strategy set for the Space. This allows a wider range of users to submit Proposals for consideration by the community.\n\nAfter the creation of the Space, only the Controller can change the settings for your Space (e.g., sub-spaces; additional voting requirements such as voting delay, voting period, and quorum; shielded voting; delegation; custom domain; skin; treasuries; plugins, etc.) or delete your Space at any time.\n\n### 4.2 Voting Strategies\n\nVoting strategies (\"**Voting Strategies**\") calculate the voting power of each Wallet that has voted on a Proposal. The [Controller or Author] will be able to select between the following Voting Strategies (non-exhaustive list):\n\n- **Token Weighted Voting**: The voting power of each Wallet is weighted by the amount of the relevant governance tokens held by that wallet.\n- **One Person One Vote**: Each Wallet has one vote. Token holdings are not relevant for the calculation of the voting power.\n- **Custom Setup**: You can combine up to eight Voting Strategies. You can use the Voting Strategies created by members of the Community or design your own custom Voting Strategy. You can either whitelist specific Wallets to be able to vote or allow any Wallet to vote.\n\nUsers are encouraged to thoroughly understand how each Voting Strategy works and its potential impact on governance outcomes before participating in voting or creating Proposals.\n\nVoting Strategies should be considered in conjunction with other Space settings such as Proposal validation rules and voter eligibility criteria.\n\nThe Space [Controller or Author] should ensure that the combination of Voting Strategies and other settings aligns with the desired governance model and objectives of the Space. We do not guarantee the accuracy, reliability, or suitability of any information, content, or Voting Strategies provided by any Users or third parties. Further, Snapshot does not endorse, verify, or take responsibility for actions, decisions, or outcomes resulting from the use of any one or more Voting Strategies.\n\n### 4.3 Validation Strategies\n\nValidation strategies (\"**Validation Strategies**\") define if a Wallet holding a specific token can vote or create a Proposal in a specific Space. You can set specific Validation Strategies that a Wallet must meet to be eligible to create or vote on Proposals. You will be able to select between the following Validation Strategies (non-exhaustive list):\n\n- **Proposal Validation**: You determine whether a particular Wallet can create a new Proposal in Space.\n- **Voting Validation**: You determine whether a specific Wallet can participate in the voting process (i.e., vote on Proposals).\n\n## 5. Proposals and Voting\n\n### 5.1 Overview\n\nThe proposals (\"**Proposals**\") allow the DAO's Community to participate in the decentralized decision-making process by creating and voting on Proposals without the payment of any gas fees on the underlying blockchain network.\n\n### 5.2 Creation, Modification, and Deletion of Proposals\n\nProvided you have the necessary Permissions (see Section 4.1), you can create a Proposal by connecting your Wallet. When creating a Proposal, you will be required to define the title of the Proposal, the description of the Proposal, the relevant details and content of the Proposal, Voting Types (as defined in Section 5.3), the voting options, the start and duration of the Proposal.\n\nAs long as the voting period of the Proposal has not started, you can edit and delete the Proposal or its details.\n\n### 5.3 Voting Types\n\nVoting types (\"**Voting Types**\") define how Wallets can cast their votes on Proposals and how the result is calculated. You will be able to select between the following Voting Types for your Proposal (non-exhaustive list):\n\n- **Single Choice Voting**: Each Wallet can select only one option (e.g., ‘yes’ or ‘no’). The results will reflect these votes as percentages of the total voting power of all Wallets that have voted on the Proposal.\n- **Weighted Voting**: Each Wallet can spread their voting power across any number of choices from one to all. Their voting power will be divided between their chosen options according to how much weight they attribute to each option by increasing or decreasing the voting power fraction.\n- **Approval Voting**: Each Wallet can select (approve) any number of choices; each selected choice will receive equal voting power.\n- **Quadratic Voting**: Each Wallet can spread their voting power over several multiple options. Each Wallet's influence is calculated by taking the square root of their voting power for each option.\n- **Ranked Choice Voting (Instant Runoff Voting)**: Each Wallet must rank all choices in a desired order.\n- **Basic Voting**: Each Wallet can select one of three options: “for”, “against”, and “abstain”.\n\n### 5.4 Voting on Proposals\n\n#### Voting Process\n\nYou can only vote for a Proposal if you connect your Wallet and you are in compliance with the Voting Strategies (see Section 4.2) and the Validation Strategies (see Section 4.3) of the Space and the Proposals.\n\nBy voting on a Proposal, you are required to vote with your Wallet. The signature process with your Wallet does not require the payment of any gas fees to the underlying blockchain network as the casting of a vote is gasless.\n\nYou agree that you will not vote on Proposals whose content may be illegal, fraudulent, offensive, abusive, indecent, harmful, defamatory, obscene, or menacing, abusive, threatening, objectionable, invasive of privacy, in breach of confidentiality toward a third party, infringe intellectual property rights, or which may threaten the safety of any User or third party. Snapshot reserves the right to hide such Proposals. Snapshot is not liable for the content of Proposals.\n\n#### Delegation of Voting Power\n\nYou can delegate your voting power to other Wallets. When you delegate your voting power, you have to enter the Wallet to which you want to delegate and specify whether the delegation is limited to a specific Space. If no Space is selected, the delegation applies to all Spaces on Snapshot.\n\n## 6. Archiving on IPFS\n\nBy using the Features, you acknowledge that information which is published on Snapshot, including but not limited to the Proposals’ content and voting results, will be permanently and publicly stored on the InterPlanetary File System (IPFS) and will be available to anyone, and that neither we nor any third party will have the power to restrict access to the data or delete such data stored on IPFS. Please be aware that the permanence of data on IPFS means that even if the original data is removed from Snapshot, copies of the data may still exist and be accessible on IPFS. While Snapshot facilitates the creation and voting process for Proposals, Snapshot does not have control over any data stored on IPFS and does not store any data on IPFS but rather relies on the decentralized nature of IPFS for storage and retrieval.\n\nThis information can also contain data which is considered personal data relating to an identified or identifiable natural person. If you want to ensure that your or third party’s privacy rights are not affected in any way, you should not use our Features as certain rights arising out of data protection laws may not be fully available or exercisable by you or us due to the technological infrastructure of IPFS.\n\nBy using Snapshot, you acknowledge and accept the implications of data permanence on IPFS. You should carefully consider the content you include in Proposals and your voting actions as they cannot be undone or removed once stored on IPFS.\n\n## 7. No Influence of Foundation over Spaces and Proposals\n\nThe execution of Proposals lies at the discretion of the DAOs. The Foundation – as well as any other private individual and/or legal entity related to the Foundation – has neither access to nor any other possibility to control and/or influence Spaces and their corresponding Proposals created and voted on by the Community.\n\n## 8. Boost App\n\n### 8.1 Overview\n\nThe Boost App is a smart contract-based application on the Ethereum blockchain that allows DAOs and their Communities to incentivize participants in the governance of the DAO by defining events (\"**Boost Events**\") that trigger rewards in the DAO's own governance token (\"**Governance Rewards**\") for DAO participants (\"**Governance Participants**\").\n\n### 8.2 Creation of Boost Events\n\nWhen you create a Boost Event, you define the amount of Governance Rewards, the conditions (\"**Conditions**\"), and the Distribution Criteria (as defined in Section 8.3) under which Governance Participants can earn Governance Rewards during an eligible period (\"**Boost Term**\").\n\nOnce you lock the Governance Rewards in the Boost App and pay the fees set out in Section 8.5, you will receive an NFT, and your Boost Event will start and be visible in the DAO’s space. You will not be able to modify and/or cancel your Boost Event once it has started.\n\nBy creating a Boost Event, you accept and understand that:\n\n- the Governance Participants will have two weeks following the Boost Term (\"**Claim Period**\") to claim the Governance Rewards;\n- any Governance Rewards collected by the Governance Participants cannot be reclaimed by you;\n- DAOs may be able to block your Boost Event on their Space if your Boost Event rewards Governance Participants for voting on specific Proposal outcomes;\n- the Governance Rewards will be locked in the Boost App until the end of the Claim Period.\n\n### 8.3 Distribution Criteria\n\nYou will be able to select between the following distribution criteria (\"**Distribution Criteria**\") for your Boost Event:\n\n- **Weighted Criteria**: The weighted criteria (\"**Weighted Criteria**\") allow Governance Rewards to be distributed to Governance Participants that have satisfied the Conditions (\"**Eligible Participants**\") in proportion to their voting power within the DAO. You can also set a maximum limit on the amount of Governance Rewards per Eligible Participant to prevent a single Eligible Participant from receiving a disproportionate amount of the Governance Rewards.\n- **Randomness Criteria**: The randomness criteria (\"**Randomness Criteria**\") uses the Ethereum blockchain’s RANDAO-based RNG algorithm to randomly select one or multiple Eligible Participants based on the Eligible Participant’s voting power within the DAO. A maximum voting power limit may be set to prevent any excess voting beyond that limit from being taken into account by the Randomness Criteria.\n\n### 8.4 Collection of Governance Rewards\n\nOnce a Governance Participant has complied with the Conditions, they will be able to collect the relevant Governance Rewards from the Boost App in accordance with the Distribution Criteria.\n\nYou accept and acknowledge that the Foundation will rely on the authenticity and integrity of your Conditions and Distribution Criteria set by you and does not conduct any due diligence or similar investigations as to the factual circumstances of the compliance with the Conditions and Distribution Criteria. The Foundation will not assume any liability associated with the (non-)compliance with the Conditions and Distribution Criteria, including but not limited to issues arising from fraud, misconduct of Governance Participants, and/or defects in the Distribution Criteria, etc.\n\nAt the end of the Claim Period, you will be able to unlock the remaining Governance Rewards by transferring the NFT to the Boost App.\n\n### 8.5 Fees\n\nThe creation of Boost Events (i.e., when locking Governance Rewards in the Boost App) is subject to the payment of the following fees:\n\n- **ETH Fees**: You will be required to pay us a fee in ETH as displayed on the Boost App.\n- **Governance Reward Fees**: You will be required to pay us a fee in Governance Rewards as displayed in the Boost App. This fee will be deducted from the total amount of locked Governance Rewards at the time of creation of the Boost Event.\n\n### 8.6 Gas Fees\n\nAll interactions with the Boost App require the payment of a transaction fee (\"**Gas Fee**\"), which is paid to the validators of the Ethereum blockchain. The Gas Fee required to execute a transaction depends on the activity on the Ethereum blockchain and is entirely outside of the control of the Foundation. By using the Boost App to initiate transactions and generate Sign Requests, you acknowledge and agree that Gas Fees are non-refundable under any circumstances.\n\n## 9. Risks\n\nBy accessing or using Snapshot, you understand and accept the risks connected to the Boost App. In particular, but not exhaustively, you understand the inherent risks listed hereinafter:\n\n- the Foundation does not control the public blockchains that Users are interacting with, nor any smart contracts and protocols that may be integral to Users’ ability to complete transactions on these public blockchains. On-chain transactions may be irreversible, and accordingly, losses due to fraudulent or accidental transactions may not be recoverable. Some transactions shall be deemed to be made when recorded on a public ledger, which is not necessarily the date or time that the User initiated the transaction.\n- the Foundation is not responsible for the title, value, validity, or genuineness of any of the Governance Rewards (or any evidence of title thereto);\n- the nature of Governance Rewards as digital assets and the Boost App may lead to an increased risk of fraud or cyberattacks and may mean that technological difficulties experienced may lead to limited access to and use of the Boost App;\n- The Foundation reserves the right to stop any Boost Events for any reason.\n\n## 10. Intellectual Property Rights\n\nWhile the rights in Snapshot are held by the Foundation, the Foundation has published the source code of Snapshot (\"**Snapshot Code**\") on its public GitHub repository and released it under the MIT open-source license. You are free to use the Snapshot Code to create another Snapshot for any other purpose.\n\n## 11. Your Representations and Warranties\n\nBy accepting these Terms, you warrant and represent that:\n\n- you will not create or manage Spaces for any purpose that is fraudulent, illegal, offensive, defamatory, or otherwise tortious or unlawful, including but not limited to illegal gambling, money laundering, fraud, blackmail, extortion, ransoming data, the financing of terrorism, intellectual property infringement, or violent or abusive activities. The Foundation reserves the right to hide such Spaces on Snapshot;\n- you will not create, submit, and vote on any Proposal whose content may be illegal, fraudulent, offensive, abusive, indecent, harmful, defamatory, obscene, or menacing, abusive, threatening, objectionable, invasive of privacy, in breach of confidentiality toward a third party, infringing intellectual property rights, or which may threaten the safety of any User or third party. Snapshot reserves the right to hide such Proposals on Snapshot. Snapshot is not liable for the content of Proposals.\n- you have provided all necessary notices to and obtained all necessary consents from the relevant parties for the collection and processing of their personal data for the purposes including but not limited to the archiving of the relevant personal data at IPFS;\n- any Governance Rewards locked in the Boost App are: (a) good, clean, clear, and are of non-criminal origin; (b) completely free and clear of any liens or encumbrances of any kind, of any rights of third-party interests; and (c) have no origins that may be connected to any breach of money laundering regulations whatsoever as defined in the jurisdiction of origin or internationally;\n- any Governance Rewards qualify as utility tokens within the meaning of Swiss financial market regulations and are not payment instruments, means of payment, payment tokens, and/or securities under any applicable laws;\n- you will not pretend to be another person or entity or act on behalf of a third party;\n- you will not provide false, inaccurate, or misleading information;\n- you have done sufficient research before making any decisions to create a Boost Event, lock, and/or collect any Governance Rewards;\n- you are legally permitted to receive, hold, and make use of Governance Rewards in your jurisdiction.\n- you have the sole control over your Wallet.\n- you will not upload, distribute, or otherwise make available through Snapshot any unlawful, defamatory, harassing, abusive, fraudulent, obscene, or otherwise objectionable content;\n- you will not upload, distribute, or otherwise make available through Snapshot any content that infringes intellectual property rights of any party;\n- you will not distribute malware, viruses, worms, defects, or other items of a destructive or captive nature;\n- you will not use Snapshot for any illegal activity, including without limitation terrorism, tax evasion, or money laundering;\n- you will not facilitate transactions involving individuals sanctioned by the Swiss, US, or any European government;\n- you will not use Snapshot to carry out any financial activities subject to registration or licensing, including but not limited to creating, selling, or buying securities, commodities, options, or debt instruments;\n- you will not use Snapshot to create, sell, or buy tokens or other items that give other users rights to participate in an ICO or any securities offering or that are redeemable for securities, commodities, or other financial instruments;\n- you will not defame, abuse, extort, harass, stalk, threaten, or otherwise violate or infringe the legal rights of others, including without limitation privacy rights and intellectual property rights;\n- you will not remove any copyright, trademark, or other proprietary rights notices contained in or on Snapshot or any part of it;\n- you will not use any data mining, robot, spider, crawler, scraper, script, browser extension, offline reader, or other automated means or interface not authorized by us to access Snapshot, extract data, or otherwise interfere with or modify the rendering of service pages or functionality;\n- you will not attempt to circumvent any fees charged by the Foundation;\n- you will not use Snapshot in a manner that breaches or causes the breach of any applicable export and re-export control laws and regulations.\n\n## 12. Disclaimers & Limited Warranty\n\nThe Foundation does not guarantee that Snapshot is free from defects, errors, bugs, and security vulnerabilities or that it will be available at any time. The access to and use of Snapshot is made at your own risk.\n\nThe Foundation gives no assurance that any functionalities and Features of Snapshot will satisfy your requirements or provide the intended results or meet any performance or reliability standards. You understand and agree that Snapshot is provided on an “as is” and “as available” basis and that Snapshot expressly disclaims all warranties or conditions of any kind, whether express, implied, statutory, or otherwise.\n\nThe Foundation makes no warranties as to the security, functionality, or availability of the Boost App, including but not limited to the corresponding smart contracts and Boost Events, as it may contain bugs, defects, or errors (including any bugs, defects, or errors relating to or resulting from the display, manipulation, processing, locking, transfer, or distribution of Governance Rewards) that may materially and adversely affect the use or functionality of the Boost App or any product or system containing or used in conjunction with the Boost App.\n\nThe Governance Rewards exist only by virtue of the ownership record maintained in the associated blockchain (e.g., Ethereum blockchain). Any transfers occur on the Ethereum blockchain. The Foundation and/or any other Foundation Party (as defined in margin 46) cannot effect or otherwise control the transfer of title or right in any Governance Rewards.\n\n## 13. Limitation of Liability\n\nThe liability of the Foundation is limited to direct damages arising out of acts of intent and gross negligence. Any liability for indirect damages or consequential damages, including loss of profit and/or damages arising out of negligent conduct, is expressly excluded. In particular, the Foundation does not assume any responsibility for Snapshot (or any feature or part thereof) and shall not be liable for any damages arising out of or related to your use of Snapshot, including but not limited to your creation and management of Spaces, submission, voting, and execution of Proposals, use of Boost Events, or your inability to collect and/or distribute Governance Rewards.\n\n## 14. Indemnification\n\nYou agree to the fullest extent permitted by applicable law to indemnify, defend, and hold harmless the Foundation and the Foundation Parties from and against all actual or alleged any claims, damages, awards, judgments, losses, liabilities, obligations, penalties, interests, fees, expenses (including without limitation attorneys’ fees and expenses), and costs (including without limitation court costs, costs of settlement, and costs of pursuing indemnification and insurance) of every kind and nature whatsoever, whether known or unknown, foreseen or unforeseen, matured or unmatured, or suspected or unsuspected, in law or equity, whether in tort, contract, or otherwise (\"**Claims**\") that are caused by, arise out of, or are related to:\n\n- your use or misuse of Snapshot;\n- your violation or breach of any term of these Terms, applicable law;\n- your violation of the rights of or obligations to a third party, including another User or third-party; and\n- your negligence or willful misconduct.\n\nYou agree to promptly notify the Foundation of any Claims and cooperate with the Foundation and all past, present, and future employees, officers, council members, directors, contractors, consultants, founders, suppliers, vendors, service providers, subsidiaries, affiliates, agents, representatives, predecessors, successors, and assignees of the Foundation (\"**Foundation Parties**\") in defending such Claims. You further agree that the Foundation, respectively the Foundation Parties, shall have control of the defense or settlement of any Claims. This indemnity is in addition to and not in lieu of any other indemnities as set forth in a written agreement between you and the Foundation, respectively the Foundation Parties.\n\n## 15. Privacy Notice and Cookie Declaration\n\nPlease refer to our Privacy Notice to understand how we collect and use and disclose your personal data as well as our Cookie Declaration to understand how we use cookies and how you can change your consent to their use.\n\n## 16. Miscellaneous\n\n### 16.1 User Feedback\n\nThe Foundation appreciates and encourages you to provide feedback to Snapshot. If you provide feedback, you agree that the Foundation is free to use it and may permit others to use it without any restriction or compensation to you.\n\n### 16.2 Tax\n\nIt is your sole responsibility to seek appropriate tax advice to comply with any applicable tax and duties or similar obligations in whichever jurisdiction and to assess the tax impact of the use of Snapshot, including the Boost App, and the use of the features offered thereon.\n\nThe User has no right to request a refund of any taxes, duties, or charges from the Foundation.\n\nAll fees payable to the Foundation are to be understood as exclusive of Swiss VAT. If Swiss VAT is due by the Foundation, the Foundation is free to charge the Swiss VAT to the User.\n\n### 16.3 Entire Agreement and Severability\n\nThese Terms contain the entire agreement between the Foundation and the Users regarding the subject matter hereof and supersedes all understandings and agreements, whether written or oral. If any provision of these Terms is invalid, illegal, or unenforceable in any jurisdiction, such invalidity, illegality, or unenforceability shall not affect any other provision of these Terms or invalidate or render unenforceable such provision in any other jurisdiction. Upon such determination that any provision is invalid, illegal, or unenforceable, these Terms shall be modified to effectuate the original intent of the Parties as closely as possible.\n\n### 16.4 Governing Law and Jurisdiction\n\nThese Terms shall be governed and construed in accordance with the substantive laws of Switzerland, without regard to conflict of law rules or principles (whether of Switzerland or any other jurisdiction). The application of the United Nations Convention on Contracts for the International Sale of Goods shall be excluded.\n\nAny dispute arising out of or in conjunction with these Terms shall be submitted to the exclusive jurisdiction of the ordinary courts of the city of Zug, Switzerland.`;\n</script>\n\n<template>\n  <div>\n    <BaseContainer class=\"mb-[40px] max-w-[880px]\">\n      <BaseMarkdown :body=\"terms\" />\n    </BaseContainer>\n  </div>\n</template>\n"
  },
  {
    "path": "src/views/TimelineView.vue",
    "content": "<script setup lang=\"ts\">\nimport { PROPOSALS_QUERY } from '@/helpers/queries';\nimport { useInfiniteScroll } from '@vueuse/core';\n\nuseMeta({\n  title: {\n    key: 'metaInfo.timeline.title'\n  },\n  description: {\n    key: 'metaInfo.timeline.description'\n  }\n});\n\nconst route = useRoute();\nconst router = useRouter();\nconst { followingSpaces, loadingFollows } = useFollowSpace();\nconst { web3, web3Account } = useWeb3();\nconst { apolloQuery } = useApolloQuery();\nconst { profiles, loadProfiles } = useProfiles();\nconst { loadBy, loadingMore, stopLoadingMore, loadMore } =\n  useInfiniteLoader(12);\n\nconst {\n  store,\n  userVotedProposalIds,\n  addTimelineProposals,\n  setTimelineProposals\n} = useProposals();\n\nconst loading = ref(false);\n\nconst stateFilter = computed(() => route.query.state || 'all');\nconst isFeedJoinedSpaces = computed(\n  () => !route.query.feed || route.query.feed === 'joined'\n);\n\nasync function getProposals(skip = 0) {\n  if (\n    isFeedJoinedSpaces.value &&\n    (!web3Account.value || followingSpaces.value.length < 1)\n  )\n    return [];\n  const spaces = isFeedJoinedSpaces.value ? followingSpaces.value : undefined;\n  const verified = route.query.feed === 'all' ? true : undefined;\n\n  return (\n    apolloQuery(\n      {\n        query: PROPOSALS_QUERY,\n        variables: {\n          first: loadBy,\n          skip,\n          space_in: spaces,\n          state: stateFilter.value,\n          space_verified: verified,\n          flagged: false\n        }\n      },\n      'proposals'\n    ) ?? []\n  );\n}\n\nasync function loadMoreProposals(skip: number) {\n  const proposals = await getProposals(skip);\n  stopLoadingMore.value = proposals?.length < loadBy;\n  addTimelineProposals(proposals);\n}\n\nasync function loadProposals() {\n  if (route.name !== 'timeline') return;\n  loading.value = true;\n  const proposals = await getProposals();\n  setTimelineProposals(proposals);\n  loading.value = false;\n}\n\nfunction setStateFilter(name: string) {\n  router.push({\n    query: {\n      ...route.query,\n      ['state']: name\n    }\n  });\n}\n\nfunction setFeed(name: string) {\n  router.push({\n    query: {\n      ...route.query,\n      ['feed']: name\n    }\n  });\n}\n\nwatch(\n  () => [route.query.state, route.query.feed, followingSpaces.value],\n  () => {\n    loadProposals();\n  },\n  { immediate: true }\n);\n\nwatch(store.timeline.proposals, () => {\n  loadProfiles(store.timeline.proposals.map(proposal => proposal.author));\n});\n\nuseInfiniteScroll(\n  document,\n  () => {\n    if (!store.timeline.proposals.length) return;\n    loadMore(() => loadMoreProposals(store.timeline.proposals.length));\n  },\n  { distance: 500 }\n);\n</script>\n\n<template>\n  <TheLayout class=\"!mt-0\">\n    <template #sidebar-left>\n      <div class=\"fixed hidden w-[240px] lg:block\">\n        <BaseBlock :slim=\"true\" class=\"overflow-hidden\">\n          <div class=\"py-3\">\n            <a\n              class=\"w-full text-left\"\n              tabindex=\"0\"\n              @click=\"setFeed('joined')\"\n              @keypress=\"setFeed('joined')\"\n            >\n              <BaseSidebarNavigationItem :is-active=\"isFeedJoinedSpaces\">\n                {{ $t('joinedSpaces') }}\n              </BaseSidebarNavigationItem>\n            </a>\n\n            <a\n              class=\"w-full text-left\"\n              tabindex=\"0\"\n              @click=\"setFeed('all')\"\n              @keypress=\"setFeed('all')\"\n            >\n              <BaseSidebarNavigationItem\n                :is-active=\"route.query.feed === 'all'\"\n              >\n                {{ $t('allSpaces') }}\n              </BaseSidebarNavigationItem>\n            </a>\n          </div>\n        </BaseBlock>\n      </div>\n    </template>\n    <template #content-right>\n      <div class=\"flex justify-between px-4 pb-4 md:px-0\">\n        <h2 class=\"mt-1\" v-text=\"$t('timeline')\" />\n        <BaseMenu\n          :items=\"[\n            {\n              text: $t('proposals.states.all'),\n              action: 'all',\n              extras: { selected: stateFilter === 'all' }\n            },\n            {\n              text: $t('proposals.states.active'),\n              action: 'active',\n              extras: { selected: stateFilter === 'active' }\n            },\n            {\n              text: $t('proposals.states.pending'),\n              action: 'pending',\n              extras: { selected: stateFilter === 'pending' }\n            },\n            {\n              text: $t('proposals.states.closed'),\n              action: 'closed',\n              extras: { selected: stateFilter === 'closed' }\n            }\n          ]\"\n          :selected=\"$t(`proposals.states.${stateFilter}`)\"\n          @select=\"setStateFilter\"\n        />\n      </div>\n      <div class=\"border-skin-border bg-skin-block-bg md:rounded-lg md:border\">\n        <LoadingRow v-if=\"loading || web3.authLoading || loadingFollows\" />\n        <div\n          v-else-if=\"\n            (isFeedJoinedSpaces && followingSpaces.length < 1) ||\n            (isFeedJoinedSpaces && !web3.account)\n          \"\n          class=\"p-4 text-center\"\n        >\n          <div class=\"mb-3\">{{ $t('noSpacesJoined') }}</div>\n          <router-link :to=\"{ path: '/' }\">\n            <TuneButton tabindex=\"-1\">{{ $t('joinSpaces') }}</TuneButton>\n          </router-link>\n        </div>\n        <BaseNoResults\n          v-else-if=\"store.timeline.proposals.length < 1\"\n          class=\"mb-0 py-4\"\n        />\n        <div v-else>\n          <ProposalsItem\n            v-for=\"(proposal, i) in store.timeline.proposals\"\n            :key=\"i\"\n            :proposal=\"proposal\"\n            :space=\"proposal.space\"\n            :profiles=\"profiles\"\n            :voted=\"userVotedProposalIds.includes(proposal.id)\"\n            :to=\"{\n              name: 'spaceProposal',\n              params: { key: proposal.space.id, id: proposal.id }\n            }\"\n            show-verified-icon\n            class=\"border-b border-skin-border transition-colors first:border-t last:border-b-0 md:border-b md:first:border-t-0\"\n          />\n        </div>\n\n        <LoadingRow v-if=\"loadingMore\" class=\"border-t\" />\n      </div>\n    </template>\n  </TheLayout>\n</template>\n"
  },
  {
    "path": "tailwind.config.js",
    "content": "import formsPlugin from '@tailwindcss/forms';\n\nexport default {\n  content: ['./index.html', './src/**/*.{js,ts,vue}'],\n  theme: {\n    extend: {\n      colors: {\n        snapshot: '#F2994A',\n        boost: '#F2994A',\n        'skin-primary': 'var(--primary-color)',\n        'skin-border': 'var(--border-color)',\n        'skin-text': 'var(--text-color)',\n        'skin-link': 'var(--link-color)',\n        'skin-bg': 'var(--bg-color)',\n        'skin-block-bg': 'var(--block-bg)',\n        'skin-header-bg': 'var(--header-bg)',\n        'skin-heading': 'var(--heading-color)',\n        green: '#21b66f',\n        red: '#ff3856',\n        'skin-success': '#57B375'\n      },\n      keyframes: {\n        fadeIn: {\n          '0%': { opacity: '0' },\n          '100%': { opacity: '1' }\n        }\n      },\n      animation: {\n        'fade-in': 'fadeIn 1s',\n        'pulse-fast': 'pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite'\n      }\n    },\n    spacing: {\n      0: '0px',\n      1: '4px',\n      2: '8px',\n      3: '16px',\n      4: '24px',\n      5: '32px',\n      6: '40px'\n    },\n    screens: {\n      xs: '420px',\n      sm: '544px',\n      md: '768px',\n      lg: '1012px',\n      xl: '1280px',\n      '2xl': '1536px'\n    },\n    fontFamily: {\n      sans: [\n        'Calibre, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji'\n      ],\n      mono: ['monospace'],\n      space: ['SpaceMono']\n    },\n    fontSize: {\n      '2xl': ['36px', '50px'],\n      xl: ['28px', '44px'],\n      lg: ['24px', '32px'],\n      md: ['20px', '28px'],\n      base: ['18px', '24px'],\n      sm: ['16px'],\n      xs: ['14px']\n    },\n    boxShadow: {\n      lg: '2px 4px 9px var(--shadow-color)',\n      xl: '7px 10.5px 28px 0px var(--shadow-color)'\n    }\n  },\n  plugins: [\n    'prettier-plugin-tailwindcss',\n    formsPlugin({\n      strategy: 'class'\n    })\n  ]\n};\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    \"sourceMap\": true,\n    \"resolveJsonModule\": true,\n    \"esModuleInterop\": true,\n    \"lib\": [\"esnext\", \"dom\"],\n    \"importHelpers\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"allowJs\": true,\n    \"baseUrl\": \".\",\n    \"types\": [\"vite/client\", \"unplugin-icons/types/vue\"],\n    \"paths\": {\n      \"@/*\": [\"src/*\"]\n    },\n    \"noImplicitAny\": false,\n    \"skipLibCheck\": true,\n    \"outDir\": \"dist\"\n  },\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.d.ts\",\n    \"src/**/*.tsx\",\n    \"src/**/*.vue\",\n    \"components.d.ts\",\n    \"auto-imports.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "vercel.json",
    "content": "{\n  \"rewrites\": [\n    {\n      \"source\": \"/(.*)\",\n      \"destination\": \"/\"\n    }\n  ]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "/// <reference types=\"vitest\" />\nimport path from 'path';\nimport { defineConfig } from 'vitest/config';\nimport { getPlugins } from './src/helpers/vitePlugins';\n\nexport default defineConfig({\n  define: {\n    'process.env': process.env\n  },\n  plugins: getPlugins(),\n  resolve: {\n    alias: {\n      '@': path.resolve(__dirname, './src'),\n      'readable-stream': 'vite-compatible-readable-stream'\n    }\n  },\n  test: {\n    environment: 'happy-dom',\n    deps: {\n      inline: ['@pusher/push-notifications-web']\n    }\n  },\n  build: {\n    sourcemap: process.env.VITE_ENV === 'production',\n    target: 'esnext'\n  },\n  optimizeDeps: {\n    esbuildOptions: {\n      target: 'esnext'\n    }\n  }\n});\n"
  }
]