├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .env.example ├── .github └── workflows │ ├── anchor-test.yaml │ ├── deploy-buffer.yaml │ ├── generate-verifiable-builds.yaml │ └── verify-build.yaml ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── VERIFYING-BUILDS.md ├── audits ├── accretion-metadao-launchpad-audit-A25MET1-march-2025.pdf └── neodyme-july-2024.pdf ├── docs ├── .gitbook │ └── assets │ │ ├── God_Rays (1).png │ │ ├── God_Rays.png │ │ ├── Mediakit-colors-red (1).png │ │ ├── Mediakit-colors-red.png │ │ ├── Meta-DAO Operating Agreement.pdf │ │ ├── MetaDAO LLC - Corporate Charter Certificate.pdf │ │ ├── MetaDAO-logo (1).png │ │ ├── MetaDAO-logo (1).svg │ │ ├── MetaDAO-logo-dark (1).png │ │ ├── MetaDAO-logo-dark (1).svg │ │ ├── MetaDAO-logo-dark.png │ │ ├── MetaDAO-logo-dark.svg │ │ ├── MetaDAO-logo.png │ │ ├── MetaDAO-logo.svg │ │ ├── autocrat-markets (1).png │ │ ├── autocrat-markets.png │ │ ├── conditional-markets-dark (1).png │ │ ├── conditional-markets-dark.png │ │ ├── conditional-markets-interaction (1).png │ │ ├── conditional-markets-interaction.png │ │ ├── conditional-markets-light.png │ │ ├── conditional-markets.png │ │ ├── conditional-vault-quote (1).png │ │ ├── conditional-vault-quote.png │ │ ├── conditional-vault-underlying (1).png │ │ ├── conditional-vault-underlying.png │ │ ├── conditional-vaults (1).png │ │ ├── conditional-vaults (2).png │ │ ├── conditional-vaults.png │ │ ├── faas-colors-black (1).webp │ │ ├── faas-colors-black.webp │ │ ├── faas-colors-green (1).webp │ │ ├── faas-colors-green.webp │ │ ├── faas-colors-pink (1).webp │ │ ├── faas-colors-pink.webp │ │ ├── futarchy-protocol-diagram.png │ │ ├── grant-flow-chart (1).png │ │ ├── grant-flow-chart.png │ │ ├── grant-ideation-decision-market (1).png │ │ ├── grant-ideation-decision-market.png │ │ ├── grant-spot-evaluation (1).png │ │ ├── grant-spot-evaluation.png │ │ ├── grant-summary (1).png │ │ ├── grant-summary.png │ │ ├── image.png │ │ ├── metadao-og-image.jpg │ │ ├── protocol-map.png │ │ ├── protocol-no-background (1).png │ │ ├── protocol-no-background.png │ │ ├── rubric-score.png │ │ ├── settled-market-conditional-vault (1).png │ │ ├── settled-market-conditional-vault.png │ │ ├── twap-chart (1).png │ │ └── twap-chart.png ├── README.md ├── SUMMARY.md ├── book.json ├── examples │ └── rubric.md ├── futarchy │ ├── benefits.md │ ├── drawbacks.md │ ├── eli5.md │ └── overview.md ├── implementation │ ├── price-oracle.md │ └── program-architecture.md ├── media-kit │ └── faas.md ├── metadao-1.md ├── metadao │ ├── legal.md │ ├── meta.md │ └── overview.md ├── proposal-templates │ ├── business-direct-action.md │ ├── business-project.md │ ├── operations-direct-action.md │ └── operations-project.md └── using-the-platform │ ├── creating-a-dao.md │ ├── creating-proposals.md │ ├── trading-proposals.md │ └── value-resolved-decision-markets.md ├── package-lock.json ├── package.json ├── programs ├── amm │ ├── Cargo.toml │ ├── Xargo.toml │ └── src │ │ ├── error.rs │ │ ├── events.rs │ │ ├── instructions │ │ ├── add_liquidity.rs │ │ ├── common.rs │ │ ├── crank_that_twap.rs │ │ ├── create_amm.rs │ │ ├── mod.rs │ │ ├── remove_liquidity.rs │ │ └── swap.rs │ │ ├── lib.rs │ │ └── state │ │ ├── amm.rs │ │ └── mod.rs ├── autocrat │ ├── Cargo.toml │ ├── Xargo.toml │ └── src │ │ ├── error.rs │ │ ├── events.rs │ │ ├── instructions │ │ ├── execute_proposal.rs │ │ ├── finalize_proposal.rs │ │ ├── initialize_dao.rs │ │ ├── initialize_proposal.rs │ │ ├── mod.rs │ │ └── update_dao.rs │ │ ├── lib.rs │ │ └── state │ │ ├── dao.rs │ │ ├── mod.rs │ │ └── proposal.rs ├── autocrat_migrator │ ├── Cargo.toml │ ├── Xargo.toml │ └── src │ │ └── lib.rs ├── conditional_vault │ ├── Cargo.toml │ ├── Xargo.toml │ └── src │ │ ├── error.rs │ │ ├── events.rs │ │ ├── instructions │ │ ├── add_metadata_to_conditional_tokens.rs │ │ ├── common.rs │ │ ├── initialize_conditional_vault.rs │ │ ├── initialize_question.rs │ │ ├── merge_tokens.rs │ │ ├── mod.rs │ │ ├── redeem_tokens.rs │ │ ├── resolve_question.rs │ │ └── split_tokens.rs │ │ ├── lib.rs │ │ └── state │ │ ├── conditional_vault.rs │ │ ├── mod.rs │ │ └── question.rs ├── launchpad │ ├── Cargo.toml │ ├── Xargo.toml │ └── src │ │ ├── error.rs │ │ ├── events.rs │ │ ├── instructions │ │ ├── claim.rs │ │ ├── complete_launch.rs │ │ ├── fund.rs │ │ ├── initialize_launch.rs │ │ ├── mod.rs │ │ ├── refund.rs │ │ └── start_launch.rs │ │ ├── lib.rs │ │ └── state │ │ ├── funding_record.rs │ │ ├── launch.rs │ │ └── mod.rs └── optimistic_timelock │ ├── Cargo.toml │ ├── Xargo.toml │ └── src │ └── lib.rs ├── run.sh ├── scripts ├── README.md ├── addV03Metadata.ts ├── addV04Metadata.ts ├── assets │ ├── Binary │ │ ├── NO.json │ │ ├── NO.png │ │ ├── YES.json │ │ └── YES.png │ ├── CLOUD-USDC │ │ ├── fUSDC.json │ │ └── pUSDC.json │ ├── CLOUD │ │ ├── fCLOUD.json │ │ └── pCLOUD.json │ ├── DRIFT │ │ ├── fDRIFT.json │ │ └── pDRIFT.json │ ├── FAF │ │ ├── fFAF.json │ │ ├── fFAF.png │ │ ├── pFAF.json │ │ └── pFAF.png │ ├── ISLAND │ │ ├── fISLAND.json │ │ ├── fISLAND.png │ │ ├── pISLAND.json │ │ └── pISLAND.png │ ├── META │ │ ├── data.json │ │ ├── fMETA.png │ │ └── pMETA.png │ ├── MNDE │ │ ├── fMNDE.json │ │ └── pMNDE.json │ ├── MTN │ │ ├── MTN.json │ │ └── MTN.png │ ├── ORE │ │ ├── fORE.json │ │ ├── fORE.png │ │ ├── pORE.json │ │ └── pORE.png │ ├── SAVE │ │ ├── fSAVE.json │ │ ├── fSAVE.png │ │ ├── pSAVE.json │ │ └── pSAVE.png │ ├── Scalar │ │ ├── DOWN.json │ │ ├── DOWN.png │ │ ├── UP.json │ │ └── UP.png │ ├── USDC │ │ ├── data.json │ │ ├── fUSDC.json │ │ ├── fUSDC.png │ │ ├── pUSDC.json │ │ └── pUSDC.png │ └── sCLOUD │ │ ├── fsCLOUD.json │ │ └── psCLOUD.json ├── consts.ts ├── createProposal.ts ├── createV04DAO.ts ├── createV04Proposal.ts ├── initializeLaunch.ts ├── launchComplete.ts ├── launchInit.ts ├── launchStart.ts ├── redeemLaunch.ts └── setupMetricMarket.ts ├── sdk ├── .gitignore ├── package.json ├── src │ ├── index.ts │ ├── v0.3 │ │ ├── AmmClient.ts │ │ ├── AutocratClient.ts │ │ ├── ConditionalVaultClient.ts │ │ ├── FutarchyClient.ts │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── types │ │ │ ├── amm.ts │ │ │ ├── autocrat.ts │ │ │ ├── autocrat_migrator.ts │ │ │ ├── conditional_vault.ts │ │ │ ├── index.ts │ │ │ ├── optimistic_timelock.ts │ │ │ └── utils.ts │ │ └── utils │ │ │ ├── ammMath.ts │ │ │ ├── cu.ts │ │ │ ├── filters.ts │ │ │ ├── index.ts │ │ │ ├── instruction.ts │ │ │ ├── metadata.ts │ │ │ └── pda.ts │ └── v0.4 │ │ ├── AmmClient.ts │ │ ├── AutocratClient.ts │ │ ├── ConditionalVaultClient.ts │ │ ├── LaunchpadClient.ts │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── types │ │ ├── amm.ts │ │ ├── autocrat.ts │ │ ├── autocrat_migrator.ts │ │ ├── conditional_vault.ts │ │ ├── index.ts │ │ ├── launchpad.ts │ │ ├── optimistic_timelock.ts │ │ └── utils.ts │ │ └── utils │ │ ├── cu.ts │ │ ├── filters.ts │ │ ├── index.ts │ │ ├── instruction.ts │ │ ├── metadata.ts │ │ ├── pda.ts │ │ └── priceMath.ts ├── tsconfig.json └── yarn.lock ├── tests ├── amm │ ├── amm.ts │ ├── integration │ │ └── ammLifecycle.test.ts │ ├── main.test.ts │ └── unit │ │ ├── addLiquidity.test.ts │ │ ├── crankThatTwap.test.ts │ │ ├── initializeAmm.test.ts │ │ ├── removeLiquidity.test.ts │ │ └── swap.test.ts ├── autocrat │ ├── autocrat.ts │ └── main.test.ts ├── autocratMigrator │ └── migrator.ts ├── conditionalVault │ ├── integration │ │ ├── binaryPredictionMarket.test.ts │ │ ├── multiOptionPredictionMarket.test.ts │ │ └── scalarGrantMarket.test.ts │ ├── main.test.ts │ ├── unit.ts │ └── unit │ │ ├── addMetadataToConditionalTokens.test.ts │ │ ├── initializeConditionalVault.test.ts │ │ ├── initializeQuestion.test.ts │ │ ├── mergeTokens.test.ts │ │ ├── redeemTokens.test.ts │ │ ├── resolveQuestion.test.ts │ │ └── splitTokens.test.ts ├── fixtures │ ├── mpl_token_metadata.so │ ├── openbook_twap.json │ ├── openbook_twap.so │ ├── openbook_twap.ts │ ├── openbook_v2.so │ ├── raydium-amm-config │ ├── raydium-create-pool-fee-receive │ ├── raydium_cp_swap.so │ └── usdc ├── integration │ ├── fullLaunch.test.ts │ ├── mintAndSwap.test.ts │ ├── scalarMarkets.test.ts │ └── twap.test.ts ├── launchpad │ ├── main.test.ts │ ├── unit │ │ ├── claim.test.ts │ │ ├── completeLaunch.test.ts │ │ ├── fund.test.ts │ │ ├── initializeLaunch.test.ts │ │ ├── refund.test.ts │ │ └── startLaunch.test.ts │ └── utils.ts ├── main.test.ts ├── optimisticTimelock │ └── timelock.ts └── utils.ts ├── tsconfig.json ├── verifiable-builds ├── amm.so ├── autocrat.so ├── autocrat_migrator.so ├── autocrat_v0.so ├── conditional_vault.so └── launchpad.so └── yarn.lock /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Docker image to generate deterministic, verifiable builds of Anchor programs. 3 | # This must be run *after* a given ANCHOR_CLI version is published and a git tag 4 | # is released on GitHub. 5 | # 6 | 7 | FROM rust:1.75 8 | 9 | ARG DEBIAN_FRONTEND=noninteractive 10 | 11 | ARG SOLANA_CLI="1.17.16" 12 | ARG ANCHOR_CLI="0.29.0" 13 | ARG NODE_VERSION="v18.16.0" 14 | 15 | ENV HOME="/root" 16 | ENV PATH="${HOME}/.cargo/bin:${PATH}" 17 | ENV PATH="${HOME}/.local/share/solana/install/active_release/bin:${PATH}" 18 | ENV PATH="${HOME}/.nvm/versions/node/${NODE_VERSION}/bin:${PATH}" 19 | 20 | # Install base utilities. 21 | RUN mkdir -p /workdir && mkdir -p /tmp && \ 22 | apt-get update -qq && apt-get upgrade -qq && apt-get install -qq \ 23 | build-essential git curl wget jq pkg-config python3-pip \ 24 | libssl-dev libudev-dev 25 | 26 | #RUN wget http://nz2.archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.19_amd64.deb 27 | #RUN dpkg -i libssl1.1_1.1.1f-1ubuntu2.19_amd64.deb 28 | 29 | # Install rust. 30 | RUN curl "https://sh.rustup.rs" -sfo rustup.sh && \ 31 | sh rustup.sh -y && \ 32 | rustup component add rustfmt clippy 33 | 34 | # Install node / npm / yarn. 35 | RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash 36 | ENV NVM_DIR="${HOME}/.nvm" 37 | RUN . $NVM_DIR/nvm.sh && \ 38 | nvm install ${NODE_VERSION} && \ 39 | nvm use ${NODE_VERSION} && \ 40 | nvm alias default node && \ 41 | npm install -g yarn 42 | 43 | # Install Solana tools. 44 | RUN sh -c "$(curl -sSfL https://release.solana.com/v${SOLANA_CLI}/install)" 45 | 46 | # Install anchor. 47 | RUN cargo install --git https://github.com/coral-xyz/anchor avm --locked --force 48 | RUN avm install ${ANCHOR_CLI} && avm use ${ANCHOR_CLI} 49 | 50 | # set up keypair 51 | RUN solana-keygen new --no-bip39-passphrase 52 | 53 | WORKDIR /workdir 54 | #be sure to add `/root/.avm/bin` to your PATH to be able to run the installed binaries 55 | 56 | # install dependencies 57 | RUN yarn -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { "dockerfile": "Dockerfile" }, 3 | } 4 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Settings for launchpad initialization and starting a launch 2 | NETWORK="devnet" 3 | RPC_URL="https://api.devnet.solana.com" 4 | WALLET_PATH=".config/solana/id.json" 5 | LAUNCH_AUTHORITY_KEYPAIR_PATH=".config/solana/id.json" 6 | LAUNCH_AUTHORITY_ADDRESS="LAUNCH_AUTHORITY_ADDRESS" 7 | LAUNCH_TOKEN_NAME="Token Name" 8 | LAUNCH_TOKEN_SYMBOL="TKN" 9 | LAUNCH_TOKEN_URI="https://www.example.com/" 10 | MINIMUM_RAISE_AMOUNT="10" 11 | SECONDS_FOR_LAUNCH="10" 12 | LAUNCH_ADDRESS="" #used for starting and completing a launch -------------------------------------------------------------------------------- /.github/workflows/anchor-test.yaml: -------------------------------------------------------------------------------- 1 | name: anchor-test 2 | on: 3 | push: 4 | branches: 5 | - develop 6 | - production 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | run-anchor-test: 12 | runs-on: ubuntu-22.04 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - uses: metadaoproject/anchor-test@v2.2 18 | with: 19 | anchor-version: '0.29.0' 20 | solana-cli-version: '1.17.16' -------------------------------------------------------------------------------- /.github/workflows/deploy-buffer.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy Buffer 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | program_name: 7 | description: 'Program name' 8 | required: true 9 | type: string 10 | program_id: 11 | description: 'Program ID' 12 | required: true 13 | type: string 14 | rpc_url: 15 | description: 'RPC URL' 16 | required: true 17 | type: string 18 | 19 | 20 | jobs: 21 | deploy-buffer: 22 | runs-on: ubuntu-22.04 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: metadaoproject/setup-anchor@v3.2 26 | with: 27 | solana-cli-version: '1.17.16' 28 | anchor-version: '0.29.0' 29 | - run: echo "$DEPLOYER_KEYPAIR" > deployer-keypair.json && chmod 600 deployer-keypair.json 30 | shell: bash 31 | env: 32 | DEPLOYER_KEYPAIR: ${{ secrets.DEPLOYER_KEYPAIR }} 33 | - run: solana-keygen new -s -o buffer-keypair --no-bip39-passphrase 34 | shell: bash 35 | - name: Deploy Buffer 36 | uses: nick-fields/retry@v3 37 | with: 38 | timeout_minutes: 30 39 | max_attempts: 10 40 | shell: bash 41 | command: solana program write-buffer --max-sign-attempts 50 --with-compute-unit-price 100 --use-rpc --buffer ./buffer-keypair ./verifiable-builds/${{ inputs.program_name }}.so -u $NETWORK -------------------------------------------------------------------------------- /.github/workflows/generate-verifiable-builds.yaml: -------------------------------------------------------------------------------- 1 | name: generate-verifiable-builds 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - production 7 | - develop 8 | 9 | jobs: 10 | generate-verifiable-autocrat: 11 | runs-on: ubuntu-22.04 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: metadaoproject/anchor-verifiable-build@v0.3 15 | with: 16 | program: autocrat 17 | anchor-version: '0.29.0' 18 | solana-cli-version: '1.17.16' 19 | - run: 'git pull --rebase' 20 | - run: cp target/deploy/autocrat.so ./verifiable-builds 21 | - name: Commit verifiable build back to mainline 22 | uses: EndBug/add-and-commit@v9.1.4 23 | with: 24 | default_author: github_actions 25 | message: 'Update autocrat verifiable build' 26 | generate-verifiable-vault: 27 | runs-on: ubuntu-22.04 28 | steps: 29 | - uses: actions/checkout@v4 30 | - uses: metadaoproject/anchor-verifiable-build@v0.3 31 | with: 32 | program: conditional_vault 33 | anchor-version: '0.29.0' 34 | solana-cli-version: '1.17.16' 35 | features: 'production' 36 | - run: 'git pull --rebase' 37 | - run: cp target/deploy/conditional_vault.so ./verifiable-builds 38 | - name: Commit verifiable build back to mainline 39 | uses: EndBug/add-and-commit@v9.1.4 40 | with: 41 | default_author: github_actions 42 | message: 'Update conditional_vault verifiable build' 43 | generate-verifiable-amm: 44 | runs-on: ubuntu-22.04 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: metadaoproject/anchor-verifiable-build@v0.3 48 | with: 49 | program: amm 50 | anchor-version: '0.29.0' 51 | solana-cli-version: '1.17.16' 52 | - run: 'git pull --rebase' 53 | - run: cp target/deploy/amm.so ./verifiable-builds 54 | - name: Commit verifiable build back to mainline 55 | uses: EndBug/add-and-commit@v9.1.4 56 | with: 57 | default_author: github_actions 58 | message: 'Update amm verifiable build' 59 | generate-verifiable-launchpad: 60 | runs-on: ubuntu-22.04 61 | steps: 62 | - uses: actions/checkout@v4 63 | - uses: metadaoproject/anchor-verifiable-build@v0.3 64 | with: 65 | program: launchpad 66 | anchor-version: '0.29.0' 67 | solana-cli-version: '1.17.16' 68 | features: 'production' 69 | - run: 'git pull --rebase' 70 | - run: cp target/deploy/launchpad.so ./verifiable-builds 71 | - name: Commit verifiable build back to mainline 72 | uses: EndBug/add-and-commit@v9.1.4 73 | with: 74 | default_author: github_actions 75 | message: 'Update launchpad verifiable build' 76 | 77 | -------------------------------------------------------------------------------- /.github/workflows/verify-build.yaml: -------------------------------------------------------------------------------- 1 | name: Verify Build 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | program_name: 7 | description: 'Program name' 8 | required: true 9 | type: string 10 | program_id: 11 | description: 'Program ID' 12 | required: true 13 | type: string 14 | anchor-version: 15 | description: 'Anchor version' 16 | required: true 17 | default: '0.29.0' 18 | solana-cli-version: 19 | description: 'Solana CLI version' 20 | required: true 21 | default: '1.17.16' 22 | 23 | jobs: 24 | verify-build: 25 | runs-on: ubuntu-22.04 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: metadaoproject/setup-anchor@v3.2 29 | with: 30 | anchor-version: ${{ inputs.anchor-version }} 31 | solana-cli-version: ${{ inputs.solana-cli-version }} 32 | - name: Cache Cargo registry + index 33 | uses: actions/cache@v4 34 | id: cache-cargo-registry 35 | with: 36 | path: | 37 | ~/.cargo/registry/index/ 38 | ~/.cargo/registry/cache/ 39 | ~/.cargo/git/db/ 40 | key: cargo-${{ runner.os }}-v0001-${{ hashFiles('**/Cargo.lock') }} 41 | - name: Cache Solana Verify 42 | uses: actions/cache@v4 43 | id: cache-solana-verify 44 | with: 45 | path: | 46 | ~/.cargo/bin/solana-verify 47 | key: cargo-${{ runner.os }}-solana-verify 48 | - run: cargo install solana-verify 49 | if: steps.cache-solana-verify.outputs.cache-hit != 'true' 50 | shell: bash 51 | - name: Verify build 52 | run: | 53 | solana-verify verify-from-repo --remote -um --program-id ${{ inputs.program_id }} https://github.com/${{ github.repository }} --library-name ${{ inputs.program_name }} 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | scripts/*.js 9 | .env 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | seeds = false 5 | skip-lint = false 6 | 7 | [programs.localnet] 8 | amm = "AMMyu265tkBpRW21iGQxKGLaves3gKm2JcMUqfXNSpqD" 9 | autocrat = "autowMzCbM29YXMgVG3T62Hkgo7RcyrvgQQkd54fDQL" 10 | autocrat_migrator = "MigRDW6uxyNMDBD8fX2njCRyJC4YZk2Rx9pDUZiAESt" 11 | conditional_vault = "VLTX1ishMBbcX3rdBWGssxawAo1Q2X2qxYFYqiGodVg" 12 | launchpad = "AfJJJ5UqxhBKoE3grkKAZZsoXDE9kncbMKvqSHGsCNrE" 13 | optimistic_timelock = "tiME1hz9F5C5ZecbvE5z6Msjy8PKfTqo1UuRYXfndKF" 14 | 15 | [registry] 16 | url = "https://api.apr.dev" 17 | 18 | [provider] 19 | cluster = "Localnet" 20 | wallet = "~/.config/solana/id.json" 21 | 22 | [scripts] 23 | test = "npx mocha --import=tsx tests/main.test.ts" 24 | add-v03-metadata = "yarn run tsx scripts/addV03Metadata.ts" 25 | initialize-launch = "yarn run tsx scripts/initializeLaunch.ts" 26 | create-proposal = "yarn run tsx scripts/createProposal.ts" 27 | create-v04-dao = "yarn run tsx scripts/createV04DAO.ts" 28 | create-v04-proposal = "yarn run tsx scripts/createV04Proposal.ts" 29 | add-v04-metadata = "yarn run tsx scripts/addV04Metadata.ts" 30 | 31 | [test] 32 | startup_wait = 5000 33 | shutdown_wait = 2000 34 | upgradeable = false 35 | 36 | [[test.genesis]] 37 | address = "opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb" 38 | program = "./tests/fixtures/openbook_v2.so" 39 | 40 | [[test.genesis]] 41 | address = "TWAP7frdvD3ia7TWc8e9SxZMmrpd2Yf3ifSPAHS8VG3" 42 | program = "./tests/fixtures/openbook_twap.so" 43 | 44 | [[test.genesis]] 45 | address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" 46 | program = "./tests/fixtures/mpl_token_metadata.so" 47 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | resolver = "2" 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Futarchy 2 | 3 | ![License BSLv1.1](https://img.shields.io/badge/License-BSLv1.1-lightgray.svg) 4 | 5 | Programs for market-driven governance 6 | 7 | ## Deployments 8 | 9 | | program | tag | program ID | 10 | | ----------------- | ---- | -------------------------------------------- | 11 | | launchpad | delayed-twap-v0.4.1 | AfJJJ5UqxhBKoE3grkKAZZsoXDE9kncbMKvqSHGsCNrE | 12 | | autocrat | proposal-duration-v0.4.2 | autowMzCbM29YXMgVG3T62Hkgo7RcyrvgQQkd54fDQL | 13 | | amm | delayed-twap-v0.4.1 | AMMyu265tkBpRW21iGQxKGLaves3gKm2JcMUqfXNSpqD | 14 | | conditional_vault | v0.4 | VLTX1ishMBbcX3rdBWGssxawAo1Q2X2qxYFYqiGodVg | 15 | | autocrat | v0.3 | autoQP9RmUNkzzKRXsMkWicDVZ3h29vvyMDcAYjCxxg | 16 | | amm | v0.3 | AMM5G2nxuKUwCLRYTW7qqEwuoqCtNSjtbipwEmm2g8bH | 17 | | conditional_vault | v0.3 | VAU1T7S5UuEHmMvXtXMVmpEoQtZ2ya7eRb7gcN47wDp | 18 | | autocrat_v0 | v0.2 | metaRK9dUBnrAdZN6uUDKvxBVKW5pyCbPVmLtUZwtBp | 19 | | autocrat_migrator | v0.2 | MigRDW6uxyNMDBD8fX2njCRyJC4YZk2Rx9pDUZiAESt | 20 | | conditional_vault | v0.2 | vAuLTQjV5AZx5f3UgE75wcnkxnQowWxThn1hGjfCVwP | 21 | | autocrat_v0 | v0.1 | metaX99LHn3A7Gr7VAcCfXhpfocvpMpqQ3eyp3PGUUq | 22 | | autocrat_migrator | v0.1 | migkwAXrXFN34voCYQUhFQBXZJjHrWnpEXbSGTqZdB3 | 23 | | autocrat_v0 | v0 | meta3cxKzFBmWYgCVozmvCQAS3y9b3fGxrG9HkHL7Wi | 24 | | conditional_vault | v0 | vaU1tVLj8RFk7mNj1BxqgAsMKKaL8UvEUHvU3tdbZPe | 25 | 26 | -------------------------------------------------------------------------------- /VERIFYING-BUILDS.md: -------------------------------------------------------------------------------- 1 | # Verifying Builds 2 | 3 | If you're on the MetaDAO security council, you will need to verify builds before 4 | allowing the program to be upgraded. Here's how to do it: 5 | 6 | 1. Open up `futarchy` on GitHub.com 7 | 2. Navigate to the 'Actions' tab 8 | 3. Click on the 'generate-verifiable-builds' workflow 9 | 4. Click on the latest commit 10 | 5. Click on the relevant program 11 | 6. Open up the `anchor-verifiable-build` step 12 | 7. Verify that the action name is `metadaoproject/anchor-verifiable-build@v0.2` 13 | 8. Scroll down to the bottom of the step, you should see a hash right below "Program Solana version: vx.y.z" 14 | 9. Open up app.squads.so 15 | 10. Click on the program upgrade transaction, you should be able to see the buffer account from either simulating 16 | the transaction or looking at the transaction details 17 | 11. Now open up a terminal and run the following command: `solana-verify get-buffer-hash ${BUFFER_ACCT}` where 18 | `${BUFFER_ACCT}` is the buffer account from step 10 19 | 12. Compare the hash from step 11 with the hash from step 8, if they match, you can approve the upgrade 20 | 21 | -------------------------------------------------------------------------------- /audits/accretion-metadao-launchpad-audit-A25MET1-march-2025.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/audits/accretion-metadao-launchpad-audit-A25MET1-march-2025.pdf -------------------------------------------------------------------------------- /audits/neodyme-july-2024.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/audits/neodyme-july-2024.pdf -------------------------------------------------------------------------------- /docs/.gitbook/assets/God_Rays (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/God_Rays (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/God_Rays.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/God_Rays.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/Mediakit-colors-red (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/Mediakit-colors-red (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/Mediakit-colors-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/Mediakit-colors-red.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/Meta-DAO Operating Agreement.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/Meta-DAO Operating Agreement.pdf -------------------------------------------------------------------------------- /docs/.gitbook/assets/MetaDAO LLC - Corporate Charter Certificate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/MetaDAO LLC - Corporate Charter Certificate.pdf -------------------------------------------------------------------------------- /docs/.gitbook/assets/MetaDAO-logo (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/MetaDAO-logo (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/MetaDAO-logo (1).svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/.gitbook/assets/MetaDAO-logo-dark (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/MetaDAO-logo-dark (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/MetaDAO-logo-dark (1).svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/.gitbook/assets/MetaDAO-logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/MetaDAO-logo-dark.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/MetaDAO-logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/.gitbook/assets/MetaDAO-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/MetaDAO-logo.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/MetaDAO-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/.gitbook/assets/autocrat-markets (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/autocrat-markets (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/autocrat-markets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/autocrat-markets.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-markets-dark (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-markets-dark (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-markets-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-markets-dark.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-markets-interaction (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-markets-interaction (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-markets-interaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-markets-interaction.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-markets-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-markets-light.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-markets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-markets.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-vault-quote (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-vault-quote (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-vault-quote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-vault-quote.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-vault-underlying (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-vault-underlying (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-vault-underlying.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-vault-underlying.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-vaults (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-vaults (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-vaults (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-vaults (2).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/conditional-vaults.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/conditional-vaults.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/faas-colors-black (1).webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/faas-colors-black (1).webp -------------------------------------------------------------------------------- /docs/.gitbook/assets/faas-colors-black.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/faas-colors-black.webp -------------------------------------------------------------------------------- /docs/.gitbook/assets/faas-colors-green (1).webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/faas-colors-green (1).webp -------------------------------------------------------------------------------- /docs/.gitbook/assets/faas-colors-green.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/faas-colors-green.webp -------------------------------------------------------------------------------- /docs/.gitbook/assets/faas-colors-pink (1).webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/faas-colors-pink (1).webp -------------------------------------------------------------------------------- /docs/.gitbook/assets/faas-colors-pink.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/faas-colors-pink.webp -------------------------------------------------------------------------------- /docs/.gitbook/assets/futarchy-protocol-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/futarchy-protocol-diagram.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/grant-flow-chart (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/grant-flow-chart (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/grant-flow-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/grant-flow-chart.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/grant-ideation-decision-market (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/grant-ideation-decision-market (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/grant-ideation-decision-market.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/grant-ideation-decision-market.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/grant-spot-evaluation (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/grant-spot-evaluation (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/grant-spot-evaluation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/grant-spot-evaluation.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/grant-summary (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/grant-summary (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/grant-summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/grant-summary.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/image.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/metadao-og-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/metadao-og-image.jpg -------------------------------------------------------------------------------- /docs/.gitbook/assets/protocol-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/protocol-map.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/protocol-no-background (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/protocol-no-background (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/protocol-no-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/protocol-no-background.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/rubric-score.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/rubric-score.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/settled-market-conditional-vault (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/settled-market-conditional-vault (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/settled-market-conditional-vault.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/settled-market-conditional-vault.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/twap-chart (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/twap-chart (1).png -------------------------------------------------------------------------------- /docs/.gitbook/assets/twap-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/docs/.gitbook/assets/twap-chart.png -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | ## The problem 4 | 5 | Voting doesn't work. In theory, voting should be a rational process where voters select the best option. In practice, voting systems suffer three big issues: 6 | 7 | * **Low participation:** it's hard to get people to vote. 8 | * **Uninformed voters**: even when you can get people to vote, they often very limited understanding of the decision at hand. 9 | * **Whale and insider influence**: insiders can have huge sway on the vote outcome. Crypto abounds with "governance theater." 10 | 11 | Over the last 7 millennia, many attempts have been made to fix voting. So far, none of these have delivered substantial improvements. 12 | 13 | ## Futarchy 14 | 15 | It's long been said that people buy with their head but vote with their heart. What if we could flip that on its head and use market processes to make decisions? That is the central idea behind **futarchy**_._ 16 | 17 | In a futarchy, decisions don't go to votes: they get traded. Proposals pass when the market speculates that they're good. Proposals fail when the market speculates that they're bad. 18 | 19 | ## MetaDAO 20 | 21 | Even though futarchy was invented by economist Robin Hanson in 2000, MetaDAO is the first project to put it into practice. It provides a platform to create, manage, and participate in futarchies, and is itself governed by a futarchy. 22 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Introduction](README.md) 4 | 5 | ## Using the platform 6 | 7 | * [Creating a DAO](using-the-platform/creating-a-dao.md) 8 | * [Creating Proposals](using-the-platform/creating-proposals.md) 9 | * [Trading Proposals](using-the-platform/trading-proposals.md) 10 | * [Metric Markets](using-the-platform/value-resolved-decision-markets.md) 11 | 12 | ## Futarchy 13 | 14 | * [ELI5](futarchy/eli5.md) 15 | * [Overview](futarchy/overview.md) 16 | * [Benefits](futarchy/benefits.md) 17 | * [Drawbacks](futarchy/drawbacks.md) 18 | 19 | ## Implementation 20 | 21 | * [Program Architecture](implementation/program-architecture.md) 22 | * [Price Oracle](implementation/price-oracle.md) 23 | 24 | ## MetaDAO 25 | 26 | * [Overview](metadao/overview.md) 27 | * [What is META?](metadao/meta.md) 28 | * [Legal](metadao/legal.md) 29 | 30 | *** 31 | 32 | * [Media Kit](metadao-1.md) 33 | 34 | ## Media Kit 35 | 36 | * [FaaS](media-kit/faas.md) 37 | 38 | ## 🔗 Links 39 | 40 | * [Discord](https://discord.gg/metadao) 41 | * [X](https://x.com/MetaDAOProject) 42 | * [Blog](https://blog.metadao.fi/) 43 | * [GitHub](https://github.com/metaDAOproject) 44 | * [YouTube](https://www.youtube.com/@metaDAOproject) 45 | 46 | ## Careers 47 | 48 | * [Linkedin](https://linkedin.com/company/metadaoproject) 49 | * [WellFound](https://wellfound.com/company/metadao) 50 | * [Job Board](https://jobs.metadao.fi) 51 | -------------------------------------------------------------------------------- /docs/book.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "open-graph" 4 | ], 5 | "pluginsConfig": { 6 | "open-graph": { 7 | "baseURL": "https://docs.metadao.fi", 8 | "defaultDescription": "Explore MetaDAO's documentation to learn about futarchy, a revolutionary decision-making system. Discover how MetaDAO implements market-based governance to solve traditional voting problems.", 9 | "defaultImage": ".gitbook/assets/metadao-og-image.jpg" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /docs/examples/rubric.md: -------------------------------------------------------------------------------- 1 | # Example Rubric 2 | 3 | ACME Corp is the developer of an innovative L1 blockchain written in FORTRAN. To bootstrap its ecosystem, ACME funds teams who build applications with $100k grants. 4 | 5 | ACME retroactively grades grants on two criteria: project completion and project adoption. Each of these is half the weight of a grant’s score. 6 | 7 |
Scoring Mechanism
8 | 9 | ## Completion 10 | Projects can receive a 0 to 0.5 completion score. Here’s how the number is determined: 11 | 12 | - 0: this project essentially ran off with the money, releasing nothing publicly. 13 | - 0.25: this project did not launch but it did develop part of the product; there’s a codebase that another project would be able to use. 14 | - 0.5: this project fully launched. 15 | 16 | ## Adoption 17 | Projects can receive a 0 to 0.5 adoption score. Here’s how the number is determined: 18 | 19 | - 0: if it’s a consumer product, 50 people or less have used this product; if it’s a DeFi product, it’s acquired less than $50k in TVL. 20 | - 0.5: if it’s a consumer product, 500 people or less have used this product; if it’s a DeFi product, it’s acquired less than $500k in TVL. 21 | - 1: if it’s a consumer product, 2,500 people or more have used it; if it’s a DeFi product, it’s acquired more than $2.5M in TVL. 22 | 23 | ## Methodology 24 | Grants are scored by a grants committee of 5 people, 3 from ACME and 2 from MetaDAO. The total score is computed by averaging the scores of the 5 committee members. 25 | 26 | ## Timeline 27 | Grants are scored 3 months after the grant has been given. 28 | 29 | -------------------------------------------------------------------------------- /docs/futarchy/benefits.md: -------------------------------------------------------------------------------- 1 | # Benefits 2 | 3 | Compared to the other ways of making decisions, futarchy has a number of benefits. 4 | 5 | ### Markets provide incentives to correct bad decisions 6 | 7 | > _In an efficient capital market, asset prices reflect all relevant information and thus provide the best prediction of future events given the current information._\ 8 | > Paul Rhode and Koleman Strumpf, [Historical Presidential Betting Markets](https://users.wfu.edu/strumpks/papers/JEP\_2004.pdf) 9 | 10 | Consider [election prediction markets](https://en.wikipedia.org/wiki/Election\_stock\_market). If Donald Trump has a 50% chance of winning but you can buy ‘Donald Trump win’ contracts at $0.32, you are incentivized to buy. And if they reach $0.65, you are incentivized to sell or short. 11 | 12 | In general, when an asset deviates from its intrinsic value - equal to the net present value of its future cash flows - traders are incentivized to buy or sell until the two converge. 13 | 14 | What this means in a futarchy is that there's an incentive for traders to correct for bad decisions. For example, if "stock conditional on giving away all of the company's cash to charity" is trading for the same price as normal stock, traders are incentivized to sell the conditional stock, causing its price to decline. 15 | 16 | ### Markets are hard to manipulate 17 | 18 | This also makes it hard to manipulate decisions. Any time you manipulate a market, you push it away from intrinsic value and thus create an incentive for someone to correct your manipulations. In fact, there's [evidence](https://www.sciencedirect.com/science/article/abs/pii/S0167268105001575) to indicate that manipulation actually _increases_ the accuracy of prices. 19 | 20 | This is in contrast to voting, where votes and politicians alike can be bought. 21 | 22 | ### Over time, markets shift power from bad predictors to good predictors 23 | 24 | Markets give more power over time to those who are better predictors. This is because high returns both directly increase a trader’s capital and improve their ability to raise capital from investors. 25 | 26 | ### Generally, markets beat other systems of aggregating information 27 | 28 | Markets aren't perfectly efficient. But according to most empirical evidence, markets are better aggregators of information than individual experts: 29 | 30 | * Prediction markets [outperform](https://repository.arizona.edu/bitstream/handle/10150/666656/azu\_etd\_hr\_2021\_0133\_sip1\_m.pdf?sequence=1) [professional pollsters](https://www.jstor.org/stable/40467652) in predicting elections. 31 | * Commodities futures markets [outperform government forecasts](https://www.jstor.org/stable/40467652) in predicting weather. 32 | * Companies like [Google](https://googleblog.blogspot.com/2005/09/putting-crowd-wisdom-to-work.html) and [HP](https://authors.library.caltech.edu/44358/1/wp1131.pdf) have used prediction markets to successfully forecast things like launch dates, printer sales, and the dates of new office openings. 33 | * Famously, while it took the US government more than 5 months to identify the Morton-Thiakol O-Rings as the root cause of the Challenger crash, [the market had priced it into Morton-Thiakol's stock price within 14 minutes](https://maloney.people.clemson.edu/855/9.pdf). 34 | -------------------------------------------------------------------------------- /docs/futarchy/drawbacks.md: -------------------------------------------------------------------------------- 1 | # Drawbacks 2 | 3 | But of course, futarchy isn't perfect. These are some potential pitfalls with the approach. 4 | 5 | ### Keynesian Beauty Contests 6 | 7 | Sometimes, investors buy what they think others will buy, not what they think the fundamentals support. The [GameStop short squeeze](https://en.wikipedia.org/wiki/GameStop\_short\_squeeze) is an example of this, and many more can be found in crypto. 8 | 9 | A potential solution to this is to use whitelisted markets. Investors would apply to be a part of futarchic markets, demonstrating that they have traded based on fundamentals in the past. 10 | 11 | ### Conditional Markets are Zero-Sum 12 | 13 | Futarchy uses conditional markets, which are zero-sum: any gain you have in the market is someone else's loss. Since intelligent market participants don't engage in zero-sum games, this will constrict liquidity and participation in futarchic markets. 14 | 15 | You may be able to resolve this by providing an incentive to trade or provide liquidity in the markets. Because the futarchy creates positive externalities (good decisions), this incentive can be sustainable. 16 | 17 | ### Scalability 18 | 19 | Futarchy is good for big decisions like whether to fire the CEO, but not as good for smaller decisions like whether to fire a division leader because those actions are usually too inconsequential to reflect in the share price. 20 | 21 | A solution to this problem is described in MetaDAO's whitepaper. 22 | -------------------------------------------------------------------------------- /docs/futarchy/eli5.md: -------------------------------------------------------------------------------- 1 | # ELI5 2 | 3 | A basic way to think about futarchy is: 4 | 5 | 1. Proposals are raised to a DAO 6 | 2. People speculate on whether the proposal would make the DAO's token go up 7 | 8 | When the market thinks that the proposal would increase the value of the token, the proposal passes 9 | 10 | When the market thinks that the proposal would decrease the value of the token, the proposal fails 11 | -------------------------------------------------------------------------------- /docs/futarchy/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | > _Since speculative markets excel at a task where democracies struggle, we might try to improve democracy by having it rely more on speculative markets._\ 4 | > Robin Hanson 5 | 6 | Futarchy was invented by economist Robin Hanson in 2000. The basic idea is to make decisions via markets. 7 | 8 | It does this through _decision markets_. In a decision market, you speculate on what an asset would be worth if an organization took a specific decision. For example, you could speculate on what the value of AAPL would be if Apple fired Tim Cook. 9 | 10 | Futarchy then uses the prices in these markets to make decisions. For example, a company organized as a futarchy would fire the CEO if the decision markets say that this would increase the value of that company's stock. 11 | 12 | {% embed url="https://youtu.be/PRog7R37MA0?si=y69R2JRbLqm5uyFH" %} 13 | -------------------------------------------------------------------------------- /docs/implementation/price-oracle.md: -------------------------------------------------------------------------------- 1 | # Price Oracle 2 | 3 | For futarchy to work, you need a way of extracting the price of a proposal's conditional market. 4 | 5 | The naive approach is to just use the spot price at the time of proposal finalization. But this is highly manipulable. For example, someone could pump the price of the pass market right before finalization in order to force the proposal to pass. 6 | 7 |

Someone could bid up the pass price in the last minute to force a proposal through

8 | 9 | ### TWAP 10 | 11 | Less naive is to use a time-weighted average price (TWAP). TWAPs are much harder to manipulate. For example, if TOKEN's pass price is $100 for the first 72 hours of a proposal and then a manipulator pushes the price to $1000 for the last 15 minutes, the TWAP would be $103.11, only a 3% difference from the 'true price.' 12 | 13 | However, TWAPs also have their flaws. Importantly, Solana validators can manipulate TWAPs by setting the price extremely high for a few slots. Because the validator controls the slot, they know that noone would be able to sell into their extremely high price. If a validator controls 1% of slots, they could force a proposal through by 100xing the pass price during their slots. 14 | 15 | ### Lagging price TWAP 16 | 17 | We deal with this by using a special form of TWAP we call a lagging price TWAP. In a lagging price TWAP, the number that gets fed into the TWAP isn't the raw price - it's a number that tries to approximate the price but which can only move a certain amount per update. We call this an _observation_. Each DAO must configure the _first observation_ and _max observation change per update_ that get used in its proposals' markets. 18 | 19 |
20 | 21 | To take an example, imagine that MetaDAO's first observation is set to $500 and its max change per update is $5. If a proposal opens with a pass market of $550, it will take 10 updates before the observation accurately reflects the price. Assuming each update is spaced evenly and the price stays at $550, the TWAP after 10 updates will be $527.5 (\[$505 + $510 + $515 + $520 + $525 + $530 + $535 + $540 + $545 + $550] / 10). After 10 more updates, it will be $538.75. 22 | 23 | ### One minute between updates 24 | 25 | Ideally, the TWAP would be highly sensitive to normal price movements and highly insensitive to manipulated price movements. We originally allowed one update per slot, but this gives the opposite effect: an attacker may be able to land in every slot, whereas normal trading activity isn't as frequent (yet!), so an attacker would move the price more than genuine price movements. To deal with this, we only allow one update per minute. 26 | -------------------------------------------------------------------------------- /docs/media-kit/faas.md: -------------------------------------------------------------------------------- 1 | --- 2 | hidden: true 3 | --- 4 | 5 | # FaaS 6 | 7 | ## Colors 8 | 9 |
Background#0C0C0Cfaas-colors-black.webp
Pink#DB2777tailwind pinkfaas-colors-pink.webp
Green#059669tailwind emeraldfaas-colors-green.webp
10 | 11 | ## Fonts 12 | 13 | ### Headings 14 | 15 | **Geist** | semibold | shadow 0 0 7 #FFFFFF66 16 | 17 | ### Body 18 | 19 | **Geist** | 14px 20 | 21 | ### Label 22 | 23 | **Geist Mono** | tailwind neutral-600 (#525252) | Uppercase - 12px 24 | 25 | [Download Geist and Geist Mono](https://github.com/vercel/geist-font/releases/tag/1.3.0) 26 | 27 | ## Assets 28 | 29 | {% file src="../.gitbook/assets/God_Rays.png" %} 30 | -------------------------------------------------------------------------------- /docs/metadao-1.md: -------------------------------------------------------------------------------- 1 | # Media Kit 2 | 3 | ## Logo 4 | 5 | {% file src=".gitbook/assets/MetaDAO-logo.png" %} 6 | 7 | {% file src=".gitbook/assets/MetaDAO-logo.svg" %} 8 | 9 | {% file src=".gitbook/assets/MetaDAO-logo-dark.png" %} 10 | 11 | {% file src=".gitbook/assets/MetaDAO-logo-dark.svg" %} 12 | 13 | ## Colors 14 | 15 |
Red#FC494AMediakit-colors-red.png
16 | -------------------------------------------------------------------------------- /docs/metadao/legal.md: -------------------------------------------------------------------------------- 1 | # Legal 2 | 3 | MetaDAO is not a legal entity. It's a cybernetic entity orchestrated by programs stored on the Solana blockchain. It controls MetaDAO LLC, which is a DAO LLC registered with the Marshall Islands through [MIDAO](https://www.midao.org/). MetaDAO LLC retains legal ownership over the domain names, codebases, and social media accounts associated with MetaDAO. 4 | 5 | The operating agreement and corporate charter of MetaDAO LLC are below: 6 | 7 | {% file src="broken-reference" %} 8 | 9 | {% file src="broken-reference" %} 10 | -------------------------------------------------------------------------------- /docs/metadao/meta.md: -------------------------------------------------------------------------------- 1 | # What is META? 2 | 3 | MetaDAO is governed by META, whose mint is METADDFL6wWMWEoKTFJwcThTbUmtarRJZjRpzUvkxhr. 4 | 5 | On November 15th 2023, 10,000 META was [airdropped to 60 wallets](https://blog.metadao.fi/the-meta-dao-decentralizes-d2d01dd5aa45), with the remaining 990,000 META being sent to MetaDAO's treasury. 6 | 7 | **All META from the treasury has entered the circulating supply through market-approved governance proposals.** 8 | 9 | In March 2024, a group of community members created a proposal to [burn 979,000 META from the treasury](https://futarchy.metadao.fi/metadao/proposals/ELwCkHt1U9VBpUFJ7qGoVMatEwLSr1HYj9q9t8JQ1NcU). That proposal passed, capping the supply of the current mint to 21,000 META. MetaDAO may choose to migrate to a new token mint which gives the DAO the ability to mint new tokens. 10 | -------------------------------------------------------------------------------- /docs/metadao/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | hidden: true 3 | --- 4 | 5 | # Overview 6 | 7 | MetaDAO is the DAO responsible for developing futarchy. It is itself a futarchy, and uses META as its reference asset. 8 | -------------------------------------------------------------------------------- /docs/proposal-templates/business-direct-action.md: -------------------------------------------------------------------------------- 1 | Business Direct Action Template 2 | 3 | This is the template you can use for creating business direct action proposals. Just delete this part at the top. 4 | 5 | # Proposal x - INSERT NAME HERE 6 | 7 | ## Overview 8 | 9 | A brief description of this proposal. Cover why it's good for the Meta-DAO. 10 | 11 | ## Financial projections 12 | 13 | If you can, cover how this is projected to affect the cash flows and enterprise value of the Meta-DAO. -------------------------------------------------------------------------------- /docs/proposal-templates/business-project.md: -------------------------------------------------------------------------------- 1 | Business Project Template 2 | 3 | This is the template you can use for creating business project proposals. Just delete this part at the top. 4 | 5 | # Proposal x - INSERT NAME HERE 6 | 7 | **Entrepreneur(s):** insert name(s) here 8 | 9 | ## Overview 10 | 11 | Insert a brief description of the product being built. Be sure to touch on the following: 12 | - Who the target customer is 13 | - What problem the product would solve for them 14 | - How the product would monetize 15 | - What the key metrics would be (e.g., DAUs, MRR, TVL, volume, etc.) 16 | 17 | Also insert a very brief (1-2 sentence) description of how this project relates to the Meta-DAO's business: 18 | - How much value this could create for the Meta-DAO 19 | - An estimated budget 20 | 21 | ## Problem 22 | 23 | Talk about what problem the target customer is currently facing. You can prove that this is a problem for the customer in a few different ways: 24 | - Citing customers complaining (e.g., publicly on Twitter / in DMs) 25 | - Showing that customers are using other products to solve this problem (in hopefully a worse way t -------------------------------------------------------------------------------- /docs/proposal-templates/operations-direct-action.md: -------------------------------------------------------------------------------- 1 | Operations Direct Action Template 2 | 3 | This is the template you can use for creating operations direct action proposals. Just delete this part at the top. 4 | 5 | # Proposal x - INSERT NAME HERE 6 | 7 | #### Type 8 | 9 | Operations Direct Action 10 | 11 | #### Author(s) 12 | 13 | {name #1}, {name #2} 14 | 15 | ## Overview 16 | 17 | A brief description of this proposal. Cover why it's good for the Meta-DAO. If the Meta-DAO has previously committed to taking this action, link that committment (e.g., a transaction signature of a passed proposal). -------------------------------------------------------------------------------- /docs/proposal-templates/operations-project.md: -------------------------------------------------------------------------------- 1 | Operations Project Template 2 | 3 | This is the template you can use for creating operations project proposals. Just delete this part at the top. 4 | 5 | # Proposal x - INSERT NAME HERE 6 | 7 | #### Type 8 | 9 | Operations project 10 | 11 | #### Entrepreneur(s) 12 | 13 | insert name(s) here 14 | 15 | ## Overview 16 | 17 | Insert a brief description of the project. Be sure to touch on the following: 18 | - What problem the Meta-DAO is currently facing or what metric this project is supposed to improve 19 | - What this project would do to address the issue or improve the metrics 20 | - What metrics others can use to measure success 21 | 22 | Also insert a very brief (1-2 sentence) description of how this project relates to the Meta-DAO's business: 23 | - How much value this could create for the Meta-DAO, if applicable 24 | - An estimated budget 25 | 26 | ## Focus area 27 | 28 | Talk about what problem this project is intended to address or what metrics this project should improve. 29 | 30 | ## Project 31 | 32 | Describe in 1-3 paragraphs what the project would consist of and why it would improve the relevant metrics. 33 | 34 | -------------------------------------------------------------------------------- /docs/using-the-platform/creating-a-dao.md: -------------------------------------------------------------------------------- 1 | # Creating a DAO 2 | 3 | So, you want to use futarchy for your project - great! To create a futarchy, you need to specify a few parameters: 4 | 5 | * **Proposal time**: the amount of time a proposal should be active before it can pass or fail. Three days by default. Specified in Solana slots. 6 | * **Pass threshold:** the percentage that the pass price needs to be above the fail price in order for a proposal to pass. 7 | * **Min liquidity:** to prevent spam, proposers are required to lock AMM liquidity in their proposal markets. The amount they are required to lock, in both USDC and the futarchy's token, is specified by each DAO. 8 | * **TWAP sensitivity parameters:** as explained in the price oracle section, the price that gets factored into the TWAP can only move by a certain dollar amount per minute. Each DAO must specify this dollar amount. We recommend 1-5% of the spot price. 9 | 10 | Once these parameters are decided, you can reach out to Proph3t (@metaproph3t on Twitter, Telegram, and Discord) to create the DAO for you. Today, we're working with a small number of DAOs, but we will eventually make this a permissionless process. 11 | -------------------------------------------------------------------------------- /docs/using-the-platform/creating-proposals.md: -------------------------------------------------------------------------------- 1 | --- 2 | hidden: true 3 | --- 4 | 5 | # Creating Proposals 6 | 7 | [Introduction To MetaDAO](https://blog.metadao.fi/a-futards-guide-to-the-meta-dao-7a6b8d66443a) 8 | 9 | [Explainer Blog Post](https://blog.metadao.fi/so-you-want-to-raise-a-proposal-2d83304c0b9d) 10 | 11 | Business projects are how MetaDAO converts financial capital into revenue-generating products. Business direct actions operate over those products, tweaking parameters in the pursuit of customer satisfaction and profitability. Operations projects and direct actions support the business, ensuring that MetaDAO has the right people and resources to create new products and manage existing ones. 12 | 13 | Each of these four proposal types has its own template. These are listed here: 14 | 15 | {% content-ref url="../proposal-templates/business-project.md" %} 16 | [business-project.md](../proposal-templates/business-project.md) 17 | {% endcontent-ref %} 18 | 19 | {% content-ref url="../proposal-templates/business-direct-action.md" %} 20 | [business-direct-action.md](../proposal-templates/business-direct-action.md) 21 | {% endcontent-ref %} 22 | 23 | {% content-ref url="../proposal-templates/operations-project.md" %} 24 | [operations-project.md](../proposal-templates/operations-project.md) 25 | {% endcontent-ref %} 26 | 27 | {% content-ref url="../proposal-templates/operations-direct-action.md" %} 28 | [operations-direct-action.md](../proposal-templates/operations-direct-action.md) 29 | {% endcontent-ref %} 30 | 31 | Project proposals should generally be raised by entrepreneurs who are accountable to the DAO for their execution. Direct action proposals can be raised by anyone. 32 | 33 | You can use any document app you want to create proposals. 34 | -------------------------------------------------------------------------------- /docs/using-the-platform/trading-proposals.md: -------------------------------------------------------------------------------- 1 | --- 2 | hidden: true 3 | --- 4 | 5 | # Trading Proposals 6 | 7 | Here's a guide on trading proposal markets: 8 | 9 | {% embed url="https://www.youtube.com/watch?v=dA6YyQHCYDk" %} 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "license": "BSL-1.0", 4 | "scripts": { 5 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 6 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check", 7 | "setup-metric-market": "tsx --tsconfig tsconfig.json scripts/setupMetricMarket.ts", 8 | "launch-init": "NODE_OPTIONS=\"--no-deprecation\" tsx --tsconfig tsconfig.json scripts/launchInit.ts", 9 | "launch-start": "NODE_OPTIONS=\"--no-deprecation\" tsx --tsconfig tsconfig.json scripts/launchStart.ts", 10 | "launch-complete": "NODE_OPTIONS=\"--no-deprecation\" tsx --tsconfig tsconfig.json scripts/launchComplete.ts", 11 | "redeem-launch": "NODE_OPTIONS=\"--no-deprecation\" tsx --tsconfig tsconfig.json scripts/redeemLaunch.ts" 12 | }, 13 | "dependencies": { 14 | "@coral-xyz/anchor": "0.29.0", 15 | "@inquirer/prompts": "^7.3.3", 16 | "@metadaoproject/futarchy": "0.4.0-alpha.73", 17 | "@metaplex-foundation/mpl-token-metadata": "^3.2.0", 18 | "@metaplex-foundation/umi": "^0.9.1", 19 | "@metaplex-foundation/umi-bundle-defaults": "^0.9.1", 20 | "@metaplex-foundation/umi-signer-wallet-adapters": "^1.1.1", 21 | "@metaplex-foundation/umi-uploader-bundlr": "^0.9.1", 22 | "@metaplex-foundation/umi-web3js-adapters": "^1.1.1", 23 | "@noble/ed25519": "^2.0.0", 24 | "@noble/secp256k1": "^2.0.0", 25 | "@solana/spl-token": "^0.3.7", 26 | "@solana/web3.js": "^1.90.0", 27 | "anchor-bankrun": "^0.3.0", 28 | "arweave": "^1.14.4", 29 | "solana-bankrun": "^0.3.0", 30 | "spl-token-bankrun": "0.2.6" 31 | }, 32 | "devDependencies": { 33 | "@solana/spl-memo": "^0.2.3", 34 | "@solana/spl-token-registry": "^0.2.4574", 35 | "@types/bn.js": "^5.1.0", 36 | "@types/chai": "^4.3.0", 37 | "@types/inquirer": "^9.0.7", 38 | "@types/mocha": "^10.0.7", 39 | "@types/node": "^20.8.6", 40 | "chai": "^4.3.4", 41 | "dotenv": "^16.4.7", 42 | "mocha": "^9.0.3", 43 | "prettier": "^2.6.2", 44 | "ts-mocha": "^10.0.0", 45 | "ts-node": "^10.9.2", 46 | "tsx": "^4.7.1", 47 | "typescript": "^4.3.5" 48 | }, 49 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 50 | } 51 | -------------------------------------------------------------------------------- /programs/amm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "amm" 3 | version = "0.4.1" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "amm" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = { version = "0.29.0", features = ["init-if-needed", "event-cpi"] } 20 | anchor-spl = "0.29.0" 21 | solana-security-txt = "1.1.1" -------------------------------------------------------------------------------- /programs/amm/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/amm/src/error.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[error_code] 4 | pub enum AmmError { 5 | #[msg("An assertion failed")] 6 | AssertFailed, 7 | #[msg("Can't get a TWAP before some observations have been stored")] 8 | NoSlotsPassed, 9 | #[msg("Can't swap through a pool without token reserves on either side")] 10 | NoReserves, 11 | #[msg("Input token amount is too large for a swap, causes overflow")] 12 | InputAmountOverflow, 13 | #[msg("Add liquidity calculation error")] 14 | AddLiquidityCalculationError, 15 | #[msg("Error in decimal scale conversion")] 16 | DecimalScaleError, 17 | #[msg("You can't create an AMM pool where the token mints are the same")] 18 | SameTokenMints, 19 | #[msg("A user wouldn't have gotten back their `output_amount_min`, reverting")] 20 | SwapSlippageExceeded, 21 | #[msg("The user had insufficient balance to do this")] 22 | InsufficientBalance, 23 | #[msg("Must remove a non-zero amount of liquidity")] 24 | ZeroLiquidityRemove, 25 | #[msg("Cannot add liquidity with 0 tokens on either side")] 26 | ZeroLiquidityToAdd, 27 | #[msg("Must specify a non-zero `min_lp_tokens` when adding to an existing pool")] 28 | ZeroMinLpTokens, 29 | #[msg("LP wouldn't have gotten back `lp_token_min`")] 30 | AddLiquiditySlippageExceeded, 31 | #[msg("LP would have spent more than `max_base_amount`")] 32 | AddLiquidityMaxBaseExceeded, 33 | #[msg("`quote_amount` must be greater than 100000000 when initializing a pool")] 34 | InsufficientQuoteAmount, 35 | #[msg("Users must swap a non-zero amount")] 36 | ZeroSwapAmount, 37 | #[msg("K should always be increasing")] 38 | ConstantProductInvariantFailed, 39 | #[msg("Casting has caused an overflow")] 40 | CastingOverflow, 41 | } 42 | -------------------------------------------------------------------------------- /programs/amm/src/events.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::{state::SwapType, Amm}; 4 | 5 | #[derive(AnchorSerialize, AnchorDeserialize)] 6 | pub struct CommonFields { 7 | pub slot: u64, 8 | pub unix_timestamp: i64, 9 | pub user: Pubkey, 10 | pub amm: Pubkey, 11 | pub post_base_reserves: u64, 12 | pub post_quote_reserves: u64, 13 | pub oracle_last_price: u128, 14 | pub oracle_last_observation: u128, 15 | pub oracle_aggregator: u128, 16 | pub seq_num: u64, 17 | } 18 | 19 | impl CommonFields { 20 | pub fn new(clock: &Clock, user: Pubkey, amm: &Account<'_, Amm>) -> Self { 21 | Self { 22 | slot: clock.slot, 23 | unix_timestamp: clock.unix_timestamp, 24 | user, 25 | amm: amm.key(), 26 | post_base_reserves: amm.base_amount, 27 | post_quote_reserves: amm.quote_amount, 28 | oracle_last_price: amm.oracle.last_price, 29 | oracle_last_observation: amm.oracle.last_observation, 30 | oracle_aggregator: amm.oracle.aggregator, 31 | seq_num: amm.seq_num, 32 | } 33 | } 34 | } 35 | 36 | #[event] 37 | pub struct SwapEvent { 38 | pub common: CommonFields, 39 | pub input_amount: u64, 40 | pub output_amount: u64, 41 | pub swap_type: SwapType, 42 | } 43 | 44 | #[event] 45 | pub struct AddLiquidityEvent { 46 | pub common: CommonFields, 47 | pub quote_amount: u64, 48 | pub max_base_amount: u64, 49 | pub min_lp_tokens: u64, 50 | pub base_amount: u64, 51 | pub lp_tokens_minted: u64, 52 | } 53 | 54 | #[event] 55 | pub struct RemoveLiquidityEvent { 56 | pub common: CommonFields, 57 | pub lp_tokens_burned: u64, 58 | pub min_quote_amount: u64, 59 | pub min_base_amount: u64, 60 | pub base_amount: u64, 61 | pub quote_amount: u64, 62 | } 63 | 64 | #[event] 65 | pub struct CreateAmmEvent { 66 | pub common: CommonFields, 67 | pub twap_initial_observation: u128, 68 | pub twap_max_observation_change_per_update: u128, 69 | pub lp_mint: Pubkey, 70 | pub base_mint: Pubkey, 71 | pub quote_mint: Pubkey, 72 | pub vault_ata_base: Pubkey, 73 | pub vault_ata_quote: Pubkey, 74 | } 75 | 76 | #[event] 77 | pub struct CrankThatTwapEvent { 78 | pub common: CommonFields, 79 | } 80 | -------------------------------------------------------------------------------- /programs/amm/src/instructions/common.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{Mint, Token, TokenAccount}; 3 | 4 | use crate::*; 5 | 6 | #[event_cpi] 7 | #[derive(Accounts)] 8 | pub struct AddOrRemoveLiquidity<'info> { 9 | #[account(mut)] 10 | pub user: Signer<'info>, 11 | #[account( 12 | mut, 13 | has_one = lp_mint, 14 | )] 15 | pub amm: Account<'info, Amm>, 16 | #[account(mut)] 17 | pub lp_mint: Box>, 18 | #[account( 19 | mut, 20 | token::mint = lp_mint, 21 | token::authority = user, 22 | )] 23 | pub user_lp_account: Box>, 24 | #[account( 25 | mut, 26 | token::mint = amm.base_mint, 27 | token::authority = user, 28 | )] 29 | pub user_base_account: Box>, 30 | #[account( 31 | mut, 32 | token::mint = amm.quote_mint, 33 | token::authority = user, 34 | )] 35 | pub user_quote_account: Box>, 36 | #[account( 37 | mut, 38 | associated_token::mint = amm.base_mint, 39 | associated_token::authority = amm, 40 | )] 41 | pub vault_ata_base: Account<'info, TokenAccount>, 42 | #[account( 43 | mut, 44 | associated_token::mint = amm.quote_mint, 45 | associated_token::authority = amm, 46 | )] 47 | pub vault_ata_quote: Account<'info, TokenAccount>, 48 | pub token_program: Program<'info, Token>, 49 | } 50 | -------------------------------------------------------------------------------- /programs/amm/src/instructions/crank_that_twap.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::events::{CommonFields, CrankThatTwapEvent}; 4 | use crate::state::*; 5 | 6 | #[event_cpi] 7 | #[derive(Accounts)] 8 | pub struct CrankThatTwap<'info> { 9 | #[account(mut)] 10 | pub amm: Account<'info, Amm>, 11 | } 12 | 13 | impl CrankThatTwap<'_> { 14 | pub fn handle(ctx: Context) -> Result<()> { 15 | let CrankThatTwap { 16 | amm, 17 | program: _, 18 | event_authority: _, 19 | } = ctx.accounts; 20 | 21 | amm.update_twap(Clock::get()?.slot)?; 22 | 23 | amm.seq_num += 1; 24 | 25 | let clock = Clock::get()?; 26 | emit_cpi!(CrankThatTwapEvent { 27 | common: CommonFields::new(&clock, Pubkey::default(), amm), 28 | }); 29 | 30 | Ok(()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /programs/amm/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub use add_liquidity::*; 2 | pub use common::*; 3 | pub use crank_that_twap::*; 4 | pub use create_amm::*; 5 | pub use remove_liquidity::*; 6 | pub use swap::*; 7 | 8 | pub mod add_liquidity; 9 | pub mod common; 10 | pub mod crank_that_twap; 11 | pub mod create_amm; 12 | pub mod remove_liquidity; 13 | pub mod swap; 14 | -------------------------------------------------------------------------------- /programs/amm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[cfg(not(feature = "no-entrypoint"))] 4 | use solana_security_txt::security_txt; 5 | 6 | #[cfg(not(feature = "no-entrypoint"))] 7 | security_txt! { 8 | name: "amm", 9 | project_url: "https://metadao.fi", 10 | contacts: "email:metaproph3t@protonmail.com", 11 | policy: "The market will decide whether we pay a bug bounty.", 12 | source_code: "https://github.com/metaDAOproject/futarchy", 13 | source_release: "delayed-twap-v0.4.1", 14 | auditors: "Neodyme", 15 | acknowledgements: "DCF = (CF1 / (1 + r)^1) + (CF2 / (1 + r)^2) + ... (CFn / (1 + r)^n)" 16 | } 17 | 18 | pub mod error; 19 | pub mod events; 20 | pub mod instructions; 21 | pub mod state; 22 | 23 | use crate::events::*; 24 | use crate::instructions::*; 25 | use crate::state::*; 26 | 27 | declare_id!("AMMyu265tkBpRW21iGQxKGLaves3gKm2JcMUqfXNSpqD"); 28 | 29 | #[program] 30 | pub mod amm { 31 | use self::add_liquidity::AddLiquidityArgs; 32 | 33 | use super::*; 34 | 35 | #[access_control(ctx.accounts.validate())] 36 | pub fn create_amm(ctx: Context, args: CreateAmmArgs) -> Result<()> { 37 | CreateAmm::handle(ctx, args) 38 | } 39 | 40 | pub fn add_liquidity(ctx: Context, args: AddLiquidityArgs) -> Result<()> { 41 | AddOrRemoveLiquidity::handle_add(ctx, args) 42 | } 43 | 44 | pub fn remove_liquidity( 45 | ctx: Context, 46 | args: RemoveLiquidityArgs, 47 | ) -> Result<()> { 48 | AddOrRemoveLiquidity::handle_remove(ctx, args) 49 | } 50 | 51 | pub fn swap(ctx: Context, args: SwapArgs) -> Result<()> { 52 | Swap::handle(ctx, args) 53 | } 54 | 55 | pub fn crank_that_twap(ctx: Context) -> Result<()> { 56 | CrankThatTwap::handle(ctx) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /programs/amm/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub use amm::*; 2 | 3 | pub mod amm; 4 | 5 | pub const TEN_SECONDS_IN_SLOTS: u64 = 25; 6 | pub const ONE_MINUTE_IN_SLOTS: u64 = TEN_SECONDS_IN_SLOTS * 6; 7 | pub const PRICE_SCALE: u128 = 1_000_000_000_000; 8 | pub const MAX_PRICE: u128 = u64::MAX as u128 * PRICE_SCALE; 9 | 10 | pub const AMM_SEED_PREFIX: &[u8] = b"amm__"; 11 | pub const AMM_LP_MINT_SEED_PREFIX: &[u8] = b"amm_lp_mint"; 12 | -------------------------------------------------------------------------------- /programs/autocrat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "autocrat" 3 | version = "0.4.2" 4 | description = "SVM-based program for running futarchy" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "autocrat" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = "^0.29.0" 20 | anchor-spl = "^0.29.0" 21 | solana-security-txt = "1.1.1" 22 | conditional_vault = { path = "../conditional_vault", features = ["cpi"] } 23 | amm = { path = "../amm", features = ["cpi"] } 24 | -------------------------------------------------------------------------------- /programs/autocrat/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/autocrat/src/error.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[error_code] 4 | pub enum AutocratError { 5 | #[msg("Amms must have been created within 5 minutes (counted in slots) of proposal initialization")] 6 | AmmTooOld, 7 | #[msg("An amm has an `initial_observation` that doesn't match the `dao`'s config")] 8 | InvalidInitialObservation, 9 | #[msg( 10 | "An amm has a `max_observation_change_per_update` that doesn't match the `dao`'s config" 11 | )] 12 | InvalidMaxObservationChange, 13 | #[msg("An amm has a `start_delay_slots` that doesn't match the `dao`'s config")] 14 | InvalidStartDelaySlots, 15 | #[msg("One of the vaults has an invalid `settlement_authority`")] 16 | InvalidSettlementAuthority, 17 | #[msg("Proposal is too young to be executed or rejected")] 18 | ProposalTooYoung, 19 | #[msg("Markets too young for proposal to be finalized. TWAP might need to be cranked")] 20 | MarketsTooYoung, 21 | #[msg("This proposal has already been finalized")] 22 | ProposalAlreadyFinalized, 23 | #[msg("A conditional vault has an invalid nonce. A nonce should encode the proposal number")] 24 | InvalidVaultNonce, 25 | #[msg("This proposal can't be executed because it isn't in the passed state")] 26 | ProposalNotPassed, 27 | #[msg("The proposer has fewer pass or fail LP tokens than they requested to lock")] 28 | InsufficientLpTokenBalance, 29 | #[msg("The LP tokens passed in have less liquidity than the DAO's `min_quote_futarchic_liquidity` or `min_base_futachic_liquidity`")] 30 | InsufficientLpTokenLock, 31 | } 32 | -------------------------------------------------------------------------------- /programs/autocrat/src/events.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::{ProposalInstruction, ProposalState}; 4 | 5 | #[derive(AnchorSerialize, AnchorDeserialize)] 6 | pub struct CommonFields { 7 | pub slot: u64, 8 | pub unix_timestamp: i64, 9 | } 10 | 11 | impl CommonFields { 12 | pub fn new(clock: &Clock) -> Self { 13 | Self { 14 | slot: clock.slot, 15 | unix_timestamp: clock.unix_timestamp, 16 | } 17 | } 18 | } 19 | 20 | #[event] 21 | pub struct InitializeDaoEvent { 22 | pub common: CommonFields, 23 | pub dao: Pubkey, 24 | pub token_mint: Pubkey, 25 | pub usdc_mint: Pubkey, 26 | pub treasury: Pubkey, 27 | pub pass_threshold_bps: u16, 28 | pub slots_per_proposal: u64, 29 | pub twap_initial_observation: u128, 30 | pub twap_max_observation_change_per_update: u128, 31 | pub min_quote_futarchic_liquidity: u64, 32 | pub min_base_futarchic_liquidity: u64, 33 | } 34 | 35 | #[event] 36 | pub struct UpdateDaoEvent { 37 | pub common: CommonFields, 38 | pub dao: Pubkey, 39 | pub pass_threshold_bps: u16, 40 | pub slots_per_proposal: u64, 41 | pub twap_initial_observation: u128, 42 | pub twap_max_observation_change_per_update: u128, 43 | pub min_quote_futarchic_liquidity: u64, 44 | pub min_base_futarchic_liquidity: u64, 45 | } 46 | 47 | #[event] 48 | pub struct InitializeProposalEvent { 49 | pub common: CommonFields, 50 | pub proposal: Pubkey, 51 | pub dao: Pubkey, 52 | pub question: Pubkey, 53 | pub quote_vault: Pubkey, 54 | pub base_vault: Pubkey, 55 | pub pass_amm: Pubkey, 56 | pub fail_amm: Pubkey, 57 | pub pass_lp_mint: Pubkey, 58 | pub fail_lp_mint: Pubkey, 59 | pub proposer: Pubkey, 60 | pub nonce: u64, 61 | pub number: u32, 62 | pub pass_lp_tokens_locked: u64, 63 | pub fail_lp_tokens_locked: u64, 64 | pub pda_bump: u8, 65 | pub instruction: ProposalInstruction, 66 | pub duration_in_slots: u64, 67 | } 68 | 69 | #[event] 70 | pub struct FinalizeProposalEvent { 71 | pub common: CommonFields, 72 | pub proposal: Pubkey, 73 | pub dao: Pubkey, 74 | pub pass_market_twap: u128, 75 | pub fail_market_twap: u128, 76 | pub threshold: u128, 77 | pub state: ProposalState, 78 | } 79 | 80 | #[event] 81 | pub struct ExecuteProposalEvent { 82 | pub common: CommonFields, 83 | pub proposal: Pubkey, 84 | pub dao: Pubkey, 85 | } 86 | -------------------------------------------------------------------------------- /programs/autocrat/src/instructions/execute_proposal.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Accounts)] 4 | #[event_cpi] 5 | pub struct ExecuteProposal<'info> { 6 | #[account(mut, has_one = dao)] 7 | pub proposal: Account<'info, Proposal>, 8 | pub dao: Box>, 9 | } 10 | 11 | impl ExecuteProposal<'_> { 12 | pub fn validate(&self) -> Result<()> { 13 | require!( 14 | self.proposal.state == ProposalState::Passed, 15 | AutocratError::ProposalNotPassed 16 | ); 17 | 18 | Ok(()) 19 | } 20 | 21 | pub fn handle(ctx: Context) -> Result<()> { 22 | let ExecuteProposal { 23 | proposal, 24 | dao, 25 | event_authority: _, 26 | program: _, 27 | } = ctx.accounts; 28 | 29 | proposal.state = ProposalState::Executed; 30 | 31 | let dao_key = dao.key(); 32 | let treasury_seeds = &[dao_key.as_ref(), &[dao.treasury_pda_bump]]; 33 | let signer = &[&treasury_seeds[..]]; 34 | 35 | let mut svm_instruction: Instruction = proposal.instruction.borrow().into(); 36 | for acc in svm_instruction.accounts.iter_mut() { 37 | if acc.pubkey == dao.treasury.key() { 38 | acc.is_signer = true; 39 | } 40 | } 41 | 42 | solana_program::program::invoke_signed(&svm_instruction, ctx.remaining_accounts, signer)?; 43 | 44 | let clock = Clock::get()?; 45 | 46 | emit_cpi!(ExecuteProposalEvent { 47 | common: CommonFields::new(&clock), 48 | proposal: proposal.key(), 49 | dao: dao.key(), 50 | }); 51 | 52 | Ok(()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /programs/autocrat/src/instructions/initialize_dao.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] 4 | pub struct InitializeDaoParams { 5 | pub twap_initial_observation: u128, 6 | pub twap_max_observation_change_per_update: u128, 7 | pub twap_start_delay_slots: u64, 8 | pub min_quote_futarchic_liquidity: u64, 9 | pub min_base_futarchic_liquidity: u64, 10 | pub pass_threshold_bps: Option, 11 | pub slots_per_proposal: Option, 12 | } 13 | 14 | #[derive(Accounts)] 15 | #[event_cpi] 16 | pub struct InitializeDao<'info> { 17 | #[account( 18 | init, 19 | payer = payer, 20 | space = 8 + std::mem::size_of::() 21 | )] 22 | pub dao: Account<'info, Dao>, 23 | #[account(mut)] 24 | pub payer: Signer<'info>, 25 | pub system_program: Program<'info, System>, 26 | pub token_mint: Account<'info, Mint>, 27 | // todo: statically check that this is USDC given a feature flag 28 | #[account(mint::decimals = 6)] 29 | pub usdc_mint: Account<'info, Mint>, 30 | } 31 | 32 | impl InitializeDao<'_> { 33 | pub fn handle(ctx: Context, params: InitializeDaoParams) -> Result<()> { 34 | let InitializeDaoParams { 35 | twap_initial_observation, 36 | twap_max_observation_change_per_update, 37 | twap_start_delay_slots, 38 | min_base_futarchic_liquidity, 39 | min_quote_futarchic_liquidity, 40 | pass_threshold_bps, 41 | slots_per_proposal, 42 | } = params; 43 | 44 | let dao = &mut ctx.accounts.dao; 45 | 46 | let (treasury, treasury_pda_bump) = 47 | Pubkey::find_program_address(&[dao.key().as_ref()], ctx.program_id); 48 | 49 | dao.set_inner(Dao { 50 | token_mint: ctx.accounts.token_mint.key(), 51 | usdc_mint: ctx.accounts.usdc_mint.key(), 52 | treasury_pda_bump, 53 | treasury, 54 | proposal_count: 0, 55 | pass_threshold_bps: pass_threshold_bps.unwrap_or(DEFAULT_PASS_THRESHOLD_BPS), 56 | slots_per_proposal: slots_per_proposal.unwrap_or(THREE_DAYS_IN_SLOTS), 57 | twap_initial_observation, 58 | twap_max_observation_change_per_update, 59 | twap_start_delay_slots, 60 | min_base_futarchic_liquidity, 61 | min_quote_futarchic_liquidity, 62 | seq_num: 0, 63 | }); 64 | 65 | let clock = Clock::get()?; 66 | emit_cpi!(InitializeDaoEvent { 67 | common: CommonFields::new(&clock), 68 | dao: dao.key(), 69 | token_mint: ctx.accounts.token_mint.key(), 70 | usdc_mint: ctx.accounts.usdc_mint.key(), 71 | treasury, 72 | pass_threshold_bps: dao.pass_threshold_bps, 73 | slots_per_proposal: dao.slots_per_proposal, 74 | twap_initial_observation: dao.twap_initial_observation, 75 | twap_max_observation_change_per_update: dao.twap_max_observation_change_per_update, 76 | min_quote_futarchic_liquidity: dao.min_quote_futarchic_liquidity, 77 | min_base_futarchic_liquidity: dao.min_base_futarchic_liquidity, 78 | }); 79 | 80 | Ok(()) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /programs/autocrat/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod execute_proposal; 4 | pub mod finalize_proposal; 5 | pub mod initialize_dao; 6 | pub mod initialize_proposal; 7 | pub mod update_dao; 8 | 9 | pub use execute_proposal::*; 10 | pub use finalize_proposal::*; 11 | pub use initialize_dao::*; 12 | pub use initialize_proposal::*; 13 | pub use update_dao::*; 14 | -------------------------------------------------------------------------------- /programs/autocrat/src/instructions/update_dao.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] 4 | pub struct UpdateDaoParams { 5 | pub pass_threshold_bps: Option, 6 | pub slots_per_proposal: Option, 7 | pub twap_initial_observation: Option, 8 | pub twap_max_observation_change_per_update: Option, 9 | pub min_quote_futarchic_liquidity: Option, 10 | pub min_base_futarchic_liquidity: Option, 11 | } 12 | 13 | #[derive(Accounts)] 14 | #[event_cpi] 15 | pub struct UpdateDao<'info> { 16 | #[account(mut, has_one = treasury)] 17 | pub dao: Account<'info, Dao>, 18 | pub treasury: Signer<'info>, 19 | } 20 | 21 | impl UpdateDao<'_> { 22 | pub fn handle(ctx: Context, dao_params: UpdateDaoParams) -> Result<()> { 23 | let dao = &mut ctx.accounts.dao; 24 | 25 | macro_rules! update_dao_if_passed { 26 | ($field:ident) => { 27 | if let Some(value) = dao_params.$field { 28 | dao.$field = value; 29 | } 30 | }; 31 | } 32 | 33 | update_dao_if_passed!(pass_threshold_bps); 34 | update_dao_if_passed!(slots_per_proposal); 35 | update_dao_if_passed!(twap_initial_observation); 36 | update_dao_if_passed!(twap_max_observation_change_per_update); 37 | update_dao_if_passed!(min_quote_futarchic_liquidity); 38 | update_dao_if_passed!(min_base_futarchic_liquidity); 39 | 40 | dao.seq_num += 1; 41 | 42 | let clock = Clock::get()?; 43 | emit_cpi!(UpdateDaoEvent { 44 | common: CommonFields::new(&clock), 45 | dao: dao.key(), 46 | pass_threshold_bps: dao.pass_threshold_bps, 47 | slots_per_proposal: dao.slots_per_proposal, 48 | twap_initial_observation: dao.twap_initial_observation, 49 | twap_max_observation_change_per_update: dao.twap_max_observation_change_per_update, 50 | min_quote_futarchic_liquidity: dao.min_quote_futarchic_liquidity, 51 | min_base_futarchic_liquidity: dao.min_base_futarchic_liquidity, 52 | }); 53 | 54 | Ok(()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /programs/autocrat/src/state/dao.rs: -------------------------------------------------------------------------------- 1 | pub use super::*; 2 | 3 | #[account] 4 | pub struct Dao { 5 | pub treasury_pda_bump: u8, 6 | pub treasury: Pubkey, 7 | pub token_mint: Pubkey, 8 | pub usdc_mint: Pubkey, 9 | pub proposal_count: u32, 10 | // the percentage, in basis points, the pass price needs to be above the 11 | // fail price in order for the proposal to pass 12 | pub pass_threshold_bps: u16, 13 | pub slots_per_proposal: u64, 14 | /// For manipulation-resistance the TWAP is a time-weighted average observation, 15 | /// where observation tries to approximate price but can only move by 16 | /// `twap_max_observation_change_per_update` per update. Because it can only move 17 | /// a little bit per update, you need to check that it has a good initial observation. 18 | /// Otherwise, an attacker could create a very high initial observation in the pass 19 | /// market and a very low one in the fail market to force the proposal to pass. 20 | /// 21 | /// We recommend setting an initial observation around the spot price of the token, 22 | /// and max observation change per update around 2% the spot price of the token. 23 | /// For example, if the spot price of META is $400, we'd recommend setting an initial 24 | /// observation of 400 (converted into the AMM prices) and a max observation change per 25 | /// update of 8 (also converted into the AMM prices). Observations can be updated once 26 | /// a minute, so 2% allows the proposal market to reach double the spot price or 0 27 | /// in 50 minutes. 28 | pub twap_initial_observation: u128, 29 | pub twap_max_observation_change_per_update: u128, 30 | /// Forces TWAP calculation to start after amm.created_at_slot + twap_start_delay_slots 31 | pub twap_start_delay_slots: u64, 32 | /// As an anti-spam measure and to help liquidity, you need to lock up some liquidity 33 | /// in both futarchic markets in order to create a proposal. 34 | /// 35 | /// For example, for META, we can use a `min_quote_futarchic_liquidity` of 36 | /// 5000 * 1_000_000 (5000 USDC) and a `min_base_futarchic_liquidity` of 37 | /// 10 * 1_000_000_000 (10 META). 38 | pub min_quote_futarchic_liquidity: u64, 39 | pub min_base_futarchic_liquidity: u64, 40 | pub seq_num: u64, 41 | } 42 | -------------------------------------------------------------------------------- /programs/autocrat/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dao; 2 | pub mod proposal; 3 | 4 | pub use dao::*; 5 | pub use proposal::*; 6 | 7 | pub use super::*; 8 | -------------------------------------------------------------------------------- /programs/autocrat/src/state/proposal.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] 4 | pub enum ProposalState { 5 | Pending, 6 | Passed, 7 | Failed, 8 | Executed, 9 | } 10 | 11 | #[derive(Clone, AnchorSerialize, AnchorDeserialize, Debug, PartialEq, Eq)] 12 | pub struct ProposalAccount { 13 | pub pubkey: Pubkey, 14 | pub is_signer: bool, 15 | pub is_writable: bool, 16 | } 17 | 18 | #[derive(Clone, AnchorSerialize, AnchorDeserialize, Debug, PartialEq, Eq)] 19 | pub struct ProposalInstruction { 20 | pub program_id: Pubkey, 21 | pub accounts: Vec, 22 | pub data: Vec, 23 | } 24 | 25 | #[account] 26 | pub struct Proposal { 27 | pub number: u32, 28 | pub proposer: Pubkey, 29 | pub description_url: String, 30 | pub slot_enqueued: u64, 31 | pub state: ProposalState, 32 | pub instruction: ProposalInstruction, 33 | pub pass_amm: Pubkey, 34 | pub fail_amm: Pubkey, 35 | pub base_vault: Pubkey, 36 | pub quote_vault: Pubkey, 37 | pub dao: Pubkey, 38 | pub pass_lp_tokens_locked: u64, 39 | pub fail_lp_tokens_locked: u64, 40 | /// We need to include a per-proposer nonce to prevent some weird proposal 41 | /// front-running edge cases. Using a `u64` means that proposers are unlikely 42 | /// to run into collisions, even if they generate nonces randomly - I've run 43 | /// the math :D 44 | pub nonce: u64, 45 | pub pda_bump: u8, 46 | pub question: Pubkey, 47 | pub duration_in_slots: u64, 48 | } 49 | 50 | impl From<&ProposalInstruction> for Instruction { 51 | fn from(ix: &ProposalInstruction) -> Self { 52 | Self { 53 | program_id: ix.program_id, 54 | data: ix.data.clone(), 55 | accounts: ix.accounts.iter().map(Into::into).collect(), 56 | } 57 | } 58 | } 59 | 60 | impl From<&ProposalAccount> for AccountMeta { 61 | fn from(acc: &ProposalAccount) -> Self { 62 | Self { 63 | pubkey: acc.pubkey, 64 | is_signer: acc.is_signer, 65 | is_writable: acc.is_writable, 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /programs/autocrat_migrator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "autocrat_migrator" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "autocrat_migrator" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = "0.29.0" 20 | anchor-spl = "0.29.0" 21 | -------------------------------------------------------------------------------- /programs/autocrat_migrator/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/conditional_vault/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "conditional_vault" 3 | version = "0.4.0" 4 | description = "SVM-based program for minting conditional tokens" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "conditional_vault" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | production = [] 18 | 19 | [dependencies] 20 | anchor-lang = { version = "0.29.0", features = ["init-if-needed", "event-cpi"] } 21 | anchor-spl = { version = "0.29.0", features = ["metadata"] } 22 | solana-security-txt = "1.1.1" 23 | -------------------------------------------------------------------------------- /programs/conditional_vault/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/conditional_vault/src/error.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[error_code] 4 | pub enum VaultError { 5 | #[msg("An assertion failed")] 6 | AssertFailed, 7 | #[msg("Insufficient underlying token balance to mint this amount of conditional tokens")] 8 | InsufficientUnderlyingTokens, 9 | #[msg("Insufficient conditional token balance to merge this `amount`")] 10 | InsufficientConditionalTokens, 11 | #[msg("This `vault_underlying_token_account` is not this vault's `underlying_token_account`")] 12 | InvalidVaultUnderlyingTokenAccount, 13 | #[msg("This conditional token mint is not this vault's conditional token mint")] 14 | InvalidConditionalTokenMint, 15 | #[msg("Question needs to be resolved before users can redeem conditional tokens for underlying tokens")] 16 | CantRedeemConditionalTokens, 17 | #[msg("Questions need 2 or more conditions")] 18 | InsufficientNumConditions, 19 | #[msg("Invalid number of payout numerators")] 20 | InvalidNumPayoutNumerators, 21 | #[msg("Client needs to pass in the list of conditional mints for a vault followed by the user's token accounts for those tokens")] 22 | InvalidConditionals, 23 | #[msg("Conditional mint not in vault")] 24 | ConditionalMintMismatch, 25 | #[msg("Unable to deserialize a conditional token mint")] 26 | BadConditionalMint, 27 | #[msg("Unable to deserialize a conditional token account")] 28 | BadConditionalTokenAccount, 29 | #[msg("User conditional token account mint does not match conditional mint")] 30 | ConditionalTokenMintMismatch, 31 | #[msg("Payouts must sum to 1 or more")] 32 | PayoutZero, 33 | #[msg("Question already resolved")] 34 | QuestionAlreadyResolved, 35 | #[msg("Conditional token metadata already set")] 36 | ConditionalTokenMetadataAlreadySet, 37 | } 38 | -------------------------------------------------------------------------------- /programs/conditional_vault/src/events.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[derive(AnchorSerialize, AnchorDeserialize)] 4 | pub struct CommonFields { 5 | pub slot: u64, 6 | pub unix_timestamp: i64, 7 | } 8 | 9 | impl CommonFields { 10 | pub fn new(clock: &Clock) -> Self { 11 | Self { 12 | slot: clock.slot, 13 | unix_timestamp: clock.unix_timestamp, 14 | } 15 | } 16 | } 17 | 18 | #[event] 19 | pub struct AddMetadataToConditionalTokensEvent { 20 | pub common: CommonFields, 21 | pub vault: Pubkey, 22 | pub conditional_token_mint: Pubkey, 23 | pub conditional_token_metadata: Pubkey, 24 | pub name: String, 25 | pub symbol: String, 26 | pub uri: String, 27 | pub seq_num: u64, 28 | } 29 | 30 | // TODO add `vault` to this event 31 | #[event] 32 | pub struct InitializeConditionalVaultEvent { 33 | pub common: CommonFields, 34 | pub vault: Pubkey, 35 | pub question: Pubkey, 36 | pub underlying_token_mint: Pubkey, 37 | pub vault_underlying_token_account: Pubkey, 38 | pub conditional_token_mints: Vec, 39 | pub pda_bump: u8, 40 | pub seq_num: u64, 41 | } 42 | 43 | #[event] 44 | pub struct InitializeQuestionEvent { 45 | pub common: CommonFields, 46 | pub question_id: [u8; 32], 47 | pub oracle: Pubkey, 48 | pub num_outcomes: u8, 49 | pub question: Pubkey, 50 | } 51 | 52 | #[event] 53 | pub struct MergeTokensEvent { 54 | pub common: CommonFields, 55 | pub user: Pubkey, 56 | pub vault: Pubkey, 57 | pub amount: u64, 58 | pub post_user_underlying_balance: u64, 59 | pub post_vault_underlying_balance: u64, 60 | pub post_user_conditional_token_balances: Vec, 61 | pub post_conditional_token_supplies: Vec, 62 | pub seq_num: u64, 63 | } 64 | 65 | #[event] 66 | pub struct RedeemTokensEvent { 67 | pub common: CommonFields, 68 | pub user: Pubkey, 69 | pub vault: Pubkey, 70 | pub amount: u64, 71 | pub post_user_underlying_balance: u64, 72 | pub post_vault_underlying_balance: u64, 73 | pub post_conditional_token_supplies: Vec, 74 | pub seq_num: u64, 75 | } 76 | 77 | #[event] 78 | pub struct ResolveQuestionEvent { 79 | pub common: CommonFields, 80 | pub question: Pubkey, 81 | pub payout_numerators: Vec, 82 | } 83 | 84 | #[event] 85 | pub struct SplitTokensEvent { 86 | pub common: CommonFields, 87 | pub user: Pubkey, 88 | pub vault: Pubkey, 89 | pub amount: u64, 90 | pub post_user_underlying_balance: u64, 91 | pub post_vault_underlying_balance: u64, 92 | pub post_user_conditional_token_balances: Vec, 93 | pub post_conditional_token_supplies: Vec, 94 | pub seq_num: u64, 95 | } 96 | -------------------------------------------------------------------------------- /programs/conditional_vault/src/instructions/common.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[event_cpi] 4 | #[derive(Accounts)] 5 | pub struct InteractWithVault<'info> { 6 | pub question: Account<'info, Question>, 7 | #[account(mut, has_one = question)] 8 | pub vault: Account<'info, ConditionalVault>, 9 | #[account( 10 | mut, 11 | constraint = vault_underlying_token_account.key() == vault.underlying_token_account @ VaultError::InvalidVaultUnderlyingTokenAccount 12 | )] 13 | pub vault_underlying_token_account: Account<'info, TokenAccount>, 14 | pub authority: Signer<'info>, 15 | #[account( 16 | mut, 17 | token::authority = authority, 18 | token::mint = vault.underlying_token_mint 19 | )] 20 | pub user_underlying_token_account: Account<'info, TokenAccount>, 21 | pub token_program: Program<'info, Token>, 22 | } 23 | 24 | impl<'info, 'c: 'info> InteractWithVault<'info> { 25 | pub fn get_mints_and_user_token_accounts( 26 | ctx: &Context<'_, '_, 'c, 'info, Self>, 27 | ) -> Result<(Vec>, Vec>)> { 28 | let remaining_accs = &mut ctx.remaining_accounts.iter(); 29 | 30 | let expected_num_conditional_tokens = ctx.accounts.question.num_outcomes(); 31 | require_eq!( 32 | remaining_accs.len(), 33 | expected_num_conditional_tokens * 2, 34 | VaultError::InvalidConditionals 35 | ); 36 | 37 | let mut conditional_token_mints = vec![]; 38 | let mut user_conditional_token_accounts = vec![]; 39 | 40 | for i in 0..expected_num_conditional_tokens { 41 | let conditional_token_mint = next_account_info(remaining_accs)?; 42 | require_eq!( 43 | ctx.accounts.vault.conditional_token_mints[i], 44 | conditional_token_mint.key(), 45 | VaultError::ConditionalMintMismatch 46 | ); 47 | 48 | // really, this should never fail because we initialize mints when we initialize the vault 49 | conditional_token_mints.push( 50 | Account::::try_from(conditional_token_mint) 51 | .or(Err(VaultError::BadConditionalMint))?, 52 | ); 53 | } 54 | 55 | for i in 0..expected_num_conditional_tokens { 56 | let user_conditional_token_account = next_account_info(remaining_accs)?; 57 | 58 | let user_conditional_token_account = 59 | Account::::try_from(user_conditional_token_account) 60 | .or(Err(VaultError::BadConditionalTokenAccount))?; 61 | 62 | require_eq!( 63 | user_conditional_token_account.mint, 64 | conditional_token_mints[i].key(), 65 | VaultError::ConditionalTokenMintMismatch 66 | ); 67 | 68 | user_conditional_token_accounts.push(user_conditional_token_account); 69 | } 70 | 71 | Ok((conditional_token_mints, user_conditional_token_accounts)) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /programs/conditional_vault/src/instructions/initialize_question.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(AnchorSerialize, AnchorDeserialize)] 4 | pub struct InitializeQuestionArgs { 5 | pub question_id: [u8; 32], 6 | pub oracle: Pubkey, 7 | pub num_outcomes: u8, 8 | } 9 | 10 | #[event_cpi] 11 | #[derive(Accounts)] 12 | #[instruction(args: InitializeQuestionArgs)] 13 | pub struct InitializeQuestion<'info> { 14 | #[account( 15 | init, 16 | payer = payer, 17 | space = 8 + 32 + 32 + 1 + 4 + (args.num_outcomes as usize * 4) + 4, 18 | seeds = [ 19 | b"question", 20 | args.question_id.as_ref(), 21 | args.oracle.key().as_ref(), 22 | &[args.num_outcomes], 23 | ], 24 | bump 25 | )] 26 | pub question: Box>, 27 | #[account(mut)] 28 | pub payer: Signer<'info>, 29 | pub system_program: Program<'info, System>, 30 | } 31 | 32 | impl InitializeQuestion<'_> { 33 | pub fn handle(ctx: Context, args: InitializeQuestionArgs) -> Result<()> { 34 | require_gte!(args.num_outcomes, 2, VaultError::InsufficientNumConditions); 35 | 36 | let question = &mut ctx.accounts.question; 37 | 38 | let InitializeQuestionArgs { 39 | question_id, 40 | oracle, 41 | num_outcomes, 42 | } = args; 43 | 44 | question.set_inner(Question { 45 | question_id, 46 | oracle, 47 | payout_numerators: vec![0; num_outcomes as usize], 48 | payout_denominator: 0, 49 | }); 50 | 51 | let clock = Clock::get()?; 52 | emit_cpi!(InitializeQuestionEvent { 53 | common: CommonFields { 54 | slot: clock.slot, 55 | unix_timestamp: clock.unix_timestamp, 56 | }, 57 | question_id, 58 | oracle, 59 | num_outcomes, 60 | question: question.key(), 61 | }); 62 | 63 | Ok(()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /programs/conditional_vault/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod add_metadata_to_conditional_tokens; 4 | pub mod common; 5 | pub mod initialize_conditional_vault; 6 | pub mod initialize_question; 7 | pub mod merge_tokens; 8 | pub mod redeem_tokens; 9 | pub mod resolve_question; 10 | pub mod split_tokens; 11 | 12 | pub use add_metadata_to_conditional_tokens::*; 13 | pub use common::*; 14 | pub use initialize_conditional_vault::*; 15 | pub use initialize_question::*; 16 | pub use resolve_question::*; 17 | // pub use split_tokens::*; 18 | // pub use merge_tokens::*; 19 | // pub use redeem_tokens::*; 20 | -------------------------------------------------------------------------------- /programs/conditional_vault/src/instructions/resolve_question.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(AnchorSerialize, AnchorDeserialize)] 4 | pub struct ResolveQuestionArgs { 5 | pub payout_numerators: Vec, 6 | } 7 | 8 | #[event_cpi] 9 | #[derive(Accounts)] 10 | #[instruction(args: ResolveQuestionArgs)] 11 | pub struct ResolveQuestion<'info> { 12 | #[account(mut, has_one = oracle)] 13 | pub question: Account<'info, Question>, 14 | pub oracle: Signer<'info>, 15 | } 16 | 17 | impl ResolveQuestion<'_> { 18 | pub fn handle(ctx: Context, args: ResolveQuestionArgs) -> Result<()> { 19 | let question = &mut ctx.accounts.question; 20 | 21 | require_eq!( 22 | question.payout_denominator, 23 | 0, 24 | VaultError::QuestionAlreadyResolved 25 | ); 26 | 27 | require_eq!( 28 | args.payout_numerators.len(), 29 | question.num_outcomes(), 30 | VaultError::InvalidNumPayoutNumerators 31 | ); 32 | 33 | question.payout_denominator = args.payout_numerators.iter().sum(); 34 | question.payout_numerators = args.payout_numerators.clone(); 35 | 36 | require_gt!(question.payout_denominator, 0, VaultError::PayoutZero); 37 | 38 | let clock = Clock::get()?; 39 | emit_cpi!(ResolveQuestionEvent { 40 | common: CommonFields { 41 | slot: clock.slot, 42 | unix_timestamp: clock.unix_timestamp, 43 | }, 44 | question: question.key(), 45 | payout_numerators: args.payout_numerators, 46 | }); 47 | 48 | Ok(()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /programs/conditional_vault/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::metadata::{ 3 | create_metadata_accounts_v3, mpl_token_metadata::types::DataV2, CreateMetadataAccountsV3, 4 | Metadata, 5 | }; 6 | use anchor_spl::{ 7 | associated_token::AssociatedToken, 8 | token::{self, Burn, Mint, MintTo, Token, TokenAccount, Transfer}, 9 | }; 10 | 11 | pub mod error; 12 | pub mod events; 13 | pub mod instructions; 14 | pub mod state; 15 | 16 | pub use error::VaultError; 17 | pub use events::*; 18 | pub use instructions::*; 19 | pub use state::*; 20 | 21 | #[cfg(not(feature = "no-entrypoint"))] 22 | use solana_security_txt::security_txt; 23 | 24 | #[cfg(not(feature = "no-entrypoint"))] 25 | security_txt! { 26 | name: "conditional_vault", 27 | project_url: "https://metadao.fi", 28 | contacts: "email:metaproph3t@protonmail.com", 29 | policy: "The market will decide whether we pay a bug bounty.", 30 | source_code: "https://github.com/metaDAOproject/futarchy", 31 | source_release: "v0.4", 32 | auditors: "Neodyme (v0.3)", 33 | acknowledgements: "DCF = (CF1 / (1 + r)^1) + (CF2 / (1 + r)^2) + ... (CFn / (1 + r)^n)" 34 | } 35 | 36 | declare_id!("VLTX1ishMBbcX3rdBWGssxawAo1Q2X2qxYFYqiGodVg"); 37 | 38 | #[program] 39 | pub mod conditional_vault { 40 | use super::*; 41 | 42 | pub fn initialize_question( 43 | ctx: Context, 44 | args: InitializeQuestionArgs, 45 | ) -> Result<()> { 46 | InitializeQuestion::handle(ctx, args) 47 | } 48 | 49 | pub fn resolve_question( 50 | ctx: Context, 51 | args: ResolveQuestionArgs, 52 | ) -> Result<()> { 53 | ResolveQuestion::handle(ctx, args) 54 | } 55 | 56 | pub fn initialize_conditional_vault<'c: 'info, 'info>( 57 | ctx: Context<'_, '_, 'c, 'info, InitializeConditionalVault<'info>>, 58 | ) -> Result<()> { 59 | InitializeConditionalVault::handle(ctx) 60 | } 61 | 62 | pub fn split_tokens<'c: 'info, 'info>( 63 | ctx: Context<'_, '_, 'c, 'info, InteractWithVault<'info>>, 64 | amount: u64, 65 | ) -> Result<()> { 66 | InteractWithVault::handle_split_tokens(ctx, amount) 67 | } 68 | 69 | pub fn merge_tokens<'c: 'info, 'info>( 70 | ctx: Context<'_, '_, 'c, 'info, InteractWithVault<'info>>, 71 | amount: u64, 72 | ) -> Result<()> { 73 | InteractWithVault::handle_merge_tokens(ctx, amount) 74 | } 75 | 76 | #[access_control(ctx.accounts.validate_redeem_tokens())] 77 | pub fn redeem_tokens<'c: 'info, 'info>( 78 | ctx: Context<'_, '_, 'c, 'info, InteractWithVault<'info>>, 79 | ) -> Result<()> { 80 | InteractWithVault::handle_redeem_tokens(ctx) 81 | } 82 | 83 | #[access_control(ctx.accounts.validate())] 84 | pub fn add_metadata_to_conditional_tokens( 85 | ctx: Context, 86 | args: AddMetadataToConditionalTokensArgs, 87 | ) -> Result<()> { 88 | AddMetadataToConditionalTokens::handle(ctx, args) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /programs/conditional_vault/src/state/conditional_vault.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Eq)] 4 | pub enum VaultStatus { 5 | Active, 6 | Finalized, 7 | Reverted, 8 | } 9 | 10 | #[account] 11 | pub struct ConditionalVault { 12 | pub question: Pubkey, 13 | pub underlying_token_mint: Pubkey, 14 | pub underlying_token_account: Pubkey, 15 | pub conditional_token_mints: Vec, 16 | pub pda_bump: u8, 17 | pub decimals: u8, 18 | pub seq_num: u64, 19 | } 20 | 21 | impl ConditionalVault { 22 | /// Checks that the vault's assets are always greater than its potential 23 | /// liabilities. Should be called anytime you mint or burn conditional 24 | /// tokens. 25 | /// 26 | /// `conditional_token_supplies` should be in the same order as 27 | /// `vault.conditional_token_mints`. 28 | pub fn invariant( 29 | &self, 30 | question: &Question, 31 | conditional_token_supplies: Vec, 32 | vault_underlying_balance: u64, 33 | ) -> Result<()> { 34 | // if the question isn't resolved, the vault should have more underlying 35 | // tokens than ANY conditional token mint's supply 36 | 37 | // if the question is resolved, the vault should have more underlying 38 | // tokens than the sum of the conditional token mint's supplies multiplied 39 | // by their respective payouts 40 | 41 | let max_possible_liability = if !question.is_resolved() { 42 | // safe because conditional_token_supplies is non-empty 43 | *conditional_token_supplies.iter().max().unwrap() 44 | } else { 45 | conditional_token_supplies 46 | .iter() 47 | .enumerate() 48 | .map(|(i, supply)| { 49 | *supply as u128 * question.payout_numerators[i] as u128 50 | / question.payout_denominator as u128 51 | }) 52 | .sum::() as u64 53 | }; 54 | 55 | require_gte!( 56 | vault_underlying_balance, 57 | max_possible_liability, 58 | VaultError::AssertFailed 59 | ); 60 | 61 | Ok(()) 62 | } 63 | } 64 | 65 | #[macro_export] 66 | macro_rules! generate_vault_seeds { 67 | ($vault:expr) => {{ 68 | &[ 69 | b"conditional_vault", 70 | $vault.question.as_ref(), 71 | $vault.underlying_token_mint.as_ref(), 72 | &[$vault.pda_bump], 73 | ] 74 | }}; 75 | } 76 | -------------------------------------------------------------------------------- /programs/conditional_vault/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod conditional_vault; 4 | pub mod question; 5 | 6 | pub use conditional_vault::*; 7 | pub use question::*; 8 | -------------------------------------------------------------------------------- /programs/conditional_vault/src/state/question.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// Questions represent statements about future events. 4 | /// 5 | /// These statements include: 6 | /// - "Will this proposal pass?" 7 | /// - "Who, if anyone, will be hired?" 8 | /// - "How effective will the grant committee deem this grant?" 9 | /// 10 | /// Questions have 2 or more possible outcomes. For a question like "will this 11 | /// proposal pass," the outcomes are "yes" and "no." For a question like "who 12 | /// will be hired," the outcomes could be "Alice," "Bob," and "neither." 13 | /// 14 | /// Outcomes resolve to a number between 0 and 1. Binary questions like "will 15 | /// this proposal pass" have outcomes that resolve to exactly 0 or 1. You can 16 | /// also have questions with scalar outcomes. For example, the question "how 17 | /// effective will the grant committee deem this grant" could have two outcomes: 18 | /// "ineffective" and "effective." If the grant committee deems the grant 70% 19 | /// effective, the "effective" outcome would resolve to 0.7 and the "ineffective" 20 | /// outcome would resolve to 0.3. 21 | /// 22 | /// Once resolved, the sum of all outcome resolutions is exactly 1. 23 | #[account] 24 | pub struct Question { 25 | pub question_id: [u8; 32], 26 | pub oracle: Pubkey, 27 | pub payout_numerators: Vec, 28 | pub payout_denominator: u32, 29 | } 30 | 31 | impl Question { 32 | pub fn num_outcomes(&self) -> usize { 33 | self.payout_numerators.len() 34 | } 35 | 36 | pub fn is_resolved(&self) -> bool { 37 | self.payout_denominator != 0 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /programs/launchpad/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "launchpad" 3 | version = "0.4.1" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "launchpad" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | devnet = ["raydium-cpmm-cpi/devnet"] 18 | production = [] 19 | 20 | [dependencies] 21 | anchor-lang = "0.29.0" 22 | anchor-spl = "0.29.0" 23 | autocrat = { path = "../autocrat", features = ["cpi"] } 24 | raydium-cpmm-cpi = { git = "https://github.com/raydium-io/raydium-cpi", package = "raydium-cpmm-cpi", branch = "anchor-0.29.0" } 25 | spl-memo = "=4.0.0" 26 | solana-program = "=1.17.14" 27 | spl-token = "=4.0.0" 28 | ahash = "=0.8.6" 29 | solana-security-txt = "1.1.1" 30 | 31 | -------------------------------------------------------------------------------- /programs/launchpad/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/launchpad/src/error.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[error_code] 4 | pub enum LaunchpadError { 5 | #[msg("Invalid amount")] 6 | InvalidAmount, 7 | #[msg("Supply must be zero")] 8 | SupplyNonZero, 9 | #[msg("Launch period must be between 1 hour and 2 weeks")] 10 | InvalidSecondsForLaunch, 11 | #[msg("Insufficient funds")] 12 | InsufficientFunds, 13 | #[msg("Token mint key must end in 'meta'")] 14 | InvalidTokenKey, 15 | #[msg("Invalid launch state")] 16 | InvalidLaunchState, 17 | #[msg("Launch period not over")] 18 | LaunchPeriodNotOver, 19 | #[msg("Launch is complete, no more funding allowed")] 20 | LaunchExpired, 21 | #[msg("Launch needs to be in refunding state to get a refund")] 22 | LaunchNotRefunding, 23 | #[msg("Launch must be initialized to be started")] 24 | LaunchNotInitialized, 25 | #[msg("Freeze authority can't be set on launchpad tokens")] 26 | FreezeAuthoritySet, 27 | } 28 | -------------------------------------------------------------------------------- /programs/launchpad/src/events.rs: -------------------------------------------------------------------------------- 1 | use crate::state::LaunchState; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(AnchorSerialize, AnchorDeserialize)] 5 | pub struct CommonFields { 6 | pub slot: u64, 7 | pub unix_timestamp: i64, 8 | pub launch_seq_num: u64, 9 | } 10 | 11 | impl CommonFields { 12 | pub fn new(clock: &Clock, launch_seq_num: u64) -> Self { 13 | Self { 14 | slot: clock.slot, 15 | unix_timestamp: clock.unix_timestamp, 16 | launch_seq_num, 17 | } 18 | } 19 | } 20 | 21 | #[event] 22 | pub struct LaunchInitializedEvent { 23 | pub common: CommonFields, 24 | pub launch: Pubkey, 25 | pub minimum_raise_amount: u64, 26 | pub launch_authority: Pubkey, 27 | pub launch_signer: Pubkey, 28 | pub launch_signer_pda_bump: u8, 29 | pub launch_usdc_vault: Pubkey, 30 | pub launch_token_vault: Pubkey, 31 | pub token_mint: Pubkey, 32 | pub usdc_mint: Pubkey, 33 | pub pda_bump: u8, 34 | pub seconds_for_launch: u32, 35 | } 36 | 37 | #[event] 38 | pub struct LaunchStartedEvent { 39 | pub common: CommonFields, 40 | pub launch: Pubkey, 41 | pub launch_authority: Pubkey, 42 | pub slot_started: u64, 43 | } 44 | 45 | #[event] 46 | pub struct LaunchFundedEvent { 47 | pub common: CommonFields, 48 | pub funding_record: Pubkey, 49 | pub launch: Pubkey, 50 | pub funder: Pubkey, 51 | pub amount: u64, 52 | pub total_committed_by_funder: u64, 53 | pub total_committed: u64, 54 | pub funding_record_seq_num: u64, 55 | } 56 | 57 | #[event] 58 | pub struct LaunchCompletedEvent { 59 | pub common: CommonFields, 60 | pub launch: Pubkey, 61 | pub final_state: LaunchState, 62 | pub total_committed: u64, 63 | pub dao: Option, 64 | pub dao_treasury: Option, 65 | } 66 | 67 | #[event] 68 | pub struct LaunchRefundedEvent { 69 | pub common: CommonFields, 70 | pub launch: Pubkey, 71 | pub funder: Pubkey, 72 | pub usdc_refunded: u64, 73 | pub funding_record: Pubkey, 74 | } 75 | 76 | #[event] 77 | pub struct LaunchClaimEvent { 78 | pub common: CommonFields, 79 | pub launch: Pubkey, 80 | pub funder: Pubkey, 81 | pub tokens_claimed: u64, 82 | pub funding_record: Pubkey, 83 | } 84 | -------------------------------------------------------------------------------- /programs/launchpad/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod claim; 2 | pub mod complete_launch; 3 | pub mod fund; 4 | pub mod initialize_launch; 5 | pub mod refund; 6 | pub mod start_launch; 7 | 8 | pub use claim::*; 9 | pub use complete_launch::*; 10 | pub use fund::*; 11 | pub use initialize_launch::*; 12 | pub use refund::*; 13 | pub use start_launch::*; 14 | -------------------------------------------------------------------------------- /programs/launchpad/src/instructions/refund.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount, Transfer}; 3 | 4 | use crate::error::LaunchpadError; 5 | use crate::events::{CommonFields, LaunchRefundedEvent}; 6 | use crate::state::{FundingRecord, Launch, LaunchState}; 7 | 8 | #[event_cpi] 9 | #[derive(Accounts)] 10 | pub struct Refund<'info> { 11 | #[account( 12 | mut, 13 | has_one = launch_usdc_vault, 14 | has_one = launch_signer, 15 | )] 16 | pub launch: Account<'info, Launch>, 17 | 18 | #[account( 19 | mut, 20 | close = funder, 21 | has_one = funder, 22 | seeds = [b"funding_record", launch.key().as_ref(), funder.key().as_ref()], 23 | bump = funding_record.pda_bump 24 | )] 25 | pub funding_record: Account<'info, FundingRecord>, 26 | 27 | #[account(mut)] 28 | pub launch_usdc_vault: Account<'info, TokenAccount>, 29 | 30 | /// CHECK: just a signer 31 | pub launch_signer: UncheckedAccount<'info>, 32 | 33 | #[account(mut)] 34 | pub funder: Signer<'info>, 35 | 36 | #[account(mut)] 37 | pub funder_usdc_account: Account<'info, TokenAccount>, 38 | 39 | pub token_program: Program<'info, Token>, 40 | pub system_program: Program<'info, System>, 41 | } 42 | 43 | impl Refund<'_> { 44 | pub fn validate(&self) -> Result<()> { 45 | require!( 46 | self.launch.state == LaunchState::Refunding, 47 | LaunchpadError::LaunchNotRefunding 48 | ); 49 | Ok(()) 50 | } 51 | 52 | pub fn handle(ctx: Context) -> Result<()> { 53 | let launch = &mut ctx.accounts.launch; 54 | let launch_key = launch.key(); 55 | let funding_record = &ctx.accounts.funding_record; 56 | 57 | let seeds = &[ 58 | b"launch_signer", 59 | launch_key.as_ref(), 60 | &[launch.launch_signer_pda_bump], 61 | ]; 62 | let signer = &[&seeds[..]]; 63 | 64 | // Transfer USDC back to the user 65 | token::transfer( 66 | CpiContext::new_with_signer( 67 | ctx.accounts.token_program.to_account_info(), 68 | Transfer { 69 | from: ctx.accounts.launch_usdc_vault.to_account_info(), 70 | to: ctx.accounts.funder_usdc_account.to_account_info(), 71 | authority: ctx.accounts.launch_signer.to_account_info(), 72 | }, 73 | signer, 74 | ), 75 | funding_record.committed_amount, 76 | )?; 77 | 78 | launch.seq_num += 1; 79 | 80 | let clock = Clock::get()?; 81 | emit_cpi!(LaunchRefundedEvent { 82 | common: CommonFields::new(&clock, launch.seq_num), 83 | launch: ctx.accounts.launch.key(), 84 | funder: ctx.accounts.funder.key(), 85 | usdc_refunded: funding_record.committed_amount, 86 | funding_record: ctx.accounts.funding_record.key(), 87 | }); 88 | 89 | Ok(()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /programs/launchpad/src/instructions/start_launch.rs: -------------------------------------------------------------------------------- 1 | use crate::error::LaunchpadError; 2 | use crate::events::{CommonFields, LaunchStartedEvent}; 3 | use crate::state::{Launch, LaunchState}; 4 | use anchor_lang::prelude::*; 5 | 6 | #[event_cpi] 7 | #[derive(Accounts)] 8 | pub struct StartLaunch<'info> { 9 | #[account( 10 | mut, 11 | has_one = launch_authority, 12 | )] 13 | pub launch: Account<'info, Launch>, 14 | 15 | pub launch_authority: Signer<'info>, 16 | } 17 | 18 | impl StartLaunch<'_> { 19 | pub fn validate(&self) -> Result<()> { 20 | require!( 21 | self.launch.state == LaunchState::Initialized, 22 | LaunchpadError::LaunchNotInitialized 23 | ); 24 | 25 | Ok(()) 26 | } 27 | 28 | pub fn handle(ctx: Context) -> Result<()> { 29 | let launch = &mut ctx.accounts.launch; 30 | let clock = Clock::get()?; 31 | 32 | launch.state = LaunchState::Live; 33 | launch.unix_timestamp_started = clock.unix_timestamp; 34 | 35 | launch.seq_num += 1; 36 | 37 | emit_cpi!(LaunchStartedEvent { 38 | common: CommonFields::new(&clock, launch.seq_num), 39 | launch: ctx.accounts.launch.key(), 40 | launch_authority: ctx.accounts.launch_authority.key(), 41 | slot_started: clock.slot, 42 | }); 43 | 44 | Ok(()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /programs/launchpad/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A smart contract that facilitates the creation of new futarchic DAOs. 2 | use anchor_lang::prelude::*; 3 | 4 | pub mod error; 5 | pub mod events; 6 | pub mod instructions; 7 | pub mod state; 8 | 9 | use instructions::*; 10 | 11 | #[cfg(not(feature = "no-entrypoint"))] 12 | use solana_security_txt::security_txt; 13 | 14 | #[cfg(not(feature = "no-entrypoint"))] 15 | security_txt! { 16 | name: "launchpad", 17 | project_url: "https://metadao.fi", 18 | contacts: "email:metaproph3t@protonmail.com", 19 | policy: "The market will decide whether we pay a bug bounty.", 20 | source_code: "https://github.com/metaDAOproject/futarchy", 21 | source_release: "delayed-twap-v0.4.1", 22 | auditors: "Accretion", 23 | acknowledgements: "DCF = (CF1 / (1 + r)^1) + (CF2 / (1 + r)^2) + ... (CFn / (1 + r)^n)" 24 | } 25 | 26 | declare_id!("AfJJJ5UqxhBKoE3grkKAZZsoXDE9kncbMKvqSHGsCNrE"); 27 | 28 | /// 10M tokens with 6 decimals 29 | pub const AVAILABLE_TOKENS: u64 = 10_000_000 * 1_000_000; 30 | 31 | pub mod usdc_mint { 32 | use anchor_lang::prelude::declare_id; 33 | 34 | #[cfg(feature = "devnet")] 35 | declare_id!("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); 36 | 37 | #[cfg(not(feature = "devnet"))] 38 | declare_id!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); 39 | } 40 | 41 | #[program] 42 | pub mod launchpad { 43 | use super::*; 44 | 45 | #[access_control(ctx.accounts.validate(&args))] 46 | pub fn initialize_launch( 47 | ctx: Context, 48 | args: InitializeLaunchArgs, 49 | ) -> Result<()> { 50 | InitializeLaunch::handle(ctx, args) 51 | } 52 | 53 | #[access_control(ctx.accounts.validate())] 54 | pub fn start_launch(ctx: Context) -> Result<()> { 55 | StartLaunch::handle(ctx) 56 | } 57 | 58 | #[access_control(ctx.accounts.validate(amount))] 59 | pub fn fund(ctx: Context, amount: u64) -> Result<()> { 60 | Fund::handle(ctx, amount) 61 | } 62 | 63 | #[access_control(ctx.accounts.validate())] 64 | pub fn complete_launch(ctx: Context) -> Result<()> { 65 | CompleteLaunch::handle(ctx) 66 | } 67 | 68 | #[access_control(ctx.accounts.validate())] 69 | pub fn refund(ctx: Context) -> Result<()> { 70 | Refund::handle(ctx) 71 | } 72 | 73 | #[access_control(ctx.accounts.validate())] 74 | pub fn claim(ctx: Context) -> Result<()> { 75 | Claim::handle(ctx) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /programs/launchpad/src/state/funding_record.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[account] 4 | pub struct FundingRecord { 5 | /// The PDA bump. 6 | pub pda_bump: u8, 7 | /// The funder. 8 | pub funder: Pubkey, 9 | /// The launch. 10 | pub launch: Pubkey, 11 | /// The amount of USDC that has been committed by the funder. 12 | pub committed_amount: u64, 13 | /// The sequence number of this funding record. Useful for sorting events. 14 | pub seq_num: u64, 15 | } 16 | -------------------------------------------------------------------------------- /programs/launchpad/src/state/launch.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Eq)] 4 | pub enum LaunchState { 5 | Initialized, 6 | Live, 7 | Complete, 8 | Refunding, 9 | } 10 | 11 | #[account] 12 | pub struct Launch { 13 | /// The PDA bump. 14 | pub pda_bump: u8, 15 | /// The minimum amount of USDC that must be raised, otherwise 16 | /// everyone can get their USDC back. 17 | pub minimum_raise_amount: u64, 18 | /// The account that can start the launch. 19 | pub launch_authority: Pubkey, 20 | /// The launch signer address. Needed because Raydium pools need a SOL payer and this PDA can't hold SOL. 21 | pub launch_signer: Pubkey, 22 | /// The PDA bump for the launch signer. 23 | pub launch_signer_pda_bump: u8, 24 | /// The USDC vault that will hold the USDC raised until the launch is over. 25 | pub launch_usdc_vault: Pubkey, 26 | /// The token vault, used to send tokens to Raydium. 27 | pub launch_token_vault: Pubkey, 28 | /// The token that will be minted to funders and that will control the DAO. 29 | pub token_mint: Pubkey, 30 | /// The USDC mint. 31 | pub usdc_mint: Pubkey, 32 | /// The unix timestamp when the launch was started. 33 | pub unix_timestamp_started: i64, 34 | /// The amount of USDC that has been committed by the users. 35 | pub total_committed_amount: u64, 36 | /// The state of the launch. 37 | pub state: LaunchState, 38 | /// The sequence number of this launch. Useful for sorting events. 39 | pub seq_num: u64, 40 | /// The number of seconds that the launch will be live for. 41 | pub seconds_for_launch: u32, 42 | /// The DAO, if the launch is complete. 43 | pub dao: Option, 44 | /// The DAO treasury that USDC / LP is sent to, if the launch is complete. 45 | pub dao_treasury: Option, 46 | } 47 | -------------------------------------------------------------------------------- /programs/launchpad/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod funding_record; 2 | pub mod launch; 3 | 4 | pub use funding_record::*; 5 | pub use launch::*; 6 | -------------------------------------------------------------------------------- /programs/optimistic_timelock/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "timelock" 3 | version = "0.3.0" 4 | description = "Optimistic timelock" 5 | authors = ["Proph3t , Henry"] 6 | edition = "2021" 7 | repository = "https://github.com/metaDAOproject/futarchy" 8 | categories = ["cryptography::cryptocurrencies"] 9 | keywords = ["solana", "defi", "ponzu"] 10 | 11 | [lib] 12 | crate-type = ["cdylib", "lib"] 13 | name = "optimistic_timelock" 14 | 15 | [features] 16 | no-entrypoint = [] 17 | no-idl = [] 18 | no-log-ix-name = [] 19 | cpi = ["no-entrypoint"] 20 | default = [] 21 | 22 | [dependencies] 23 | anchor-lang = "0.29.0" 24 | solana-security-txt = "1.1.1" -------------------------------------------------------------------------------- /programs/optimistic_timelock/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # Scripts 2 | 3 | A collection of scripts to interact with various parts of the futarchy protocol 4 | 5 | ## Setup 6 | 7 | It's best to use these scripts with the latest build of the SDK. 8 | 9 | To do this, run the following commands, starting in the root dir of the repository: 10 | 11 | ```sh 12 | cd sdk # Move into the repository dir 13 | 14 | yarn 15 | yarn build 16 | yarn link 17 | 18 | cd .. # Move back into the root dir 19 | yarn link @metadaoproject/futarchy # Link to your local build of the futarchy sdk 20 | ``` 21 | 22 | Afterwards, you can run the scripts as you see fit. 23 | 24 | ## Launchpad 25 | 26 | In the root directory of this repo, you will find a `.env.example` file with settings for launches. Move into `.env`, change everything, and then simply hold enter throughout the script. If you don't, you can always enter everything manually. 27 | 28 | To initialize a launch, run: 29 | ```sh 30 | yarn launch-init 31 | ``` 32 | 33 | To start a launch, run: 34 | ```sh 35 | yarn launch-start 36 | ``` 37 | 38 | To complete a launch, run: 39 | ```sh 40 | yarn launch-complete 41 | ``` 42 | -------------------------------------------------------------------------------- /scripts/addV03Metadata.ts: -------------------------------------------------------------------------------- 1 | import * as token from "@solana/spl-token"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import * as anchor from "@coral-xyz/anchor"; 4 | import { 5 | AutocratClient, 6 | ConditionalVaultClient, 7 | } from "@metadaoproject/futarchy/v0.3"; 8 | import { BN } from "bn.js"; 9 | 10 | const provider = anchor.AnchorProvider.env(); 11 | const payer = provider.wallet["payer"]; 12 | const autocrat: AutocratClient = AutocratClient.createClient({ provider }); 13 | const vaultProgram: ConditionalVaultClient = 14 | ConditionalVaultClient.createClient({ provider }); 15 | 16 | const USDC = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); 17 | const ORE = new PublicKey("oreoU2P8bN6jkk3jbaiVxYnG1dCXcYxwhwyK9jSybcp"); 18 | 19 | const PROPOSAL = new PublicKey("A19yLRVqxvUf4cTDm6mKNKadasd7YSYDrzk6AYEyubAC"); 20 | 21 | async function main() { 22 | const proposal = await autocrat.getProposal(PROPOSAL); 23 | const baseVault: PublicKey = proposal.baseVault; 24 | const quoteVault: PublicKey = proposal.quoteVault; 25 | 26 | console.log(baseVault.toBase58()); 27 | console.log(quoteVault.toBase58()); 28 | 29 | const baseVaultInfo = await vaultProgram.getVault(baseVault); 30 | const quoteVaultInfo = await vaultProgram.getVault(quoteVault); 31 | 32 | console.log(baseVaultInfo); 33 | console.log(quoteVaultInfo); 34 | 35 | // await vaultProgram 36 | // .addMetadataToConditionalTokensIx(baseVault, ORE, 1, "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/ORE/pORE.json", "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/ORE/fORE.json") 37 | // .rpc(); 38 | 39 | await vaultProgram 40 | .addMetadataToConditionalTokensIx( 41 | quoteVault, 42 | USDC, 43 | 1, 44 | "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/USDC/pUSDC.json", 45 | "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/USDC/fUSDC.json" 46 | ) 47 | .rpc(); 48 | 49 | // console.log(vaultProgram.vaultProgram.programId.toBase58()); 50 | } 51 | 52 | main(); 53 | -------------------------------------------------------------------------------- /scripts/addV04Metadata.ts: -------------------------------------------------------------------------------- 1 | import { ComputeBudgetProgram, PublicKey, Transaction } from "@solana/web3.js"; 2 | import * as anchor from "@coral-xyz/anchor"; 3 | import { 4 | AutocratClient, 5 | ConditionalVaultClient, 6 | } from "@metadaoproject/futarchy/v0.4"; 7 | 8 | const provider = anchor.AnchorProvider.env(); 9 | const autocrat: AutocratClient = AutocratClient.createClient({ provider }); 10 | const vaultProgram: ConditionalVaultClient = 11 | ConditionalVaultClient.createClient({ provider }); 12 | 13 | const PROPOSAL = new PublicKey("3kfEHobCtMn4rDCXmo2H735xX6oEacVN1DBuL41wj7az"); 14 | const baseSymbol = "FAF"; 15 | 16 | async function main() { 17 | const proposal = await autocrat.getProposal(PROPOSAL); 18 | const baseVault: PublicKey = proposal.baseVault; 19 | const quoteVault: PublicKey = proposal.quoteVault; 20 | 21 | const quoteFail = await vaultProgram 22 | .addMetadataToConditionalTokensIx( 23 | quoteVault, 24 | 0, 25 | "Fail USDC", 26 | "fUSDC", 27 | "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/USDC/fUSDC.json" 28 | ) 29 | .transaction(); 30 | 31 | const quotePass = await vaultProgram 32 | .addMetadataToConditionalTokensIx( 33 | quoteVault, 34 | 1, 35 | "Pass USDC", 36 | "pUSDC", 37 | "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/USDC/pUSDC.json" 38 | ) 39 | .transaction(); 40 | 41 | const baseFail = await vaultProgram 42 | .addMetadataToConditionalTokensIx( 43 | baseVault, 44 | 0, 45 | `Fail ${baseSymbol}`, 46 | `f${baseSymbol}`, 47 | `https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/${baseSymbol}/f${baseSymbol}.json` 48 | ) 49 | .transaction(); 50 | 51 | 52 | const basePass = await vaultProgram 53 | .addMetadataToConditionalTokensIx( 54 | baseVault, 55 | 1, 56 | `Pass ${baseSymbol}`, 57 | `p${baseSymbol}`, 58 | `https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/${baseSymbol}/p${baseSymbol}.json` 59 | ) 60 | .transaction(); 61 | 62 | const tx = new Transaction().add(ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 100 }), quoteFail, quotePass, baseFail, basePass); 63 | 64 | const sig = await provider.sendAndConfirm(tx, undefined, { commitment: "confirmed" }); 65 | 66 | console.log(sig); 67 | } 68 | 69 | main(); 70 | -------------------------------------------------------------------------------- /scripts/assets/Binary/NO.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NO", 3 | "symbol": "pNO", 4 | "description": "Redeemable relative to the metric.", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/Binary/NO.png" 6 | } 7 | -------------------------------------------------------------------------------- /scripts/assets/Binary/NO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/Binary/NO.png -------------------------------------------------------------------------------- /scripts/assets/Binary/YES.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "YES", 3 | "symbol": "pYES", 4 | "description": "Redeemable relative to the metric.", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/Binary/YES.png" 6 | } 7 | -------------------------------------------------------------------------------- /scripts/assets/Binary/YES.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/Binary/YES.png -------------------------------------------------------------------------------- /scripts/assets/CLOUD-USDC/fUSDC.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fail USDC", 3 | "symbol": "fUSDC", 4 | "description": "A token that redeems back to USDC if the proposal fails", 5 | "image": "https://arweave.net/MvDKkY-F7dDAQOlqzlpNNYnGZT9dSGltEzgjLC0R2AY" 6 | } -------------------------------------------------------------------------------- /scripts/assets/CLOUD-USDC/pUSDC.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pass USDC", 3 | "symbol": "pUSDC", 4 | "description": "A token that redeems back to USDC if the proposal passes", 5 | "image": "https://arweave.net/4JRlLx374SQGILjAeBtl9pEOnul0TQkW87e04c92Uqg" 6 | } -------------------------------------------------------------------------------- /scripts/assets/CLOUD/fCLOUD.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fail CLOUD", 3 | "symbol": "fCLOUD", 4 | "description": "A token that redeems back to CLOUD if the proposal fails", 5 | "image": "https://arweave.net/0bz91Sgw53LBptksqIuETJ5RBhAogJBLV7lh9qiSZJM" 6 | } -------------------------------------------------------------------------------- /scripts/assets/CLOUD/pCLOUD.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pass CLOUD", 3 | "symbol": "pCLOUD", 4 | "description": "A token that redeems back to CLOUD if the proposal passes", 5 | "image": "https://arweave.net/eTi_qoA4bB18OFU9OP5gc3Suuef8FI_DZt-bd2Y13FA" 6 | } -------------------------------------------------------------------------------- /scripts/assets/DRIFT/fDRIFT.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fail DRIFT", 3 | "symbol": "fDRIFT", 4 | "description": "A token that redeems back to DRIFT if the proposal fails", 5 | "image": "https://imagedelivery.net/HYEnlujCFMCgj6yA728xIw/cb191bce-1f8b-4faf-fdc1-f80e73b84500/public" 6 | } -------------------------------------------------------------------------------- /scripts/assets/DRIFT/pDRIFT.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pass DRIFT", 3 | "symbol": "pDRIFT", 4 | "description": "A token that redeems back to DRIFT if the proposal passes", 5 | "image": "https://imagedelivery.net/HYEnlujCFMCgj6yA728xIw/7f095bfc-9f48-49b6-ca22-a5f2799c9a00/public" 6 | } -------------------------------------------------------------------------------- /scripts/assets/FAF/fFAF.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fail FAF", 3 | "symbol": "fFAF", 4 | "description": "A token that redeems back to FAF if the proposal fails", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/FAF/fFAF.png" 6 | } -------------------------------------------------------------------------------- /scripts/assets/FAF/fFAF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/FAF/fFAF.png -------------------------------------------------------------------------------- /scripts/assets/FAF/pFAF.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pass FAF", 3 | "symbol": "pFAF", 4 | "description": "A token that redeems back to FAF if the proposal passes", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/FAF/pFAF.png" 6 | } -------------------------------------------------------------------------------- /scripts/assets/FAF/pFAF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/FAF/pFAF.png -------------------------------------------------------------------------------- /scripts/assets/ISLAND/fISLAND.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fail ISLAND", 3 | "symbol": "fISLAND", 4 | "description": "A token that redeems back to ISLAND if the proposal fails", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/ISLAND/fISLAND.png" 6 | } -------------------------------------------------------------------------------- /scripts/assets/ISLAND/fISLAND.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/ISLAND/fISLAND.png -------------------------------------------------------------------------------- /scripts/assets/ISLAND/pISLAND.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pass ISLAND", 3 | "symbol": "pISLAND", 4 | "description": "A token that redeems back to ISLAND if the proposal passes", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/ISLAND/pISLAND.png" 6 | } -------------------------------------------------------------------------------- /scripts/assets/ISLAND/pISLAND.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/ISLAND/pISLAND.png -------------------------------------------------------------------------------- /scripts/assets/META/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "passImage": "https://arweave.net/iuqi7PRRESdDxj1oRyk2WzR90_zdFcmZsuWicv3XGfs", 3 | "failImage": "https://arweave.net/tGxvOjMZw7B0qHsdCcIMO57oH5g5OaItOZdXo3BXKz8" 4 | } -------------------------------------------------------------------------------- /scripts/assets/META/fMETA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/META/fMETA.png -------------------------------------------------------------------------------- /scripts/assets/META/pMETA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/META/pMETA.png -------------------------------------------------------------------------------- /scripts/assets/MNDE/fMNDE.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fail MNDE", 3 | "symbol": "fMNDE", 4 | "description": "A token that redeems back to MNDE if the proposal fails", 5 | "image": "https://imagedelivery.net/HYEnlujCFMCgj6yA728xIw/f7fd499e-367d-44cc-4252-31bd59215b00/public" 6 | } -------------------------------------------------------------------------------- /scripts/assets/MNDE/pMNDE.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pass MNDE", 3 | "symbol": "pMNDE", 4 | "description": "A token that redeems back to MNDE if the proposal passes", 5 | "image": "https://imagedelivery.net/HYEnlujCFMCgj6yA728xIw/cf98dac2-3ba6-4675-7130-3ea52e73bc00/public" 6 | } -------------------------------------------------------------------------------- /scripts/assets/MTN/MTN.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mtnCapital", 3 | "symbol": "MTN", 4 | "description": "First futarchy-governed investment fund", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/MTN/MTN.png" 6 | } -------------------------------------------------------------------------------- /scripts/assets/MTN/MTN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/MTN/MTN.png -------------------------------------------------------------------------------- /scripts/assets/ORE/fORE.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fail ORE", 3 | "symbol": "fORE", 4 | "description": "A token that redeems back to ORE if the proposal fails", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/ORE/fORE.png" 6 | } -------------------------------------------------------------------------------- /scripts/assets/ORE/fORE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/ORE/fORE.png -------------------------------------------------------------------------------- /scripts/assets/ORE/pORE.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pass ORE", 3 | "symbol": "pORE", 4 | "description": "A token that redeems back to ORE if the proposal passes", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/ORE/pORE.png" 6 | } 7 | -------------------------------------------------------------------------------- /scripts/assets/ORE/pORE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/ORE/pORE.png -------------------------------------------------------------------------------- /scripts/assets/SAVE/fSAVE.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fail SAVE", 3 | "symbol": "fSAVE", 4 | "description": "A token that redeems back to SAVE if the proposal fails", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/SAVE/fSAVE.png" 6 | } -------------------------------------------------------------------------------- /scripts/assets/SAVE/fSAVE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/SAVE/fSAVE.png -------------------------------------------------------------------------------- /scripts/assets/SAVE/pSAVE.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pass SAVE", 3 | "symbol": "pSAVE", 4 | "description": "A token that redeems back to SAVE if the proposal passes", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/SAVE/pSAVE.png" 6 | } -------------------------------------------------------------------------------- /scripts/assets/SAVE/pSAVE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/SAVE/pSAVE.png -------------------------------------------------------------------------------- /scripts/assets/Scalar/DOWN.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Down", 3 | "symbol": "pDOWN", 4 | "description": "Redeemable relative to the metric.", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/Scalar/DOWN.png" 6 | } -------------------------------------------------------------------------------- /scripts/assets/Scalar/DOWN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/Scalar/DOWN.png -------------------------------------------------------------------------------- /scripts/assets/Scalar/UP.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Up", 3 | "symbol": "pUP", 4 | "description": "Redeemable relative to the metric.", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/Scalar/UP.png" 6 | } -------------------------------------------------------------------------------- /scripts/assets/Scalar/UP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/Scalar/UP.png -------------------------------------------------------------------------------- /scripts/assets/USDC/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "passImage": "https://arweave.net/e4IO7F59F_RKCiuB--_ABPot7Qh1yFsGkWzVhcXuKDU", 3 | "failImage": "https://arweave.net/DpvxeAyVbaoivhIVCLjdf566k2SwVn0YVBL0sTOezWk" 4 | } -------------------------------------------------------------------------------- /scripts/assets/USDC/fUSDC.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fail USDC", 3 | "symbol": "fUSDC", 4 | "description": "A token that redeems back to USDC if the proposal fails", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/USDC/fUSDC.png" 6 | } -------------------------------------------------------------------------------- /scripts/assets/USDC/fUSDC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/USDC/fUSDC.png -------------------------------------------------------------------------------- /scripts/assets/USDC/pUSDC.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pass USDC", 3 | "symbol": "pUSDC", 4 | "description": "A token that redeems back to USDC if the proposal passes", 5 | "image": "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/USDC/pUSDC.png" 6 | } -------------------------------------------------------------------------------- /scripts/assets/USDC/pUSDC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/scripts/assets/USDC/pUSDC.png -------------------------------------------------------------------------------- /scripts/assets/sCLOUD/fsCLOUD.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fail sCLOUD", 3 | "symbol": "fsCLOUD", 4 | "description": "A token that redeems back to sCLOUD if the proposal fails", 5 | "image": "https://arweave.net/0bz91Sgw53LBptksqIuETJ5RBhAogJBLV7lh9qiSZJM" 6 | } -------------------------------------------------------------------------------- /scripts/assets/sCLOUD/psCLOUD.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pass sCLOUD", 3 | "symbol": "psCLOUD", 4 | "description": "A token that redeems back to sCLOUD if the proposal passes", 5 | "image": "https://arweave.net/eTi_qoA4bB18OFU9OP5gc3Suuef8FI_DZt-bd2Y13FA" 6 | } -------------------------------------------------------------------------------- /scripts/consts.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | // DAO Tokens 4 | // DeansList 5 | export const DEAN_DEVNET = new PublicKey( 6 | "DEANPaCEAfW2SCJCdEtGvV1nT9bAShWrajnieSzUcWzh" 7 | ); 8 | export const DEAN = new PublicKey( 9 | "Ds52CDgqdWbTWsua1hgT3AuSSy4FNx2Ezge1br3jQ14a" 10 | ); 11 | // Future DAO 12 | export const FUTURE_DEVNET = new PublicKey( 13 | "DUMm13RrZZoJAaqr1Tz7hv44xUcrYWXADw7SEBGAvbcK" 14 | ); 15 | export const FUTURE = new PublicKey( 16 | "FUTURETnhzFApq2TiZiNbWLQDXMx4nWNpFtmvTf11pMy" 17 | ); 18 | // MetaDAO for Mainnet and Devnet 19 | export const META = new PublicKey( 20 | "METADDFL6wWMWEoKTFJwcThTbUmtarRJZjRpzUvkxhr" 21 | ); 22 | // Base Tokens 23 | // Meta USDC (created for use in our contracts) 24 | export const DEVNET_MUSDC = new PublicKey( 25 | "ABizbp4pXowKQJ1pWgPeWPYDfSKwg34A7Xy1fxTu7No9" 26 | ); 27 | // Circle Devnet USDC (from faucet) 28 | export const DEVNET_USDC = new PublicKey( 29 | "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU" 30 | ); 31 | // Circle USDC Mainnet 32 | export const USDC = new PublicKey( 33 | "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" 34 | ); 35 | 36 | export const DEVNET_DARK = new PublicKey( 37 | "FfCGxfj1NQdPZpukoEgdX427NEjNdCuh3gpFi6oEf6Ka" 38 | ); 39 | 40 | export const DRIFT = new PublicKey( 41 | "DriFtupJYLTosbwoN8koMbEYSx54aFAVLddWsbksjwg7" 42 | ); 43 | 44 | export const DEVNET_DRIFT = new PublicKey( 45 | "Hy9aVik24Uy92FuJd8d1hTH6LRCLmquipeTLBGnXqWr1" 46 | ); 47 | 48 | export const DEVNET_ORE = new PublicKey( 49 | "Et9wvs9gEBQtFY2RkvLw2XVLaBqjotHPvQBDvqkKxDR8" 50 | ); 51 | -------------------------------------------------------------------------------- /scripts/createV04DAO.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { Keypair, PublicKey } from "@solana/web3.js"; 3 | import { AutocratClient } from "@metadaoproject/futarchy/v0.4"; 4 | import * as token from "@solana/spl-token"; 5 | 6 | async function main() { 7 | const provider = anchor.AnchorProvider.env(); 8 | const autocratProgram = AutocratClient.createClient({ provider }); 9 | const payer = provider.wallet["payer"]; 10 | 11 | // Create META token mint 12 | const metaMint = await token.createMint( 13 | provider.connection, 14 | payer, 15 | payer.publicKey, // mint authority 16 | payer.publicKey, // freeze authority 17 | 9 // 9 decimals like in tests 18 | ); 19 | console.log("Created META mint:", metaMint.toString()); 20 | 21 | // Create USDC mint 22 | const usdcMint = await token.createMint( 23 | provider.connection, 24 | payer, 25 | payer.publicKey, 26 | payer.publicKey, 27 | 6 // 6 decimals for USDC 28 | ); 29 | console.log("Created USDC mint:", usdcMint.toString()); 30 | 31 | // Create token accounts for the payer 32 | const metaAccount = await token.createAssociatedTokenAccount( 33 | provider.connection, 34 | payer, 35 | metaMint, 36 | payer.publicKey 37 | ); 38 | console.log("Created META account:", metaAccount.toString()); 39 | 40 | const usdcAccount = await token.createAssociatedTokenAccount( 41 | provider.connection, 42 | payer, 43 | usdcMint, 44 | payer.publicKey 45 | ); 46 | console.log("Created USDC account:", usdcAccount.toString()); 47 | 48 | // Mint initial tokens to the payer 49 | await token.mintTo( 50 | provider.connection, 51 | payer, 52 | metaMint, 53 | metaAccount, 54 | payer, 55 | 1000n * 1_000_000_000n // 1000 META with 9 decimals 56 | ); 57 | console.log("Minted 1000 META to payer"); 58 | 59 | await token.mintTo( 60 | provider.connection, 61 | payer, 62 | usdcMint, 63 | usdcAccount, 64 | payer, 65 | 200_000n * 1_000_000n // 200,000 USDC with 6 decimals (like in tests) 66 | ); 67 | console.log("Minted 200,000 USDC to payer"); 68 | 69 | // Initialize the DAO 70 | const tokenPriceUiAmount = 1.0; // Initial token price in USDC 71 | const minBaseFutarchicLiquidity = 5; // Lower minimum requirement (5 META) 72 | const minQuoteFutarchicLiquidity = 5; // Lower minimum requirement (5 USDC) 73 | const daoKeypair = Keypair.generate(); 74 | 75 | const dao = await autocratProgram.initializeDao( 76 | metaMint, 77 | tokenPriceUiAmount, 78 | minBaseFutarchicLiquidity, 79 | minQuoteFutarchicLiquidity, 80 | usdcMint, 81 | daoKeypair 82 | ); 83 | 84 | console.log("DAO created with address:", dao.toString()); 85 | console.log("DAO keypair public key:", daoKeypair.publicKey.toString()); 86 | } 87 | 88 | main().catch((error) => { 89 | console.error(error); 90 | process.exit(1); 91 | }); 92 | -------------------------------------------------------------------------------- /scripts/createV04Proposal.ts: -------------------------------------------------------------------------------- 1 | import * as token from "@solana/spl-token"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import * as anchor from "@coral-xyz/anchor"; 4 | import { 5 | AutocratClient, 6 | AUTOCRAT_PROGRAM_ID, 7 | AutocratIDL, 8 | PriceMath, 9 | } from "@metadaoproject/futarchy/v0.4"; 10 | 11 | async function main() { 12 | // Initialize clients 13 | const provider = anchor.AnchorProvider.env(); 14 | const payer = provider.wallet["payer"]; 15 | const autocratProgram = AutocratClient.createClient({ provider }); 16 | 17 | // Use the existing DAO address 18 | const dao = new PublicKey("Hv7b7Kw2Xy7fGZZ8qWiciwfivay2hARmY7qC9HH4qWuS"); 19 | 20 | // Get the DAO's data 21 | const storedDao = await autocratProgram.getDao(dao); 22 | console.log("DAO Token Mint:", storedDao.tokenMint.toString()); 23 | console.log("DAO USDC Mint:", storedDao.usdcMint.toString()); 24 | 25 | // Create or get token accounts for the payer 26 | const metaAccount = await token.getOrCreateAssociatedTokenAccount( 27 | provider.connection, 28 | payer, 29 | storedDao.tokenMint, 30 | payer.publicKey 31 | ); 32 | console.log("META account:", metaAccount.address.toString()); 33 | 34 | const usdcAccount = await token.getOrCreateAssociatedTokenAccount( 35 | provider.connection, 36 | payer, 37 | storedDao.usdcMint, 38 | payer.publicKey 39 | ); 40 | console.log("USDC account:", usdcAccount.address.toString()); 41 | 42 | // Check balances 43 | const metaBalance = metaAccount.amount; 44 | const usdcBalance = usdcAccount.amount; 45 | console.log("Current META balance:", metaBalance.toString()); 46 | console.log("Current USDC balance:", usdcBalance.toString()); 47 | 48 | // Ensure we have enough tokens for the proposal 49 | const requiredMeta = PriceMath.getChainAmount(10, 9); // 10 META for more liquidity 50 | const requiredUsdc = PriceMath.getChainAmount(10000, 6); // 10000 USDC for more liquidity 51 | 52 | if (metaBalance < BigInt(requiredMeta.toString()) || usdcBalance < BigInt(requiredUsdc.toString())) { 53 | console.log("Insufficient balance for proposal creation"); 54 | console.log("Required META:", requiredMeta.toString()); 55 | console.log("Required USDC:", requiredUsdc.toString()); 56 | return; 57 | } 58 | 59 | const autocrat = new anchor.Program( 60 | AutocratIDL, 61 | AUTOCRAT_PROGRAM_ID, 62 | provider 63 | ); 64 | 65 | const accounts = [ 66 | { 67 | pubkey: dao, 68 | isSigner: true, 69 | isWritable: true, 70 | }, 71 | ]; 72 | 73 | const data = autocrat.coder.instruction.encode("update_dao", { 74 | daoParams: { 75 | passThresholdBps: 500, 76 | baseBurnLamports: null, 77 | burnDecayPerSlotLamports: null, 78 | slotsPerProposal: null, 79 | marketTakerFee: null, 80 | }, 81 | }); 82 | 83 | const ix = { 84 | programId: autocratProgram.getProgramId(), 85 | accounts, 86 | data, 87 | }; 88 | 89 | // Initialize the proposal 90 | const proposal = await autocratProgram.initializeProposal( 91 | dao, 92 | "https://example.com/proposal", // proposal description URL 93 | ix, 94 | PriceMath.getChainAmount(10, 9), // 10 META for more liquidity 95 | PriceMath.getChainAmount(10000, 6) // 10000 USDC for more liquidity 96 | ); 97 | 98 | console.log("Proposal created with address:", proposal.toString()); 99 | } 100 | 101 | main().catch((error) => { 102 | console.error(error); 103 | process.exit(1); 104 | }); 105 | -------------------------------------------------------------------------------- /scripts/initializeLaunch.ts: -------------------------------------------------------------------------------- 1 | import * as token from "@solana/spl-token"; 2 | import * as anchor from "@coral-xyz/anchor"; 3 | import { 4 | AutocratClient, 5 | ConditionalVaultClient, 6 | getDaoTreasuryAddr, 7 | getLaunchAddr, 8 | getLaunchSignerAddr, 9 | LaunchpadClient, 10 | } from "@metadaoproject/futarchy/v0.4"; 11 | import { BN } from "bn.js"; 12 | import { DEVNET_MUSDC, USDC } from "./consts.js"; 13 | import { createMetadataAccountV3 } from "@metaplex-foundation/mpl-token-metadata"; 14 | import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"; 15 | import { mplTokenMetadata } from "@metaplex-foundation/mpl-token-metadata"; 16 | import { walletAdapterIdentity } from "@metaplex-foundation/umi-signer-wallet-adapters"; 17 | import { fromWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters"; 18 | import { ComputeBudgetProgram, Keypair, PublicKey, SystemProgram, Transaction } from "@solana/web3.js"; 19 | import * as fs from "fs"; 20 | 21 | // Use the RPC endpoint of your choice. 22 | 23 | const provider = anchor.AnchorProvider.env(); 24 | const payer = provider.wallet["payer"]; 25 | 26 | const umi = createUmi(provider.connection.rpcEndpoint).use(mplTokenMetadata()); 27 | umi.use(walletAdapterIdentity(provider.wallet)); 28 | 29 | const autocrat: AutocratClient = AutocratClient.createClient({ provider }); 30 | const vaultProgram: ConditionalVaultClient = 31 | ConditionalVaultClient.createClient({ provider }); 32 | const launchpad: LaunchpadClient = LaunchpadClient.createClient({ provider }); 33 | 34 | const ONE_MINUTE_IN_SECONDS = 60; 35 | const ONE_HOUR_IN_SECONDS = ONE_MINUTE_IN_SECONDS * 60; 36 | const ONE_DAY_IN_SECONDS = ONE_HOUR_IN_SECONDS * 24; 37 | const SEVEN_DAYS_IN_SECONDS = ONE_DAY_IN_SECONDS * 7; 38 | const KOLLAN_PUBKEY = new PublicKey("CRANkLNAUCPFapK5zpc1BvXA1WjfZpo6wEmssyECxuxf"); 39 | 40 | async function main() { 41 | const seed = "186fMCnZjcoD8i9K"; 42 | const MTN = await PublicKey.createWithSeed(payer.publicKey, seed, token.TOKEN_PROGRAM_ID); 43 | 44 | const [launch] = getLaunchAddr(launchpad.getProgramId(), MTN); 45 | const [launchSigner] = getLaunchSignerAddr( 46 | launchpad.getProgramId(), 47 | launch 48 | ); 49 | 50 | console.log(launch.toBase58()); 51 | 52 | const lamports = await provider.connection.getMinimumBalanceForRentExemption(token.MINT_SIZE); 53 | 54 | const tx = new Transaction().add( 55 | SystemProgram.createAccountWithSeed({ 56 | fromPubkey: payer.publicKey, 57 | newAccountPubkey: MTN, 58 | basePubkey: payer.publicKey, 59 | seed, 60 | lamports: lamports, 61 | space: token.MINT_SIZE, 62 | programId: token.TOKEN_PROGRAM_ID, 63 | }), 64 | token.createInitializeMint2Instruction(MTN, 6, launchSigner, null) 65 | ); 66 | tx.recentBlockhash = (await provider.connection.getLatestBlockhash()).blockhash; 67 | tx.feePayer = payer.publicKey; 68 | tx.sign(payer); 69 | 70 | await provider.connection.sendRawTransaction(tx.serialize()); 71 | 72 | await launchpad 73 | .initializeLaunchIx( 74 | "mtnCapital", 75 | "MTN", 76 | "https://raw.githubusercontent.com/metaDAOproject/futarchy/refs/heads/develop/scripts/assets/MTN/MTN.json", 77 | new BN(0), 78 | SEVEN_DAYS_IN_SECONDS, 79 | MTN, 80 | KOLLAN_PUBKEY, 81 | false, 82 | payer.publicKey, 83 | ) 84 | .preInstructions([ 85 | ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }), 86 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1_000 }), 87 | ]) 88 | .rpc(); 89 | } 90 | 91 | main(); 92 | -------------------------------------------------------------------------------- /scripts/launchComplete.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComputeBudgetProgram, 3 | Keypair, 4 | Transaction, 5 | PublicKey, 6 | } from "@solana/web3.js"; 7 | import * as anchor from "@coral-xyz/anchor"; 8 | import { LaunchpadClient } from "@metadaoproject/futarchy/v0.4"; 9 | import { homedir } from "os"; 10 | import { join } from "path"; 11 | import { input } from "@inquirer/prompts"; 12 | 13 | import dotenv from "dotenv"; 14 | 15 | dotenv.config(); 16 | 17 | const rpcUrl = await input({ 18 | message: "Enter your RPC URL:", 19 | default: process.env.RPC_URL, 20 | }); 21 | 22 | const walletPath = await input({ 23 | message: "Enter the path (relative to home directory) to your wallet file", 24 | default: join(homedir(), process.env.WALLET_PATH), 25 | }); 26 | process.env.ANCHOR_WALLET = walletPath; 27 | const provider = anchor.AnchorProvider.local(rpcUrl, { 28 | commitment: "confirmed", 29 | }); 30 | const payer = provider.wallet["payer"]; 31 | 32 | const launchAddr = new PublicKey( 33 | await input({ 34 | message: "Enter the launch address", 35 | default: process.env.LAUNCH_ADDRESS, 36 | }) 37 | ); 38 | 39 | const launchpad: LaunchpadClient = LaunchpadClient.createClient({ provider }); 40 | 41 | async function main() { 42 | const launch = await launchpad.getLaunch(launchAddr); 43 | 44 | const tx = await launchpad 45 | .completeLaunchIx(launchAddr, launch.tokenMint, true) 46 | .transaction(); 47 | 48 | await sendAndConfirmTransaction(tx, "Complete launch"); 49 | 50 | console.log("Launch completed!"); 51 | } 52 | 53 | // Make sure the promise rejection is handled 54 | main().catch((error) => { 55 | console.error("Fatal error:", error); 56 | process.exit(1); 57 | }); 58 | 59 | async function sendAndConfirmTransaction( 60 | tx: Transaction, 61 | label: string, 62 | signers: Keypair[] = [] 63 | ) { 64 | tx.feePayer = payer.publicKey; 65 | tx.recentBlockhash = ( 66 | await provider.connection.getLatestBlockhash() 67 | ).blockhash; 68 | tx.partialSign(payer, ...signers); 69 | const txHash = await provider.connection.sendRawTransaction(tx.serialize()); 70 | console.log(`${label} transaction sent:`, txHash); 71 | 72 | await provider.connection.confirmTransaction(txHash, "confirmed"); 73 | const txStatus = await provider.connection.getTransaction(txHash, { 74 | maxSupportedTransactionVersion: 0, 75 | commitment: "confirmed", 76 | }); 77 | if (txStatus?.meta?.err) { 78 | throw new Error( 79 | `Transaction failed: ${txHash}\nError: ${JSON.stringify( 80 | txStatus?.meta?.err 81 | )}\n\n${txStatus?.meta?.logMessages?.join("\n")}` 82 | ); 83 | } 84 | console.log(`${label} transaction confirmed`); 85 | return txHash; 86 | } 87 | -------------------------------------------------------------------------------- /scripts/launchStart.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, PublicKey, Transaction } from "@solana/web3.js"; 2 | import * as anchor from "@coral-xyz/anchor"; 3 | import { getLaunchAddr, LaunchpadClient } from "@metadaoproject/futarchy/v0.4"; 4 | import { homedir } from "os"; 5 | import { join } from "path"; 6 | import fs from "fs"; 7 | import { input } from "@inquirer/prompts"; 8 | 9 | import dotenv from "dotenv"; 10 | 11 | dotenv.config(); 12 | 13 | const rpcUrl = await input({ 14 | message: "Enter your RPC URL:", 15 | default: process.env.RPC_URL, 16 | }); 17 | 18 | const walletPath = await input({ 19 | message: "Enter the path (relative to home directory) to your wallet file", 20 | default: join(homedir(), process.env.WALLET_PATH), 21 | }); 22 | process.env.ANCHOR_WALLET = walletPath; 23 | const provider = anchor.AnchorProvider.local(rpcUrl, { 24 | commitment: "confirmed", 25 | }); 26 | const payer = provider.wallet["payer"]; 27 | 28 | const launchAuthorityKeypairPath = await input({ 29 | message: 30 | "Enter the path (relative to home directory) to your launch authority keypair file", 31 | default: join(homedir(), process.env.LAUNCH_AUTHORITY_KEYPAIR_PATH), 32 | }); 33 | 34 | const launchAddr = new PublicKey( 35 | await input({ 36 | message: "Enter the launch address", 37 | default: process.env.LAUNCH_ADDRESS, 38 | }) 39 | ); 40 | 41 | const launchpad: LaunchpadClient = LaunchpadClient.createClient({ provider }); 42 | 43 | async function main() { 44 | const launchAuthorityFile = fs.readFileSync(launchAuthorityKeypairPath); 45 | const launchAuthorityKeypair = Keypair.fromSecretKey( 46 | Buffer.from(JSON.parse(launchAuthorityFile.toString()) as Uint8Array) 47 | ); 48 | 49 | if (!launchAuthorityKeypair) { 50 | throw new Error("Could not read launch authority keypair."); 51 | } 52 | 53 | console.log( 54 | "Launch authority public key:", 55 | launchAuthorityKeypair.publicKey.toBase58() 56 | ); 57 | 58 | console.log("Starting launch..."); 59 | 60 | const tx = await launchpad 61 | .startLaunchIx(launchAddr, launchAuthorityKeypair.publicKey) 62 | .transaction(); 63 | 64 | await sendAndConfirmTransaction(tx, "Start launch", [launchAuthorityKeypair]); 65 | 66 | console.log("Launch started!"); 67 | console.log("Launch address:", launchAddr.toBase58()); 68 | } 69 | 70 | // Make sure the promise rejection is handled 71 | main().catch((error) => { 72 | console.error("Fatal error:", error); 73 | process.exit(1); 74 | }); 75 | 76 | async function sendAndConfirmTransaction( 77 | tx: Transaction, 78 | label: string, 79 | signers: Keypair[] = [] 80 | ) { 81 | tx.feePayer = payer.publicKey; 82 | tx.recentBlockhash = ( 83 | await provider.connection.getLatestBlockhash() 84 | ).blockhash; 85 | tx.partialSign(payer, ...signers); 86 | const txHash = await provider.connection.sendRawTransaction(tx.serialize()); 87 | console.log(`${label} transaction sent:`, txHash); 88 | 89 | await provider.connection.confirmTransaction(txHash, "confirmed"); 90 | const txStatus = await provider.connection.getTransaction(txHash, { 91 | maxSupportedTransactionVersion: 0, 92 | commitment: "confirmed", 93 | }); 94 | if (txStatus?.meta?.err) { 95 | throw new Error( 96 | `Transaction failed: ${txHash}\nError: ${JSON.stringify( 97 | txStatus?.meta?.err 98 | )}\n\n${txStatus?.meta?.logMessages?.join("\n")}` 99 | ); 100 | } 101 | console.log(`${label} transaction confirmed`); 102 | return txHash; 103 | } 104 | -------------------------------------------------------------------------------- /sdk/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | tsconfig.tsbuildinfo 3 | -------------------------------------------------------------------------------- /sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metadaoproject/futarchy", 3 | "version": "0.4.0-alpha.73", 4 | "type": "module", 5 | "main": "dist/index.js", 6 | "module": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "license": "BSL-1.0", 9 | "files": [ 10 | "/dist" 11 | ], 12 | "exports": { 13 | ".": "./dist/index.js", 14 | "./v0.3": "./dist/v0.3/index.js", 15 | "./v0.4": "./dist/v0.4/index.js" 16 | }, 17 | "scripts": { 18 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 19 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check", 20 | "build": "rm -rf ./dist && cp ../target/types/* ./src/v0.4/types && yarn lint:fix && yarn tsc" 21 | }, 22 | "dependencies": { 23 | "@coral-xyz/anchor": "^0.29.0", 24 | "@metaplex-foundation/umi": "^0.9.2", 25 | "@metaplex-foundation/umi-bundle-defaults": "^0.9.2", 26 | "@metaplex-foundation/umi-uploader-bundlr": "^0.9.2", 27 | "@noble/hashes": "^1.4.0", 28 | "@solana/spl-token": "^0.3.7", 29 | "@solana/web3.js": "^1.74.0", 30 | "bn.js": "^5.2.1", 31 | "decimal.js": "^10.4.3", 32 | "esbuild": "^0.17.15" 33 | }, 34 | "devDependencies": { 35 | "@types/bn.js": "^5.1.0", 36 | "@types/chai": "^4.3.0", 37 | "@types/mocha": "^9.0.0", 38 | "chai": "^4.3.4", 39 | "mocha": "^9.0.3", 40 | "prettier": "^2.6.2", 41 | "solana-bankrun": "^0.2.0", 42 | "spl-token-bankrun": "0.2.3", 43 | "ts-mocha": "^10.0.0", 44 | "typescript": "^4.9.5" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sdk/src/index.ts: -------------------------------------------------------------------------------- 1 | export { sha256 } from "@noble/hashes/sha256"; 2 | -------------------------------------------------------------------------------- /sdk/src/v0.3/constants.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | export const AUTOCRAT_PROGRAM_ID = new PublicKey( 4 | "autoQP9RmUNkzzKRXsMkWicDVZ3h29vvyMDcAYjCxxg" 5 | ); 6 | export const AMM_PROGRAM_ID = new PublicKey( 7 | "AMM5G2nxuKUwCLRYTW7qqEwuoqCtNSjtbipwEmm2g8bH" 8 | ); 9 | export const CONDITIONAL_VAULT_PROGRAM_ID = new PublicKey( 10 | "VAU1T7S5UuEHmMvXtXMVmpEoQtZ2ya7eRb7gcN47wDp" 11 | ); 12 | 13 | export const MPL_TOKEN_METADATA_PROGRAM_ID = new PublicKey( 14 | "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" 15 | ); 16 | 17 | export const META_MINT = new PublicKey( 18 | "3gN1WVEJwSHNWjo7hr87DgZp6zkf8kWgAJD29DmfE2Gr" 19 | ); 20 | export const MAINNET_USDC = new PublicKey( 21 | "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" 22 | ); 23 | 24 | export const USDC_DECIMALS = 6; 25 | 26 | export const AUTOCRAT_LUTS: PublicKey[] = []; 27 | -------------------------------------------------------------------------------- /sdk/src/v0.3/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types/index.js"; 2 | export * from "./utils/index.js"; 3 | export * from "./constants.js"; 4 | export * from "./AmmClient.js"; 5 | export * from "./AutocratClient.js"; 6 | export * from "./ConditionalVaultClient.js"; 7 | export * from "./FutarchyClient.js"; 8 | -------------------------------------------------------------------------------- /sdk/src/v0.3/types/index.ts: -------------------------------------------------------------------------------- 1 | import { Autocrat } from "./autocrat.js"; 2 | export { Autocrat, IDL as AutocratIDL } from "./autocrat.js"; 3 | 4 | import { Amm } from "./amm.js"; 5 | export { Amm, IDL as AmmIDL } from "./amm.js"; 6 | 7 | import { ConditionalVault } from "./conditional_vault.js"; 8 | export { 9 | ConditionalVault, 10 | IDL as ConditionalVaultIDL, 11 | } from "./conditional_vault.js"; 12 | 13 | export { LowercaseKeys } from "./utils.js"; 14 | 15 | import type { IdlAccounts, IdlTypes } from "@coral-xyz/anchor"; 16 | import { PublicKey } from "@solana/web3.js"; 17 | 18 | export type InitializeDaoParams = IdlTypes["InitializeDaoParams"]; 19 | export type UpdateDaoParams = IdlTypes["UpdateDaoParams"]; 20 | export type ProposalInstruction = IdlTypes["ProposalInstruction"]; 21 | 22 | export type Proposal = IdlAccounts["proposal"]; 23 | export type ProposalWrapper = { 24 | account: Proposal; 25 | publicKey: PublicKey; 26 | }; 27 | 28 | export type DaoAccount = IdlAccounts["dao"]; 29 | export type ProposalAccount = IdlAccounts["proposal"]; 30 | 31 | export type AmmAccount = IdlAccounts["amm"]; 32 | 33 | export type ConditionalVaultAccount = 34 | IdlAccounts["conditionalVault"]; 35 | -------------------------------------------------------------------------------- /sdk/src/v0.3/types/utils.ts: -------------------------------------------------------------------------------- 1 | export type LowercaseKeys = { 2 | [K in keyof T as Lowercase]: T[K]; 3 | }; 4 | -------------------------------------------------------------------------------- /sdk/src/v0.3/utils/cu.ts: -------------------------------------------------------------------------------- 1 | export const MaxCUs = { 2 | initializeDao: 20_000, 3 | createIdempotent: 25_000, 4 | initializeConditionalVault: 45_000, 5 | mintConditionalTokens: 35_000, 6 | initializeAmm: 120_000, 7 | addLiquidity: 120_000, 8 | initializeProposal: 60_000, 9 | }; 10 | 11 | export const DEFAULT_CU_PRICE = 1; 12 | -------------------------------------------------------------------------------- /sdk/src/v0.3/utils/filters.ts: -------------------------------------------------------------------------------- 1 | import { GetProgramAccountsFilter, PublicKey } from "@solana/web3.js"; 2 | 3 | export const filterPositionsByUser = ( 4 | userAddr: PublicKey 5 | ): GetProgramAccountsFilter => ({ 6 | memcmp: { 7 | offset: 8, // discriminator 8 | bytes: userAddr.toBase58(), 9 | }, 10 | }); 11 | 12 | export const filterPositionsByAmm = ( 13 | ammAddr: PublicKey 14 | ): GetProgramAccountsFilter => ({ 15 | memcmp: { 16 | offset: 17 | 8 + // discriminator 18 | 32, // user address 19 | bytes: ammAddr.toBase58(), 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /sdk/src/v0.3/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./filters.js"; 2 | export * from "./pda.js"; 3 | export * from "./ammMath.js"; 4 | export * from "./metadata.js"; 5 | export * from "./cu.js"; 6 | export * from "./instruction.js"; 7 | 8 | import { AccountMeta, ComputeBudgetProgram, PublicKey } from "@solana/web3.js"; 9 | 10 | export enum PriorityFeeTier { 11 | NORMAL = 35, 12 | HIGH = 3571, 13 | TURBO = 357142, 14 | } 15 | 16 | export const addComputeUnits = (num_units: number = 1_400_000) => 17 | ComputeBudgetProgram.setComputeUnitLimit({ 18 | units: num_units, 19 | }); 20 | 21 | export const addPriorityFee = (pf: number) => 22 | ComputeBudgetProgram.setComputeUnitPrice({ 23 | microLamports: pf, 24 | }); 25 | 26 | export const pubkeyToAccountInfo = ( 27 | pubkey: PublicKey, 28 | isWritable: boolean, 29 | isSigner = false 30 | ): AccountMeta => { 31 | return { 32 | pubkey: pubkey, 33 | isSigner: isSigner, 34 | isWritable: isWritable, 35 | }; 36 | }; 37 | 38 | export async function sleep(ms: number) { 39 | return new Promise((resolve) => setTimeout(resolve, ms)); 40 | } 41 | -------------------------------------------------------------------------------- /sdk/src/v0.3/utils/instruction.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { TransactionInstruction } from "@solana/web3.js"; 3 | 4 | export class InstructionUtils { 5 | public static async getInstructions( 6 | ...methodBuilders: any[] 7 | ): Promise { 8 | let instructions: TransactionInstruction[] = []; 9 | 10 | for (const methodBuilder of methodBuilders) { 11 | instructions.push(...(await methodBuilder.transaction()).instructions); 12 | } 13 | 14 | return instructions; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sdk/src/v0.3/utils/metadata.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"; 3 | import { Connection } from "@solana/web3.js"; 4 | import { bundlrUploader } from "@metaplex-foundation/umi-uploader-bundlr"; 5 | import { UmiPlugin } from "@metaplex-foundation/umi"; 6 | 7 | export const assetImageMap: Record = { 8 | fMETA: "https://arweave.net/tGxvOjMZw7B0qHsdCcIMO57oH5g5OaItOZdXo3BXKz8", 9 | fUSDC: "https://arweave.net/DpvxeAyVbaoivhIVCLjdf566k2SwVn0YVBL0sTOezWk", 10 | pMETA: "https://arweave.net/iuqi7PRRESdDxj1oRyk2WzR90_zdFcmZsuWicv3XGfs", 11 | pUSDC: "https://arweave.net/e4IO7F59F_RKCiuB--_ABPot7Qh1yFsGkWzVhcXuKDU", 12 | }; 13 | 14 | // Upload some JSON, returning its URL 15 | export const uploadConditionalTokenMetadataJson = async ( 16 | connection: Connection, 17 | identityPlugin: UmiPlugin, 18 | proposalNumber: number, 19 | symbol: string 20 | // proposal: BN, 21 | // conditionalToken: string, 22 | // image: string 23 | ): Promise => { 24 | // use bundlr, targeting arweave 25 | const umi = createUmi(connection); 26 | umi.use(bundlrUploader()); 27 | umi.use(identityPlugin); 28 | 29 | return umi.uploader.uploadJson({ 30 | name: `Proposal ${proposalNumber}: ${symbol}`, 31 | image: assetImageMap[symbol], 32 | symbol, 33 | description: "A conditional token for use in futarchy.", 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /sdk/src/v0.3/utils/pda.ts: -------------------------------------------------------------------------------- 1 | import { AccountMeta, PublicKey } from "@solana/web3.js"; 2 | import { utils } from "@coral-xyz/anchor"; 3 | import { 4 | ASSOCIATED_TOKEN_PROGRAM_ID, 5 | TOKEN_PROGRAM_ID, 6 | } from "@solana/spl-token"; 7 | import BN from "bn.js"; 8 | import { 9 | fromWeb3JsPublicKey, 10 | toWeb3JsPublicKey, 11 | } from "@metaplex-foundation/umi-web3js-adapters"; 12 | import { MPL_TOKEN_METADATA_PROGRAM_ID } from "../constants.js"; 13 | 14 | export const getVaultAddr = ( 15 | programId: PublicKey, 16 | settlementAuthority: PublicKey, 17 | underlyingTokenMint: PublicKey 18 | ) => { 19 | return PublicKey.findProgramAddressSync( 20 | [ 21 | utils.bytes.utf8.encode("conditional_vault"), 22 | settlementAuthority.toBuffer(), 23 | underlyingTokenMint.toBuffer(), 24 | ], 25 | programId 26 | ); 27 | }; 28 | 29 | export const getVaultFinalizeMintAddr = ( 30 | programId: PublicKey, 31 | vault: PublicKey 32 | ) => { 33 | return getVaultMintAddr(programId, vault, "conditional_on_finalize_mint"); 34 | }; 35 | 36 | export const getMetadataAddr = (mint: PublicKey) => { 37 | return PublicKey.findProgramAddressSync( 38 | [ 39 | utils.bytes.utf8.encode("metadata"), 40 | MPL_TOKEN_METADATA_PROGRAM_ID.toBuffer(), 41 | mint.toBuffer(), 42 | ], 43 | MPL_TOKEN_METADATA_PROGRAM_ID 44 | ); 45 | }; 46 | 47 | export const getVaultRevertMintAddr = ( 48 | programId: PublicKey, 49 | vault: PublicKey 50 | ) => { 51 | return getVaultMintAddr(programId, vault, "conditional_on_revert_mint"); 52 | }; 53 | 54 | const getVaultMintAddr = ( 55 | programId: PublicKey, 56 | vault: PublicKey, 57 | seed: string 58 | ) => { 59 | return PublicKey.findProgramAddressSync( 60 | [utils.bytes.utf8.encode(seed), vault.toBuffer()], 61 | programId 62 | ); 63 | }; 64 | 65 | export const getDaoTreasuryAddr = ( 66 | programId: PublicKey, 67 | dao: PublicKey 68 | ): [PublicKey, number] => { 69 | return PublicKey.findProgramAddressSync([dao.toBuffer()], programId); 70 | }; 71 | 72 | export const getProposalAddr = ( 73 | programId: PublicKey, 74 | proposer: PublicKey, 75 | nonce: BN 76 | ): [PublicKey, number] => { 77 | return PublicKey.findProgramAddressSync( 78 | [ 79 | utils.bytes.utf8.encode("proposal"), 80 | proposer.toBuffer(), 81 | nonce.toArrayLike(Buffer, "le", 8), 82 | ], 83 | programId 84 | ); 85 | }; 86 | 87 | export const getAmmAddr = ( 88 | programId: PublicKey, 89 | baseMint: PublicKey, 90 | quoteMint: PublicKey 91 | ): [PublicKey, number] => { 92 | return PublicKey.findProgramAddressSync( 93 | [ 94 | utils.bytes.utf8.encode("amm__"), 95 | baseMint.toBuffer(), 96 | quoteMint.toBuffer(), 97 | ], 98 | programId 99 | ); 100 | }; 101 | 102 | export const getAmmLpMintAddr = ( 103 | programId: PublicKey, 104 | amm: PublicKey 105 | ): [PublicKey, number] => { 106 | return PublicKey.findProgramAddressSync( 107 | [utils.bytes.utf8.encode("amm_lp_mint"), amm.toBuffer()], 108 | programId 109 | ); 110 | }; 111 | -------------------------------------------------------------------------------- /sdk/src/v0.4/constants.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import * as anchor from "@coral-xyz/anchor"; 3 | import { BN } from "bn.js"; 4 | 5 | export const AUTOCRAT_PROGRAM_ID = new PublicKey( 6 | "autowMzCbM29YXMgVG3T62Hkgo7RcyrvgQQkd54fDQL" 7 | ); 8 | export const AMM_PROGRAM_ID = new PublicKey( 9 | "AMMyu265tkBpRW21iGQxKGLaves3gKm2JcMUqfXNSpqD" 10 | ); 11 | export const CONDITIONAL_VAULT_PROGRAM_ID = new PublicKey( 12 | "VLTX1ishMBbcX3rdBWGssxawAo1Q2X2qxYFYqiGodVg" 13 | ); 14 | 15 | export const MPL_TOKEN_METADATA_PROGRAM_ID = new PublicKey( 16 | "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" 17 | ); 18 | 19 | export const RAYDIUM_CP_SWAP_PROGRAM_ID = new PublicKey( 20 | "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C" 21 | ); 22 | 23 | export const DEVNET_RAYDIUM_CP_SWAP_PROGRAM_ID = new PublicKey( 24 | "CPMDWBwJDtYax9qW7AyRuVC19Cc4L4Vcy4n2BHAbHkCW" 25 | ); 26 | 27 | export const META_MINT = new PublicKey( 28 | "3gN1WVEJwSHNWjo7hr87DgZp6zkf8kWgAJD29DmfE2Gr" 29 | ); 30 | export const MAINNET_USDC = new PublicKey( 31 | "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" 32 | ); 33 | 34 | export const DEVNET_USDC = new PublicKey( 35 | "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU" 36 | ); 37 | 38 | export const USDC_DECIMALS = 6; 39 | 40 | export const AUTOCRAT_LUTS: PublicKey[] = []; 41 | 42 | export const LAUNCHPAD_PROGRAM_ID = new PublicKey( 43 | "AfJJJ5UqxhBKoE3grkKAZZsoXDE9kncbMKvqSHGsCNrE" 44 | ); 45 | 46 | export const RAYDIUM_AUTHORITY = PublicKey.findProgramAddressSync( 47 | [anchor.utils.bytes.utf8.encode("vault_and_lp_mint_auth_seed")], 48 | RAYDIUM_CP_SWAP_PROGRAM_ID 49 | )[0]; 50 | 51 | export const DEVNET_RAYDIUM_AUTHORITY = PublicKey.findProgramAddressSync( 52 | [anchor.utils.bytes.utf8.encode("vault_and_lp_mint_auth_seed")], 53 | DEVNET_RAYDIUM_CP_SWAP_PROGRAM_ID 54 | )[0]; 55 | 56 | export const LOW_FEE_RAYDIUM_CONFIG = new PublicKey( 57 | "D4FPEruKEHrG5TenZ2mpDGEfu1iUvTiqBxvpU8HLBvC2" 58 | ); 59 | 60 | export const DEVNET_LOW_FEE_RAYDIUM_CONFIG = PublicKey.findProgramAddressSync( 61 | [ 62 | anchor.utils.bytes.utf8.encode("amm_config"), 63 | new BN(0).toArrayLike(Buffer, "be", 2), 64 | ], 65 | DEVNET_RAYDIUM_CP_SWAP_PROGRAM_ID 66 | )[0]; 67 | 68 | export const RAYDIUM_CREATE_POOL_FEE_RECEIVE = new PublicKey( 69 | "DNXgeM9EiiaAbaWvwjHj9fQQLAX5ZsfHyvmYUNRAdNC8" 70 | ); 71 | 72 | export const DEVNET_RAYDIUM_CREATE_POOL_FEE_RECEIVE = new PublicKey( 73 | "G11FKBRaAkHAKuLCgLM6K6NUc9rTjPAznRCjZifrTQe2" 74 | ); 75 | -------------------------------------------------------------------------------- /sdk/src/v0.4/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types/index.js"; 2 | export * from "./utils/index.js"; 3 | export * from "./constants.js"; 4 | export * from "./AmmClient.js"; 5 | export * from "./AutocratClient.js"; 6 | export * from "./ConditionalVaultClient.js"; 7 | export * from "./LaunchpadClient.js"; 8 | -------------------------------------------------------------------------------- /sdk/src/v0.4/types/utils.ts: -------------------------------------------------------------------------------- 1 | export type LowercaseKeys = { 2 | [K in keyof T as Lowercase]: T[K]; 3 | }; 4 | -------------------------------------------------------------------------------- /sdk/src/v0.4/utils/cu.ts: -------------------------------------------------------------------------------- 1 | export const MaxCUs = { 2 | initializeDao: 40_000, 3 | createIdempotent: 25_000, 4 | initializeConditionalVault: 45_000, 5 | mintConditionalTokens: 35_000, 6 | initializeAmm: 120_000, 7 | addLiquidity: 120_000, 8 | initializeProposal: 60_000, 9 | }; 10 | 11 | export const DEFAULT_CU_PRICE = 1; 12 | -------------------------------------------------------------------------------- /sdk/src/v0.4/utils/filters.ts: -------------------------------------------------------------------------------- 1 | import { GetProgramAccountsFilter, PublicKey } from "@solana/web3.js"; 2 | 3 | export const filterPositionsByUser = ( 4 | userAddr: PublicKey 5 | ): GetProgramAccountsFilter => ({ 6 | memcmp: { 7 | offset: 8, // discriminator 8 | bytes: userAddr.toBase58(), 9 | }, 10 | }); 11 | 12 | export const filterPositionsByAmm = ( 13 | ammAddr: PublicKey 14 | ): GetProgramAccountsFilter => ({ 15 | memcmp: { 16 | offset: 17 | 8 + // discriminator 18 | 32, // user address 19 | bytes: ammAddr.toBase58(), 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /sdk/src/v0.4/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./filters.js"; 2 | export * from "./pda.js"; 3 | export * from "./priceMath.js"; 4 | export * from "./metadata.js"; 5 | export * from "./cu.js"; 6 | export * from "./instruction.js"; 7 | 8 | import { AccountMeta, ComputeBudgetProgram, PublicKey } from "@solana/web3.js"; 9 | 10 | export enum PriorityFeeTier { 11 | NORMAL = 35, 12 | HIGH = 3571, 13 | TURBO = 357142, 14 | } 15 | 16 | export const addComputeUnits = (num_units: number = 1_400_000) => 17 | ComputeBudgetProgram.setComputeUnitLimit({ 18 | units: num_units, 19 | }); 20 | 21 | export const addPriorityFee = (pf: number) => 22 | ComputeBudgetProgram.setComputeUnitPrice({ 23 | microLamports: pf, 24 | }); 25 | 26 | export const pubkeyToAccountInfo = ( 27 | pubkey: PublicKey, 28 | isWritable: boolean, 29 | isSigner = false 30 | ): AccountMeta => { 31 | return { 32 | pubkey: pubkey, 33 | isSigner: isSigner, 34 | isWritable: isWritable, 35 | }; 36 | }; 37 | 38 | export async function sleep(ms: number) { 39 | return new Promise((resolve) => setTimeout(resolve, ms)); 40 | } 41 | -------------------------------------------------------------------------------- /sdk/src/v0.4/utils/instruction.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { TransactionInstruction } from "@solana/web3.js"; 3 | 4 | export class InstructionUtils { 5 | public static async getInstructions( 6 | ...methodBuilders: any[] 7 | ): Promise { 8 | let instructions: TransactionInstruction[] = []; 9 | 10 | for (const methodBuilder of methodBuilders) { 11 | instructions.push(...(await methodBuilder.transaction()).instructions); 12 | } 13 | 14 | return instructions; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sdk/src/v0.4/utils/metadata.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"; 3 | import { Connection } from "@solana/web3.js"; 4 | import { bundlrUploader } from "@metaplex-foundation/umi-uploader-bundlr"; 5 | import { UmiPlugin } from "@metaplex-foundation/umi"; 6 | 7 | export const assetImageMap: Record = { 8 | fMETA: "https://arweave.net/tGxvOjMZw7B0qHsdCcIMO57oH5g5OaItOZdXo3BXKz8", 9 | fUSDC: "https://arweave.net/DpvxeAyVbaoivhIVCLjdf566k2SwVn0YVBL0sTOezWk", 10 | pMETA: "https://arweave.net/iuqi7PRRESdDxj1oRyk2WzR90_zdFcmZsuWicv3XGfs", 11 | pUSDC: "https://arweave.net/e4IO7F59F_RKCiuB--_ABPot7Qh1yFsGkWzVhcXuKDU", 12 | }; 13 | 14 | // Upload some JSON, returning its URL 15 | export const uploadConditionalTokenMetadataJson = async ( 16 | connection: Connection, 17 | identityPlugin: UmiPlugin, 18 | proposalNumber: number, 19 | symbol: string 20 | // proposal: BN, 21 | // conditionalToken: string, 22 | // image: string 23 | ): Promise => { 24 | // use bundlr, targeting arweave 25 | const umi = createUmi(connection); 26 | umi.use(bundlrUploader()); 27 | umi.use(identityPlugin); 28 | 29 | return umi.uploader.uploadJson({ 30 | name: `Proposal ${proposalNumber}: ${symbol}`, 31 | image: assetImageMap[symbol], 32 | symbol, 33 | description: "A conditional token for use in futarchy.", 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": [ 4 | "mocha", 5 | "chai" 6 | ], 7 | "paths": { 8 | "@metadaoproject/futarchy/v0.3": ["./dist/v0.3/types/index.d.ts"], 9 | "@metadaoproject/futarchy/v0.4": ["./dist/v0.4/types/index.d.ts"] 10 | }, 11 | "typeRoots": [ 12 | "./node_modules/@types" 13 | ], 14 | "lib": [ 15 | "esnext" 16 | ], 17 | "module": "NodeNext", 18 | "target": "esnext", 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "moduleResolution": "nodenext", 22 | "sourceMap": true, 23 | "outDir": "dist", 24 | "strict": true, 25 | "declaration": true 26 | }, 27 | "include": [ 28 | "src/*", 29 | "src/v0.3/*", 30 | "src/v0.4/*" 31 | ], 32 | "exclude": [ 33 | "node_modules", 34 | "target", 35 | "tests", 36 | "migrations" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /tests/amm/integration/ammLifecycle.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AmmClient, 3 | getAmmAddr, 4 | getAmmLpMintAddr, 5 | } from "@metadaoproject/futarchy/v0.4"; 6 | import { Keypair, PublicKey } from "@solana/web3.js"; 7 | import { assert } from "chai"; 8 | import { 9 | createMint, 10 | createAssociatedTokenAccount, 11 | mintTo, 12 | getAccount, 13 | getMint, 14 | } from "spl-token-bankrun"; 15 | import * as anchor from "@coral-xyz/anchor"; 16 | import * as token from "@solana/spl-token"; 17 | import { DAY_IN_SLOTS, expectError, toBN } from "../../utils.js"; 18 | import { BN } from "bn.js"; 19 | 20 | export default async function () { 21 | let ammClient: AmmClient; 22 | let META: PublicKey; 23 | let USDC: PublicKey; 24 | let amm: PublicKey; 25 | 26 | ammClient = this.ammClient; 27 | META = await createMint( 28 | this.banksClient, 29 | this.payer, 30 | this.payer.publicKey, 31 | this.payer.publicKey, 32 | 9 33 | ); 34 | USDC = await createMint( 35 | this.banksClient, 36 | this.payer, 37 | this.payer.publicKey, 38 | this.payer.publicKey, 39 | 6 40 | ); 41 | 42 | await this.createTokenAccount(META, this.payer.publicKey); 43 | await this.createTokenAccount(USDC, this.payer.publicKey); 44 | 45 | await this.mintTo(META, this.payer.publicKey, this.payer, 100 * 10 ** 9); 46 | await this.mintTo(USDC, this.payer.publicKey, this.payer, 10_000 * 10 ** 6); 47 | 48 | let proposal = Keypair.generate().publicKey; 49 | amm = await ammClient.createAmm(proposal, META, USDC, toBN(DAY_IN_SLOTS), 500); 50 | 51 | // 1. Initialize AMM 52 | const initialAmm = await ammClient.getAmm(amm); 53 | assert.isTrue(initialAmm.baseAmount.eqn(0)); 54 | assert.isTrue(initialAmm.quoteAmount.eqn(0)); 55 | 56 | // 2. Add initial liquidity 57 | await ammClient.addLiquidity(amm, 1000, 2); 58 | const ammAfterInitialLiquidity = await ammClient.getAmm(amm); 59 | assert.isTrue(ammAfterInitialLiquidity.baseAmount.gt(new BN(0))); 60 | assert.isTrue(ammAfterInitialLiquidity.quoteAmount.gt(new BN(0))); 61 | 62 | // 3. Perform swaps 63 | await ammClient.swap(amm, { buy: {} }, 100, 0.1); 64 | await ammClient.swap(amm, { sell: {} }, 0.1, 50); 65 | 66 | // 4. Add more liquidity 67 | await ammClient.addLiquidity(amm, 500, 1); 68 | 69 | // 5. Remove some liquidity 70 | let userLpAccount = token.getAssociatedTokenAddressSync( 71 | getAmmLpMintAddr(ammClient.program.programId, amm)[0], 72 | this.payer.publicKey 73 | ); 74 | let userLpBalance = (await getAccount(this.banksClient, userLpAccount)) 75 | .amount; 76 | await ammClient 77 | .removeLiquidityIx( 78 | amm, 79 | META, 80 | USDC, 81 | new BN(Number(userLpBalance) / 2), 82 | new BN(0), 83 | new BN(0) 84 | ) 85 | .rpc(); 86 | 87 | // 6. Perform more swaps 88 | await ammClient.swap(amm, { buy: {} }, 200, 0.2); 89 | await ammClient.swap(amm, { sell: {} }, 0.2, 100); 90 | 91 | // 7. Remove all remaining liquidity 92 | userLpBalance = (await getAccount(this.banksClient, userLpAccount)).amount; 93 | await ammClient 94 | .removeLiquidityIx( 95 | amm, 96 | META, 97 | USDC, 98 | new BN(userLpBalance), 99 | new BN(0), 100 | new BN(0) 101 | ) 102 | .rpc(); 103 | 104 | const finalAmm = await ammClient.getAmm(amm); 105 | assert.isTrue(finalAmm.baseAmount.eqn(0)); 106 | assert.isTrue(finalAmm.quoteAmount.eqn(0)); 107 | } 108 | -------------------------------------------------------------------------------- /tests/amm/main.test.ts: -------------------------------------------------------------------------------- 1 | import initializeAmm from "./unit/initializeAmm.test.js"; 2 | import addLiquidity from "./unit/addLiquidity.test.js"; 3 | import swap from "./unit/swap.test.js"; 4 | import removeLiquidity from "./unit/removeLiquidity.test.js"; 5 | import ammLifecycle from "./integration/ammLifecycle.test.js"; 6 | import crankThatTwap from "./unit/crankThatTwap.test.js"; 7 | 8 | export default function suite() { 9 | describe("#initialize_amm", initializeAmm); 10 | describe("#add_liquidity", addLiquidity); 11 | describe("#swap", swap); 12 | describe("#crank_that_twap", crankThatTwap); 13 | describe("#remove_liquidity", removeLiquidity); 14 | it("AMM lifecycle", ammLifecycle); 15 | } 16 | -------------------------------------------------------------------------------- /tests/amm/unit/addLiquidity.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AmmClient, 3 | getAmmAddr, 4 | getAmmLpMintAddr, 5 | } from "@metadaoproject/futarchy/v0.4"; 6 | import { Keypair, PublicKey } from "@solana/web3.js"; 7 | import { assert } from "chai"; 8 | import { 9 | createMint, 10 | createAssociatedTokenAccount, 11 | mintTo, 12 | getAccount, 13 | } from "spl-token-bankrun"; 14 | import * as anchor from "@coral-xyz/anchor"; 15 | import { DAY_IN_SLOTS, expectError, toBN } from "../../utils.js"; 16 | import * as token from "@solana/spl-token"; 17 | import { BN } from "bn.js"; 18 | 19 | export default function suite() { 20 | let ammClient: AmmClient; 21 | let META: PublicKey; 22 | let USDC: PublicKey; 23 | let amm: PublicKey; 24 | 25 | beforeEach(async function () { 26 | ammClient = this.ammClient; 27 | META = await createMint( 28 | this.banksClient, 29 | this.payer, 30 | this.payer.publicKey, 31 | this.payer.publicKey, 32 | 9 33 | ); 34 | USDC = await createMint( 35 | this.banksClient, 36 | this.payer, 37 | this.payer.publicKey, 38 | this.payer.publicKey, 39 | 6 40 | ); 41 | 42 | let proposal = Keypair.generate().publicKey; 43 | amm = await ammClient.createAmm(proposal, META, USDC, toBN(DAY_IN_SLOTS), 500); 44 | 45 | await this.createTokenAccount(META, this.payer.publicKey); 46 | await this.createTokenAccount(USDC, this.payer.publicKey); 47 | 48 | await this.mintTo(META, this.payer.publicKey, this.payer, 100 * 10 ** 9); 49 | await this.mintTo(USDC, this.payer.publicKey, this.payer, 10_000 * 10 ** 6); 50 | }); 51 | 52 | it("adds initial liquidity to an amm", async function () { 53 | await ammClient 54 | .addLiquidityIx( 55 | amm, 56 | META, 57 | USDC, 58 | new BN(5000 * 10 ** 6), 59 | new BN(6 * 10 ** 9), 60 | new BN(0) 61 | ) 62 | .rpc(); 63 | 64 | const storedAmm = await ammClient.getAmm(amm); 65 | 66 | assert.equal(storedAmm.seqNum.toString(), "1"); 67 | 68 | assert.isTrue(storedAmm.baseAmount.eq(new BN(6 * 10 ** 9))); 69 | assert.isTrue(storedAmm.quoteAmount.eq(new BN(5000 * 10 ** 6))); 70 | 71 | const lpMint = await getAccount( 72 | this.banksClient, 73 | token.getAssociatedTokenAddressSync( 74 | getAmmLpMintAddr(ammClient.program.programId, amm)[0], 75 | this.payer.publicKey 76 | ) 77 | ); 78 | 79 | assert.equal(lpMint.amount.toString(), (5000 * 10 ** 6).toString()); 80 | }); 81 | 82 | it("adds liquidity after it's already been added", async function () { 83 | await ammClient 84 | .addLiquidityIx( 85 | amm, 86 | META, 87 | USDC, 88 | new BN(5000 * 10 ** 6), 89 | new BN(6 * 10 ** 9), 90 | new BN(0) 91 | ) 92 | .rpc(); 93 | 94 | const storedAmm = await ammClient.getAmm(amm); 95 | 96 | assert.isTrue(storedAmm.baseAmount.eq(new BN(6 * 10 ** 9))); 97 | assert.isTrue(storedAmm.quoteAmount.eq(new BN(5000 * 10 ** 6))); 98 | 99 | // const lpMint = await getAccount( 100 | // this.banksClient, 101 | // getAmmLpMintAddr(ammClient.program.programId, amm)[0] 102 | // ); 103 | 104 | // assert.equal(lpMint.amount.toString(), (7500 * 10 ** 6).toString()); 105 | }); 106 | } 107 | -------------------------------------------------------------------------------- /tests/amm/unit/initializeAmm.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AmmClient, 3 | getAmmAddr, 4 | getAmmLpMintAddr, 5 | PriceMath, 6 | } from "@metadaoproject/futarchy/v0.4"; 7 | import { Keypair, PublicKey } from "@solana/web3.js"; 8 | import { assert } from "chai"; 9 | import { createMint } from "spl-token-bankrun"; 10 | import * as anchor from "@coral-xyz/anchor"; 11 | import { DAY_IN_SLOTS, expectError, toBN } from "../../utils.js"; 12 | import { BN } from "bn.js"; 13 | 14 | export default function suite() { 15 | let ammClient: AmmClient; 16 | let META: PublicKey; 17 | let USDC: PublicKey; 18 | 19 | before(async function () { 20 | ammClient = this.ammClient; 21 | META = await createMint( 22 | this.banksClient, 23 | this.payer, 24 | this.payer.publicKey, 25 | this.payer.publicKey, 26 | 9 27 | ); 28 | USDC = await createMint( 29 | this.banksClient, 30 | this.payer, 31 | this.payer.publicKey, 32 | this.payer.publicKey, 33 | 6 34 | ); 35 | }); 36 | 37 | it("creates an amm", async function () { 38 | let expectedInitialObservation = new BN(500_000_000_000); 39 | let expectedMaxObservationChangePerUpdate = new BN(10_000_000_000); 40 | let twapStartDelaySlots = toBN(DAY_IN_SLOTS); 41 | 42 | let bump: number; 43 | let amm: PublicKey; 44 | [amm, bump] = getAmmAddr(ammClient.program.programId, META, USDC); 45 | 46 | await ammClient.createAmm(Keypair.generate().publicKey, META, USDC, twapStartDelaySlots, 500); 47 | 48 | const ammAcc = await ammClient.getAmm(amm); 49 | 50 | assert.equal(ammAcc.bump, bump); 51 | assert.isTrue(ammAcc.createdAtSlot.eq(ammAcc.oracle.lastUpdatedSlot)); 52 | assert.equal(ammAcc.baseMint.toBase58(), META.toBase58()); 53 | assert.equal(ammAcc.quoteMint.toBase58(), USDC.toBase58()); 54 | assert.equal(ammAcc.baseMintDecimals, 9); 55 | assert.equal(ammAcc.quoteMintDecimals, 6); 56 | assert.isTrue(ammAcc.baseAmount.eqn(0)); 57 | assert.isTrue(ammAcc.quoteAmount.eqn(0)); 58 | assert.isTrue(ammAcc.oracle.lastObservation.eq(expectedInitialObservation)); 59 | assert.isTrue(ammAcc.oracle.aggregator.eqn(0)); 60 | assert.isTrue( 61 | ammAcc.oracle.maxObservationChangePerUpdate.eq( 62 | expectedMaxObservationChangePerUpdate 63 | ) 64 | ); 65 | assert.isTrue( 66 | ammAcc.oracle.initialObservation.eq(expectedInitialObservation) 67 | ); 68 | assert.equal(ammAcc.seqNum.toString(), "0"); 69 | assert.isTrue( 70 | ammAcc.oracle.startDelaySlots.eq( 71 | twapStartDelaySlots 72 | ) 73 | ); 74 | }); 75 | 76 | it("fails to create an amm with two identical mints", async function () { 77 | let twapStartDelaySlots = toBN(DAY_IN_SLOTS); 78 | let [twapFirstObservationScaled, twapMaxObservationChangePerUpdateScaled] = 79 | PriceMath.getAmmPrices(9, 9, 100, 1); 80 | 81 | const callbacks = expectError( 82 | "SameTokenMints", 83 | "create AMM succeeded despite same token mints" 84 | ); 85 | 86 | let proposal = Keypair.generate().publicKey; 87 | 88 | await ammClient 89 | .initializeAmmIx( 90 | META, 91 | META, 92 | twapStartDelaySlots, 93 | twapFirstObservationScaled, 94 | twapMaxObservationChangePerUpdateScaled 95 | ) 96 | .rpc() 97 | .then(callbacks[0], callbacks[1]); 98 | }); 99 | } 100 | -------------------------------------------------------------------------------- /tests/autocrat/main.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/tests/autocrat/main.test.ts -------------------------------------------------------------------------------- /tests/conditionalVault/integration/scalarGrantMarket.test.ts: -------------------------------------------------------------------------------- 1 | import { ConditionalVaultClient, sha256 } from "@metadaoproject/futarchy"; 2 | import { Keypair, PublicKey } from "@solana/web3.js"; 3 | import BN from "bn.js"; 4 | import { assert } from "chai"; 5 | import { 6 | createMint, 7 | getMint, 8 | mintTo, 9 | createAssociatedTokenAccount, 10 | transfer, 11 | getAccount, 12 | } from "spl-token-bankrun"; 13 | import * as token from "@solana/spl-token"; 14 | 15 | export default async function test() { 16 | // A scalar grant market test. Alice splits 100 USDC into E-UP and E-DOWN tokens. 17 | // She sends 30 E-UPs to Bob. The grant committee resolves the question with 60% effectiveness. 18 | // Alice and Bob redeem their tokens. 19 | 20 | let vaultClient: ConditionalVaultClient = this.vaultClient; 21 | 22 | let alice: Keypair = Keypair.generate(); 23 | let bob: Keypair = Keypair.generate(); 24 | let grantCommittee: Keypair = Keypair.generate(); 25 | 26 | let question: PublicKey = await vaultClient.initializeQuestion( 27 | sha256( 28 | new TextEncoder().encode( 29 | "What is the effectiveness of the grant?/E-UP/E-DOWN" 30 | ) 31 | ), 32 | grantCommittee.publicKey, 33 | 2 34 | ); 35 | 36 | let USDC: PublicKey = await this.createMint(this.payer.publicKey, 6); 37 | 38 | await this.createTokenAccount(USDC, alice.publicKey); 39 | await this.createTokenAccount(USDC, bob.publicKey); 40 | 41 | await this.mintTo(USDC, alice.publicKey, this.payer, 100); 42 | 43 | const vault = await vaultClient.initializeVault(question, USDC, 2); 44 | const storedVault = await vaultClient.fetchVault(vault); 45 | 46 | await vaultClient 47 | .splitTokensIx(question, vault, USDC, new BN(100), 2, alice.publicKey) 48 | .signers([alice]) 49 | .rpc(); 50 | 51 | const E_UP = storedVault.conditionalTokenMints[0]; 52 | const E_DOWN = storedVault.conditionalTokenMints[1]; 53 | 54 | await this.createTokenAccount(E_UP, bob.publicKey); 55 | 56 | await this.transfer(E_UP, alice, bob.publicKey, 30); 57 | 58 | // Grant committee resolves the question with 60% effectiveness 59 | await vaultClient.resolveQuestionIx(question, grantCommittee, [6, 4]).rpc(); 60 | 61 | await vaultClient 62 | .redeemTokensIx(question, vault, USDC, 2, alice.publicKey) 63 | .signers([alice]) 64 | .rpc(); 65 | await vaultClient 66 | .redeemTokensIx(question, vault, USDC, 2, bob.publicKey) 67 | .signers([bob]) 68 | .rpc(); 69 | 70 | await this.assertBalance(USDC, bob.publicKey, 18); 71 | await this.assertBalance(USDC, alice.publicKey, 82); 72 | } 73 | 74 | // add tests for: 75 | // - different numbers for outcomes (test 0) 76 | // - 77 | -------------------------------------------------------------------------------- /tests/conditionalVault/main.test.ts: -------------------------------------------------------------------------------- 1 | import initializeQuestion from "./unit/initializeQuestion.test.js"; 2 | import initializeConditionalVault from "./unit/initializeConditionalVault.test.js"; 3 | import resolveQuestion from "./unit/resolveQuestion.test.js"; 4 | import splitTokens from "./unit/splitTokens.test.js"; 5 | import mergeTokens from "./unit/mergeTokens.test.js"; 6 | import redeemTokens from "./unit/redeemTokens.test.js"; 7 | import addMetadataToConditionalTokens from "./unit/addMetadataToConditionalTokens.test.js"; 8 | import binaryPredictionMarket from "./integration/binaryPredictionMarket.test.js"; 9 | import scalarGrantMarket from "./integration/scalarGrantMarket.test.js"; 10 | import multiOptionPredictionMarket from "./integration/multiOptionPredictionMarket.test.js"; 11 | 12 | // TODO add a many-outcome integration test 13 | export default function suite() { 14 | it("binary prediction market", binaryPredictionMarket); 15 | it("scalar grant market", scalarGrantMarket); 16 | it("multi option prediction market", multiOptionPredictionMarket); 17 | describe("#initialize_question", initializeQuestion); 18 | describe("#initialize_conditional_vault", initializeConditionalVault); 19 | describe("#resolve_question", resolveQuestion); 20 | describe("#split_tokens", splitTokens); 21 | describe("#merge_tokens", mergeTokens); 22 | describe("#redeem_tokens", redeemTokens); 23 | describe( 24 | "#add_metadata_to_conditional_tokens", 25 | addMetadataToConditionalTokens 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /tests/conditionalVault/unit/initializeConditionalVault.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ConditionalVaultClient, 3 | getVaultAddr, 4 | getConditionalTokenMintAddr, 5 | } from "@metadaoproject/futarchy/v0.4"; 6 | import { sha256 } from "@metadaoproject/futarchy"; 7 | import { Keypair, PublicKey } from "@solana/web3.js"; 8 | import { assert } from "chai"; 9 | import { createMint, getMint } from "spl-token-bankrun"; 10 | import * as anchor from "@coral-xyz/anchor"; 11 | import * as token from "@solana/spl-token"; 12 | 13 | export default function suite() { 14 | let vaultClient: ConditionalVaultClient; 15 | let underlyingTokenMint: PublicKey; 16 | 17 | before(async function () { 18 | vaultClient = this.vaultClient; 19 | underlyingTokenMint = await createMint( 20 | this.banksClient, 21 | this.payer as Keypair, 22 | Keypair.generate().publicKey, 23 | null, 24 | 8 25 | ); 26 | }); 27 | 28 | const testCases = [ 29 | { name: "2-outcome question", idArray: [3, 2, 1], outcomes: 2 }, 30 | { name: "3-outcome question", idArray: [4, 5, 6], outcomes: 3 }, 31 | { name: "4-outcome question", idArray: [7, 8, 9], outcomes: 4 }, 32 | ]; 33 | 34 | testCases.forEach(({ name, idArray, outcomes }) => { 35 | describe(name, function () { 36 | let question: PublicKey; 37 | let oracle: Keypair = Keypair.generate(); 38 | 39 | beforeEach(async function () { 40 | let questionId = sha256(new Uint8Array(idArray)); 41 | question = await vaultClient.initializeQuestion( 42 | questionId, 43 | oracle.publicKey, 44 | outcomes 45 | ); 46 | }); 47 | 48 | it("initializes vaults correctly", async function () { 49 | await vaultClient 50 | .initializeVaultIx(question, underlyingTokenMint, outcomes) 51 | .rpc(); 52 | 53 | const [vault, pdaBump] = getVaultAddr( 54 | vaultClient.vaultProgram.programId, 55 | question, 56 | underlyingTokenMint 57 | ); 58 | 59 | const storedVault = await vaultClient.fetchVault(vault); 60 | assert.ok(storedVault.question.equals(question)); 61 | assert.ok(storedVault.underlyingTokenMint.equals(underlyingTokenMint)); 62 | 63 | const vaultUnderlyingTokenAccount = token.getAssociatedTokenAddressSync( 64 | underlyingTokenMint, 65 | vault, 66 | true 67 | ); 68 | assert.ok( 69 | storedVault.underlyingTokenAccount.equals(vaultUnderlyingTokenAccount) 70 | ); 71 | const storedConditionalTokenMints = storedVault.conditionalTokenMints; 72 | storedConditionalTokenMints.forEach((mint, i) => { 73 | const [expectedMint] = getConditionalTokenMintAddr( 74 | vaultClient.vaultProgram.programId, 75 | vault, 76 | i 77 | ); 78 | assert.ok(mint.equals(expectedMint)); 79 | }); 80 | assert.equal(storedVault.pdaBump, pdaBump); 81 | assert.equal(storedVault.decimals, 8); 82 | assert.equal(storedVault.seqNum.toString(), "0"); 83 | 84 | for (let mint of storedConditionalTokenMints) { 85 | const storedMint = await getMint(this.banksClient, mint); 86 | assert.ok(storedMint.mintAuthority.equals(vault)); 87 | assert.equal(storedMint.supply.toString(), "0"); 88 | assert.equal(storedMint.decimals, 8); 89 | assert.isNull(storedMint.freezeAuthority); 90 | } 91 | }); 92 | }); 93 | }); 94 | } 95 | -------------------------------------------------------------------------------- /tests/conditionalVault/unit/initializeQuestion.test.ts: -------------------------------------------------------------------------------- 1 | import { sha256 } from "@metadaoproject/futarchy"; 2 | // const { ConditionalVaultClient, getQuestionAddr } = futarchy; 3 | import { Keypair } from "@solana/web3.js"; 4 | import { assert } from "chai"; 5 | import { expectError } from "../../utils"; 6 | import { 7 | ConditionalVaultClient, 8 | getQuestionAddr, 9 | } from "@metadaoproject/futarchy/v0.4"; 10 | // import { getQuestionAddr } from "@metadaoproject/futarchy/dist/v0.4"; 11 | 12 | export default function suite() { 13 | let vaultClient: ConditionalVaultClient; 14 | before(function () { 15 | vaultClient = this.vaultClient; 16 | }); 17 | 18 | it("initializes 2-outcome questions", async function () { 19 | let questionId = sha256(new Uint8Array([1, 2, 3])); 20 | 21 | let oracle = Keypair.generate(); 22 | 23 | await vaultClient 24 | .initializeQuestionIx(questionId, oracle.publicKey, 2) 25 | .rpc(); 26 | 27 | let [question] = getQuestionAddr( 28 | vaultClient.vaultProgram.programId, 29 | questionId, 30 | oracle.publicKey, 31 | 2 32 | ); 33 | 34 | const storedQuestion = await vaultClient.fetchQuestion(question); 35 | assert.deepEqual(storedQuestion.questionId, Array.from(questionId)); 36 | assert.ok(storedQuestion.oracle.equals(oracle.publicKey)); 37 | assert.deepEqual(storedQuestion.payoutNumerators, [0, 0]); 38 | assert.equal(storedQuestion.payoutDenominator, 0); 39 | }); 40 | 41 | it("throws error when initializing a question with insufficient conditions", async function () { 42 | const callbacks = expectError( 43 | "InsufficientNumConditions", 44 | "question initialization succeeded despite insufficient conditions" 45 | ); 46 | 47 | await vaultClient 48 | .initializeQuestionIx( 49 | sha256(new Uint8Array([4, 5, 6])), 50 | Keypair.generate().publicKey, 51 | 1 52 | ) 53 | .rpc() 54 | .then(callbacks[0], callbacks[1]); 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /tests/conditionalVault/unit/resolveQuestion.test.ts: -------------------------------------------------------------------------------- 1 | import { sha256 } from "@metadaoproject/futarchy"; 2 | import { ConditionalVaultClient } from "@metadaoproject/futarchy/v0.4"; 3 | import { Keypair, PublicKey } from "@solana/web3.js"; 4 | import { assert } from "chai"; 5 | import { expectError } from "../../utils.js"; 6 | 7 | export default function suite() { 8 | let vaultClient: ConditionalVaultClient; 9 | let question: PublicKey; 10 | let settlementAuthority: Keypair; 11 | 12 | before(function () { 13 | vaultClient = this.vaultClient; 14 | }); 15 | 16 | beforeEach(async function () { 17 | let questionId = sha256(new Uint8Array([4, 2, 1])); 18 | settlementAuthority = Keypair.generate(); 19 | question = await vaultClient.initializeQuestion( 20 | questionId, 21 | settlementAuthority.publicKey, 22 | 2 23 | ); 24 | }); 25 | 26 | it("resolves questions", async function () { 27 | let storedQuestion = await vaultClient.fetchQuestion(question); 28 | 29 | assert.deepEqual(storedQuestion.payoutNumerators, [0, 0]); 30 | assert.equal(storedQuestion.payoutDenominator, 0); 31 | 32 | await vaultClient 33 | .resolveQuestionIx(question, settlementAuthority, [1, 0]) 34 | .rpc(); 35 | 36 | storedQuestion = await vaultClient.fetchQuestion(question); 37 | 38 | assert.deepEqual(storedQuestion.payoutNumerators, [1, 0]); 39 | assert.equal(storedQuestion.payoutDenominator, 1); 40 | }); 41 | 42 | it("throws error when resolving a question with invalid number of payout numerators", async function () { 43 | const callbacks = expectError( 44 | "InvalidNumPayoutNumerators", 45 | "question resolution succeeded despite invalid number of payout numerators" 46 | ); 47 | 48 | await vaultClient 49 | .resolveQuestionIx(question, settlementAuthority, [1, 0, 1]) 50 | .rpc() 51 | .then(callbacks[0], callbacks[1]); 52 | }); 53 | 54 | it("throws error when resolving a question with zero payout", async function () { 55 | const callbacks = expectError( 56 | "PayoutZero", 57 | "question resolution succeeded despite zero payout" 58 | ); 59 | 60 | await vaultClient 61 | .resolveQuestionIx(question, settlementAuthority, [0, 0]) 62 | .rpc() 63 | .then(callbacks[0], callbacks[1]); 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /tests/fixtures/mpl_token_metadata.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/tests/fixtures/mpl_token_metadata.so -------------------------------------------------------------------------------- /tests/fixtures/openbook_twap.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/tests/fixtures/openbook_twap.so -------------------------------------------------------------------------------- /tests/fixtures/openbook_v2.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/tests/fixtures/openbook_v2.so -------------------------------------------------------------------------------- /tests/fixtures/raydium-amm-config: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/tests/fixtures/raydium-amm-config -------------------------------------------------------------------------------- /tests/fixtures/raydium-create-pool-fee-receive: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/tests/fixtures/raydium-create-pool-fee-receive -------------------------------------------------------------------------------- /tests/fixtures/raydium_cp_swap.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/tests/fixtures/raydium_cp_swap.so -------------------------------------------------------------------------------- /tests/fixtures/usdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/tests/fixtures/usdc -------------------------------------------------------------------------------- /tests/launchpad/main.test.ts: -------------------------------------------------------------------------------- 1 | import initializeLaunch from "./unit/initializeLaunch.test.js"; 2 | import startLaunch from "./unit/startLaunch.test.js"; 3 | import fund from "./unit/fund.test.js"; 4 | import completeLaunch from "./unit/completeLaunch.test.js"; 5 | import claim from "./unit/claim.test.js"; 6 | import refund from "./unit/refund.test.js"; 7 | 8 | // TODO add a many-outcome integration test 9 | export default function suite() { 10 | describe("#initialize_launch", initializeLaunch); 11 | describe("#start_launch", startLaunch); 12 | describe("#fund", fund); 13 | describe("#complete_launch", completeLaunch); 14 | describe("#claim", claim); 15 | describe("#refund", refund); 16 | } 17 | -------------------------------------------------------------------------------- /tests/launchpad/unit/startLaunch.test.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, PublicKey } from "@solana/web3.js"; 2 | import { assert } from "chai"; 3 | import { 4 | AutocratClient, 5 | getLaunchAddr, 6 | getLaunchSignerAddr, 7 | LaunchpadClient, 8 | } from "@metadaoproject/futarchy/v0.4"; 9 | import { createMint } from "spl-token-bankrun"; 10 | import { BN } from "bn.js"; 11 | import { 12 | createSetAuthorityInstruction, 13 | AuthorityType, 14 | getAssociatedTokenAddressSync, 15 | } from "@solana/spl-token"; 16 | import { initializeMintWithSeeds } from "../utils.js"; 17 | 18 | export default function suite() { 19 | let autocratClient: AutocratClient; 20 | let launchpadClient: LaunchpadClient; 21 | let dao: PublicKey; 22 | let METAKP: Keypair; 23 | let META: PublicKey; 24 | let launch: PublicKey; 25 | let launchSigner: PublicKey; 26 | const minRaise = new BN(1000_000000); // 1000 USDC 27 | 28 | before(async function () { 29 | autocratClient = this.autocratClient; 30 | launchpadClient = this.launchpadClient; 31 | }); 32 | 33 | beforeEach(async function () { 34 | const result = await initializeMintWithSeeds( 35 | this.banksClient, 36 | this.launchpadClient, 37 | this.payer 38 | ); 39 | 40 | META = result.tokenMint; 41 | launch = result.launch; 42 | launchSigner = result.launchSigner; 43 | 44 | // Initialize launch 45 | await launchpadClient 46 | .initializeLaunchIx( 47 | "META", 48 | "MTA", 49 | "https://example.com", 50 | minRaise, 51 | 60 * 60 * 24 * 2, 52 | META 53 | ) 54 | .rpc(); 55 | }); 56 | 57 | it("starts launch correctly", async function () { 58 | // Check initial state 59 | let launchAccount = await launchpadClient.fetchLaunch(launch); 60 | assert.equal(launchAccount.unixTimestampStarted.toString(), "0"); 61 | assert.exists(launchAccount.state.initialized); 62 | 63 | // Get current slot for comparison 64 | const clock = await this.banksClient.getClock(); 65 | 66 | // Start the launch 67 | await launchpadClient.startLaunchIx(launch).rpc(); 68 | 69 | // Check final state 70 | launchAccount = await launchpadClient.fetchLaunch(launch); 71 | assert.equal( 72 | launchAccount.unixTimestampStarted.toString(), 73 | clock.unixTimestamp.toString() 74 | ); 75 | assert.exists(launchAccount.state.live); 76 | }); 77 | } 78 | -------------------------------------------------------------------------------- /tests/launchpad/utils.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, Signer, SystemProgram, Transaction } from '@solana/web3.js'; 2 | import * as token from '@solana/spl-token'; 3 | import { BanksClient } from 'solana-bankrun'; 4 | import { LaunchpadClient } from '@metadaoproject/futarchy/v0.4'; 5 | import { getLaunchAddr, getLaunchSignerAddr } from '@metadaoproject/futarchy/v0.4'; 6 | 7 | export async function initializeMintWithSeeds( 8 | banksClient: BanksClient, 9 | launchpadClient: LaunchpadClient, 10 | payer: Signer 11 | ): Promise<{ tokenMint: PublicKey, launch: PublicKey, launchSigner: PublicKey }> { 12 | const seed = Math.random().toString(36).substring(2, 15); 13 | const tokenMint = await PublicKey.createWithSeed( 14 | payer.publicKey, 15 | seed, 16 | token.TOKEN_PROGRAM_ID 17 | ); 18 | 19 | const [launch] = getLaunchAddr(launchpadClient.getProgramId(), tokenMint); 20 | const [launchSigner] = getLaunchSignerAddr( 21 | launchpadClient.getProgramId(), 22 | launch 23 | ); 24 | 25 | const rent = await banksClient.getRent(); 26 | const lamports = Number(rent.minimumBalance(BigInt(token.MINT_SIZE))); 27 | 28 | const tx = new Transaction().add( 29 | SystemProgram.createAccountWithSeed({ 30 | fromPubkey: payer.publicKey, 31 | newAccountPubkey: tokenMint, 32 | basePubkey: payer.publicKey, 33 | seed, 34 | lamports: lamports, 35 | space: token.MINT_SIZE, 36 | programId: token.TOKEN_PROGRAM_ID, 37 | }), 38 | token.createInitializeMint2Instruction(tokenMint, 6, launchSigner, null) 39 | ); 40 | tx.recentBlockhash = (await banksClient.getLatestBlockhash())[0]; 41 | tx.feePayer = payer.publicKey; 42 | tx.sign(payer); 43 | 44 | await banksClient.processTransaction(tx); 45 | 46 | return { 47 | tokenMint, 48 | launch, 49 | launchSigner 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /tests/utils.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import { Clock, ProgramTestContext } from "solana-bankrun"; 3 | import { BN } from "bn.js"; 4 | 5 | export const TEN_SECONDS_IN_SLOTS = 25n; 6 | export const ONE_MINUTE_IN_SLOTS = TEN_SECONDS_IN_SLOTS * 6n; 7 | export const HOUR_IN_SLOTS = ONE_MINUTE_IN_SLOTS * 60n; 8 | export const DAY_IN_SLOTS = HOUR_IN_SLOTS * 24n; 9 | 10 | export const toBN = (val: bigint): typeof BN.prototype => new BN(val.toString()); 11 | 12 | export const expectError = ( 13 | expectedError: string, 14 | message: string 15 | ): [() => void, (e: any) => void] => { 16 | return [ 17 | () => assert.fail(message), 18 | (e) => { 19 | assert(e.error != undefined, `problem retrieving program error: ${e}`); 20 | assert( 21 | e.error.errorCode != undefined, 22 | "problem retrieving program error code" 23 | ); 24 | //for (let idlError of program.idl.errors) { 25 | // if (idlError.code == e.code) { 26 | // assert.equal(idlError.name, expectedError); 27 | // return; 28 | // } 29 | //} 30 | assert.equal( 31 | e.error.errorCode.code, 32 | expectedError, 33 | `the program threw for a reason that we didn't expect. error : ${e}` 34 | ); 35 | /* assert.fail("error doesn't match idl"); */ 36 | /* console.log(program.idl.errors); */ 37 | /* assert( */ 38 | /* e["error"] != undefined, */ 39 | /* `the program threw for a reason that we didn't expect. error: ${e}` */ 40 | /* ); */ 41 | /* assert.equal(e.error.errorCode.code, expectedErrorCode); */ 42 | }, 43 | ]; 44 | }; 45 | 46 | export const advanceBySlots = async ( 47 | context: ProgramTestContext, 48 | slots: bigint 49 | ) => { 50 | const currentClock = await context.banksClient.getClock(); 51 | context.setClock( 52 | new Clock( 53 | currentClock.slot + slots, 54 | currentClock.epochStartTimestamp, 55 | currentClock.epoch, 56 | currentClock.leaderScheduleEpoch, 57 | 50n 58 | ) 59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": [ 4 | "mocha", 5 | "chai" 6 | ], 7 | "typeRoots": [ 8 | "./node_modules/@types" 9 | ], 10 | "lib": [ 11 | "es2015", 12 | "dom" 13 | ], 14 | "module": "nodenext", 15 | "target": "ES2020", 16 | "moduleResolution": "nodenext", 17 | "esModuleInterop": true, 18 | "resolveJsonModule": true, 19 | "paths": { 20 | "@solana/spl-token": [ 21 | "./node_modules/@solana/spl-token" 22 | ] 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /verifiable-builds/amm.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/verifiable-builds/amm.so -------------------------------------------------------------------------------- /verifiable-builds/autocrat.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/verifiable-builds/autocrat.so -------------------------------------------------------------------------------- /verifiable-builds/autocrat_migrator.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/verifiable-builds/autocrat_migrator.so -------------------------------------------------------------------------------- /verifiable-builds/autocrat_v0.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/verifiable-builds/autocrat_v0.so -------------------------------------------------------------------------------- /verifiable-builds/conditional_vault.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/verifiable-builds/conditional_vault.so -------------------------------------------------------------------------------- /verifiable-builds/launchpad.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaDAOproject/futarchy/f0f6f02b9cc3b19be53b7a8a90f131aedde2ba32/verifiable-builds/launchpad.so --------------------------------------------------------------------------------