├── .envrc ├── .github └── workflows │ ├── advance_ois.yml │ ├── anchor-idl.yml │ ├── anchor.yml │ ├── clippy.yml │ ├── pre-commit.yml │ ├── publish-staking.yml │ ├── release.yml │ └── test-frontend.yaml ├── .gitignore ├── .npmrc ├── .nvmrc ├── .pre-commit-config.yaml ├── .prettierignore ├── .vscode └── settings.json ├── CODEOWNERS ├── LICENSE ├── SECURITY.md ├── docs ├── constitution │ ├── diagrams │ │ └── pyth_dao.light.png │ ├── drawio_assets │ │ └── pyth_dao.drawio │ └── pyth-dao-constitution.md ├── councils │ ├── price-feed │ │ └── election │ │ │ ├── Auph-LeapLabs.md │ │ │ ├── Giulio-PythDataAssociation │ │ │ ├── Harnaik-DL.md │ │ │ ├── Lanre-DropCopy.md │ │ │ ├── Lawrence-NOBI.md │ │ │ ├── Matt-Synthetix.md │ │ │ ├── Nicholas-DouroLabs.md │ │ │ ├── README.md │ │ │ ├── Robert-ReactorFusion.md │ │ │ ├── antoine-swissborg.md │ │ │ └── name-protocol.md │ └── pythian │ │ └── election │ │ ├── 1 │ │ ├── Lanre-DropCopy.md │ │ ├── Marc Tillement - Pyth Data Association.md │ │ ├── adam-thala.md │ │ ├── afif-synthetix.md │ │ ├── antoine-swissborg.md │ │ ├── carnation-y2k.md │ │ ├── guilhem-flowdesk.md │ │ ├── guillermo-douro.md │ │ ├── jayant-douro.md │ │ ├── khalil-PythCommunity │ │ ├── naive │ │ ├── nope-solend.md │ │ ├── robinson-wormhole.md │ │ ├── shu-hmx.md │ │ └── travis-vela.md │ │ ├── README.md │ │ └── name-protocol.md └── pips │ ├── constitutional │ ├── pip-0.md │ ├── pip-1.md │ └── pip-2.md │ └── operational │ ├── op-pip-1.md │ ├── op-pip-2.md │ ├── op-pip-3.md │ ├── op-pip-4.md │ ├── op-pip-5.md │ ├── op-pip-6.md │ ├── op-pip-7.md │ ├── op-pip-8.md │ └── op-pip-9.md ├── frontend ├── .babelrc ├── .env.sample ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── components │ ├── Footer.tsx │ ├── Header.tsx │ ├── Layout.tsx │ ├── SEO.tsx │ ├── Spinner.tsx │ ├── Tooltip.tsx │ ├── WalletModalButton.tsx │ ├── config.ts │ ├── icons │ │ ├── CloseIcon.tsx │ │ ├── DiscordIcon.tsx │ │ ├── GithubIcon.tsx │ │ ├── InfoIcon.tsx │ │ ├── LockedIcon.tsx │ │ ├── SiteIcon.tsx │ │ ├── TelegramIcon.tsx │ │ ├── TwitterIcon.tsx │ │ ├── UnlockedIcon.tsx │ │ ├── UnvestedIcon.tsx │ │ └── YoutubeIcon.tsx │ ├── modals │ │ ├── BaseModal.tsx │ │ ├── LockedModal.tsx │ │ ├── LockedTokenActionModal.tsx │ │ ├── ProfileModal.tsx │ │ ├── StakedModal.tsx │ │ └── UnstakedModal.tsx │ └── panels │ │ ├── StakePanel.tsx │ │ ├── UnstakePanel.tsx │ │ ├── WithdrawPanel.tsx │ │ └── components │ │ ├── ActionButton.tsx │ │ ├── AmountInput.tsx │ │ ├── AmountInputLabel.tsx │ │ ├── Description.tsx │ │ ├── Layout.tsx │ │ └── index.ts ├── hooks │ ├── useBalance.ts │ ├── useDepositMutation.ts │ ├── useNextVestingEvent.ts │ ├── usePreunstakeLockedMutation.ts │ ├── useProfile.ts │ ├── useProfileConnection.ts │ ├── usePythBalance.ts │ ├── useSplitRequest.ts │ ├── useStakeAccounts.ts │ ├── useStakeConnection.ts │ ├── useStakeLockedMutation.ts │ ├── useUnlockMutation.ts │ ├── useUnstakeLockedMutation.ts │ ├── useUpdateProfileMutation.ts │ ├── useVestingAccountState.ts │ └── useWithdrawMutation.ts ├── netlify.toml ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── api │ │ ├── getPythTokenBalance.ts │ │ └── v1 │ │ │ ├── all_locked_accounts.ts │ │ │ ├── cmc │ │ │ └── supply.ts │ │ │ └── locked_accounts.ts │ ├── approve.tsx │ ├── create_election_governance.tsx │ ├── create_locked_account.tsx │ ├── governance.tsx │ ├── index.tsx │ ├── profile.tsx │ ├── request.tsx │ └── test.tsx ├── postcss.config.js ├── prettier.config.js ├── public │ ├── .well-known │ │ └── walletconnect.txt │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-light.ico │ ├── favicon.ico │ ├── mstile-150x150.png │ ├── orb.png │ ├── pyth-logo-white.svg │ ├── pyth.svg │ ├── safari-pinned-tab.svg │ └── site.webmanifest ├── styles │ └── globals.css ├── tailwind.config.js ├── tsconfig.json └── utils │ ├── capitalizeFirstLetter.ts │ ├── classNames.ts │ ├── isSufficientBalance.ts │ └── validAmountChange.ts ├── lerna.json ├── metrics ├── .gitignore ├── Dockerfile ├── entrypoint.sh ├── governance_metrics.ts ├── package.json ├── server.ts ├── tsconfig.json └── yarn.lock ├── package-lock.json ├── package.json ├── rustfmt.toml ├── scripts ├── vercel_build.sh └── vercel_install.sh ├── shell.nix ├── sources.json ├── sources.nix ├── staking ├── .dockerignore ├── .env.sample ├── .gitignore ├── .nvmrc ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── app │ ├── PositionAccountJs.ts │ ├── ProfileConnection.ts │ ├── PythClient.ts │ ├── StakeConnection.ts │ ├── api_utils.ts │ ├── constants.ts │ ├── deploy │ │ ├── 1_create_realm.ts │ │ ├── 2_init_staking.ts │ │ ├── 3_create_governance.ts │ │ ├── 4_transfer_authorities_to_multisig.ts │ │ ├── 5_add_pyth_token_metadata.ts │ │ ├── create_account_lookup_table.ts │ │ ├── create_vesting_account.ts │ │ ├── devnet.ts │ │ ├── ledger.ts │ │ ├── mainnet_beta.ts │ │ └── pyth_token_offchain_metadata.json │ ├── index.ts │ ├── keypairs │ │ ├── alice.json │ │ ├── bob.json │ │ ├── localnet_authority.json │ │ └── pyth_mint.json │ ├── lockedAccounts.ts │ ├── pythBalance.ts │ └── scripts │ │ ├── devnet_upgrade.ts │ │ ├── localnet.ts │ │ ├── locked_stake_accounts.ts │ │ └── snapshot.ts ├── cli │ ├── Cargo.toml │ └── src │ │ ├── cli.rs │ │ ├── instructions.rs │ │ └── main.rs ├── clippy.toml ├── integration-tests │ ├── Cargo.toml │ ├── fixtures │ │ ├── governance │ │ │ ├── governance.json │ │ │ ├── governance.so │ │ │ ├── realm.json │ │ │ └── realm_config.json │ │ └── staking │ │ │ ├── stake_account_custody.json │ │ │ ├── stake_account_metadata.json │ │ │ ├── stake_account_positions.json │ │ │ ├── target_account.json │ │ │ └── voter_record.json │ ├── src │ │ ├── governance │ │ │ ├── addresses.rs │ │ │ ├── helper_functions.rs │ │ │ ├── instructions.rs │ │ │ └── mod.rs │ │ ├── integrity_pool │ │ │ ├── helper_functions.rs │ │ │ ├── instructions.rs │ │ │ ├── mod.rs │ │ │ └── pda.rs │ │ ├── lib.rs │ │ ├── publisher_caps │ │ │ ├── helper_functions.rs │ │ │ ├── instructions.rs │ │ │ ├── mod.rs │ │ │ └── utils.rs │ │ ├── setup.rs │ │ ├── solana │ │ │ ├── instructions.rs │ │ │ ├── mod.rs │ │ │ └── utils.rs │ │ ├── staking │ │ │ ├── helper_functions.rs │ │ │ ├── instructions.rs │ │ │ ├── mod.rs │ │ │ └── pda.rs │ │ └── utils │ │ │ ├── clock.rs │ │ │ ├── constants.rs │ │ │ ├── error.rs │ │ │ └── mod.rs │ └── tests │ │ ├── advance.rs │ │ ├── claim.rs │ │ ├── delegate.rs │ │ ├── initialize_pool.rs │ │ ├── integrity_pool_slash.rs │ │ ├── max_positions.rs │ │ ├── merge_delegation_positions.rs │ │ ├── pool_authority.rs │ │ ├── publisher_caps.rs │ │ ├── set_publisher_stake_account.rs │ │ ├── stability.rs │ │ ├── staking_slash.rs │ │ ├── transfer_account.rs │ │ └── voting.rs ├── package.json ├── programs │ ├── integrity-pool │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── context.rs │ │ │ ├── error.rs │ │ │ ├── lib.rs │ │ │ ├── state │ │ │ ├── delegation_record.rs │ │ │ ├── event.rs │ │ │ ├── mod.rs │ │ │ ├── pool.rs │ │ │ └── slash.rs │ │ │ └── utils │ │ │ ├── clock.rs │ │ │ ├── constants.rs │ │ │ ├── mod.rs │ │ │ └── types.rs │ ├── profile │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── publisher-caps │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── staking │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ └── src │ │ │ ├── context.rs │ │ │ ├── error.rs │ │ │ ├── lib.rs │ │ │ ├── state │ │ │ ├── global_config.rs │ │ │ ├── max_voter_weight_record.rs │ │ │ ├── mod.rs │ │ │ ├── positions.rs │ │ │ ├── split_request.rs │ │ │ ├── stake_account.rs │ │ │ ├── target.rs │ │ │ ├── vesting.rs │ │ │ └── voter_weight_record.rs │ │ │ ├── utils │ │ │ ├── clock.rs │ │ │ ├── mod.rs │ │ │ ├── risk.rs │ │ │ └── voter_weight.rs │ │ │ └── wasm.rs │ └── wallet-tester │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── rust-toolchain ├── scripts │ └── build_wasm.sh ├── target │ ├── idl │ │ ├── profile.json │ │ ├── staking.json │ │ └── wallet_tester.json │ └── types │ │ ├── profile.ts │ │ ├── staking.ts │ │ └── wallet_tester.ts ├── tests │ ├── api_test.ts │ ├── clock_api_test.ts │ ├── config.ts │ ├── create_product.ts │ ├── max_pos.ts │ ├── pool_authority.ts │ ├── position_lifecycle.ts │ ├── profile.ts │ ├── programs │ │ ├── chat.so │ │ └── governance.so │ ├── pyth_balance.ts │ ├── recover_account.ts │ ├── split_vesting_account.ts │ ├── staking.ts │ ├── unlock_api_test.ts │ ├── utils │ │ ├── after.ts │ │ ├── api_utils.ts │ │ ├── before.ts │ │ ├── governance_utils.ts │ │ ├── keys.ts │ │ └── utils.ts │ ├── vesting_test.ts │ ├── voter_weight_test.ts │ ├── voting.ts │ └── wallet_tester.ts ├── tsconfig.api.json └── tsconfig.json ├── vercel.json └── wasm └── package.json /.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # the shebang is ignored, but nice for editors 3 | 4 | if type -P lorri &>/dev/null; then 5 | eval "$(lorri direnv)" 6 | else 7 | echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]' 8 | use nix 9 | fi 10 | -------------------------------------------------------------------------------- /.github/workflows/advance_ois.yml: -------------------------------------------------------------------------------- 1 | name: Advance OIS 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * 4" # Every Thursday at 00:00 UTC - start of epoch 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: ⚡ Cache 14 | uses: actions/cache@v4 15 | with: 16 | path: | 17 | ~/.cargo/registry 18 | ~/.cargo/git 19 | staking/target 20 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 21 | restore-keys: | 22 | ${{ runner.os }}-cargo- 23 | 24 | - name: Install Rust directly 25 | run: | 26 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.75.0 27 | echo "$HOME/.cargo/bin" >> $GITHUB_PATH 28 | source "$HOME/.cargo/env" 29 | 30 | - name: Generate Solana Keypair 31 | run: | 32 | echo "${{ secrets.STAKING_PRIVATE_KEY }}" > key.json 33 | 34 | - name: Install required packages for cargo build 35 | run: | 36 | sudo apt-get update 37 | sudo apt-get install -y libudev-dev 38 | 39 | - name: Build 40 | run: | 41 | cd ./staking 42 | cargo build --release --locked 43 | 44 | - name: Advance OIS Mainnet 45 | uses: nick-fields/retry@v2 46 | with: 47 | timeout_minutes: 60 48 | max_attempts: 50 49 | command: | 50 | staking/target/release/staking-cli --keypair key.json --rpc-url https://api.mainnet-beta.solana.com/ advance --hermes-url https://hermes.pyth.network/ --wormhole HDwcJBJXjL9FpJ7UBsYBtaDjsBUhuLCUYoz3zr8SWWaQ 51 | 52 | - name: Advance OIS Devnet 53 | uses: nick-fields/retry@v2 54 | with: 55 | timeout_minutes: 60 56 | max_attempts: 50 57 | command: | 58 | staking/target/release/staking-cli --keypair key.json --rpc-url https://api.devnet.solana.com/ advance --hermes-url https://hermes.pyth.network/ --wormhole HDwcJBJXjL9FpJ7UBsYBtaDjsBUhuLCUYoz3zr8SWWaQ 59 | -------------------------------------------------------------------------------- /.github/workflows/anchor-idl.yml: -------------------------------------------------------------------------------- 1 | name: Anchor IDL CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: nightly-2024-02-01 18 | components: rustfmt, clippy 19 | - uses: actions/checkout@v2 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v2 22 | with: 23 | node-version: "18.19.1" 24 | - name: Npm install 25 | run: npm ci 26 | - name: Install Solana 27 | run: | 28 | sh -c "$(curl -sSfL https://release.anza.xyz/v1.18.16/install)" 29 | echo "/home/runner/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH 30 | - name: Install Anchor 31 | working-directory: ./staking 32 | run: npm i -g @coral-xyz/anchor-cli@0.30.1 33 | - name: Build IDL 34 | working-directory: ./staking 35 | env: 36 | RUSTUP_TOOLCHAIN: nightly-2024-02-01 37 | run: anchor build 38 | - name: Check commited idl is up to date 39 | working-directory: ./staking 40 | run: git diff -G".*" --exit-code target/* 41 | -------------------------------------------------------------------------------- /.github/workflows/anchor.yml: -------------------------------------------------------------------------------- 1 | name: Anchor Test CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Install libudev-dev 16 | run: sudo apt-get update && sudo apt-get install libudev-dev 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: "18.19.1" 21 | - name: Npm install 22 | run: npm ci 23 | - name: Install Solana 24 | run: | 25 | sh -c "$(curl -sSfL https://release.anza.xyz/v1.18.16/install)" 26 | echo "/home/runner/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH 27 | - name: Install Solana Verify CLI 28 | run: | 29 | cargo install solana-verify --git https://github.com/Ellipsis-Labs/solana-verifiable-build --rev 098551f 30 | - name: Javascript tests 31 | working-directory: ./staking 32 | run: npm run test:ci 33 | # Remove debug folder to avoid hitting space limit in ci 34 | - name: Clean up 35 | working-directory: ./staking 36 | run: rm -r ./target/debug 37 | # Anchor test will build the program, so we can run the tests here 38 | - name: Cargo tests 39 | working-directory: ./staking 40 | run: RUST_MIN_STACK=33554432 cargo test 41 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: Clippy 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Install libudev-dev 16 | run: sudo apt-get update && sudo apt-get install libudev-dev 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: nightly-2024-02-01 21 | components: clippy 22 | - name: Run clippy check 23 | working-directory: ./staking 24 | run: cargo +nightly-2024-02-01 clippy --tests -- -D warnings 25 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: Pre-commit checks 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | 8 | jobs: 9 | pre-commit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | with: 14 | # Need to grab the history of the PR 15 | fetch-depth: 0 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | profile: minimal 19 | toolchain: nightly-2023-03-01 20 | components: rustfmt, clippy 21 | - uses: pre-commit/action@v3.0.0 22 | if: ${{ github.event_name == 'pull_request' }} 23 | with: 24 | # Run only on files changed in the PR 25 | extra_args: --from-ref ${{ github.event.pull_request.base.sha }} --to-ref ${{ github.event.pull_request.head.sha }} 26 | - uses: pre-commit/action@v3.0.0 27 | if: ${{ github.event_name != 'pull_request' }} 28 | -------------------------------------------------------------------------------- /.github/workflows/publish-staking.yml: -------------------------------------------------------------------------------- 1 | name: Publish Pyth Staking SDK 2 | 3 | on: 4 | push: 5 | tags: 6 | - pyth-staking-v* 7 | jobs: 8 | publish-js: 9 | name: Publish Javascript Packages to NPM 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: "16" 16 | registry-url: "https://registry.npmjs.org" 17 | - run: npm ci 18 | - run: npx lerna run build --no-private 19 | - run: npx lerna publish from-package --no-private --no-git-tag-version --yes 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Install Solana Verify CLI 14 | run: | 15 | cargo install solana-verify --git https://github.com/Ellipsis-Labs/solana-verifiable-build --rev 098551f 16 | - name: Build 17 | working-directory: ./staking 18 | run: solana-verify build 19 | - name: Publish program binaries 20 | uses: svenstaro/upload-release-action@133984371c30d34e38222a64855679a414cb7575 21 | with: 22 | repo_token: ${{ secrets.GITHUB_TOKEN }} 23 | file: ./staking/target/deploy/*.so 24 | tag: ${{ github.ref }} 25 | file_glob: true 26 | -------------------------------------------------------------------------------- /.github/workflows/test-frontend.yaml: -------------------------------------------------------------------------------- 1 | name: Test Frontend 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | 7 | pull_request: 8 | types: [opened, synchronize] 9 | 10 | jobs: 11 | Test_Lint: 12 | name: Test Lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Check out codebase 16 | uses: actions/checkout@v4 17 | 18 | - name: Install node 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version-file: ".nvmrc" 22 | 23 | - name: Install node packages 24 | run: npm ci 25 | 26 | - name: Run lint tests 27 | run: npm run -w frontend test:lint 28 | 29 | Test_Format: 30 | name: Test Format 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Check out codebase 34 | uses: actions/checkout@v4 35 | 36 | - name: Install node 37 | uses: actions/setup-node@v4 38 | with: 39 | node-version-file: ".nvmrc" 40 | 41 | - name: Install node packages 42 | run: npm ci 43 | 44 | - name: Run format checks 45 | run: npm run -w frontend test:format 46 | 47 | Check_Types: 48 | name: Check Types 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: Check out codebase 52 | uses: actions/checkout@v4 53 | 54 | - name: Install node 55 | uses: actions/setup-node@v4 56 | with: 57 | node-version-file: ".nvmrc" 58 | 59 | - name: Install node packages 60 | run: npm ci 61 | 62 | - name: Build staking package 63 | run: npm run -w staking build 64 | 65 | - name: Check typescript types 66 | run: npm run -w frontend test:types 67 | 68 | Test_Nix: 69 | name: Test (nix) 70 | runs-on: ubuntu-latest 71 | steps: 72 | - name: Check out codebase 73 | uses: actions/checkout@v4 74 | 75 | - name: Install nix 76 | uses: cachix/install-nix-action@v26 77 | with: 78 | nix_path: nixpkgs=channel:nixpkgs-unstable 79 | 80 | - name: Install node packages 81 | run: nix-shell --run "cli install" 82 | 83 | - name: Build staking package 84 | run: nix-shell --run "npm run -w staking build" 85 | 86 | - name: Test 87 | run: nix-shell --run "cli test" 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .idea/ 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | v20 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20 2 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v3.2.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | exclude: staking/target 8 | - id: check-added-large-files 9 | exclude: package-lock.json|staking/tests/programs 10 | # Hook to format many type of files in the repo 11 | - repo: https://github.com/pre-commit/mirrors-prettier 12 | rev: "v2.7.1" 13 | hooks: 14 | - id: prettier 15 | additional_dependencies: 16 | - "prettier@2.8.8" 17 | - repo: local 18 | hooks: 19 | # Hooks for the staking program 20 | - id: cargo-fmt-staking 21 | name: Cargo format for staking 22 | language: "rust" 23 | entry: cargo +nightly-2023-03-01 fmt --manifest-path ./staking/Cargo.toml --all -- --config-path rustfmt.toml 24 | pass_filenames: false 25 | files: staking 26 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | staking/target 2 | staking/lib 3 | wasm 4 | staking/app/keypairs 5 | docs/ 6 | frontend/ 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "terminal.integrated.env.osx": { 3 | // 32MB 4 | "RUST_MIN_STACK": "33554432" 5 | }, 6 | "terminal.integrated.env.linux": { 7 | // 32MB 8 | "RUST_MIN_STACK": "33554432" 9 | }, 10 | "[rust]": { 11 | "editor.defaultFormatter": "rust-lang.rust-analyzer" 12 | }, 13 | "rust-analyzer.check.command": "clippy", 14 | "rust-analyzer.rustfmt.overrideCommand": ["rustfmt", "+nightly"], 15 | "cSpell.words": [ 16 | "accs", 17 | "blockhash", 18 | "borsh", 19 | "bytemuck", 20 | "delegators", 21 | "funder", 22 | "Keypair", 23 | "keypairs", 24 | "lamports", 25 | "litesvm", 26 | "Merkle", 27 | "Metas", 28 | "PREUNLOCKING", 29 | "Pubkey", 30 | "pubkeys", 31 | "pyth", 32 | "pythnet", 33 | "quickcheck", 34 | "reqwest", 35 | "shellexpand", 36 | "snapshotted", 37 | "solana", 38 | "sysvar", 39 | "Undelegate", 40 | "undelegated", 41 | "UNDELEGATION", 42 | "vaas", 43 | "Zeroable" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | /frontend/ @cctdaniel 2 | /metrics/ @guibescos 3 | /staking/ @guibescos 4 | /wasm/ @guibescos 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Pyth Contributors. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | ## Bug Bounty Program (_work in progress_) 4 | 5 | We are in the process of designing a **bug bounty program** for Pyth. 6 | 7 | In the mean-time, if you find a security issue in Pyth, we ask that you reach out to our team via **[Discord](https://discord.com/invite/pythnetwork)**. 8 | 9 | ## 3rd Party Security Audits 10 | 11 | We engage 3rd party firms to conduct independent security audits of Pyth. At any given time, we likely have multiple audit streams in progress. 12 | 13 | As these 3rd party audits are completed and issues are sufficiently addressed, we make those audit reports public. 14 | 15 | - **[May 6, 2022 - Zellic](https://github.com/pyth-network/audit-reports/blob/main/2022_05_06/pyth_governance_zellic.pdf)** 16 | - **Scope**: _Pyth Governance_ 17 | - **[July 8, 2022 - OtterSec](https://github.com/pyth-network/audit-reports/blob/main/2022_07_08/pyth-governance-ottersec.pdf)** 18 | - **Scope**: _Pyth Governance_ 19 | -------------------------------------------------------------------------------- /docs/constitution/diagrams/pyth_dao.light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/docs/constitution/diagrams/pyth_dao.light.png -------------------------------------------------------------------------------- /docs/councils/price-feed/election/Auph-LeapLabs.md: -------------------------------------------------------------------------------- 1 | Wallet address 2 | 3 | * 7p2SpWKRc7DUjc5zbqoBYTGXw7FtZknE23FBnA8C4ztJ 4 | 5 | Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). 6 | * Ledger 7 | 8 | Name of Candidate 9 | 10 | * Auph 11 | Twitter: @auph 12 | Discord: @auph 13 | Github: @auph 14 | 15 | Current Role / Ecosystem 16 | * Board Member, X.LEAP Labs 17 | * Chief Technology Officer, INWEB3.xyz 18 | * Community & Expansions, Worldcoin (Singapore) 19 | 20 | Main Location 21 | * Singapore 22 | 23 | Motivations to participate in the {Pythian, Price Feed} Council 24 | 25 | Advisor to several crypto firms in Asia. Led product teams technology innovation and product management. Launched several web3 products involving DeSo, NFT and DeFi. 26 | Auph has also significantly contributed to the global web3 discourse as a regular speaker at renowned events such as NFT.London, NFT.NYC, and MetaJam Summit Singapore. His efforts are devoted to advocating for the adoption and innovation of web3 technology. 27 | 28 | Relevant Experience 29 | 30 | Over 13 years in technology consulting 31 | 5+ years crypto experience 32 | Core contributor of DeSo protocol 33 | Raised $3M in 2022 34 | -------------------------------------------------------------------------------- /docs/councils/price-feed/election/Giulio-PythDataAssociation: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana): 2X9BcSSnAXyizMA6f3AwSt51SQXSU5drWwUadHqsNCeP 2 | 3 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). Ledger hardware wallet 4 | 5 | - Name of Candidate 6 | - Full Name: Giulio Alessio 7 | - Twitter handle: @Gnothis3auton 8 | - Discord handle: gnothis3auton 9 | - Github handle: gnothis3auton 10 | 11 | - Current Role / Ecosystem 12 | - Title: Head of Operations 13 | - Company name (if applicable) or Protocol: [Pyth Data Association](https://pythdataassociation.com/) 14 | 15 | - Main Location 16 | - Continent: Europe 17 | 18 | - Motivations to participate in the Price Feed Council 19 | - Committed to assisting the Pyth Network achieve total decentralization. 20 | - Recognize the critical role of managing and selecting high-quality price feeds and data providers to meet the network's objectives 21 | - Pyth Data Association is a contributor to the Pyth Network 22 | 23 | - Relevant Experience 24 | - Contributing to the Pyth Network through my role at the Pyth Data Association 25 | - Have been working in crypto for over 4 years, with focus on markets and institutions 26 | -------------------------------------------------------------------------------- /docs/councils/price-feed/election/Harnaik-DL.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana): 2 | Eh32UTCozwrmyhjEazGMuuWG1e2bXG2kNYmhzHRNp5Sa 3 | 4 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc): 5 | Hardware wallet 6 | 7 | - Name of Candidate 8 | - Full Name: Harnaik Kalirai 9 | - Twitter handle: silverfox8465 10 | - Discord handle: silverfox8465 11 | - Github handle: hkaliraipyth 12 | 13 | - Current Role / Ecosystem 14 | - Title: Head of Integration 15 | - Company name (if applicable) or Protocol: Douro Labs 16 | 17 | - Main Location 18 | - Continent: Europe 19 | 20 | - Motivations to participate in the Price Feed Council 21 | - To ensure robustness and reliability of new and existing price feeds on the oracle 22 | - Ensure price feeds have passed the relative conformance tests within Pythtest before going live in Pythnet. This is to prevent the risk of price deviation and correlated errors within Pythnet 23 | - To support the oracle's expansion into additional markets and diverse asset classes 24 | 25 | - Relevant Experience 26 | - 2+ Years worth experience as a key contributor to Pyth within the price feed process and technical integration of new publishers 27 | - 10+ Years experience working across multiple tradfi and crypto native firms, offering trade and technical support across all asset classes 28 | - Effectively managed global teams, ensuring alignment of price feed strategies with respect to volumes and liquidity across multiple venues 29 | -------------------------------------------------------------------------------- /docs/councils/price-feed/election/Lanre-DropCopy.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana): 3WvDj4K915DZYp2MSSqWGCYiKYuC7fBXvGkjKbBnQ3YH 2 | 3 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc): Ledger hardware wallet 4 | 5 | - Name of Candidate 6 | - Lanre 7 | - @dropcopy_io 8 | - DropCopy 9 | - DropCopy 10 | 11 | - Current Role / Ecosystem 12 | - Title: Co-Founder/ CEO 13 | - Company name (if applicable) or Protocol: DropCopy 14 | 15 | - Main Location 16 | - Continent: North America 17 | 18 | - Motivations to participate in the {Pythian, Price Feed} Council 19 | - DropCopy parimutuel prediction game is a heavy user of Pyth. We have probabably built more custom infra to distribute the Pyth data than any other system other than Pyth Network itself. 20 | - Pyth is only just scratching the surface of what's possible with the protocol. I want to see it reach its full potential 21 | 22 | - Relevant Experience 23 | - I have been a market maker with trading experience that has spanned almost every type of financial products from cash/spot to various derivative products. 24 | - I have built and certified many feed handlers from more exchanges (TradFi, CeFi, stocks, OTC) using several protocols (FIX, FAST, SBE, JSON, protobuf, ITCH/OUCH) than I like to remember. 25 | - Heavily involved in OpenBook and other DeFi projects that make use of Pyth. 26 | -------------------------------------------------------------------------------- /docs/councils/price-feed/election/Lawrence-NOBI.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana) `8v4z4pyhQ1NXZRJ3LJbkLtQirmZdxTtfqkb3vzNDLztg` 2 | 3 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc) `Ledger` 4 | 5 | - Name of Candidate 6 | - Full Name `Lawrence Samantha` 7 | - Twitter handle `@LawrenceGS` 8 | - Discord handle `LawrenceGS` 9 | - Github handle `LawrenceGS` 10 | 11 | - Current Role / Ecosystem 12 | - Title `CEO / Cofounder` 13 | - Company name (if applicable) or Protocol `NOBI` 14 | 15 | - Main Location 16 | - Continent `Asia (UTC+7)` 17 | 18 | - Motivations to participate in the Price Feed Council 19 | - NOBI has been contributing to the Pyth Network as a data provider since June 2023. 20 | - Keen to deepen our involvement by joining the Pythian Council, utilizing our market insights to support governance. 21 | - Eager to contribute further to the Pyth Price Feed's advancement. 22 | 23 | - Relevant Experience 24 | - 15+ years in tech with a foundation in Computer Science & Engineering. 25 | - 13 years of crypto experience; started by mining Bitcoin with a CPU. 26 | - Founded and successfully exited two tech startups. 27 | - Angel investor and partner at S3 Ventures. 28 | -------------------------------------------------------------------------------- /docs/councils/price-feed/election/Matt-Synthetix.md: -------------------------------------------------------------------------------- 1 | - Wallet address 2 | - 5X8etNCroWSpcWbVAqNpyyqqahvBVQPhbMdzYdB7YWWu 3 | 4 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). 5 | - Hardware Wallet 6 | 7 | - Name of Candidate 8 | - Full Name: Matt Losquadro 9 | - Twitter handle: @MattLosquadro 10 | - Discord handle: @MattSynthetix 11 | - Github handle: @MattLosquadro 12 | 13 | 14 | - Current Role / Ecosystem 15 | - Core Contributor, Synthetix 16 | 17 | - Main Location 18 | - North America 19 | 20 | - Motivations to participate in the {Pythian, Price Feed} Council 21 | - To support the Pyth Network and its growth by onboarding new feeds and supporting new features, publishers, and users. 22 | - Represent Synthetix (& SNX Ambassadors) as a major Pyth Oracles user and contributor to Pyth Network. 23 | - To help Pyth introduce new features and markets to support using new Oracle services. 24 | 25 | 26 | - Relevant Experience 27 | - 2+ years as a contributor to Synthetix, focusing on governance, communications, and partner support. 28 | - Contributors to Optimism governance as a member of OP grants council, external governance support, etc. 29 | -------------------------------------------------------------------------------- /docs/councils/price-feed/election/Nicholas-DouroLabs.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana): 2 | HTq8PB7piUrHuUC9xMSEphFRL2BAeyBLxWirxzKqzo6g 3 | 4 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc): 5 | Ledger hardware wallet 6 | 7 | - Name of Candidate 8 | - Full Name: Nicholas Diakomihalis 9 | - Twitter: @ndiakom 10 | - Discord: @ndiakom 11 | - Github: @ndiakom 12 | 13 | - Current Role / Ecosystem 14 | - Title: Senior Integration Specialist 15 | - Company name (if applicable) or Protocol: Douro Labs 16 | 17 | - Main Location 18 | - Continent: Europe 19 | 20 | - Motivations to participate in the Price Feed Council 21 | - To continue contributing towards the Pyth Network oracle by onboarding new publishers and getting feeds live 22 | - To scale Douro's integration efforts by building automations and improving processes, particularly with (but not limited to) conformance testing, publisher permissioning and feed monitoring 23 | - To ensure the quality and growth of Pyth's price feeds in order to help Pyth succeed 24 | 25 | - Relevant Experience 26 | - Nearly 1 year as a contributor to Pyth, focused on building integration automations while onboarding new publishers and getting feeds live 27 | - Nearly 15 years of experience in TradFi firms in multiple capacities such as development, integration, solution implementation, pre-sales and client onboarding 28 | -------------------------------------------------------------------------------- /docs/councils/price-feed/election/Robert-ReactorFusion.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana) 2 | HiHaM78P75gz4UBosf3bWWAuP8HoRCin4bL6qiK6dvfV 3 | 4 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc) : Hardware wallet 5 | 6 | - Name of Candidate 7 | - Full Name : Robert 8 | - Twitter handle : @ReactorFusionR 9 | - Discord handle : @robert_reactorfusion 10 | - Github handle : @ReactorFusion 11 | 12 | - Current Role / Ecosystem 13 | - Title : Head of operation 14 | - Company name (if applicable) or Protocol : ReactorFusionR 15 | 16 | - Main Location 17 | - Continent : AsiaPacific 18 | 19 | - Motivations to participate in the Price Feed Council 20 | - As a user and contributor who has consistently used and updated the price feed since the early launch of Pyth on zkSyncEra, We aim to further align with Pyth in the long term to enhance security and strengthen our partnership. 21 | - We plan to use the Pyth oracle on zkSyncEra and all of our future chain launches if possible, and we believe participating in governance will help further solidify our relationship. 22 | - Relevant Experience 23 | - Launched in April last year as zkSyncEra's leading lending protocol, we have been periodically updating the Pyth price feed through automated bots, operating the lending protocol steadily without a single incident. 24 | - We are not an individual dev team but a blockchain tech firm composed of members with 6yrs+ of DeFi experience, ensuring stable operations without financial issues and swift problem-solving capabilities. 25 | -------------------------------------------------------------------------------- /docs/councils/price-feed/election/antoine-swissborg.md: -------------------------------------------------------------------------------- 1 | - Wallet address 2 | - 3s6T3fotjMkkawXJqA5ee3QxM5589JEe4tiQtt2fsu8W 3 | 4 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). 5 | - Ledger 6 | 7 | - Name of Candidate 8 | - Antoine Bellanger 9 | - Twitter: @antoinebllngr 10 | - Discord: @antoine_borg 11 | - Github: @antoineborg 12 | 13 | - Current Role / Ecosystem 14 | - Senior DeFi Engineer, SwissBorg 15 | 16 | - Main Location 17 | - Europe 18 | 19 | - Motivations to participate in the {Pythian, Price Feed} Council 20 | - SwissBorg is a price publisher providing FX and Crypto data to Pyth. 21 | - Built a wider partnership with Pyth, listing their token on our app and having an oracle for our token. 22 | - We are very thrilled by Pyth and would love to contribute to its future. 23 | 24 | - Relevant Experience 25 | - 6+ years crypto experience 26 | - Anon core contributor of a DeFi protocol 27 | - Helped build core Crypto/DeFi features at SwissBorg ($1.2Bn+ assets, $200M+ in DeFi) 28 | -------------------------------------------------------------------------------- /docs/councils/price-feed/election/name-protocol.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana) 2 | 3 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). 4 | 5 | - Name of Candidate 6 | - Full Name 7 | - Twitter handle 8 | - Discord handle 9 | - Github handle 10 | 11 | - Current Role / Ecosystem 12 | - Title 13 | - Company name (if applicable) or Protocol 14 | 15 | - Main Location 16 | - Continent 17 | 18 | - Motivations to participate in the Price Feed Council 19 | - Max 3 points 20 | 21 | - Relevant Experience 22 | - Max 3 points 23 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/Lanre-DropCopy.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana): 3WvDj4K915DZYp2MSSqWGCYiKYuC7fBXvGkjKbBnQ3YH 2 | 3 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc): Ledger hardware wallet 4 | 5 | - Name of Candidate 6 | - Lanre 7 | - @dropcopy_io 8 | - DropCopy 9 | - DropCopy 10 | 11 | - Current Role / Ecosystem 12 | - Title: Co-Founder/ CEO 13 | - Company name (if applicable) or Protocol: DropCopy 14 | 15 | - Main Location 16 | - Continent: North America 17 | 18 | - Motivations to participate in the {Pythian, Price Feed} Council 19 | - DropCopy parimutuel prediction game is a heavy user of Pyth. We have probabably built more custom infra to distribute the Pyth data than any other system other than Pyth Network itself. 20 | - Pyth is only just scratching the surface of what's possible with the protocol. I want to see it reach its full potential 21 | 22 | - Relevant Experience 23 | - I have been a market maker with trading experience that has spanned almost every type of financial products from cash/spot to various derivative products. 24 | - I have built and certified many feed handlers from more exchanges (TradFi, CeFi, stocks, OTC) using several protocols (FIX, FAST, SBE, JSON, protobuf, ITCH/OUCH) than I like to remember. 25 | - Heavily involved in OpenBook and other DeFi projects that make use of Pyth. 26 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/Marc Tillement - Pyth Data Association.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana): 9sfogFjG8CJKJGWvNDB7NgPusALti3hWKkHGZhKmmxD7 2 | 3 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). Ledger hardware wallet 4 | 5 | - Name of Candidate 6 | - Full Name: Marc Tillement 7 | - Twitter handle: @KemarTiti 8 | - Discord handle: kemartiti 9 | - Github handle: KemarTiti 10 | 11 | - Current Role / Ecosystem 12 | - Title: Director 13 | - Company name (if applicable) or Protocol: Pyth Data Association (https://pythdataassociation.com/) 14 | 15 | - Main Location 16 | - Continent: Europe 17 | 18 | - Motivations to participate in the {Pythian, Price Feed} Council 19 | - Continue contributing towards the Pyth Network oracle 20 | - Represent a key stakeholder of the network: the Pyth Data Association 21 | - Help guide the Pyth Network towards a full decentralization and success of both data users and data contributors 22 | 23 | - Relevant Experience 24 | - ≈ 3 years contributing to the Pyth Network through my role at the Pyth Data Association — mostly focused on the DeFi footprint of the Pyth Network oracle 25 | - Engaged with external governances like Synthetix (Pyth Integration SIP Presentation), Aave and Compound, as well as with various layer-1 like Optimism (Cycle 13 grant and RPGF) 26 | - ≈ 5 years in crypto and a specific interest towards DeFi ; so attuned to the Pyth Network target market 27 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/adam-thala.md: -------------------------------------------------------------------------------- 1 | - Wallet address 2 | 7BBrwAi5tPncDxdvxrDogYhKmoujJ4uWzjMLk3qNWC7k 3 | 4 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). 5 | Will use a Ledger hardware wallet. 6 | 7 | - Name of Candidate 8 | - Adam Cader 9 | - Twitter: @adamcader_ 10 | - Discord: @adam009088 11 | - Github: @AdamCader00 12 | 13 | - Current Role / Ecosystem 14 | - Co-Founder 15 | - Thala Labs 16 | 17 | - Main Location 18 | - North America 19 | 20 | - Motivations to participate in the {Pythian, Price Feed} Council 21 | - $100m+ in TVL on Thala is secured by Pyth 22 | - Thala is a Pyth price publisher and looking to massively scale up publishing for new assets 23 | - Given this want to contribute to the governance and security of the network 24 | 25 | - Relevant Experience 26 | - Previously on the Investment Team at ParaFi Capital ($1bn+ AUM crypto-native fund). Also did some Software Engineering. 27 | - Contributor to various DAOs governance (AAVE, MakerDAO) 28 | - Was a Core Contributor to a DeFi protocol on Arbitrum 29 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/afif-synthetix.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana) 8TdeB3rUECTGg7hSH6TDS7qVAcC1QMiE5zRazTAuxLZF 2 | 3 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). Ledger hardware wallet 4 | 5 | - Name of Candidate 6 | - Afif Bandak 7 | - @0xAfif 8 | - fifa_synthetix 9 | - @aband1 10 | 11 | - Current Role / Ecosystem 12 | - Contributor 13 | - Synthetix 14 | 15 | - Main Location 16 | - North America 17 | 18 | - Motivations to participate in the Pythian Council 19 | - Synthetix is a top consumer of Pyth data with plans for further collaboration in the future. 20 | 21 | - Relevant Experience 22 | - Contributor to the Synthetix protocol for 3 years. 23 | - Expertise with onchain derivatives, oracle systems, general DeFi knowledge 24 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/antoine-swissborg.md: -------------------------------------------------------------------------------- 1 | - Wallet address 2 | - 3s6T3fotjMkkawXJqA5ee3QxM5589JEe4tiQtt2fsu8W 3 | 4 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). 5 | - Ledger 6 | 7 | - Name of Candidate 8 | - Antoine Bellanger 9 | - Twitter: @antoinebllngr 10 | - Discord: @antoine_borg 11 | - Github: @antoineborg 12 | 13 | - Current Role / Ecosystem 14 | - Senior DeFi Engineer, SwissBorg 15 | 16 | - Main Location 17 | - Europe 18 | 19 | - Motivations to participate in the {Pythian, Price Feed} Council 20 | - SwissBorg is a price publisher providing FX and Crypto data to Pyth. 21 | - Built a wider partnership with Pyth, listing their token on our app and having an oracle for our token. 22 | - We are very thrilled by Pyth and would love to contribute to its future. 23 | 24 | - Relevant Experience 25 | - 6+ years crypto experience 26 | - Anon core contributor of a DeFi protocol 27 | - Helped build core Crypto/DeFi features at SwissBorg ($1.2Bn+ assets, $200M+ in DeFi) 28 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/carnation-y2k.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana) 4dNCzhLgF2taBGk9PSMcQzep2ZBUr6qyLWWMJdfgYsBZ 2 | 3 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). Ledger hardware wallet 4 | 5 | - Name of Candidate 6 | - Full Name: Carnation 7 | - Twitter handle: @0xcarnation 8 | - Discord handle: 0xcarnation 9 | - Github handle: zeroxcarnation 10 | 11 | - Current Role / Ecosystem 12 | - Contributor, Bizdev 13 | - Y2K Finance 14 | 15 | - Main Location 16 | - North America 17 | 18 | - Motivations to participate in the Pythian Council 19 | - Y2K Finance is a consumer of numerous Pyth price feeds, especially for pegged assets 20 | - Y2K Finance actively monitors Pyth price feeds, and reports any anomalies observed 21 | - Y2K Finance is motivated to help onboard new DeFi projects to spin up Pyth feeds, so derivative markets can be offered on Y2K's platform 22 | 23 | - Relevant Experience 24 | - Contributed to several DeFi protocols for over 2 years now 25 | - Expertise with derivative products and oracle solutions 26 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/guilhem-flowdesk.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana) : DUYaUTikwfCQaRwzE1xJhTKGxAVPqKmRKY4Y2ZqsvM3k 2 | 3 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc) : Fireblocks 4 | 5 | - Name of Candidate 6 | - Full Name : Guilhem Chaumont 7 | - Twitter handle : @flowdesk_co 8 | - Discord handle : guilhemch 9 | - Github handle : guilhemch 10 | 11 | - Current Role / Ecosystem 12 | - Title : CEO 13 | - Company name : Flowdesk 14 | 15 | - Main Location 16 | - Continent : Europe 17 | 18 | - Motivations to participate in the Pythian Council 19 | Flowdesk is already contributing to the Pyth Network as a data provider since September 2022. We are eager to deepen our commitment by becoming a member of the Pythian Council, bringing our firsthand market knowledge to the governance process. 20 | 21 | 22 | - Relevant Experience 23 | - Co-founder and CEO of Flowdesk, a full-service digital asset trading and technology firm 24 | - Previously Co-founder & CEO of a privacy-centric blockchain 25 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/guillermo-douro.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana) 2 | E5KR7yfb9UyVB6ZhmhQki1rM1eBcxHvyGKFZakAC5uc 3 | 4 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). 5 | Ledger hardware wallet 6 | 7 | - Name of Candidate 8 | - Guillermo Bescos 9 | - Twitter: @guibescos_ 10 | - Discord: robinhoodrefugee 11 | - Github: @guibescos 12 | 13 | - Current Role / Ecosystem 14 | - Software Engineer 15 | - Douro Labs 16 | 17 | - Main Location 18 | - Europe 19 | 20 | - Motivations to participate in the Pythian Council 21 | - Douro Labs is a significant technical contributor to the Pyth protocol 22 | - An effective DAO and council are critical to the success of the network, I want to help the council with my technical expertise. 23 | 24 | - Relevant Experience 25 | - I have been working on the Pyth codebase for over 2 years and I built most of the governance software stack for the Pyth DAO. 26 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/jayant-douro.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana) 2 | 3ouXHhNH6QgyxDRLmjgCddbTqYBkPSufLiLgTp2BVy2p 3 | 4 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). 5 | Ledger hardware wallet 6 | 7 | - Name of Candidate 8 | - Jayant Krishnamurthy 9 | - Twitter: @jayantkrish 10 | - Discord: otiosetortoise 11 | - Github: @jayantk 12 | 13 | - Current Role / Ecosystem 14 | - Co-founder and CTO 15 | - Douro Labs 16 | 17 | - Main Location 18 | - North America 19 | 20 | - Motivations to participate in the Pythian Council 21 | - Douro Labs is a significant technical contributor to the Pyth protocol 22 | - Given the above, I can help the council with any significant technical decisions 23 | 24 | - Relevant Experience 25 | - I have been contributing software to Pyth for over 2 years now, working across all parts of the codebase. 26 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/khalil-PythCommunity: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana): 6JaiQffmypVaqdRDM7GKjKc4XoEkHnZuBqL1GSwNPWzZ 2 | - Describe how you will custody your private key: An offline paper based cold storage solution. 3 | 4 | - Name of Candidate 5 | - Full Name: Khalil Abuhussain 6 | - Twitter handle: @Abuhussaink 7 | - Discord handle: @fbahrain 8 | - Github handle: @Abuhussaink 9 | 10 | - Current Role / Ecosystem 11 | - Title: Ambassador 12 | - Protocol: Pyth Network 13 | 14 | - Main Location 15 | A GCC Nation within the Middle East. 16 | 17 | - Motivations to participate in the Pythian Council 18 | As a long standing community member I understand how the Community is involved and drives the Pyth Network. This unique perspective allows me to assist in driving innovation within the network and ensuring that all stakeholders are considered in major decisions. 19 | In my professional life I am a Cyber Security analyst. This not only allows me to have high personal standards but will also provide benefits to the Council and the Pyth Network as a whole. 20 | I am standing for election because I have a strongly held view that the entire diverse membership of Pyth Network should be represented on the Council. 21 | 22 | 23 | - Relevant Experience 24 | I understand the importance of active engagement in shaping the Pyth Network’s future. Because of my ability and experience in fostering discussions, addressing queries, and promoting awareness of Pyth I was awarded the role of Ambassador in 2023. 25 | With 15 years of experience as IT technician and consultant, alongside my cybersecurity qualifications, I bring a wealth of knowledge in securing digital systems. Specifically, my expertise lies in safeguarding data, mitigating risks, and ensuring the integrity of critical infrastructure. 26 | I now have years of experience in Web3 and in DeFi. My enthusiasm for this transformative technology is unwavering. I aspire to continue to contribute to the decentralized finance (DeFi) world and drive innovation through effective collaboration, transparent dialogue and consensus-driven decision-making. 27 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/naive: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana): 62ptfWpHmhjwsxRAHJZqQVdR3QNBptW8FJ3K1wJB2e7g 2 | 3 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). Ledger hardware wallet 4 | 5 | - Name of Candidate 6 | - naive 7 | - Twitter handle: @iiinaive 8 | - Discord handle: _naive_ 9 | - Github handle: iamnaive 10 | 11 | - Current Role / Ecosystem 12 | - Title: Pyth Ambassador 13 | 14 | - Main Location 15 | - Continent: Asia 16 | 17 | - Motivations to participate in the {Pythian, Price Feed} Council 18 | Pyth is an infrastructure that improves the space around. I want to be here, keep an eye on it, and hold my bags. 19 | 20 | - Relevant Experience 21 | I don't have similar experience that would fit this position, but I've been in crypto since 2019 and have expertise, insight, and understand the trends. 22 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/nope-solend.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana) 2 | - 38XhwqCgQGdeNeXyHQ94Jrdsoup71G6ioiYuvimXwH3z 3 | 4 | - Describe how you will custody your private key 5 | - Ledger with backup 6 | 7 | - Name of Candidate 8 | - Name: Nope 9 | - Twitter: nope_sol 10 | - Discord: nopesol 11 | - Github: nope-finance 12 | 13 | - Current Role / Ecosystem 14 | - CTO 15 | - Solend 16 | 17 | - Main Location 18 | - North America 19 | 20 | - Motivations to participate in the {Pythian, Price Feed} Council 21 | - Solend is a longtime user of pyth 22 | - Ensure safety and quality of pyth feeds for everyone in the ecosystem 23 | - Be able to advise on any upcoming pyth features 24 | 25 | - Relevant Experience 26 | - Building on Solana using pyth for almost 3 years 27 | - Serve on ecosystem multisigs for spl-stake-pool, openbook-dex, and marinade 28 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/robinson-wormhole.md: -------------------------------------------------------------------------------- 1 | **Wallet address (SPL/Solana)** 9uDaJ8nG4yhxdhLnaNqARa1d3DjaCT2X7L7LtDR2NQUH 2 | 3 | **Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc).** Will use ledger hardware wallet 4 | 5 | **Name of Candidate** 6 | 7 | - Twitter handle @robinsonburkey 8 | - Discord handle robinsonburkey 9 | - Github handle robinsonburkey 10 | 11 | **Current Role / Ecosystem** 12 | 13 | - Title CCO and Co-Founder 14 | - Company name (if applicable) or Protocol Wormhole Foundation 15 | 16 | **Main Location** 17 | 18 | - Continent Europe 19 | 20 | **Motivations to participate in the {Pythian, Price Feed} Council** 21 | 22 | Wormhole is critical infrastructure for the Pyth Network and as such, it would be beneficial to participate in the governance, direction, and security of the pyth network 23 | 24 | **Relevant Experience** 25 | 26 | - Lead the Wormhole Foundation, a core contributor to the Wormhole Protocol 27 | - Previously core contributor to Acala Network 28 | - Participate in various DAOs across evm and non-evm chains 29 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/shu-hmx.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana) 2 | CTojzJc5FRs6vjD7Z2g2gWPvQ3vEsHw4ykw5LYmU8aPC 3 | 4 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). 5 | Ledger Nano X 6 | 7 | - Name of Candidate 8 | - Full Name: Shu Tsu Wei 9 | - Twitter handle: https://twitter.com/shutsuwei88 10 | - Discord handle: shutsuwei 11 | - Github handle: https://github.com/shutsuwei 12 | 13 | - Current Role / Ecosystem 14 | - Contributor 15 | - HMX 16 | 17 | - Main Location 18 | - Asia 19 | 20 | - Motivations to participate in the {Pythian, Price Feed} Council 21 | - HMX has been a significant consumer of the Pyth Price Feed from day one, supporting more than 100,000 trades and integrating Pyth in new and unique ways. 22 | - Improve oracle stability and shorten the feedback loop from actual consumers to further grow the Pyth Ecosystem. 23 | - Help design an incentive program for protocols that utilize Pyth, aiming to grow key metrics and statistics 24 | 25 | - Relevant Experience 26 | - ~6 years experience building DeFi 27 | - Expertise in Smart Contract development, delivered protocol that proceced more than $13 billions in trading volume. 28 | - ~6 months of continuously working closely with Pyth core team to improve Pyth Price Feed. 29 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/1/travis-vela.md: -------------------------------------------------------------------------------- 1 | - Wallet address 2 | PhvHHzfLj8znqWHCoLNjEaGa6ZzHeNGgva3iei212nn 3 | 4 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). 5 | Offline / Cold Storage 6 | 7 | - Name of Candidate 8 | - Travis Skweres 9 | - Twitter: @TravDoesDeFi 10 | - Discord: @tskweres 11 | - Github: @tskweres 12 | 13 | - Current Role / Ecosystem 14 | - Co-Founder 15 | - Vela Exchange 16 | 17 | - Main Location 18 | - North America 19 | 20 | - Motivations to participate in the {Pythian, Price Feed} Council 21 | - Vela Exchange is a top consumer of Pyth off-chain & on-chain data with $Billions traded 22 | - Active price publisher, preparing to be a data provider as well 23 | - Heavily invested in the Pyth ecosystem, its security & success 24 | 25 | - Relevant Experience 26 | - Vela Co-Founder & Chief Architect 27 | - 11+ years experience in crypto & DeFi 28 | - Anon developer & contributor to several dapps / DeFi projects 29 | -------------------------------------------------------------------------------- /docs/councils/pythian/election/name-protocol.md: -------------------------------------------------------------------------------- 1 | - Wallet address (SPL/Solana) 2 | 3 | - Describe how you will custody your private key (hardware wallet, MPC provider such as Fireblocks, etc). 4 | 5 | - Name of Candidate 6 | - Full Name 7 | - Twitter handle 8 | - Discord handle 9 | - Github handle 10 | 11 | - Current Role / Ecosystem 12 | - Title 13 | - Company name (if applicable) or Protocol 14 | 15 | - Main Location 16 | - Continent 17 | 18 | - Motivations to participate in the {Pythian, Price Feed} Council 19 | - Max 3 points 20 | 21 | - Relevant Experience 22 | - Max 3 points 23 | -------------------------------------------------------------------------------- /docs/pips/constitutional/pip-0.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/docs/pips/constitutional/pip-0.md -------------------------------------------------------------------------------- /docs/pips/constitutional/pip-1.md: -------------------------------------------------------------------------------- 1 | # PIP-1 : Adoption of the Pyth DAO Constitution 2 | 3 | - Abstract: 4 | - This document ("PIP-1”) proposes the adoption of a decision framework for the Pyth DAO and the governing framework for the holders of $PYTH, the “Pyth DAO Constitution”, as formulated here: https://github.com/pyth-network/governance/blob/309c716599617971c6abb18f2097412d977b954f/docs/constitution/pyth-dao-constitution.md 5 | - Once the Pyth DAO Constitution adopted, the Pyth DAO will operate as outlined in the constitution. 6 | 7 | - Rationale: 8 | - The Pyth DAO should adopt a clear framework regarding its governance, operations, and processes. The framework should fully inform its members of how the DAO functions and how $PYTH token holders relate to the DAO. 9 | - Through PIP-1, the Pyth DAO will have the ability to submit subsequent Pyth Improvement Proposals (“**PIPs**”), collectively decide on them and implement them, and delegate certain responsibilities to elected councils. 10 | 11 | - Key Terms: 12 | 13 | PIP-1 proposes to adopt the Pyth DAO Constitution, a framework that: 14 | 15 | - sets rules and procedures on the submission and voting on PIPs 16 | - suggests the formation of two councils: the Pythian Council and the Price Feed Council which are respectively responsible for the voting and implementation of certain Operational PIPs as defined in the proposed constitution 17 | - lists a set of values and principles for the Pyth DAO community centered around ownership, governance, and inclusivity for the long-term benefit of the Pyth DAO 18 | 19 | - Implementation Plan: 20 | - Approval of PIP-1 21 | 22 | - Timeline: 23 | - PIP-1 is subject to a 7-day on-chain voting period from the time of posting in the Pyth Forum 24 | -------------------------------------------------------------------------------- /docs/pips/operational/op-pip-3.md: -------------------------------------------------------------------------------- 1 | # Operational PIP 3: Deactivate stake accounts (Pythian Council) 2 | 3 | ## Abstract 4 | 5 | Deactivate stake accounts associated with inactive validators on Pythnet. 6 | 7 | ## Rationale 8 | 9 | Inactive validators interfere with block production. 10 | 11 | ## Description 12 | 13 | Two Pythnet validators have been offline for multiple epochs. The operators of these two validators are unable to bring them back up. In order to reduce the Pythnet deliquent stake, we will deactivate the stake delegated to their vote accounts. 14 | 15 | Validators: 16 | - Identity: `2fCDTVa93dLzoRKPdKNn95okqwc5h6baqZ7GCv4neDnA` 17 | - Vote account: `5rvjiFE2hXYDXABREEk4L8nhQGHjU7QreVsd45jpsVJE` 18 | - Stake account: `GaWbR88EMcU1vRyxkQmaPnEm6aJ8V3W1nEVUT1jqqWvs` 19 | - Identity: `HoxprZizumTpg4LEXQiHu5s7y7Twcqvsh6QjukobSqV9` 20 | - Vote account: `FLVYvcVxuzMcZpnXchLz4RMBEGkFPysHFnw6Uy7CYLkw` 21 | - Stake account: `WWd3NK3PmMkQAx9sEGRDQvxTwtAJ8JdukeheCSoLr3A` 22 | 23 | ## Implementation Plan 24 | 25 | * Proposal id: [`DGPSH5fBSSHegaESeUP8L8KxY1R2CN3fZDQMchaqhAUp`](https://proposals.pyth.network/?tab=proposals&proposal=DGPSH5fBSSHegaESeUP8L8KxY1R2CN3fZDQMchaqhAUp) 26 | 27 | 28 | * Verify the implementation following the guide below: 29 | 30 | 1. Make sure you have the Solana CLI. You can install it [here](https://docs.solanalabs.com/cli/install) 31 | 2. Find out the `Vote Account` keys of the two deliquent validators by running `solana -u https://pythnet.rpcpool.com validators` in your terminal. The deliquent validators are indicated with a warning sign. 32 | 3. Find out their `Stake Pubkey`s by running `solana -u https://pythnet.rpcpool.com stakes ` 33 | 4. Check that the `Stake Pubkey`s from step 3 match the `stakePubkey`s in [the proposal UI](https://proposals.pyth.network/?tab=proposals&proposal=DGPSH5fBSSHegaESeUP8L8KxY1R2CN3fZDQMchaqhAUp). 34 | -------------------------------------------------------------------------------- /docs/pips/operational/op-pip-4.md: -------------------------------------------------------------------------------- 1 | # Operational PIP 4: Accept Pyth Solana receiver governance authority 2 | 3 | ## Abstract 4 | 5 | Accept Pyth Solana receiver governance authority to manage the contract. 6 | 7 | ## Rationale 8 | 9 | The Pythian council need to accept the governance authority from the current owner of the Pyth Solana receiver contract to manage the contract. 10 | This is a requirement for official announcement of the contract. 11 | 12 | ## Description 13 | 14 | Pyth Solana receiver contract has been deployed and tested at the 15 | [`rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ`](https://solscan.io/account/rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ) address. 16 | 17 | The upgrade authority of the contract has been already transferred to the Pythian council and a request has been made to transfer 18 | the governance authority to the Pythian council. 19 | 20 | ## Implementation Plan 21 | 22 | * Proposal id: [`5VH55mD4NhsYpetUiVo89kHjVopgxnuF5ZHXkcatC9EE`](https://proposals.pyth.network/?tab=proposals&proposal=5VH55mD4NhsYpetUiVo89kHjVopgxnuF5ZHXkcatC9EE) 23 | 24 | * Verify the implementation following the guide below: 25 | 26 | 1. Check out the instruction to accept the governance authority from 27 | [here](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs#L82-L91). 28 | Also, some constraints are checked in the context definition 29 | [here](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs#L276-L285). 30 | -------------------------------------------------------------------------------- /docs/pips/operational/op-pip-9.md: -------------------------------------------------------------------------------- 1 | # Operational PIP 9: Add Asymmetric Research as a Pythnet validator 2 | 3 | ## Abstract 4 | 5 | This proposal seeks the delegation of a Pythnet stake account to Asymmetric Research to enable their participation as a 6 | Pythnet validator. 7 | 8 | ## Rationale 9 | 10 | Validator diversity is crucial for network resilience and security. Asymmetric Research, having substantial experience 11 | in blockchain validation and security, aims to enhance the robustness and reliability of Pythnet by joining as a 12 | validator. Their history as a Pyth security core contributor further aligns with the network's goals for enhanced 13 | security and trusted validation processes. 14 | 15 | ## Description 16 | 17 | Pythnet, Pyth's appchain, is governed by the Pythian Council, which manages stake accounts and validator roles. 18 | 19 | [Asymmetric Research](https://www.asymmetric.re/), a boutique security venture, is deeply committed to its partnerships 20 | with L1/L2 blockchains and DeFi protocols, striving to ensure their safety. Their significant contributions to securing 21 | Pyth's infrastructure as a Pyth security core contributor are a testament to this commitment. By stepping into the 22 | validator role, Asymmetric Research aims to further extend its dedication to maintaining a secure and resilient network. 23 | 24 | 25 | ## Implementation Plan 26 | 27 | * Proposal id: [`DCCzA6f25fR7VGXfW9LL6WjyAYQMg42B8EiZmueRvDF2`](https://proposals.pyth.network/?tab=proposals&proposal=DCCzA6f25fR7VGXfW9LL6WjyAYQMg42B8EiZmueRvDF2) 28 | 29 | ### Verification 30 | 31 | Verify the implementation following the guide below: 32 | 33 | 1. Check that the stake account (``) is an undelegated stake account. You can do this by running 34 | `solana -u https://pythnet.rpcpool.com stakes` in your terminal and looking for ``. 35 | 2. Check that the associated vote account (`) is the key provided by Asymmetric Research as a part of this proposal: 36 | `AR1vDzBzaq1nD19naVMfgTKeM5ifHd7rbxdvyMuG6njg` 37 | -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"] 3 | } 4 | -------------------------------------------------------------------------------- /frontend/.env.sample: -------------------------------------------------------------------------------- 1 | ENDPOINT="https://api.devnet.solana.com" 2 | BACKEND_ENDPOINT="https://api.devnet.solana.com" 3 | CLUSTER="devnet" 4 | -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | 25 | # env 26 | .env 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | package.json 3 | package-lock.json 4 | public 5 | staking/target 6 | .next/ 7 | node_modules/ 8 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | ## Getting started 2 | 3 | ### 0. Dependecies 4 | 5 | The versions are the ones that work for me locally. 6 | 7 | - [Rust](https://www.rust-lang.org/tools/install) 8 | - [Solana CLI tools](https://docs.solana.com/cli/install-solana-cli-tools) (`v1.18.16`) 9 | - [Anchor CLI](https://www.anchor-lang.com/docs/installation) (`v0.29.0`) 10 | - [Node](https://github.com/nvm-sh/nvm)(`v18.19.1`) 11 | - [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/)(`v0.12.1`) 12 | 13 | ### 1. Install and build 14 | 15 | From the root directory : 16 | 17 | ```bash 18 | npm install 19 | npx lerna run build --scope=@pythnetwork/staking 20 | ``` 21 | 22 | ### 2. Start the test validator 23 | 24 | ```bash 25 | cd staking 26 | npm run localnet 27 | ``` 28 | 29 | This command spawns a validator at `http://localhost:8899` with the staking program deployed in the genesis block at the address : 30 | `pytS9TjG1qyAZypk7n8rw8gfW9sUaqqYyMhJQ4E7JCQ` 31 | 32 | Additionally it will : 33 | 34 | - Initialize and configure the staking program 35 | - Create the Pyth token at the address in `staking/app/keypairs/pyth_mint.json` 36 | - Airdrop SOL to two hypothetical users Alice and Bob (their keys are in `staking/app/keypairs/`) 37 | - Airdrop Pyth tokens to Alice and Bob 38 | - Create some stake accounts for Alice and Bob and deposit some tokens 39 | 40 | ### 3. Run the frontend in dev mode 41 | 42 | Once that's done, keep the process running. Open a new terminal, change directory to `frontend/` and run: 43 | 44 | ```bash 45 | npm run dev 46 | ``` 47 | -------------------------------------------------------------------------------- /frontend/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import * as React from 'react' 3 | import Footer from './Footer' 4 | import Header from './Header' 5 | 6 | const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => { 7 | return ( 8 |
9 |
10 |
{children}
11 | 15 |
16 |
17 | ) 18 | } 19 | 20 | Layout.propTypes = { 21 | children: PropTypes.node.isRequired, 22 | } 23 | 24 | export default Layout 25 | -------------------------------------------------------------------------------- /frontend/components/SEO.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import React from 'react' 3 | import config from './config' 4 | 5 | const SEO: React.FC<{ title: string }> = ({ title }) => { 6 | const siteTitle = config.title 7 | const description = config.description 8 | 9 | return ( 10 | 11 | {`${title} | ${siteTitle}`} 12 | 13 | 14 | 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | export default SEO 22 | -------------------------------------------------------------------------------- /frontend/components/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Spinner = () => { 4 | return ( 5 | 11 | 19 | 24 | 25 | ) 26 | } 27 | export default Spinner 28 | -------------------------------------------------------------------------------- /frontend/components/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import Tippy from '@tippyjs/react' 2 | import React, { ReactNode } from 'react' 3 | import 'tippy.js/animations/scale.css' 4 | 5 | type TooltipProps = { 6 | content: ReactNode 7 | placement?: any 8 | className?: string 9 | children?: ReactNode 10 | contentClassName?: string 11 | } 12 | 13 | const Tooltip = ({ 14 | children, 15 | content, 16 | className, 17 | contentClassName, 18 | placement = 'top', 19 | }: TooltipProps) => { 20 | return content ? ( 21 | document.body} 25 | maxWidth="15rem" 26 | interactive 27 | content={ 28 |
31 | {content} 32 |
33 | } 34 | > 35 |
{children}
36 |
37 | ) : ( 38 | <>{children} 39 | ) 40 | } 41 | 42 | const Content: React.FC<{ className: string; children: React.ReactNode }> = ({ 43 | className = '', 44 | children, 45 | }) => { 46 | return
{children}
47 | } 48 | 49 | Tooltip.Content = Content 50 | 51 | export default Tooltip 52 | -------------------------------------------------------------------------------- /frontend/components/WalletModalButton.tsx: -------------------------------------------------------------------------------- 1 | import { WalletModalButton as SolanaWalletModalButton } from '@solana/wallet-adapter-react-ui' 2 | 3 | export function WalletModalButton() { 4 | return ( 5 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /frontend/components/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Pyth Network', 3 | author: { 4 | name: 'Pyth Network', 5 | }, 6 | description: 7 | 'Pyth is building a way to deliver a decentralized, cross-chain market of verifiable data from high-quality nodes to any smart contract, anywhere.', 8 | } 9 | -------------------------------------------------------------------------------- /frontend/components/icons/CloseIcon.tsx: -------------------------------------------------------------------------------- 1 | const CloseIcon = () => { 2 | return ( 3 | 10 | 14 | 15 | ) 16 | } 17 | 18 | export default CloseIcon 19 | -------------------------------------------------------------------------------- /frontend/components/icons/DiscordIcon.tsx: -------------------------------------------------------------------------------- 1 | const DiscordIcon = () => { 2 | return ( 3 | 10 | 14 | 15 | ) 16 | } 17 | 18 | export default DiscordIcon 19 | -------------------------------------------------------------------------------- /frontend/components/icons/GithubIcon.tsx: -------------------------------------------------------------------------------- 1 | const GithubIcon = () => { 2 | return ( 3 | 10 | 16 | 17 | ) 18 | } 19 | 20 | export default GithubIcon 21 | -------------------------------------------------------------------------------- /frontend/components/icons/InfoIcon.tsx: -------------------------------------------------------------------------------- 1 | const InfoIcon = () => { 2 | return ( 3 | 9 | 16 | 17 | ) 18 | } 19 | 20 | export default InfoIcon 21 | -------------------------------------------------------------------------------- /frontend/components/icons/LockedIcon.tsx: -------------------------------------------------------------------------------- 1 | const LockedIcon = () => { 2 | return ( 3 | 10 | 17 | 21 | 25 | 26 | ) 27 | } 28 | 29 | export default LockedIcon 30 | -------------------------------------------------------------------------------- /frontend/components/icons/SiteIcon.tsx: -------------------------------------------------------------------------------- 1 | const SiteIcon = () => { 2 | return ( 3 | 10 | 17 | 18 | ) 19 | } 20 | 21 | export default SiteIcon 22 | -------------------------------------------------------------------------------- /frontend/components/icons/TelegramIcon.tsx: -------------------------------------------------------------------------------- 1 | const UnvestedIcon = () => { 2 | return ( 3 | 10 | 14 | 15 | ) 16 | } 17 | 18 | export default UnvestedIcon 19 | -------------------------------------------------------------------------------- /frontend/components/icons/TwitterIcon.tsx: -------------------------------------------------------------------------------- 1 | const TwitterIcon = () => { 2 | return ( 3 | 10 | 14 | 15 | ) 16 | } 17 | 18 | export default TwitterIcon 19 | -------------------------------------------------------------------------------- /frontend/components/icons/UnlockedIcon.tsx: -------------------------------------------------------------------------------- 1 | const UnlockedIcon = () => { 2 | return ( 3 | 10 | 17 | 21 | 27 | 28 | ) 29 | } 30 | 31 | export default UnlockedIcon 32 | -------------------------------------------------------------------------------- /frontend/components/icons/UnvestedIcon.tsx: -------------------------------------------------------------------------------- 1 | const UnvestedIcon = () => { 2 | return ( 3 | 10 | 17 | 21 | 22 | ) 23 | } 24 | 25 | export default UnvestedIcon 26 | -------------------------------------------------------------------------------- /frontend/components/icons/YoutubeIcon.tsx: -------------------------------------------------------------------------------- 1 | const YoutubeIcon = () => { 2 | return ( 3 | 10 | 14 | 15 | ) 16 | } 17 | 18 | export default YoutubeIcon 19 | -------------------------------------------------------------------------------- /frontend/components/modals/BaseModal.tsx: -------------------------------------------------------------------------------- 1 | import CloseIcon from '@components/icons/CloseIcon' 2 | import { Dialog, Transition } from '@headlessui/react' 3 | import { Fragment, ReactNode } from 'react' 4 | 5 | export type BaseModalProps = { 6 | isModalOpen: boolean 7 | setIsModalOpen: (open: boolean) => void 8 | title: string 9 | children: ReactNode 10 | } 11 | 12 | export function BaseModal({ 13 | isModalOpen, 14 | setIsModalOpen, 15 | title, 16 | children, 17 | }: BaseModalProps) { 18 | const closeModal = () => setIsModalOpen(false) 19 | 20 | return ( 21 | 22 | 23 | 32 |
33 | 34 |
35 |
36 | 45 | 46 | 49 |
50 | 51 | {title} 52 | 53 | {children} 54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /frontend/components/modals/LockedTokenActionModal.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ActionButton, 3 | AmountInput, 4 | AmountInputLabel, 5 | } from '@components/panels/components' 6 | import { BaseModal } from './BaseModal' 7 | import { PythBalance } from '@pythnetwork/staking' 8 | import { useState } from 'react' 9 | import { validAmountChange } from 'utils/validAmountChange' 10 | import { useStakeConnection } from 'hooks/useStakeConnection' 11 | import { isSufficientBalance as isSufficientBalanceFn } from 'utils/isSufficientBalance' 12 | import { MainStakeAccount } from 'pages' 13 | 14 | type LockedTokenActionModal = { 15 | isModalOpen: boolean 16 | setIsModalOpen: (open: boolean) => void 17 | title: string 18 | actionLabel: string 19 | mainStakeAccount: MainStakeAccount 20 | balance: PythBalance 21 | onAction: (amount: string) => void 22 | } 23 | 24 | export function LockedTokenActionModal({ 25 | isModalOpen, 26 | setIsModalOpen, 27 | mainStakeAccount, 28 | title, 29 | actionLabel, 30 | balance, 31 | onAction, 32 | }: LockedTokenActionModal) { 33 | const [amount, setAmount] = useState('') 34 | const handleAmountChange = (amount: string) => { 35 | if (validAmountChange(amount)) setAmount(amount) 36 | } 37 | const { data: stakeConnection } = useStakeConnection() 38 | const isSufficientBalance = isSufficientBalanceFn(amount, balance) 39 | return ( 40 | 45 | {' '} 46 | <> 47 | <> 48 | 53 | 54 | 55 | { 58 | onAction(amount) 59 | }} 60 | isActionDisabled={ 61 | !isSufficientBalance || 62 | mainStakeAccount === undefined || 63 | stakeConnection === undefined 64 | } 65 | isActionLoading={false} 66 | tooltipContentOnDisabled={'Insufficient balance'} 67 | /> 68 | 69 | 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /frontend/components/modals/ProfileModal.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { BaseModal } from './BaseModal' 3 | 4 | export type ProfileModalProps = { 5 | isProfileModalOpen: boolean 6 | setIsProfileModalOpen: (open: boolean) => void 7 | } 8 | export function ProfileModal({ 9 | isProfileModalOpen, 10 | setIsProfileModalOpen, 11 | }: ProfileModalProps) { 12 | return ( 13 | 18 |

19 | $PYTH stakers and governance participants can now map their EVM wallet 20 | addresses to their Solana (SPL) addresses. 21 |

{' '} 22 |

23 | Click{' '} 24 | 25 | here 26 | {' '} 27 | to set up your Pyth Profile. 28 |

29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /frontend/components/modals/StakedModal.tsx: -------------------------------------------------------------------------------- 1 | import { PythBalance } from '@pythnetwork/staking' 2 | import { BaseModal } from './BaseModal' 3 | 4 | type StakedModalProps = { 5 | isStakedModalOpen: boolean 6 | setIsStakedModalOpen: (open: boolean) => void 7 | stakedPythBalance?: PythBalance 8 | stakingPythBalance?: PythBalance 9 | } 10 | export function StakedModal({ 11 | isStakedModalOpen, 12 | setIsStakedModalOpen, 13 | stakedPythBalance, 14 | stakingPythBalance, 15 | }: StakedModalProps) { 16 | return ( 17 | <> 18 | 23 |

24 | Staked tokens enable you to participate in Pyth Network governance. 25 | Newly-staked tokens become eligible to vote in governance at the 26 | beginning of the next epoch. (Epochs start every Thursday at 00:00 27 | UTC). 28 |

29 |

30 | You currently have {stakedPythBalance?.toString()} staked tokens. 31 |

32 | {stakingPythBalance && !stakingPythBalance.isZero() ? ( 33 |

34 | {stakingPythBalance.toString()} tokens will be staked from the 35 | beginning of the next epoch. 36 |

37 | ) : null} 38 |
39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /frontend/components/modals/UnstakedModal.tsx: -------------------------------------------------------------------------------- 1 | import { PythBalance } from '@pythnetwork/staking' 2 | import { BaseModal } from './BaseModal' 3 | 4 | export type UnstakedModalProps = { 5 | isUnstakedModalOpen: boolean 6 | setIsUnstakedModalOpen: (open: boolean) => void 7 | unstakedPythBalance?: PythBalance 8 | unstakingPythBalance?: PythBalance 9 | } 10 | export function UnstakedModal({ 11 | isUnstakedModalOpen, 12 | setIsUnstakedModalOpen, 13 | unstakedPythBalance, 14 | unstakingPythBalance, 15 | }: UnstakedModalProps) { 16 | return ( 17 | 22 |

23 | Unstaking tokens enables you to withdraw them from the program after a 24 | cooldown period of one epoch once the current epoch ends. (Epochs start 25 | every Thursday at 00:00 UTC and last 7 days). Unstaked tokens cannot 26 | participate in governance. 27 |

28 |

29 | You currently have {unstakedPythBalance?.toString()} unstaked tokens. 30 |

31 | {unstakingPythBalance && !unstakingPythBalance.isZero() ? ( 32 |

33 | {unstakingPythBalance.toString()} tokens have to go through a cooldown 34 | period for one full epoch before they can be withdrawn. 35 |

36 | ) : null} 37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /frontend/components/panels/components/ActionButton.tsx: -------------------------------------------------------------------------------- 1 | import Spinner from '@components/Spinner' 2 | import Tooltip from '@components/Tooltip' 3 | 4 | export type ActionButtonProps = { 5 | actionLabel: string 6 | onAction: () => void 7 | isActionDisabled: boolean | undefined 8 | isActionLoading: boolean | undefined 9 | tooltipContentOnDisabled?: string 10 | } 11 | export function ActionButton({ 12 | actionLabel, 13 | onAction, 14 | isActionDisabled, 15 | isActionLoading, 16 | tooltipContentOnDisabled, 17 | }: ActionButtonProps) { 18 | return ( 19 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /frontend/components/panels/components/AmountInput.tsx: -------------------------------------------------------------------------------- 1 | export type AmountInputProps = { 2 | amount: string 3 | onAmountChange: (amount: string) => void 4 | } 5 | export function AmountInput({ amount, onAmountChange }: AmountInputProps) { 6 | return ( 7 | onAmountChange(e.target.value)} 14 | className="input-no-spin mb-8 mt-1 block h-14 w-full rounded-full bg-darkGray4 px-4 text-center text-lg font-semibold focus:outline-none" 15 | /> 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /frontend/components/panels/components/AmountInputLabel.tsx: -------------------------------------------------------------------------------- 1 | import { PythBalance } from '@pythnetwork/staking' 2 | import BN from 'bn.js' 3 | 4 | export type AmountInputLabelProps = { 5 | balance: PythBalance | undefined 6 | isBalanceLoading: boolean 7 | setAmount: (amount: string) => void 8 | } 9 | export function AmountInputLabel({ 10 | setAmount, 11 | isBalanceLoading, 12 | balance, 13 | }: AmountInputLabelProps) { 14 | // set input amount to half of pyth balance in wallet 15 | const handleHalfBalanceClick = () => { 16 | if (balance) { 17 | setAmount(new PythBalance(balance.toBN().div(new BN(2))).toString()) 18 | } 19 | } 20 | 21 | // set input amount to max of pyth balance in wallet 22 | const handleMaxBalanceClick = () => { 23 | if (balance) { 24 | setAmount(balance.toString()) 25 | } 26 | } 27 | 28 | return ( 29 |
30 | 33 |
34 | {isBalanceLoading ? ( 35 |
36 | ) : ( 37 |

Balance: {balance?.toString()}

38 | )} 39 |
40 | 46 | 52 |
53 |
54 |
55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /frontend/components/panels/components/Description.tsx: -------------------------------------------------------------------------------- 1 | export function Description({ children }: { children: string }) { 2 | return
{children}
3 | } 4 | -------------------------------------------------------------------------------- /frontend/components/panels/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | export function Layout({ children }: { children: ReactNode }) { 4 | return ( 5 |
{children}
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /frontend/components/panels/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ActionButton' 2 | export * from './AmountInput' 3 | export * from './AmountInputLabel' 4 | export * from './Description' 5 | export * from './Layout' 6 | -------------------------------------------------------------------------------- /frontend/hooks/useDepositMutation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PythBalance, 3 | StakeAccount, 4 | StakeConnection, 5 | } from '@pythnetwork/staking' 6 | import toast from 'react-hot-toast' 7 | import { StakeConnectionQueryKey } from './useStakeConnection' 8 | import { useMutation, useQueryClient } from 'react-query' 9 | 10 | export function useDepositMutation() { 11 | const queryClient = useQueryClient() 12 | 13 | return useMutation( 14 | ['deposit-mutation'], 15 | async ({ 16 | amount, 17 | stakeConnection, 18 | mainStakeAccount, 19 | }: { 20 | amount: string 21 | stakeConnection: StakeConnection 22 | mainStakeAccount: StakeAccount | 'NA' 23 | }) => { 24 | if (!amount) { 25 | throw new Error('Please enter a valid amount!') 26 | } 27 | const depositAmount = PythBalance.fromString(amount) 28 | if (depositAmount.gt(PythBalance.zero())) { 29 | await stakeConnection?.depositAndLockTokens( 30 | // Throughout the website we have used mainStakeAccount is 'NA' if there is no 31 | // prev mainStakeAccount. It is undefined if things are loading. 32 | // It is defined if there is one 33 | // But this library method doesn't make that distinction. 34 | // We are handling this disparity here only where the two codebase meet. 35 | mainStakeAccount === 'NA' ? undefined : mainStakeAccount, 36 | depositAmount 37 | ) 38 | toast.success(`Deposit and locked ${amount} PYTH tokens!`) 39 | } else { 40 | throw new Error('Amount must be greater than 0.') 41 | } 42 | }, 43 | { 44 | onSuccess() { 45 | // invalidate all except stake connection 46 | queryClient.invalidateQueries({ 47 | predicate: (query) => query.queryKey[0] !== StakeConnectionQueryKey, 48 | }) 49 | }, 50 | onError(error: Error) { 51 | toast.error(error.message) 52 | }, 53 | } 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /frontend/hooks/useNextVestingEvent.ts: -------------------------------------------------------------------------------- 1 | import { PythBalance } from '@pythnetwork/staking' 2 | import BN from 'bn.js' 3 | import { useQuery } from 'react-query' 4 | import { useStakeConnection } from './useStakeConnection' 5 | import { MainStakeAccount } from 'pages' 6 | 7 | export function useNextVestingEvent(mainStakeAccount: MainStakeAccount) { 8 | const { data: stakeConnection } = useStakeConnection() 9 | 10 | return useQuery( 11 | ['next-vesting-event', mainStakeAccount], 12 | // enabled only when stakeConnection and mainStakeAccount is defined 13 | async () => { 14 | if (mainStakeAccount === 'NA') return undefined 15 | 16 | const currentTime = await stakeConnection!.getTime() 17 | const nextVestingEvent = mainStakeAccount!.getNextVesting(currentTime) 18 | if (nextVestingEvent) { 19 | return { 20 | nextVestingAmount: new PythBalance( 21 | new BN(nextVestingEvent.amount.toString()) 22 | ), 23 | nextVestingDate: new Date(Number(nextVestingEvent.time) * 1000), 24 | } 25 | } 26 | 27 | return undefined 28 | }, 29 | { 30 | enabled: stakeConnection !== undefined && mainStakeAccount !== undefined, 31 | } 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /frontend/hooks/usePreunstakeLockedMutation.ts: -------------------------------------------------------------------------------- 1 | import { StakeAccount, StakeConnection } from '@pythnetwork/staking' 2 | import { useMutation, useQueryClient } from 'react-query' 3 | import { StakeConnectionQueryKey } from './useStakeConnection' 4 | import toast from 'react-hot-toast' 5 | import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' 6 | 7 | export function usePreunstakeLockedMutation() { 8 | const queryClient = useQueryClient() 9 | 10 | return useMutation( 11 | ['preunstake-locked-mutation'], 12 | async ({ 13 | mainStakeAccount, 14 | stakeConnection, 15 | }: { 16 | mainStakeAccount: StakeAccount 17 | stakeConnection: StakeConnection 18 | }) => { 19 | await stakeConnection?.unlockBeforeVestingEvent(mainStakeAccount) 20 | toast.success('Tokens have started unstaking.') 21 | // TODO: 22 | // toast.success( 23 | // `${nextVestingAmount 24 | // ?.add(lockedPythBalance ?? PythBalance.zero()) 25 | // .toString()} tokens have started unlocking. You will be able to withdraw them after ${nextVestingDate?.toLocaleString()}` 26 | // ) 27 | }, 28 | { 29 | onSuccess() { 30 | // invalidate all except stake connection 31 | queryClient.invalidateQueries({ 32 | predicate: (query) => query.queryKey[0] !== StakeConnectionQueryKey, 33 | }) 34 | }, 35 | onError(error: Error) { 36 | toast.error(capitalizeFirstLetter(error.message)) 37 | }, 38 | } 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /frontend/hooks/useProfile.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from 'react-query' 2 | import { useProfileConnection } from './useProfileConnection' 3 | import { UserProfile } from '@pythnetwork/staking/lib/app/ProfileConnection' 4 | 5 | export const ProfileQueryKey = 'profile' 6 | 7 | export function useProfile() { 8 | const { data: profileConnection } = useProfileConnection() 9 | 10 | return useQuery( 11 | [ProfileQueryKey, profileConnection?.userPublicKey().toString()], 12 | async () => { 13 | return await profileConnection?.getProfile( 14 | profileConnection.userPublicKey() 15 | ) 16 | }, 17 | { 18 | // we should only fetch when profile connection is defined 19 | enabled: profileConnection !== undefined, 20 | } 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /frontend/hooks/useProfileConnection.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from '@coral-xyz/anchor' 2 | import { ProfileConnection } from '@pythnetwork/staking' 3 | import { useAnchorWallet, useConnection } from '@solana/wallet-adapter-react' 4 | import toast from 'react-hot-toast' 5 | import { useQuery } from 'react-query' 6 | import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' 7 | 8 | export const ProfileConnectionQueryKey = 'create-profile-connection' 9 | export function useProfileConnection() { 10 | const { connection } = useConnection() 11 | const anchorWallet = useAnchorWallet() 12 | 13 | return useQuery( 14 | [ProfileConnectionQueryKey, anchorWallet?.publicKey.toString()], 15 | async () => { 16 | return new ProfileConnection(connection, anchorWallet as Wallet) 17 | }, 18 | { 19 | onError(err: Error) { 20 | toast.error(capitalizeFirstLetter(err.message)) 21 | }, 22 | // we should only fetch when anchor wallet is defined 23 | enabled: anchorWallet !== undefined, 24 | } 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /frontend/hooks/usePythBalance.ts: -------------------------------------------------------------------------------- 1 | import { useAnchorWallet, useConnection } from '@solana/wallet-adapter-react' 2 | import { getPythTokenBalance } from 'pages/api/getPythTokenBalance' 3 | import { useQuery } from 'react-query' 4 | import { useStakeConnection } from './useStakeConnection' 5 | import { PythBalance } from '@pythnetwork/staking' 6 | import toast from 'react-hot-toast' 7 | import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' 8 | 9 | export const PythBalanceQueryKeyPrefix = 'pyth-balance' 10 | 11 | export function usePythBalance() { 12 | const { connection } = useConnection() 13 | const anchorWallet = useAnchorWallet() 14 | const { data: stakeConnection } = useStakeConnection() 15 | 16 | return useQuery( 17 | [PythBalanceQueryKeyPrefix, anchorWallet?.publicKey.toString()], 18 | async (): Promise => { 19 | if (anchorWallet === undefined || stakeConnection === undefined) 20 | return undefined 21 | return await getPythTokenBalance( 22 | connection, 23 | anchorWallet.publicKey, 24 | stakeConnection.config.pythTokenMint 25 | ) 26 | }, 27 | { 28 | onError(err: Error) { 29 | toast.error(capitalizeFirstLetter(err.message)) 30 | }, 31 | enabled: anchorWallet !== undefined && stakeConnection !== undefined, 32 | } 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /frontend/hooks/useSplitRequest.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from 'react-query' 2 | import { useStakeConnection } from './useStakeConnection' 3 | import { MainStakeAccount } from 'pages' 4 | 5 | export const SplitRequestQueryPrefix = 'split-request' 6 | 7 | export function useSplitRequest(mainStakeAccount: MainStakeAccount) { 8 | const { data: stakeConnection } = useStakeConnection() 9 | 10 | return useQuery( 11 | [SplitRequestQueryPrefix, mainStakeAccount], 12 | async () => { 13 | if (mainStakeAccount === 'NA') return undefined 14 | 15 | // only enabled when stakeConnection and mainStakeAccount is defined 16 | const splitRequest = await stakeConnection?.getSplitRequest( 17 | mainStakeAccount! 18 | ) 19 | 20 | return splitRequest 21 | }, 22 | { 23 | enabled: stakeConnection !== undefined && mainStakeAccount !== undefined, 24 | } 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /frontend/hooks/useStakeAccounts.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from '@coral-xyz/anchor' 2 | import { useAnchorWallet } from '@solana/wallet-adapter-react' 3 | import toast from 'react-hot-toast' 4 | import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' 5 | import { useStakeConnection } from './useStakeConnection' 6 | import { useQuery } from 'react-query' 7 | 8 | export const StakeAccountQueryPrefix = 'get-stake-accounts' 9 | 10 | // if stake connection is undefined, it will return an empty array 11 | // else it will return the fetched accounts 12 | export function useStakeAccounts() { 13 | const { data: stakeConnection } = useStakeConnection() 14 | const anchorWallet = useAnchorWallet() 15 | 16 | return useQuery( 17 | [StakeAccountQueryPrefix, anchorWallet?.publicKey.toString()], 18 | () => { 19 | // stakeConnection and anchorWallet are defined, as we have used enabled below 20 | return stakeConnection!.getStakeAccounts( 21 | (anchorWallet as Wallet).publicKey 22 | ) 23 | }, 24 | { 25 | onError(err: Error) { 26 | toast.error(capitalizeFirstLetter(err.message)) 27 | }, 28 | 29 | enabled: stakeConnection !== undefined && anchorWallet !== undefined, 30 | } 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /frontend/hooks/useStakeConnection.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from '@coral-xyz/anchor' 2 | import { STAKING_ADDRESS, StakeConnection } from '@pythnetwork/staking' 3 | import { useAnchorWallet, useConnection } from '@solana/wallet-adapter-react' 4 | import { PublicKey } from '@solana/web3.js' 5 | import toast from 'react-hot-toast' 6 | import { useQuery } from 'react-query' 7 | import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' 8 | 9 | export const StakeConnectionQueryKey = 'create-stake-connection' 10 | export function useStakeConnection() { 11 | const { connection } = useConnection() 12 | const anchorWallet = useAnchorWallet() 13 | 14 | return useQuery( 15 | [StakeConnectionQueryKey, anchorWallet?.publicKey.toString()], 16 | async () => { 17 | return await StakeConnection.createStakeConnection( 18 | connection, 19 | // anchor wallet is defined, as we have used enabled below 20 | anchorWallet as Wallet, 21 | STAKING_ADDRESS, 22 | process.env.ADDRESS_LOOKUP_TABLE 23 | ? new PublicKey(process.env.ADDRESS_LOOKUP_TABLE) 24 | : undefined, 25 | { computeUnitPriceMicroLamports: 50000 } 26 | ) 27 | }, 28 | { 29 | onError(err: Error) { 30 | toast.error(capitalizeFirstLetter(err.message)) 31 | }, 32 | // we should only fetch when anchor wallet is defined 33 | enabled: anchorWallet !== undefined, 34 | } 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /frontend/hooks/useStakeLockedMutation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PythBalance, 3 | StakeAccount, 4 | StakeConnection, 5 | } from '@pythnetwork/staking' 6 | import { useMutation, useQueryClient } from 'react-query' 7 | import { StakeConnectionQueryKey } from './useStakeConnection' 8 | import toast from 'react-hot-toast' 9 | import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' 10 | 11 | export function useStakeLockedMutation() { 12 | const queryClient = useQueryClient() 13 | 14 | return useMutation( 15 | ['stake-locked-mutation'], 16 | async ({ 17 | amount, 18 | stakeConnection, 19 | mainStakeAccount, 20 | }: { 21 | amount: string 22 | stakeConnection: StakeConnection 23 | mainStakeAccount: StakeAccount 24 | }) => { 25 | if (!amount) { 26 | throw new Error('Please enter a valid amount!') 27 | } 28 | const stakedAmount = PythBalance.fromString(amount) 29 | if (stakedAmount.gt(PythBalance.zero())) { 30 | await stakeConnection.lockTokens(mainStakeAccount, stakedAmount) 31 | toast.success('Successfully staked!') 32 | } else { 33 | throw new Error('Amount must be greater than 0.') 34 | } 35 | }, 36 | { 37 | onSuccess() { 38 | // invalidate all except stake connection 39 | queryClient.invalidateQueries({ 40 | predicate: (query) => query.queryKey[0] !== StakeConnectionQueryKey, 41 | }) 42 | }, 43 | onError(error: Error) { 44 | toast.error(capitalizeFirstLetter(error.message)) 45 | }, 46 | } 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /frontend/hooks/useUnlockMutation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PythBalance, 3 | StakeAccount, 4 | StakeConnection, 5 | } from '@pythnetwork/staking' 6 | import toast from 'react-hot-toast' 7 | import { StakeConnectionQueryKey } from './useStakeConnection' 8 | import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' 9 | import { useMutation, useQueryClient } from 'react-query' 10 | 11 | export function useUnlockMutation() { 12 | const queryClient = useQueryClient() 13 | 14 | return useMutation( 15 | ['unlock-mutation'], 16 | async ({ 17 | amount, 18 | stakeConnection, 19 | mainStakeAccount, 20 | }: { 21 | amount: string 22 | stakeConnection: StakeConnection 23 | mainStakeAccount: StakeAccount 24 | }) => { 25 | if (!amount) { 26 | throw new Error('Please enter a valid amount!') 27 | } 28 | const unlockAmount = PythBalance.fromString(amount) 29 | if (unlockAmount.gt(PythBalance.zero())) { 30 | await stakeConnection.unlockTokens(mainStakeAccount, unlockAmount) 31 | toast.success('Unlock successful!') 32 | } else { 33 | throw new Error('Amount must be greater than 0.') 34 | } 35 | }, 36 | { 37 | onSuccess() { 38 | // invalidate all except stake connection 39 | queryClient.invalidateQueries({ 40 | predicate: (query) => query.queryKey[0] !== StakeConnectionQueryKey, 41 | }) 42 | }, 43 | onError(error: Error) { 44 | toast.error(capitalizeFirstLetter(error.message)) 45 | }, 46 | } 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /frontend/hooks/useUnstakeLockedMutation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PythBalance, 3 | StakeAccount, 4 | StakeConnection, 5 | } from '@pythnetwork/staking' 6 | import { useMutation, useQueryClient } from 'react-query' 7 | import { StakeConnectionQueryKey } from './useStakeConnection' 8 | import toast from 'react-hot-toast' 9 | import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' 10 | 11 | export function useUnstakeLockedMutation() { 12 | const queryClient = useQueryClient() 13 | 14 | return useMutation( 15 | ['unstake-locked-mutation'], 16 | async ({ 17 | amount, 18 | mainStakeAccount, 19 | stakeConnection, 20 | }: { 21 | amount: string 22 | mainStakeAccount: StakeAccount 23 | stakeConnection: StakeConnection 24 | }) => { 25 | if (!amount) { 26 | throw new Error('Please enter a valid amount!') 27 | } 28 | const stakedAmount = PythBalance.fromString(amount) 29 | if (stakedAmount.gt(PythBalance.zero())) { 30 | await stakeConnection.unlockTokensUnchecked( 31 | mainStakeAccount, 32 | stakedAmount 33 | ) 34 | toast.success('Tokens have started unstaking!') 35 | } else { 36 | throw new Error('Amount must be greater than 0.') 37 | } 38 | }, 39 | { 40 | onSuccess() { 41 | // invalidate all except stake connection 42 | queryClient.invalidateQueries({ 43 | predicate: (query) => query.queryKey[0] !== StakeConnectionQueryKey, 44 | }) 45 | }, 46 | onError(error: Error) { 47 | toast.error(capitalizeFirstLetter(error.message)) 48 | }, 49 | } 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /frontend/hooks/useUpdateProfileMutation.ts: -------------------------------------------------------------------------------- 1 | import toast from 'react-hot-toast' 2 | import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' 3 | import { useMutation, useQueryClient } from 'react-query' 4 | import { 5 | UserProfile, 6 | areDifferentProfiles, 7 | } from '@pythnetwork/staking/lib/app/ProfileConnection' 8 | import { useProfileConnection } from './useProfileConnection' 9 | import { ProfileQueryKey } from './useProfile' 10 | 11 | export function useUpdateProfileMutation() { 12 | const queryClient = useQueryClient() 13 | const { data: profileConnection } = useProfileConnection() 14 | 15 | return useMutation( 16 | ['update-profile'], 17 | async ({ 18 | currProfile, 19 | newProfile, 20 | }: { 21 | currProfile: UserProfile 22 | newProfile: UserProfile 23 | }) => { 24 | if (!profileConnection) { 25 | return 26 | } 27 | const diff = areDifferentProfiles(currProfile, newProfile) 28 | if (!diff) { 29 | toast.error('There is nothing to update.') 30 | return 31 | } 32 | try { 33 | await profileConnection.updateProfile(currProfile, newProfile) 34 | toast.success( 35 | `EVM address ${ 36 | newProfile.evm === '' ? 'removed' : 'submitted' 37 | } successfully.` 38 | ) 39 | } catch (e) { 40 | toast.error(e.message) 41 | } 42 | }, 43 | { 44 | onSuccess() { 45 | queryClient.invalidateQueries({ 46 | predicate: (query) => query.queryKey[0] == ProfileQueryKey, 47 | }) 48 | }, 49 | onError(error: Error) { 50 | toast.error(capitalizeFirstLetter(error.message)) 51 | }, 52 | } 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /frontend/hooks/useVestingAccountState.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from 'react-query' 2 | import { useStakeConnection } from './useStakeConnection' 3 | import { MainStakeAccount } from 'pages' 4 | 5 | export const VestingAccountStateQueryPrefix = 'vesting-account-state' 6 | 7 | export function useVestingAccountState(mainStakeAccount: MainStakeAccount) { 8 | const { data: stakeConnection } = useStakeConnection() 9 | 10 | return useQuery( 11 | [VestingAccountStateQueryPrefix, mainStakeAccount], 12 | async () => { 13 | if (mainStakeAccount === 'NA') return undefined 14 | 15 | // only enabled when stakeConnection and mainStakeAccount is defined 16 | const currentTime = await stakeConnection!.getTime() 17 | const vestingAccountState = 18 | mainStakeAccount!.getVestingAccountState(currentTime) 19 | 20 | return vestingAccountState 21 | }, 22 | { 23 | enabled: stakeConnection !== undefined && mainStakeAccount !== undefined, 24 | } 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /frontend/hooks/useWithdrawMutation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PythBalance, 3 | StakeAccount, 4 | StakeConnection, 5 | } from '@pythnetwork/staking' 6 | import toast from 'react-hot-toast' 7 | import { StakeConnectionQueryKey } from './useStakeConnection' 8 | import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' 9 | import { useMutation, useQueryClient } from 'react-query' 10 | 11 | export function useWithdrawMutation() { 12 | const queryClient = useQueryClient() 13 | 14 | return useMutation( 15 | ['withdraw-mutation'], 16 | async ({ 17 | amount, 18 | stakeConnection, 19 | mainStakeAccount, 20 | }: { 21 | amount: string 22 | stakeConnection: StakeConnection 23 | mainStakeAccount: StakeAccount 24 | }) => { 25 | if (!amount) { 26 | throw new Error('Please enter a valid amount!') 27 | } 28 | const withdrawAmount = PythBalance.fromString(amount) 29 | if (withdrawAmount.gt(PythBalance.zero())) { 30 | await stakeConnection?.withdrawTokens(mainStakeAccount, withdrawAmount) 31 | toast.success('Withdraw successful!') 32 | } else { 33 | throw new Error('Amount must be greater than 0.') 34 | } 35 | }, 36 | { 37 | onSuccess() { 38 | // invalidate all except stake connection 39 | queryClient.invalidateQueries({ 40 | predicate: (query) => query.queryKey[0] !== StakeConnectionQueryKey, 41 | }) 42 | }, 43 | onError(error: Error) { 44 | toast.error(capitalizeFirstLetter(error.message)) 45 | }, 46 | } 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /frontend/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = ".next" 3 | 4 | [[plugins]] 5 | package = "@netlify/plugin-nextjs" 6 | -------------------------------------------------------------------------------- /frontend/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /frontend/next.config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const path = require('path') 3 | 4 | /** @type {import('next').NextConfig} */ 5 | module.exports = { 6 | reactStrictMode: true, 7 | experimental: { 8 | externalDir: true, 9 | }, 10 | swcMinify: false, 11 | env: { 12 | ENDPOINT: process.env.ENDPOINT, 13 | CLUSTER: process.env.CLUSTER, 14 | ADDRESS_LOOKUP_TABLE: process.env.ADDRESS_LOOKUP_TABLE, 15 | }, 16 | webpack(config, { isServer }) { 17 | config.experiments = { asyncWebAssembly: true, layers: true } 18 | // This is hack to fix the import of the wasm files https://github.com/vercel/next.js/issues/25852 19 | if (isServer) { 20 | config.output.webassemblyModuleFilename = 21 | './../static/wasm/[modulehash].wasm' 22 | } else { 23 | config.output.webassemblyModuleFilename = 'static/wasm/[modulehash].wasm' 24 | } 25 | config.optimization.moduleIds = 'named' 26 | // End of hack 27 | 28 | // Import the browser version of wasm instead of the node version 29 | config.resolve.alias = { 30 | ...config.resolve.alias, 31 | '@pythnetwork/staking-wasm$': path.resolve(__dirname, '../wasm/bundle'), 32 | } 33 | return config 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pyth-staking-frontend", 3 | "private": true, 4 | "scripts": { 5 | "build": "next build && next export", 6 | "dev": "next dev", 7 | "fix:format": "prettier --write .", 8 | "fix:lint": "next lint --fix", 9 | "serve": "serve out", 10 | "start": "next start", 11 | "test:format": "prettier --check .", 12 | "test:lint": "next lint", 13 | "test:types": "tsc --noEmit" 14 | }, 15 | "dependencies": { 16 | "@coral-xyz/anchor": "^0.30.1", 17 | "@coral-xyz/spl-token": "^0.30.1", 18 | "@headlessui/react": "^1.6.0", 19 | "@heroicons/react": "^1.0.6", 20 | "@pythnetwork/staking": "*", 21 | "@solana/spl-token": "^0.1.8", 22 | "@solana/wallet-adapter-base": "^0.9.22", 23 | "@solana/wallet-adapter-react": "^0.15.32", 24 | "@solana/wallet-adapter-react-ui": "^0.9.31", 25 | "@solana/wallet-adapter-wallets": "^0.19.16", 26 | "@solana/web3.js": "^1.87.5", 27 | "@tippyjs/react": "^4.2.6", 28 | "axios": "^1.6.7", 29 | "dotenv": "^16.0.0", 30 | "next": "12.2.5", 31 | "prop-types": "^15.8.1", 32 | "react": "^18.2.0", 33 | "react-dom": "^18.2.0", 34 | "react-hot-toast": "^2.2.0", 35 | "react-query": "^3.39.3", 36 | "serve": "13.0.2" 37 | }, 38 | "devDependencies": { 39 | "@tailwindcss/typography": "^0.5.2", 40 | "@types/bn.js": "^5.1.1", 41 | "@types/node": "20.3.1", 42 | "@types/react": "^18.0.1", 43 | "autoprefixer": "^10.4.0", 44 | "eslint": "^8", 45 | "eslint-config-next": "^14.2.3", 46 | "postcss": "^8.4.5", 47 | "prettier": "^2.5.1", 48 | "prettier-plugin-tailwindcss": "^0.1.1", 49 | "tailwindcss": "^3.0.7", 50 | "typescript": "5.1.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /frontend/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { Head, Html, Main, NextScript } from 'next/document' 2 | 3 | class CustomDocument extends Document { 4 | render() { 5 | return ( 6 | 7 | 8 | 12 | 17 | 22 | 27 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 | 48 | 49 | ) 50 | } 51 | } 52 | 53 | export default CustomDocument 54 | -------------------------------------------------------------------------------- /frontend/pages/api/getPythTokenBalance.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey } from '@solana/web3.js' 2 | import { PythBalance } from '@pythnetwork/staking' 3 | import { BN } from 'bn.js' 4 | import { 5 | ASSOCIATED_TOKEN_PROGRAM_ID, 6 | TOKEN_PROGRAM_ID, 7 | Token, 8 | } from '@solana/spl-token' 9 | 10 | export const getPythTokenBalance = async ( 11 | connection: Connection, 12 | publicKey: PublicKey, 13 | pythTokenMint: PublicKey 14 | ) => { 15 | const mint = new Token( 16 | connection, 17 | pythTokenMint, 18 | TOKEN_PROGRAM_ID, 19 | new Keypair() 20 | ) 21 | 22 | const pythAtaAddress = await Token.getAssociatedTokenAddress( 23 | ASSOCIATED_TOKEN_PROGRAM_ID, 24 | TOKEN_PROGRAM_ID, 25 | pythTokenMint, 26 | publicKey, 27 | true 28 | ) 29 | 30 | try { 31 | const pythAtaAccountInfo = await mint.getAccountInfo(pythAtaAddress) 32 | return new PythBalance(pythAtaAccountInfo.amount) 33 | } catch (e) { 34 | console.error(e) 35 | } 36 | 37 | return new PythBalance(new BN(0)) 38 | } 39 | -------------------------------------------------------------------------------- /frontend/pages/api/v1/all_locked_accounts.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from 'next' 2 | import { PythBalance } from '@pythnetwork/staking' 3 | import { STAKING_ADDRESS } from '@pythnetwork/staking' 4 | import { getAllLockedCustodyAccounts } from '@pythnetwork/staking' 5 | import { Connection, Keypair, PublicKey } from '@solana/web3.js' 6 | import { Program, AnchorProvider } from '@coral-xyz/anchor' 7 | import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet' 8 | import { Staking } from '@pythnetwork/staking/lib/target/types/staking' 9 | import idl from '@pythnetwork/staking/target/idl/staking.json' 10 | import { splTokenProgram } from '@coral-xyz/spl-token' 11 | import { TOKEN_PROGRAM_ID } from '@solana/spl-token' 12 | 13 | const RPC_URL = process.env.BACKEND_ENDPOINT! 14 | const connection = new Connection(RPC_URL) 15 | const provider = new AnchorProvider( 16 | connection, 17 | new NodeWallet(new Keypair()), 18 | {} 19 | ) 20 | const stakingProgram = new Program(idl as Staking, provider) 21 | const tokenProgram = splTokenProgram({ 22 | programId: TOKEN_PROGRAM_ID, 23 | provider: provider as any, 24 | }) 25 | 26 | export default async function handlerAllLockedAccounts( 27 | req: NextApiRequest, 28 | res: NextApiResponse 29 | ) { 30 | const allLockedCustodyAccounts = await getAllLockedCustodyAccounts( 31 | stakingProgram, 32 | tokenProgram 33 | ) 34 | 35 | const totalLockedAmount = allLockedCustodyAccounts.reduce( 36 | ( 37 | total: PythBalance, 38 | account: { pubkey: PublicKey; amount: PythBalance } 39 | ) => { 40 | return total.add(account.amount) 41 | }, 42 | PythBalance.zero() 43 | ) 44 | 45 | const data = { 46 | totalLockedAmount: totalLockedAmount.toString(), 47 | accounts: allLockedCustodyAccounts.map((account) => { 48 | return { 49 | custodyAccount: account.pubkey.toBase58(), 50 | actualAmount: account.amount.toString(), 51 | } 52 | }), 53 | } 54 | 55 | res.setHeader('Cache-Control', 'max-age=0, s-maxage=3600') 56 | res.status(200).json(data) 57 | } 58 | -------------------------------------------------------------------------------- /frontend/pages/api/v1/locked_accounts.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from 'next' 2 | import { STAKING_ADDRESS } from '@pythnetwork/staking' 3 | import { Connection, Keypair, PublicKey } from '@solana/web3.js' 4 | import { Program, AnchorProvider } from '@coral-xyz/anchor' 5 | import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet' 6 | import { Staking } from '@pythnetwork/staking/lib/target/types/staking' 7 | import idl from '@pythnetwork/staking/target/idl/staking.json' 8 | import { splTokenProgram } from '@coral-xyz/spl-token' 9 | import { TOKEN_PROGRAM_ID } from '@solana/spl-token' 10 | import { 11 | getStakeAccountDetails, 12 | getStakeAccountsByOwner, 13 | } from '@pythnetwork/staking' 14 | 15 | const connection = new Connection(process.env.BACKEND_ENDPOINT!) 16 | const provider = new AnchorProvider( 17 | connection, 18 | new NodeWallet(new Keypair()), 19 | {} 20 | ) 21 | const stakingProgram = new Program(idl as Staking, provider) 22 | const tokenProgram = splTokenProgram({ 23 | programId: TOKEN_PROGRAM_ID, 24 | provider: provider as any, 25 | }) 26 | 27 | export default async function handlerLockedAccounts( 28 | req: NextApiRequest, 29 | res: NextApiResponse 30 | ) { 31 | const { owner } = req.query 32 | 33 | if (owner == undefined || owner instanceof Array) { 34 | res.status(400).json({ 35 | error: "Must provide the 'owner' query parameters", 36 | }) 37 | } else { 38 | const stakeAccounts = await getStakeAccountsByOwner( 39 | connection, 40 | new PublicKey(owner) 41 | ) 42 | 43 | const stakeAccountDetails = await Promise.all( 44 | stakeAccounts.map((account) => { 45 | return getStakeAccountDetails(stakingProgram, tokenProgram, account) 46 | }) 47 | ) 48 | res.status(200).json(stakeAccountDetails) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/pages/create_election_governance.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next' 2 | import Layout from '../components/Layout' 3 | import SEO from '../components/SEO' 4 | import { StakeAccount } from '@pythnetwork/staking' 5 | import { useEffect, useState } from 'react' 6 | import toast from 'react-hot-toast' 7 | import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' 8 | import { useStakeConnection } from 'hooks/useStakeConnection' 9 | import { useStakeAccounts } from 'hooks/useStakeAccounts' 10 | 11 | const CreateElectionGovernance: NextPage = () => { 12 | const { data: stakeConnection } = useStakeConnection() 13 | const { data: stakeAccounts } = useStakeAccounts() 14 | const [selectedStakeAccount, setSelectStakeAccount] = useState() 15 | 16 | useEffect(() => { 17 | if (stakeAccounts && stakeAccounts.length > 0) 18 | setSelectStakeAccount(stakeAccounts[0]) 19 | }, [stakeAccounts]) 20 | 21 | const createElectionGovernance = async () => { 22 | if (stakeConnection && selectedStakeAccount) 23 | try { 24 | await stakeConnection.createElectionGovernance(selectedStakeAccount) 25 | toast.success('Successfully created election governance') 26 | } catch (err) { 27 | toast.error(capitalizeFirstLetter(err.message)) 28 | } 29 | } 30 | 31 | return ( 32 | 33 | 34 | {stakeConnection ? ( 35 |

36 | 42 |

43 | ) : ( 44 |

Please connect wallet

45 | )} 46 |
47 | ) 48 | } 49 | 50 | export default CreateElectionGovernance 51 | -------------------------------------------------------------------------------- /frontend/pages/governance.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next' 2 | import Layout from '../components/Layout' 3 | import SEO from '../components/SEO' 4 | 5 | const Governance: NextPage = () => { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | } 12 | 13 | export default Governance 14 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | semi: false, 4 | } 5 | -------------------------------------------------------------------------------- /frontend/public/.well-known/walletconnect.txt: -------------------------------------------------------------------------------- 1 | 55d57468-6a1c-4871-b61c-df563470b22b=b7466b3e430b1e51ee5cd299194a8d4dc5fd689fc1429a0f90602fab16b2b1df 2 | -------------------------------------------------------------------------------- /frontend/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/frontend/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/frontend/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/frontend/public/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #242235 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/frontend/public/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/frontend/public/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/public/favicon-light.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/frontend/public/favicon-light.ico -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/frontend/public/mstile-150x150.png -------------------------------------------------------------------------------- /frontend/public/orb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/frontend/public/orb.png -------------------------------------------------------------------------------- /frontend/public/pyth-logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#242235", 17 | "background_color": "#242235", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es5", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "paths": { 19 | "@components/*": ["components/*"] 20 | }, 21 | "useUnknownInCatchVariables": false 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /frontend/utils/capitalizeFirstLetter.ts: -------------------------------------------------------------------------------- 1 | export const capitalizeFirstLetter = (str: string) => { 2 | return str.replace(/^\w/, (c: string) => c.toUpperCase()) 3 | } 4 | -------------------------------------------------------------------------------- /frontend/utils/classNames.ts: -------------------------------------------------------------------------------- 1 | export const classNames = (...classes: any) => { 2 | return classes.filter(Boolean).join(' ') 3 | } 4 | -------------------------------------------------------------------------------- /frontend/utils/isSufficientBalance.ts: -------------------------------------------------------------------------------- 1 | import { PythBalance } from '@pythnetwork/staking' 2 | 3 | export function isSufficientBalance( 4 | amount: string, 5 | pythBalance: PythBalance | undefined 6 | ) { 7 | if (amount && pythBalance) { 8 | if (PythBalance.fromString(amount).gt(pythBalance)) { 9 | return false 10 | } else { 11 | return true 12 | } 13 | } else { 14 | return true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/utils/validAmountChange.ts: -------------------------------------------------------------------------------- 1 | export function validAmountChange(amount: string): boolean { 2 | const re = /^\d*\.?\d{0,6}$/ 3 | if (re.test(amount)) { 4 | return true 5 | } 6 | return false 7 | } 8 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "version": "independent" 4 | } 5 | -------------------------------------------------------------------------------- /metrics/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /metrics/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.2 2 | 3 | ARG NODE_VERSION=16.13.2 4 | ARG APP_NAME=governance_metrics 5 | 6 | FROM node:$NODE_VERSION as base 7 | 8 | WORKDIR /app 9 | COPY ./package.json ./tsconfig.json ./*.ts ./entrypoint.sh /app/ 10 | 11 | RUN chmod +x entrypoint.sh 12 | 13 | RUN yarn 14 | RUN yarn add ts-node typescript 15 | 16 | ARG APP_NAME 17 | ENV APP_NAME=$APP_NAME 18 | 19 | 20 | ENTRYPOINT ["/app/entrypoint.sh"] 21 | CMD ["yarn", "ts-node", "/app/server.ts"] 22 | -------------------------------------------------------------------------------- /metrics/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | eval "exec $@" 6 | -------------------------------------------------------------------------------- /metrics/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "governance_metrics", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "@solana/spl-governance": "^0.0.34", 8 | "@solana/web3.js": "^1.43.6", 9 | "express": "^4.18.1", 10 | "prom-client": "^14.0.1", 11 | "pyth-staking-api": "1.2.16", 12 | "pyth-staking-wasm": "^0.3.2" 13 | }, 14 | "lint-staged": { 15 | "*.ts": [ 16 | "prettier --w" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /metrics/server.ts: -------------------------------------------------------------------------------- 1 | import { Metrics } from "./governance_metrics"; 2 | import express from "express"; 3 | import { register } from "prom-client"; 4 | 5 | const metrics = new Metrics(); 6 | 7 | async function main() { 8 | while (true) { 9 | await metrics.updateAllMetrics(); 10 | console.log(await register.metrics()); 11 | await new Promise((resolve) => 12 | setTimeout(resolve, parseInt(process.env.INTERVAL || "0")) 13 | ); 14 | } 15 | } 16 | const server = express(); 17 | server.get("/metrics", async (req, res) => { 18 | try { 19 | res.set("Content-Type", register.contentType); 20 | res.end(await register.metrics()); 21 | } catch (ex) { 22 | res.status(500).end(ex); 23 | } 24 | }); 25 | 26 | const port = process.env.PORT || 3000; 27 | server.listen(port); 28 | 29 | main(); 30 | -------------------------------------------------------------------------------- /metrics/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["node"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true 10 | }, 11 | "include": ["./governance_metrics.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "workspaces": [ 5 | "staking", 6 | "wasm", 7 | "frontend" 8 | ], 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "lerna": "^7.2.0", 12 | "next": "12.2.5" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | 3 | # Merge all imports into a clean vertical list of module imports. 4 | imports_granularity = "One" 5 | group_imports = "One" 6 | imports_layout = "Vertical" 7 | 8 | # Better grep-ability. 9 | empty_item_single_line = false 10 | 11 | # Consistent pipe layout. 12 | match_arm_leading_pipes = "Preserve" 13 | 14 | # Align Fields 15 | enum_discrim_align_threshold = 80 16 | struct_field_align_threshold = 80 17 | 18 | # Allow up to two blank lines for visual grouping. 19 | blank_lines_upper_bound = 2 20 | 21 | # Wrap comments 22 | comment_width = 120 23 | wrap_comments = true 24 | -------------------------------------------------------------------------------- /scripts/vercel_build.sh: -------------------------------------------------------------------------------- 1 | source "$HOME/.cargo/env" 2 | pushd staking/ 3 | # generate wasm 4 | npm run build_wasm 5 | 6 | popd 7 | npx lerna run build --scope pyth-staking-frontend --include-dependencies 8 | -------------------------------------------------------------------------------- /scripts/vercel_install.sh: -------------------------------------------------------------------------------- 1 | # Install Rust 2 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 3 | source "$HOME/.cargo/env" 4 | 5 | # Install wasm-pack 6 | echo "Installing wasm-pack..." 7 | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y 8 | 9 | # npm install 10 | npm install 11 | -------------------------------------------------------------------------------- /sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "mkCli": { 3 | "branch": "main", 4 | "description": "Simple development environment cli generator for nix", 5 | "homepage": null, 6 | "owner": "cprussin", 7 | "repo": "mkCli", 8 | "rev": "3d356fd26d59dcfdccf47160e78f9389f150fc2c", 9 | "sha256": "1d3cy916sayj00v0gqvb0w49kdy05w1nl3pl81f6rp7gr9k6svl1", 10 | "type": "tarball", 11 | "url": "https://github.com/cprussin/mkCli/archive/3d356fd26d59dcfdccf47160e78f9389f150fc2c.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "nixpkgs": { 15 | "branch": "nixpkgs-unstable", 16 | "description": "A read-only mirror of NixOS/nixpkgs tracking the released channels. Send issues and PRs to", 17 | "homepage": "https://github.com/NixOS/nixpkgs", 18 | "owner": "NixOS", 19 | "repo": "nixpkgs", 20 | "rev": "cf8cc1201be8bc71b7cbbbdaf349b22f4f99c7ae", 21 | "sha256": "1x7nca1ij9snsb2pqqzfawzgpc1b1d5js9m7b78lmql54ayixl68", 22 | "type": "tarball", 23 | "url": "https://github.com/NixOS/nixpkgs/archive/cf8cc1201be8bc71b7cbbbdaf349b22f4f99c7ae.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | }, 26 | "rust-overlay": { 27 | "branch": "master", 28 | "description": "Pure and reproducible nix overlay of binary distributed rust toolchains", 29 | "homepage": "", 30 | "owner": "oxalica", 31 | "repo": "rust-overlay", 32 | "rev": "3e416d5067ba31ff8ac31eeb763e4388bdf45089", 33 | "sha256": "1bgdds28warwmybf95w16y8jhz3qlr3j6j7s2cdivpp5h2681i95", 34 | "type": "tarball", 35 | "url": "https://github.com/oxalica/rust-overlay/archive/3e416d5067ba31ff8ac31eeb763e4388bdf45089.tar.gz", 36 | "url_template": "https://github.com///archive/.tar.gz" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sources.nix: -------------------------------------------------------------------------------- 1 | let 2 | nivSrc = fetchTarball { 3 | url = "https://github.com/nmattia/niv/tarball/ecabfde837ccfb392ccca235f96bfcf4bb8ab186"; 4 | sha256 = "1aij19grvzbxj0dal49bsnhq1lc23nrglv6p0f00gwznl6109snj"; 5 | }; 6 | sources = import "${nivSrc}/nix/sources.nix" { 7 | sourcesFile = ./sources.json; 8 | }; 9 | niv = import nivSrc {}; 10 | in 11 | niv // sources 12 | -------------------------------------------------------------------------------- /staking/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | -------------------------------------------------------------------------------- /staking/.env.sample: -------------------------------------------------------------------------------- 1 | ENDPOINT="https://api.mainnet-beta.solana.com" 2 | -------------------------------------------------------------------------------- /staking/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | lib 8 | .env 9 | snapshots 10 | /artifacts 11 | -------------------------------------------------------------------------------- /staking/.nvmrc: -------------------------------------------------------------------------------- 1 | 18.19.1 2 | -------------------------------------------------------------------------------- /staking/Anchor.toml: -------------------------------------------------------------------------------- 1 | anchor_version = "0.24.1" 2 | 3 | [features] 4 | seeds = true 5 | 6 | [workspace] 7 | members = ["programs/staking", "programs/wallet-tester", "programs/profile", "programs/integrity-pool", "programs/publisher-caps"] 8 | 9 | [programs.localnet] 10 | staking = "pytS9TjG1qyAZypk7n8rw8gfW9sUaqqYyMhJQ4E7JCQ" 11 | governance = "pytGY6tWRgGinSCvRLnSv4fHfBTMoiDGiCsesmHWM6U" 12 | chat = "gCHAtYKrUUktTVzE4hEnZdLV4LXrdBf6Hh9qMaJALET" 13 | wallet_tester = "tstPARXbQ5yxVkRU2UcZRbYphzbUEW6t5ihzpLaafgz" 14 | profile = "prfmVhiQTN5Spgoxa8uZJba35V1s7XXReqbBiqPDWeJ" 15 | integrity_pool = "pyti8TM4zRVBjmarcgAPmTNNAXYKJv7WVHrkrm6woLN" 16 | publisher_caps = "pytcD8uUjPxSLMsNqoVnm9dXQw9tKJJf3CQnGwa8oL7" 17 | 18 | [programs.devnet] 19 | staking = "pytS9TjG1qyAZypk7n8rw8gfW9sUaqqYyMhJQ4E7JCQ" 20 | governance = "pytGY6tWRgGinSCvRLnSv4fHfBTMoiDGiCsesmHWM6U" 21 | wallet_tester = "tstPARXbQ5yxVkRU2UcZRbYphzbUEW6t5ihzpLaafgz" 22 | profile = "prfmVhiQTN5Spgoxa8uZJba35V1s7XXReqbBiqPDWeJ" 23 | integrity_pool = "pyti8TM4zRVBjmarcgAPmTNNAXYKJv7WVHrkrm6woLN" 24 | publisher_caps = "pytcD8uUjPxSLMsNqoVnm9dXQw9tKJJf3CQnGwa8oL7" 25 | 26 | 27 | [programs.mainnet] 28 | staking = "pytS9TjG1qyAZypk7n8rw8gfW9sUaqqYyMhJQ4E7JCQ" 29 | governance = "pytGY6tWRgGinSCvRLnSv4fHfBTMoiDGiCsesmHWM6U" 30 | wallet_tester = "tstPARXbQ5yxVkRU2UcZRbYphzbUEW6t5ihzpLaafgz" 31 | profile = "prfmVhiQTN5Spgoxa8uZJba35V1s7XXReqbBiqPDWeJ" 32 | integrity_pool = "pyti8TM4zRVBjmarcgAPmTNNAXYKJv7WVHrkrm6woLN" 33 | publisher_caps = "pytcD8uUjPxSLMsNqoVnm9dXQw9tKJJf3CQnGwa8oL7" 34 | 35 | 36 | 37 | [registry] 38 | url = "https://anchor.projectserum.com" 39 | 40 | [provider] 41 | cluster = "localnet" 42 | wallet = "./app/keypairs/localnet_authority.json" 43 | 44 | [scripts] 45 | test = "npx ts-mocha -p ./tsconfig.json -t 1000000 tests/staking.ts" 46 | 47 | [validator] 48 | ledger_dir = "./.anchor/test-ledger" 49 | 50 | [path] 51 | idl_path = "./target/idl/staking.json" 52 | binary_path = "./target/deploy/staking.so" 53 | governance_path = "./tests/programs/governance.so" # This is the version of governance with the extra instructions for testing, check PR 184 for more info 54 | chat_path = "./tests/programs/chat.so" 55 | wallet_tester_path = "./target/deploy/wallet_tester.so" 56 | profile_path = "./target/deploy/profile.so" 57 | integrity_pool_path = "./target/deploy/integrity_pool.so" 58 | -------------------------------------------------------------------------------- /staking/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "integration-tests", 4 | "cli", 5 | "programs/*" 6 | ] 7 | 8 | resolver = "2" 9 | 10 | [profile.release] 11 | overflow-checks = true 12 | lto = "fat" 13 | codegen-units = 1 14 | [profile.release.build-override] 15 | opt-level = 3 16 | incremental = false 17 | codegen-units = 1 18 | 19 | [workspace.dependencies] 20 | anchor-spl = "0.30.1" 21 | anchor-lang = "0.30.1" 22 | solana-program = "1.18.16" 23 | solana-sdk = "1.18.16" 24 | -------------------------------------------------------------------------------- /staking/app/PositionAccountJs.ts: -------------------------------------------------------------------------------- 1 | import { BorshCoder, IdlTypes } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import { Staking } from "../target/types/staking"; 4 | import { Position, wasm } from "./StakeConnection"; 5 | 6 | export class PositionAccountJs { 7 | public owner: PublicKey; 8 | public positions: Position[]; 9 | 10 | constructor(buffer: Buffer, idl: Staking) { 11 | const coder = new BorshCoder(idl); 12 | let i = 8; // Skip discriminator 13 | this.owner = new PublicKey(buffer.slice(i, i + 32)); 14 | let numberOfPositions = Math.floor( 15 | (buffer.length - wasm.Constants.POSITIONS_ACCOUNT_SIZE()) / 16 | wasm.Constants.POSITION_BUFFER_SIZE() 17 | ); 18 | i += 32; 19 | this.positions = []; 20 | for (let j = 0; j < numberOfPositions; j++) { 21 | if (buffer[i] === 1) { 22 | this.positions.push( 23 | coder.types.decode("position", buffer.subarray(i + 1)) 24 | ); 25 | } else { 26 | this.positions.push(null); 27 | } 28 | i += wasm.Constants.POSITION_BUFFER_SIZE(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /staking/app/PythClient.ts: -------------------------------------------------------------------------------- 1 | import { StakeConnection } from "./StakeConnection"; 2 | 3 | export const PythClient = StakeConnection; 4 | -------------------------------------------------------------------------------- /staking/app/constants.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | export function GOVERNANCE_ADDRESS(): PublicKey { 4 | return new PublicKey("pytGY6tWRgGinSCvRLnSv4fHfBTMoiDGiCsesmHWM6U"); 5 | } 6 | 7 | export const STAKING_ADDRESS = new PublicKey( 8 | "pytS9TjG1qyAZypk7n8rw8gfW9sUaqqYyMhJQ4E7JCQ" 9 | ); 10 | 11 | export const WALLET_TESTER_ADDRESS = new PublicKey( 12 | "tstPARXbQ5yxVkRU2UcZRbYphzbUEW6t5ihzpLaafgz" 13 | ); 14 | 15 | export const PROFILE_ADDRESS = new PublicKey( 16 | "prfmVhiQTN5Spgoxa8uZJba35V1s7XXReqbBiqPDWeJ" 17 | ); 18 | 19 | export const REALM_ID = new PublicKey( 20 | "4ct8XU5tKbMNRphWy4rePsS9kBqPhDdvZoGpmprPaug4" 21 | ); 22 | 23 | // This one is valid on mainnet only 24 | export const PYTH_TOKEN = new PublicKey( 25 | "HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3" 26 | ); 27 | 28 | export const EPOCH_DURATION = 3600 * 24 * 7; 29 | -------------------------------------------------------------------------------- /staking/app/deploy/1_create_realm.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GoverningTokenType, 3 | MintMaxVoteWeightSource, 4 | PROGRAM_VERSION, 5 | withCreateRealm, 6 | } from "@solana/spl-governance"; 7 | import { Transaction, Connection } from "@solana/web3.js"; 8 | import { BN } from "bn.js"; 9 | 10 | import { AUTHORITY_KEYPAIR, PYTH_TOKEN, RPC_NODE } from "./mainnet_beta"; 11 | import { STAKING_ADDRESS, GOVERNANCE_ADDRESS } from "../constants"; 12 | // Actual transaction hash : 13 | // mainnet-beta (24/10/23): 2jsDyim2R1p7V1yhbfmL92USyVvTGrzS3u2Pqa6581dUpcZGgxuhu6EYVfRj4gtjsPyhquj3M7MCcECDPcZ84A1U 14 | // devnet (10/12/23): ZoyuaKQbahuWcUkbvY4R5Cn8do8Ra1sjdKKHNQ3oVeorcn5xU7fz5uGKDAHAazD792LNytkeJz4cUu7eun8hrHr 15 | 16 | async function main() { 17 | const tx = new Transaction(); 18 | 19 | await withCreateRealm( 20 | tx.instructions, 21 | GOVERNANCE_ADDRESS(), // Address of the governance program 22 | PROGRAM_VERSION, // Version of the on-chain governance program 23 | "Pyth Governance", // `name` of the realm 24 | AUTHORITY_KEYPAIR.publicKey, // Address of the realm authority 25 | PYTH_TOKEN, // Address of the Pyth token 26 | AUTHORITY_KEYPAIR.publicKey, // Address of the payer 27 | undefined, // No council mint 28 | MintMaxVoteWeightSource.FULL_SUPPLY_FRACTION, // Irrelevant because we use the max voter weight plugin 29 | new BN( 30 | "18446744073709551615" // u64::MAX 31 | ), 32 | { 33 | voterWeightAddin: STAKING_ADDRESS, // Voter weight plugin 34 | maxVoterWeightAddin: STAKING_ADDRESS, // Max voter weight plugin 35 | tokenType: GoverningTokenType.Dormant, // Users should never deposit tokens but instead use the staking program 36 | }, 37 | undefined // No council mint 38 | ); 39 | 40 | const client = new Connection(RPC_NODE); 41 | 42 | await client.sendTransaction(tx, [AUTHORITY_KEYPAIR]); 43 | } 44 | 45 | main(); 46 | -------------------------------------------------------------------------------- /staking/app/deploy/2_init_staking.ts: -------------------------------------------------------------------------------- 1 | import { Wallet, AnchorProvider, Program } from "@coral-xyz/anchor"; 2 | import { Connection } from "@solana/web3.js"; 3 | import { getTargetAccount } from "../../tests/utils/utils"; 4 | import { AUTHORITY_KEYPAIR, PYTH_TOKEN, RPC_NODE } from "./mainnet_beta"; 5 | import { BN } from "bn.js"; 6 | import { 7 | STAKING_ADDRESS, 8 | REALM_ID, 9 | EPOCH_DURATION, 10 | GOVERNANCE_ADDRESS, 11 | } from "../constants"; 12 | 13 | // Actual transaction hash : 14 | // devnet (24/10/23): 4LDMVLijZsD3SeDMqeUZZ9mAov1TwyRJs96yuKztd7Cmv2p9ASWuP9JQXpL9fnr3eQc3gtxJqyWDZY1D7gj2NY6j 15 | // mainnet-beta (24/10/23): 3zNHezg9tW3uEEwU4ALQK6Ux3X7zYP4UKcbFhLHrPM4VkxNiQTTVbijdCtqVM2PFA4ZrAVc4LBKaKe8CDVGVkQJY 16 | 17 | async function main() { 18 | const client = new Connection(RPC_NODE); 19 | const provider = new AnchorProvider( 20 | client, 21 | new Wallet(AUTHORITY_KEYPAIR), 22 | {} 23 | ); 24 | const idl = (await Program.fetchIdl(STAKING_ADDRESS, provider))!; 25 | const program = new Program(idl, STAKING_ADDRESS, provider); 26 | 27 | const globalConfig = { 28 | governanceAuthority: AUTHORITY_KEYPAIR.publicKey, 29 | pythTokenMint: PYTH_TOKEN, 30 | pythGovernanceRealm: REALM_ID, 31 | unlockingDuration: 1, 32 | epochDuration: new BN(EPOCH_DURATION), 33 | freeze: false, 34 | pdaAuthority: AUTHORITY_KEYPAIR.publicKey, 35 | governanceProgram: GOVERNANCE_ADDRESS(), 36 | pythTokenListTime: null, 37 | agreementHash: Array.from(Buffer.alloc(0)), 38 | }; 39 | await program.methods.initConfig(globalConfig).rpc(); 40 | 41 | const votingTarget = { voting: {} }; 42 | const targetAccount = await getTargetAccount(votingTarget, STAKING_ADDRESS); 43 | await program.methods 44 | .createTarget(votingTarget) 45 | .accounts({ 46 | targetAccount, 47 | governanceSigner: AUTHORITY_KEYPAIR.publicKey, 48 | }) 49 | .rpc(); 50 | 51 | await program.methods.updateMaxVoterWeight().rpc(); 52 | } 53 | 54 | main(); 55 | -------------------------------------------------------------------------------- /staking/app/deploy/4_transfer_authorities_to_multisig.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PROGRAM_VERSION, 3 | withSetRealmAuthority, 4 | SetRealmAuthorityAction, 5 | } from "@solana/spl-governance"; 6 | import { Transaction, Connection } from "@solana/web3.js"; 7 | 8 | import { 9 | AUTHORITY_KEYPAIR, 10 | RPC_NODE, 11 | MULTISIG_AUTHORITY, 12 | } from "./mainnet_beta"; 13 | 14 | import { GOVERNANCE_ADDRESS, REALM_ID, STAKING_ADDRESS } from "../constants"; 15 | import { AnchorProvider, Idl, Program, Wallet } from "@coral-xyz/anchor"; 16 | import IDL from "../../target/idl/staking.json"; 17 | 18 | // Actual transaction hash : 19 | // mainnet-beta : 3FDjeBC946SZ6ZgSiDiNzFHKS5hs9bAXYrJKGZrGw1tuVcwi4BxXB1qvqsVmvtcnG5mzYvLM4hmPLjUTiCiY6Tfe 20 | // devnet : 54WrJp6FDXvJCVzaGojUtWz4brm8wJHx3ZTYCpSTF2EwmeswySYsQY335XhJ1A7KL2N4mhYW7NtAGJpMA2fM9M6W 21 | 22 | async function main() { 23 | const client = new Connection(RPC_NODE); 24 | const provider = new AnchorProvider( 25 | client, 26 | new Wallet(AUTHORITY_KEYPAIR), 27 | {} 28 | ); 29 | 30 | const program = new Program(IDL as Idl, STAKING_ADDRESS, provider); 31 | 32 | const tx = new Transaction(); 33 | withSetRealmAuthority( 34 | tx.instructions, 35 | GOVERNANCE_ADDRESS(), 36 | PROGRAM_VERSION, 37 | REALM_ID, 38 | AUTHORITY_KEYPAIR.publicKey, 39 | MULTISIG_AUTHORITY, 40 | SetRealmAuthorityAction.SetUnchecked 41 | ); 42 | tx.instructions.push( 43 | await program.methods 44 | .updateGovernanceAuthority(MULTISIG_AUTHORITY) 45 | .instruction() 46 | ); 47 | 48 | await client.sendTransaction(tx, [AUTHORITY_KEYPAIR]); 49 | } 50 | 51 | main(); 52 | -------------------------------------------------------------------------------- /staking/app/deploy/5_add_pyth_token_metadata.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Transaction } from "@solana/web3.js"; 2 | import { AUTHORITY_KEYPAIR, RPC_NODE, PYTH_TOKEN } from "./devnet"; 3 | import { Metaplex } from "@metaplex-foundation/js"; 4 | import { 5 | createCreateMetadataAccountV2Instruction, 6 | DataV2, 7 | } from "@metaplex-foundation/mpl-token-metadata"; 8 | 9 | /// Update metadata to arweave before running this by using https://github.com/jacobcreech/Token-Creator 10 | async function main() { 11 | const PYTH_ONCHAIN_METADATA = { 12 | name: "Pyth", 13 | symbol: "PYTH", 14 | uri: "https://arweave.net/V-UQtAKq6zfbVC7C7vkgjgxAc5lnJ6dAyXNs8MQrXyY", 15 | sellerFeeBasisPoints: 0, 16 | creators: null, 17 | collection: null, 18 | uses: null, 19 | } as DataV2; 20 | 21 | const client = new Connection(RPC_NODE); 22 | const metaplex = Metaplex.make(client); 23 | const metadataPDA = metaplex.nfts().pdas().metadata({ mint: PYTH_TOKEN }); 24 | 25 | const tx = new Transaction(); 26 | tx.instructions.push( 27 | createCreateMetadataAccountV2Instruction( 28 | { 29 | metadata: metadataPDA, 30 | mint: PYTH_TOKEN, 31 | mintAuthority: AUTHORITY_KEYPAIR.publicKey, 32 | payer: AUTHORITY_KEYPAIR.publicKey, 33 | updateAuthority: AUTHORITY_KEYPAIR.publicKey, 34 | }, 35 | { 36 | createMetadataAccountArgsV2: { 37 | data: PYTH_ONCHAIN_METADATA, 38 | isMutable: true, 39 | }, 40 | } 41 | ) 42 | ); 43 | 44 | await client.sendTransaction(tx, [AUTHORITY_KEYPAIR]); 45 | } 46 | 47 | main(); 48 | -------------------------------------------------------------------------------- /staking/app/deploy/create_account_lookup_table.ts: -------------------------------------------------------------------------------- 1 | import { Wallet, AnchorProvider } from "@coral-xyz/anchor"; 2 | import { Connection } from "@solana/web3.js"; 3 | import { AUTHORITY_KEYPAIR, PYTH_TOKEN, RPC_NODE } from "./devnet"; 4 | import { initAddressLookupTable } from "../../tests/utils/utils"; 5 | async function main() { 6 | const client = new Connection(RPC_NODE); 7 | const provider = new AnchorProvider( 8 | client, 9 | new Wallet(AUTHORITY_KEYPAIR), 10 | {} 11 | ); 12 | const lookupTableAddress = await initAddressLookupTable(provider, PYTH_TOKEN); 13 | console.log("Lookup table address: ", lookupTableAddress.toBase58()); 14 | } 15 | 16 | main(); 17 | -------------------------------------------------------------------------------- /staking/app/deploy/create_vesting_account.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from "@coral-xyz/anchor"; 2 | import { Connection, PublicKey } from "@solana/web3.js"; 3 | import { AUTHORITY_KEYPAIR, RPC_NODE } from "./devnet"; 4 | import { STAKING_ADDRESS } from "../constants"; 5 | import { BN } from "bn.js"; 6 | import { PythBalance, StakeConnection } from ".."; 7 | 8 | const TWELVE_MONTHS = 3600 * 24 * 365; 9 | const OWNER_PUBKEY = new PublicKey(0); // Populate with the beneficiary's public key 10 | const AMOUNT: PythBalance = PythBalance.fromString("1"); // Populate with the right amount 11 | 12 | async function main() { 13 | const client = new Connection(RPC_NODE); 14 | const stakeConnection = await StakeConnection.createStakeConnection( 15 | client, 16 | new Wallet(AUTHORITY_KEYPAIR), 17 | STAKING_ADDRESS 18 | ); 19 | 20 | const vestingSchedule = { 21 | periodicVestingAfterListing: { 22 | initialBalance: AMOUNT.toBN(), 23 | periodDuration: new BN(TWELVE_MONTHS), 24 | numPeriods: new BN(4), 25 | }, 26 | }; 27 | 28 | await stakeConnection.setupVestingAccount( 29 | AMOUNT, 30 | OWNER_PUBKEY, 31 | vestingSchedule, 32 | false 33 | ); 34 | } 35 | 36 | main(); 37 | -------------------------------------------------------------------------------- /staking/app/deploy/devnet.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { homedir } from "os"; 3 | import { loadKeypair } from "../../tests/utils/keys"; 4 | export const AUTHORITY_PATH = "/.config/solana/deployer.json"; 5 | export const AUTHORITY_KEYPAIR = loadKeypair(homedir() + AUTHORITY_PATH); 6 | 7 | export const MULTISIG_AUTHORITY = new PublicKey( 8 | "7g4Los4WMQnpxYiBJpU1HejBiM6xCk5RDFGCABhWE9M6" 9 | ); 10 | 11 | export const PYTH_TOKEN = new PublicKey( 12 | "7Bd6bEH4wHTMmov8D2WTXgxzLJcxJYczqE5NaDtZdhF6" 13 | ); 14 | 15 | export const RPC_NODE = "https://api.devnet.solana.com"; 16 | -------------------------------------------------------------------------------- /staking/app/deploy/mainnet_beta.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { homedir } from "os"; 3 | import { loadKeypair } from "../../tests/utils/keys"; 4 | export const AUTHORITY_PATH = "/.config/solana/deployer.json"; 5 | export const AUTHORITY_KEYPAIR = loadKeypair(homedir() + AUTHORITY_PATH); 6 | 7 | export const MULTISIG_AUTHORITY = new PublicKey( 8 | "6oXTdojyfDS8m5VtTaYB9xRCxpKGSvKJFndLUPV3V3wT" 9 | ); 10 | 11 | export const PYTH_TOKEN = new PublicKey( 12 | "HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3" 13 | ); 14 | 15 | export const RPC_NODE = "https://api.mainnet-beta.solana.com"; 16 | -------------------------------------------------------------------------------- /staking/app/deploy/pyth_token_offchain_metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pyth", 3 | "symbol": "PYTH", 4 | "description": "Pyth token", 5 | "image": "https://arweave.net/DJiziUFrQyV7CCDI_UCEuQVT2RgyUK-3HH2_r-vsv8Q?ext=png" 6 | } 7 | -------------------------------------------------------------------------------- /staking/app/index.ts: -------------------------------------------------------------------------------- 1 | export { ProfileConnection } from "./ProfileConnection"; 2 | export { PythClient } from "./PythClient"; 3 | export { 4 | StakeAccount, 5 | StakeConnection, 6 | VestingAccountState, 7 | } from "./StakeConnection"; 8 | export * from "./constants"; 9 | export * from "./api_utils"; 10 | export { PYTH_DECIMALS, PythBalance } from "./pythBalance"; 11 | -------------------------------------------------------------------------------- /staking/app/keypairs/alice.json: -------------------------------------------------------------------------------- 1 | [183,145,202,243,78,73,124,200,67,201,221,160,44,228,7,237,159,162,229,154,26,254,210,186,13,234,77,153,59,16,132,85,188,161,67,10,200,220,80,95,43,11,222,172,144,237,178,104,39,96,83,131,107,198,0,181,234,6,113,123,228,84,138,58] 2 | -------------------------------------------------------------------------------- /staking/app/keypairs/bob.json: -------------------------------------------------------------------------------- 1 | [37,161,177,49,106,56,211,117,100,37,65,105,161,118,33,36,69,3,100,111,197,40,110,39,164,106,6,28,31,0,202,107,129,118,108,33,108,0,183,136,14,121,250,232,104,229,12,174,235,3,172,223,71,113,5,23,142,62,184,88,46,19,61,248] 2 | -------------------------------------------------------------------------------- /staking/app/keypairs/localnet_authority.json: -------------------------------------------------------------------------------- 1 | [89,248,82,140,72,162,248,43,112,211,27,53,65,180,206,43,89,31,133,170,209,177,168,196,143,175,0,14,45,162,73,1,33,253,6,66,85,211,55,70,173,117,34,169,193,205,183,194,252,226,147,131,186,70,0,221,227,108,167,83,156,31,208,120] 2 | -------------------------------------------------------------------------------- /staking/app/keypairs/pyth_mint.json: -------------------------------------------------------------------------------- 1 | [215,90,118,135,142,32,217,201,81,8,215,67,40,207,247,75,131,220,66,3,46,103,79,15,176,124,112,99,239,152,200,2,237,180,74,77,106,199,181,38,238,187,114,59,13,75,239,220,101,239,96,232,237,229,82,151,219,17,7,195,165,152,132,24] 2 | -------------------------------------------------------------------------------- /staking/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "staking-cli" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | solana-sdk = {workspace = true} 8 | pyth-staking-program = {path = "../programs/staking"} 9 | integrity-pool = {path = "../programs/integrity-pool"} 10 | publisher-caps = {path = "../programs/publisher-caps"} 11 | integration-tests = {path = "../integration-tests"} 12 | anchor-lang = {workspace = true} 13 | anchor-spl = {workspace = true} 14 | clap = {version ="3.2.22", features = ["derive"]} 15 | solana-client = "1.18.0" 16 | shellexpand = "3.1.0" 17 | pythnet-sdk = {version = "2.3.0", features = ["test-utils"]} 18 | byteorder = "1.4.3" 19 | wormhole-vaas-serde = "0.1.0" 20 | serde_wormhole = "0.1.0" 21 | wormhole-core-bridge-solana = {git = "https://github.com/guibescos/wormhole", rev = "af311c20f657571460b62d20aaa8d9fce541bfde"} 22 | wormhole-solana = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"} 23 | base64 = "0.22.1" 24 | reqwest = "0.11" 25 | serde_json = "1.0.128" 26 | uriparse = "0.6.4" 27 | solana-remote-wallet = "1.18.16" 28 | solana-account-decoder = "1.18.16" 29 | chrono = "0.4.38" 30 | futures = "0.3.31" 31 | tokio = "1.42.0" 32 | tokio-stream = "0.1.17" 33 | -------------------------------------------------------------------------------- /staking/clippy.toml: -------------------------------------------------------------------------------- 1 | large-error-threshold = 256 2 | too-many-arguments-threshold = 8 3 | -------------------------------------------------------------------------------- /staking/integration-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration-tests" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | solana-sdk = {workspace = true} 8 | pyth-staking-program = {path = "../programs/staking"} 9 | integrity-pool = {path = "../programs/integrity-pool"} 10 | publisher-caps = {path = "../programs/publisher-caps"} 11 | anchor-lang = {workspace = true} 12 | anchor-spl = {workspace = true} 13 | litesvm = "0.1.0" 14 | bytemuck = {version = "1.4.0", features = ["derive", "min_const_generics"]} 15 | byteorder = "1.4.3" 16 | wormhole-vaas-serde = "0.1.0" 17 | wormhole-solana-vaas = {version="0.3.0-alpha.1", features = ["anchor", "encoded-vaa", "mainnet"]} 18 | serde_wormhole = "0.1.0" 19 | pythnet-sdk = {version = "2.3.0", features = ["test-utils"]} 20 | serde_json = "1.0.125" 21 | solana-cli-output = "1.18.16" 22 | spl-governance = { version = "4.0.0", features = ["no-entrypoint"] } 23 | 24 | [dev-dependencies] 25 | quickcheck = "1.0.3" 26 | quickcheck_macros = "1.0.0" 27 | -------------------------------------------------------------------------------- /staking/integration-tests/fixtures/governance/governance.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "HVx4oW785bu8QDQ8AwSVfD7H4iuH51ttakc2G5f9XTX8", 3 | "account": { 4 | "lamports": 2533440, 5 | "data": [ 6 | "EzXEM6GlaZcZvH6tNSPxrFpHNyxQIevjoG+tg07JsGcRDEqewCtWaB2kmwS6myTPif2A+SzxkeN9vTNvRue3E9kAAAAAAEMAkB7EvBYAAAAAAACAOgkAAAIC//////////8CAgAAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", 7 | "base64" 8 | ], 9 | "owner": "pytGY6tWRgGinSCvRLnSv4fHfBTMoiDGiCsesmHWM6U", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 236 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /staking/integration-tests/fixtures/governance/governance.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/staking/integration-tests/fixtures/governance/governance.so -------------------------------------------------------------------------------- /staking/integration-tests/fixtures/governance/realm.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "4ct8XU5tKbMNRphWy4rePsS9kBqPhDdvZoGpmprPaug4", 3 | "account": { 4 | "lamports": 2832720, 5 | "data": [ 6 | "EPXt7IRxx1Yk68QHmmNDJtlqaJ5hV9eavo9ab5RHKFO8AAAAAAAAAAAAkB7EvBYAAAAA5AtUAgAAAAAAAAAAAAAAAAAPAAAAUHl0aCBHb3Zlcm5hbmNlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 7 | "base64" 8 | ], 9 | "owner": "pytGY6tWRgGinSCvRLnSv4fHfBTMoiDGiCsesmHWM6U", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 279 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /staking/integration-tests/fixtures/governance/realm_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "GGgPCv3oW8muVhxTHpTuecmody2RPBty3SUPywyGdTuP", 3 | "account": { 4 | "lamports": 2930160, 5 | "data": [ 6 | "CzXEM6GlaZcZvH6tNSPxrFpHNyxQIevjoG+tg07JsGcRAQxKnsArVmgdpJsEupskz4n9gPks8ZHjfb0zb0bntxPZAQxKnsArVmgdpJsEupskz4n9gPks8ZHjfb0zb0bntxPZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", 7 | "base64" 8 | ], 9 | "owner": "pytGY6tWRgGinSCvRLnSv4fHfBTMoiDGiCsesmHWM6U", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 293 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /staking/integration-tests/fixtures/staking/stake_account_custody.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "5ULtZcyfuWvKuwB555WN4W1vsVW4F3yHU9UW1Gd7kAWy", 3 | "account": { 4 | "lamports": 2039280, 5 | "data": [ 6 | "9e3shHHHViTrxAeaY0Mm2WponmFX15q+j1pvlEcoU7zBcqDzD9SrNBm67cRd5OeLHvBTtVRG1Y56W4Q4bKMPxgBYC4nNQwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 165 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /staking/integration-tests/fixtures/staking/stake_account_metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "6cHMZGZ2xuggHZ5AYXzE2fdAho9aN9UTNGGQaypSHa4A", 3 | "account": { 4 | "lamports": 2282880, 5 | "data": [ 6 | "wDPLE0yxiGH/+f3/w4VE42TVGqlIn6IGF+MuZ7uuC4qn7cgeT6u21u/EJYECACAPDBIFAwCAM+EBAAAAAAQAAAAAAAAAAgABAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", 7 | "base64" 8 | ], 9 | "owner": "pytS9TjG1qyAZypk7n8rw8gfW9sUaqqYyMhJQ4E7JCQ", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 200 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /staking/integration-tests/fixtures/staking/voter_record.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "9xc4bK5K42BgMg9MkRxnnJDrWLbwuZMsS1zbHUxiBk1V", 3 | "account": { 4 | "lamports": 2032320, 5 | "data": [ 6 | "LvmbS5n4dAk1xDOhpWmXGbx+rTUj8axaRzcsUCHr46BvrYNOybBnEfXt7IRxx1Yk68QHmmNDJtlqaJ5hV9eavo9ab5RHKFO8w4VE42TVGqlIn6IGF+MuZ7uuC4qn7cgeT6u21u/EJYHr4mf8J5oFAAHR1GQQAAAAAAEBAAAAAAAAAAAAet+m1rPacXazfOj+w61pZlw+LaatKWmRAAAAAAAAAAA=", 7 | "base64" 8 | ], 9 | "owner": "pytS9TjG1qyAZypk7n8rw8gfW9sUaqqYyMhJQ4E7JCQ", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 164 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /staking/integration-tests/src/governance/addresses.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::pubkey, 3 | solana_sdk::pubkey::Pubkey, 4 | }; 5 | 6 | pub const MAINNET_GOVERNANCE_PROGRAM_ID: Pubkey = 7 | pubkey!("pytGY6tWRgGinSCvRLnSv4fHfBTMoiDGiCsesmHWM6U"); 8 | pub const MAINNET_REALM_ID: Pubkey = pubkey!("4ct8XU5tKbMNRphWy4rePsS9kBqPhDdvZoGpmprPaug4"); 9 | -------------------------------------------------------------------------------- /staking/integration-tests/src/governance/helper_functions.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::instructions::{ 3 | cast_vote, 4 | create_proposal, 5 | sign_off_proposal, 6 | }, 7 | crate::solana::utils::fetch_governance_account_data, 8 | litesvm::LiteSVM, 9 | solana_sdk::{ 10 | pubkey::Pubkey, 11 | signature::Keypair, 12 | }, 13 | spl_governance::state::proposal::ProposalV2, 14 | }; 15 | 16 | pub fn create_proposal_and_vote( 17 | svm: &mut LiteSVM, 18 | payer: &Keypair, 19 | stake_account_positions: &Pubkey, 20 | governance_address: &Pubkey, 21 | ) -> ProposalV2 { 22 | let proposal = create_proposal(svm, payer, *stake_account_positions, governance_address); 23 | sign_off_proposal(svm, payer, &proposal, governance_address).unwrap(); 24 | cast_vote( 25 | svm, 26 | payer, 27 | *stake_account_positions, 28 | governance_address, 29 | &proposal, 30 | ) 31 | .unwrap(); 32 | fetch_governance_account_data(svm, &proposal) 33 | } 34 | -------------------------------------------------------------------------------- /staking/integration-tests/src/governance/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod addresses; 2 | pub mod helper_functions; 3 | pub mod instructions; 4 | -------------------------------------------------------------------------------- /staking/integration-tests/src/integrity_pool/helper_functions.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::pda::{ 3 | get_pool_config_address, 4 | get_pool_reward_custody_address, 5 | }, 6 | crate::solana::instructions::{ 7 | airdrop_spl, 8 | initialize_ata, 9 | }, 10 | anchor_spl::associated_token::get_associated_token_address, 11 | integrity_pool::utils::types::FRAC_64_MULTIPLIER, 12 | solana_sdk::{ 13 | pubkey::Pubkey, 14 | signature::Keypair, 15 | signer::Signer, 16 | }, 17 | }; 18 | 19 | 20 | pub fn initialize_pool_reward_custody( 21 | svm: &mut litesvm::LiteSVM, 22 | payer: &Keypair, 23 | pyth_token_mint: &Keypair, 24 | reward_amount_override: Option, 25 | ) { 26 | let pool_config_pubkey = get_pool_config_address(); 27 | 28 | // Create the ATA for the pool_config_pubkey if it doesn't exist 29 | initialize_ata(svm, payer, pyth_token_mint.pubkey(), pool_config_pubkey).unwrap(); 30 | 31 | airdrop_spl( 32 | svm, 33 | payer, 34 | get_pool_reward_custody_address(pyth_token_mint.pubkey()), 35 | pyth_token_mint, 36 | reward_amount_override.unwrap_or(1_000_000 * FRAC_64_MULTIPLIER), 37 | ); 38 | } 39 | 40 | /// Default slash custody in the tests is the associated token account of the reward program 41 | /// authority 42 | pub fn get_default_slash_custody( 43 | reward_program_authority: &Pubkey, 44 | pyth_token_mint: &Pubkey, 45 | ) -> Pubkey { 46 | get_associated_token_address(reward_program_authority, pyth_token_mint) 47 | } 48 | -------------------------------------------------------------------------------- /staking/integration-tests/src/integrity_pool/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod helper_functions; 2 | pub mod instructions; 3 | pub mod pda; 4 | -------------------------------------------------------------------------------- /staking/integration-tests/src/integrity_pool/pda.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::Key, 3 | anchor_spl::associated_token::get_associated_token_address, 4 | integrity_pool::utils::constants::{ 5 | DELEGATION_RECORD, 6 | POOL_CONFIG, 7 | SLASH_EVENT, 8 | }, 9 | solana_sdk::pubkey::Pubkey, 10 | }; 11 | 12 | pub fn get_delegation_record_address(publisher: Pubkey, stake_account_positions: Pubkey) -> Pubkey { 13 | Pubkey::find_program_address( 14 | &[ 15 | DELEGATION_RECORD.as_bytes(), 16 | publisher.as_ref(), 17 | stake_account_positions.as_ref(), 18 | ], 19 | &integrity_pool::ID, 20 | ) 21 | .0 22 | } 23 | 24 | pub fn get_pool_config_address() -> Pubkey { 25 | Pubkey::find_program_address(&[POOL_CONFIG.as_bytes()], &integrity_pool::ID).0 26 | } 27 | 28 | pub fn get_pool_reward_custody_address(pyth_token_mint: Pubkey) -> Pubkey { 29 | let pool_config_pubkey = get_pool_config_address(); 30 | 31 | get_associated_token_address(&pool_config_pubkey, &pyth_token_mint) 32 | } 33 | 34 | pub fn get_slash_event_address(index: u64, publisher: Pubkey) -> Pubkey { 35 | Pubkey::find_program_address( 36 | &[ 37 | SLASH_EVENT.as_bytes(), 38 | publisher.key().as_ref(), 39 | &index.to_be_bytes(), 40 | ], 41 | &integrity_pool::ID, 42 | ) 43 | .0 44 | } 45 | -------------------------------------------------------------------------------- /staking/integration-tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod governance; 2 | pub mod integrity_pool; 3 | pub mod publisher_caps; 4 | pub mod setup; 5 | pub mod solana; 6 | pub mod staking; 7 | pub mod utils; 8 | -------------------------------------------------------------------------------- /staking/integration-tests/src/publisher_caps/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod helper_functions; 2 | pub mod instructions; 3 | pub mod utils; 4 | -------------------------------------------------------------------------------- /staking/integration-tests/src/solana/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod instructions; 2 | pub mod utils; 3 | -------------------------------------------------------------------------------- /staking/integration-tests/src/solana/utils.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | use { 3 | anchor_lang::{ 4 | prelude::borsh::BorshDeserialize, 5 | AccountDeserialize, 6 | }, 7 | bytemuck::{ 8 | Pod, 9 | Zeroable, 10 | }, 11 | solana_sdk::{ 12 | borsh0_10::try_from_slice_unchecked, 13 | pubkey::Pubkey, 14 | }, 15 | staking::state::positions::DynamicPositionArrayAccount, 16 | }; 17 | 18 | 19 | pub fn fetch_account_data( 20 | svm: &mut litesvm::LiteSVM, 21 | account: &Pubkey, 22 | ) -> T { 23 | T::try_deserialize(&mut svm.get_account(account).unwrap().data.as_ref()).unwrap() 24 | } 25 | 26 | pub fn fetch_governance_account_data( 27 | svm: &mut litesvm::LiteSVM, 28 | account: &Pubkey, 29 | ) -> T { 30 | try_from_slice_unchecked(&svm.get_account(account).unwrap().data).unwrap() 31 | } 32 | 33 | pub fn fetch_account_data_bytemuck( 34 | svm: &mut litesvm::LiteSVM, 35 | account: &Pubkey, 36 | ) -> T { 37 | let size = std::mem::size_of::(); 38 | T::try_deserialize(&mut &svm.get_account(account).unwrap().data.as_slice()[..size + 8]).unwrap() 39 | } 40 | 41 | pub fn fetch_positions_account( 42 | svm: &mut litesvm::LiteSVM, 43 | address: &Pubkey, 44 | ) -> DynamicPositionArrayAccount { 45 | let account = &svm.get_account(address).unwrap(); 46 | DynamicPositionArrayAccount::default_with_data(&account.data) 47 | } 48 | -------------------------------------------------------------------------------- /staking/integration-tests/src/staking/helper_functions.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::{ 3 | instructions::{ 4 | create_stake_account, 5 | join_dao_llc, 6 | }, 7 | pda::get_stake_account_custody_address, 8 | }, 9 | crate::{ 10 | solana::instructions::{ 11 | airdrop_spl, 12 | create_account, 13 | }, 14 | utils::constants::STAKED_TOKENS, 15 | }, 16 | solana_sdk::{ 17 | pubkey::Pubkey, 18 | signature::Keypair, 19 | }, 20 | }; 21 | 22 | 23 | pub fn initialize_new_stake_account( 24 | svm: &mut litesvm::LiteSVM, 25 | payer: &Keypair, 26 | pyth_token_mint: &Keypair, 27 | join_dao: bool, 28 | airdrop: bool, 29 | ) -> Pubkey { 30 | let stake_account_positions = create_account( 31 | svm, 32 | payer, 33 | staking::state::positions::PositionData::LEN, 34 | staking::ID, 35 | ); 36 | 37 | create_stake_account(svm, payer, pyth_token_mint, stake_account_positions).unwrap(); 38 | 39 | if join_dao { 40 | join_dao_llc(svm, payer, stake_account_positions).unwrap(); 41 | } 42 | 43 | if airdrop { 44 | let stake_account_custody = get_stake_account_custody_address(stake_account_positions); 45 | 46 | airdrop_spl( 47 | svm, 48 | payer, 49 | stake_account_custody, 50 | pyth_token_mint, 51 | STAKED_TOKENS, 52 | ); 53 | } 54 | 55 | stake_account_positions 56 | } 57 | -------------------------------------------------------------------------------- /staking/integration-tests/src/staking/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod helper_functions; 2 | pub mod instructions; 3 | pub mod pda; 4 | -------------------------------------------------------------------------------- /staking/integration-tests/src/staking/pda.rs: -------------------------------------------------------------------------------- 1 | use { 2 | solana_sdk::pubkey::Pubkey, 3 | staking::context::{ 4 | CONFIG_SEED, 5 | TARGET_SEED, 6 | VOTING_TARGET_SEED, 7 | }, 8 | }; 9 | 10 | pub fn get_config_address_bump() -> u8 { 11 | Pubkey::find_program_address(&[CONFIG_SEED.as_bytes()], &staking::ID).1 12 | } 13 | 14 | pub fn get_config_address() -> Pubkey { 15 | Pubkey::find_program_address(&[CONFIG_SEED.as_bytes()], &staking::ID).0 16 | } 17 | 18 | pub fn get_target_address() -> Pubkey { 19 | Pubkey::find_program_address( 20 | &[TARGET_SEED.as_bytes(), VOTING_TARGET_SEED.as_bytes()], 21 | &staking::ID, 22 | ) 23 | .0 24 | } 25 | 26 | pub fn get_stake_account_metadata_address(stake_account_positions: Pubkey) -> Pubkey { 27 | Pubkey::find_program_address( 28 | &[ 29 | staking::context::STAKE_ACCOUNT_METADATA_SEED.as_bytes(), 30 | stake_account_positions.as_ref(), 31 | ], 32 | &staking::ID, 33 | ) 34 | .0 35 | } 36 | 37 | pub fn get_stake_account_custody_address(stake_account_positions: Pubkey) -> Pubkey { 38 | Pubkey::find_program_address( 39 | &[ 40 | staking::context::CUSTODY_SEED.as_bytes(), 41 | stake_account_positions.as_ref(), 42 | ], 43 | &staking::ID, 44 | ) 45 | .0 46 | } 47 | 48 | pub fn get_stake_account_custody_authority_address(stake_account_positions: Pubkey) -> Pubkey { 49 | Pubkey::find_program_address( 50 | &[ 51 | staking::context::AUTHORITY_SEED.as_bytes(), 52 | stake_account_positions.as_ref(), 53 | ], 54 | &staking::ID, 55 | ) 56 | .0 57 | } 58 | 59 | pub fn get_voter_record_address(stake_account_positions: Pubkey) -> Pubkey { 60 | Pubkey::find_program_address( 61 | &[ 62 | staking::context::VOTER_RECORD_SEED.as_bytes(), 63 | stake_account_positions.as_ref(), 64 | ], 65 | &staking::ID, 66 | ) 67 | .0 68 | } 69 | 70 | pub fn get_max_voter_record_address() -> Pubkey { 71 | Pubkey::find_program_address( 72 | &[staking::context::MAX_VOTER_RECORD_SEED.as_bytes()], 73 | &staking::ID, 74 | ) 75 | .0 76 | } 77 | -------------------------------------------------------------------------------- /staking/integration-tests/src/utils/clock.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::staking::pda::get_config_address, 3 | anchor_lang::{ 4 | InstructionData, 5 | ToAccountMetas, 6 | }, 7 | integrity_pool::utils::clock::EPOCH_DURATION, 8 | litesvm::LiteSVM, 9 | solana_sdk::{ 10 | clock::Clock, 11 | instruction::Instruction, 12 | signature::Keypair, 13 | signer::Signer, 14 | transaction::Transaction, 15 | }, 16 | std::convert::TryInto, 17 | }; 18 | 19 | pub fn advance_n_epochs(svm: &mut LiteSVM, payer: &Keypair, n: u64) { 20 | svm.expire_blockhash(); 21 | let seconds = TryInto::::try_into(EPOCH_DURATION * n).unwrap(); 22 | 23 | let mut clock = svm.get_sysvar::(); 24 | clock.unix_timestamp += seconds; 25 | svm.set_sysvar::(&clock); 26 | 27 | advance_mock_clock(svm, payer, seconds) // we need this since we're still using mock clock in 28 | // the staking program 29 | } 30 | 31 | pub fn get_current_epoch(svm: &mut LiteSVM) -> u64 { 32 | let clock = svm.get_sysvar::(); 33 | clock.unix_timestamp as u64 / EPOCH_DURATION 34 | } 35 | 36 | pub fn advance_mock_clock(svm: &mut LiteSVM, payer: &Keypair, seconds: i64) { 37 | let accs = staking::accounts::AdvanceClock { 38 | config: get_config_address(), 39 | }; 40 | 41 | let data = staking::instruction::AdvanceClock { seconds }; 42 | 43 | let ix = Instruction::new_with_bytes(staking::ID, &data.data(), accs.to_account_metas(None)); 44 | 45 | let tx = Transaction::new_signed_with_payer( 46 | &[ix], 47 | Some(&payer.pubkey()), 48 | &[payer], 49 | svm.latest_blockhash(), 50 | ); 51 | 52 | svm.send_transaction(tx).unwrap(); 53 | } 54 | -------------------------------------------------------------------------------- /staking/integration-tests/src/utils/constants.rs: -------------------------------------------------------------------------------- 1 | use integrity_pool::utils::types::{ 2 | frac64, 3 | FRAC_64_MULTIPLIER, 4 | }; 5 | 6 | // 100 PYTH tokens 7 | pub const STAKED_TOKENS: frac64 = 100 * FRAC_64_MULTIPLIER; 8 | 9 | // 1% yield per epoch 10 | pub const YIELD: frac64 = FRAC_64_MULTIPLIER / 100; 11 | -------------------------------------------------------------------------------- /staking/integration-tests/src/utils/error.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! assert_anchor_program_error { 3 | ($transaction_result:expr, $expected_error:expr, $instruction_index:expr) => { 4 | assert_eq!( 5 | $transaction_result.unwrap_err().err, 6 | solana_sdk::transaction::TransactionError::InstructionError( 7 | $instruction_index, 8 | solana_sdk::instruction::InstructionError::from(u64::from( 9 | solana_sdk::program_error::ProgramError::from( 10 | anchor_lang::prelude::Error::from($expected_error) 11 | ) 12 | )) 13 | ) 14 | ); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /staking/integration-tests/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod clock; 2 | pub mod constants; 3 | pub mod error; 4 | -------------------------------------------------------------------------------- /staking/integration-tests/tests/publisher_caps.rs: -------------------------------------------------------------------------------- 1 | use { 2 | integration_tests::{ 3 | assert_anchor_program_error, 4 | publisher_caps::{ 5 | helper_functions::post_dummy_publisher_caps, 6 | instructions::close_publisher_caps, 7 | }, 8 | setup::{ 9 | setup, 10 | SetupProps, 11 | SetupResult, 12 | }, 13 | }, 14 | publisher_caps::PublisherCapsError, 15 | solana_sdk::{ 16 | signature::Keypair, 17 | signer::Signer, 18 | }, 19 | }; 20 | 21 | 22 | #[test] 23 | fn test_close_publisher_caps() { 24 | let SetupResult { 25 | mut svm, 26 | payer, 27 | publisher_keypair, 28 | .. 29 | } = setup(SetupProps { 30 | init_config: true, 31 | init_target: true, 32 | init_mint: true, 33 | init_pool_data: true, 34 | init_publishers: true, 35 | reward_amount_override: None, 36 | }); 37 | 38 | let publisher_caps = 39 | post_dummy_publisher_caps(&mut svm, &payer, publisher_keypair.pubkey(), 10); 40 | 41 | assert!(svm.get_account(&publisher_caps).unwrap().lamports > 0); 42 | let payer_balance_before = svm.get_account(&payer.pubkey()).unwrap().lamports; 43 | 44 | assert_anchor_program_error!( 45 | close_publisher_caps(&mut svm, &Keypair::new(), &payer, publisher_caps), 46 | PublisherCapsError::WrongWriteAuthority, 47 | 0 48 | ); 49 | 50 | close_publisher_caps(&mut svm, &payer, &payer, publisher_caps).unwrap(); 51 | assert_eq!(svm.get_account(&publisher_caps).unwrap().data.len(), 0); 52 | assert_eq!(svm.get_account(&publisher_caps).unwrap().lamports, 0); 53 | 54 | assert!(svm.get_account(&payer.pubkey()).unwrap().lamports > payer_balance_before); 55 | } 56 | -------------------------------------------------------------------------------- /staking/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pythnetwork/staking", 3 | "version": "2.3.0", 4 | "description": "Pyth Network Staking SDK", 5 | "main": "lib/app/index.js", 6 | "types": "lib/app/index.d.ts", 7 | "license": "MIT", 8 | "files": [ 9 | "lib", 10 | "target/idl", 11 | "target/types" 12 | ], 13 | "repository": "https://github.com/pyth-network/governance/tree/main/staking", 14 | "dependencies": { 15 | "@coral-xyz/anchor": "^0.30.1", 16 | "@pythnetwork/solana-utils": "^0.4.1", 17 | "@pythnetwork/staking-wasm": "*", 18 | "@solana/spl-governance": "0.3.26", 19 | "@solana/spl-token": "^0.1.8", 20 | "@solana/web3.js": "^1.87.5", 21 | "encoding": "^0.1.13", 22 | "ethers": "^6.10.0", 23 | "ts-node": "^10.7.0", 24 | "typescript": "^4.3.5" 25 | }, 26 | "devDependencies": { 27 | "@ledgerhq/hw-transport": "^6.27.2", 28 | "@ledgerhq/hw-transport-node-hid": "^6.27.21", 29 | "@metaplex-foundation/js": "^0.17.5", 30 | "@metaplex-foundation/mpl-token-metadata": "^2.5.1", 31 | "@oneidentity/zstd-js": "^1.0.3", 32 | "@types/bn.js": "^5.1.0", 33 | "@types/mocha": "^9.1.1", 34 | "@types/node": "^17.0.34", 35 | "@types/shelljs": "^0.8.11", 36 | "axios": "^1.6.7", 37 | "chai": "^4.3.4", 38 | "dotenv": "^16.4.1", 39 | "mocha": "^9.2.2", 40 | "papaparse": "^5.4.1", 41 | "prettier": "^2.6.2", 42 | "shelljs": "^0.8.5", 43 | "ts-mocha": "^9.0.2", 44 | "wasm-pack": "^0.10.2" 45 | }, 46 | "scripts": { 47 | "test": "npm run build_wasm && anchor build -- --features mock-clock && ts-mocha --parallel -p ./tsconfig.json -t 1000000", 48 | "test:ci": "npm run build_wasm && mkdir -p target/debug && solana-verify build -- --features mock-clock && ts-mocha --parallel -p ./tsconfig.json -t 1000000 tests/*.ts", 49 | "build": "npm run build_wasm && tsc -p tsconfig.api.json", 50 | "build_wasm": "./scripts/build_wasm.sh", 51 | "localnet": "anchor build && ts-node ./app/scripts/localnet.ts" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /staking/programs/integrity-pool/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integrity-pool" 3 | version = "1.0.0" 4 | description = "Created with Anchor" 5 | edition = "2018" 6 | 7 | [features] 8 | mock-clock = [] 9 | idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] 10 | 11 | [lib] 12 | crate-type = ["cdylib", "lib"] 13 | name = "integrity_pool" 14 | 15 | [dependencies] 16 | anchor-lang = {workspace = true, features = ["init-if-needed"]} 17 | anchor-spl = {workspace = true} 18 | bytemuck = {version = "1.4.0", features = ["derive", "min_const_generics"]} 19 | pyth-staking-program = {path = "../staking", features = ["cpi"]} 20 | publisher-caps = {path = "../publisher-caps", features = ["no-entrypoint"]} 21 | 22 | 23 | [dev-dependencies] 24 | solana-sdk = { workspace = true } 25 | -------------------------------------------------------------------------------- /staking/programs/integrity-pool/src/error.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::error_code; 2 | 3 | #[error_code] 4 | pub enum IntegrityPoolError { 5 | PublisherNotFound, 6 | PublisherOrRewardAuthorityNeedsToSign, 7 | StakeAccountOwnerNeedsToSign, 8 | OutdatedPublisherAccounting, 9 | TooManyPublishers, 10 | UnexpectedPositionState, 11 | PoolDataAlreadyUpToDate, 12 | OutdatedPublisherCaps, 13 | OutdatedDelegatorAccounting, 14 | CurrentStakeAccountShouldBeUndelegated, 15 | NewStakeAccountShouldBeUndelegated, 16 | PublisherStakeAccountMismatch, 17 | ThisCodeShouldBeUnreachable, 18 | InsufficientRewards, 19 | #[msg("Start epoch of the reward program is before the current epoch")] 20 | InvalidStartEpoch, 21 | UnverifiedPublisherCaps, 22 | #[msg("Slash event indexes must be sequential and start at 0")] 23 | InvalidSlashEventIndex, 24 | InvalidRewardProgramAuthority, 25 | InvalidPoolDataAccount, 26 | #[msg("Slashes must be executed in order of slash event index")] 27 | WrongSlashEventOrder, 28 | #[msg("Publisher custody account required")] 29 | PublisherCustodyAccountRequired, 30 | #[msg("Delegation fee must not be greater than 100%")] 31 | InvalidDelegationFee, 32 | InvalidPublisher, 33 | #[msg("Y should not be greater than 1%")] 34 | InvalidY, 35 | InvalidSlashCustodyAccount, 36 | } 37 | -------------------------------------------------------------------------------- /staking/programs/integrity-pool/src/state/delegation_record.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::error::IntegrityPoolError, 3 | anchor_lang::prelude::*, 4 | borsh::BorshSchema, 5 | }; 6 | 7 | #[account] 8 | #[derive(BorshSchema)] 9 | pub struct DelegationRecord { 10 | pub last_epoch: u64, 11 | pub next_slash_event_index: u64, 12 | } 13 | 14 | impl DelegationRecord { 15 | pub const LEN: usize = 128; 16 | } 17 | 18 | impl DelegationRecord { 19 | pub fn assert_up_to_date(&self, current_epoch: u64) -> Result<()> { 20 | require_eq!( 21 | self.last_epoch, 22 | current_epoch, 23 | IntegrityPoolError::OutdatedDelegatorAccounting 24 | ); 25 | Ok(()) 26 | } 27 | 28 | pub fn advance(&mut self, current_epoch: u64) -> Result<()> { 29 | self.last_epoch = current_epoch; 30 | Ok(()) 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use { 37 | super::*, 38 | anchor_lang::Discriminator, 39 | }; 40 | 41 | #[test] 42 | #[allow(deprecated)] 43 | fn test_delegation_record_len() { 44 | assert!( 45 | anchor_lang::solana_program::borsh0_10::get_packed_len::() 46 | + DelegationRecord::discriminator().len() 47 | <= DelegationRecord::LEN 48 | ); 49 | } 50 | 51 | #[test] 52 | fn test_advance() { 53 | let mut record = DelegationRecord { 54 | last_epoch: 0, 55 | next_slash_event_index: 0, 56 | }; 57 | record.advance(1).unwrap(); 58 | assert_eq!(record.last_epoch, 1); 59 | } 60 | 61 | #[test] 62 | fn test_assert_up_to_date() { 63 | let record = DelegationRecord { 64 | last_epoch: 100, 65 | next_slash_event_index: 0, 66 | }; 67 | assert!(record.assert_up_to_date(100).is_ok()); 68 | assert!(record.assert_up_to_date(101).is_err()); 69 | assert!(record.assert_up_to_date(99).is_err()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /staking/programs/integrity-pool/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod delegation_record; 2 | pub mod event; 3 | pub mod pool; 4 | pub mod slash; 5 | -------------------------------------------------------------------------------- /staking/programs/integrity-pool/src/state/slash.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::utils::types::frac64, 3 | anchor_lang::prelude::*, 4 | borsh::BorshSchema, 5 | }; 6 | 7 | #[account] 8 | #[derive(BorshSchema)] 9 | pub struct SlashEvent { 10 | pub epoch: u64, 11 | pub slash_ratio: frac64, 12 | pub slash_custody: Pubkey, 13 | } 14 | 15 | impl SlashEvent { 16 | pub const LEN: usize = 8 + 8 + 8 + 32; 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use { 22 | super::*, 23 | anchor_lang::Discriminator, 24 | }; 25 | 26 | #[test] 27 | #[allow(deprecated)] 28 | fn test_slash_event_len() { 29 | assert!( 30 | anchor_lang::solana_program::borsh0_10::get_packed_len::() 31 | + SlashEvent::discriminator().len() 32 | == SlashEvent::LEN 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /staking/programs/integrity-pool/src/utils/clock.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::{ 3 | prelude::*, 4 | solana_program::clock::UnixTimestamp, 5 | }, 6 | std::convert::TryInto, 7 | }; 8 | 9 | pub const EPOCH_DURATION: u64 = 60 * 60 * 24 * 7; // 1 week 10 | 11 | /// Computes Pyth clock. 12 | /// Right now it's just the current Unix timestamp divided by the epoch duration. 13 | pub fn get_current_epoch() -> Result { 14 | let now_ts = get_current_time(); 15 | time_to_epoch(now_ts) 16 | } 17 | 18 | pub fn time_to_epoch(now_ts: UnixTimestamp) -> Result { 19 | // divide now_ts by EPOCH_DURATION 20 | Ok(TryInto::::try_into(now_ts)? / EPOCH_DURATION) 21 | } 22 | 23 | pub fn get_current_time() -> UnixTimestamp { 24 | Clock::get().unwrap().unix_timestamp 25 | } 26 | -------------------------------------------------------------------------------- /staking/programs/integrity-pool/src/utils/constants.rs: -------------------------------------------------------------------------------- 1 | pub const MAX_PUBLISHERS: usize = 1024; 2 | pub const MAX_EVENTS: usize = 52; 3 | 4 | 5 | pub const POOL_CONFIG: &str = "pool_config"; 6 | pub const DELEGATION_RECORD: &str = "delegation_record"; 7 | pub const SLASH_EVENT: &str = "slash_event"; 8 | -------------------------------------------------------------------------------- /staking/programs/integrity-pool/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod clock; 2 | pub mod constants; 3 | pub mod types; 4 | -------------------------------------------------------------------------------- /staking/programs/integrity-pool/src/utils/types.rs: -------------------------------------------------------------------------------- 1 | #[allow(non_camel_case_types)] 2 | // It is used to store fractional numbers with 6 decimal places 3 | // The number 6 is coming from the decimal places of the PYTH token 4 | pub type frac64 = u64; 5 | 6 | pub const FRAC_64_MULTIPLIER: u64 = 1_000_000; 7 | pub const FRAC_64_MULTIPLIER_U128: u128 = FRAC_64_MULTIPLIER as u128; 8 | 9 | 10 | pub struct BoolArray { 11 | pub data: Vec, 12 | } 13 | 14 | impl BoolArray { 15 | pub fn new(n: usize) -> Self { 16 | Self { 17 | data: vec![0; (n + 7) / 8], 18 | } 19 | } 20 | 21 | pub fn get(&self, i: usize) -> bool { 22 | let byte = i / 8; 23 | let bit = i % 8; 24 | self.data[byte] & (1 << bit) != 0 25 | } 26 | 27 | pub fn set(&mut self, i: usize) { 28 | let byte = i / 8; 29 | let bit = i % 8; 30 | self.data[byte] |= 1 << bit; 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use { 37 | super::*, 38 | publisher_caps::MAX_CAPS, 39 | }; 40 | 41 | #[test] 42 | fn test_bool_array() { 43 | let mut arr = BoolArray::new(10); 44 | for i in 0..10 { 45 | assert!(!arr.get(i)); 46 | arr.set(i); 47 | assert!(arr.get(i)); 48 | } 49 | 50 | let mut arr = BoolArray::new(MAX_CAPS); 51 | for i in 0..MAX_CAPS { 52 | assert!(!arr.get(i)); 53 | arr.set(i); 54 | assert!(arr.get(i)); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /staking/programs/profile/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "profile" 3 | version = "1.0.0" 4 | description = "Created with Anchor" 5 | edition = "2018" 6 | 7 | [features] 8 | mock-clock = [] 9 | idl-build = ["anchor-lang/idl-build"] 10 | 11 | [lib] 12 | crate-type = ["cdylib", "lib"] 13 | name = "profile" 14 | 15 | [dependencies] 16 | anchor-lang = {workspace = true, features = ["init-if-needed"]} 17 | -------------------------------------------------------------------------------- /staking/programs/profile/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Profile program 2 | //! 3 | //! This program allows a Solana user to map their Solana address to their addresses on other chains 4 | use anchor_lang::prelude::*; 5 | 6 | declare_id!("prfmVhiQTN5Spgoxa8uZJba35V1s7XXReqbBiqPDWeJ"); 7 | 8 | #[program] 9 | pub mod profile { 10 | use super::*; 11 | 12 | pub fn update_identity(ctx: Context, identity: Identity) -> Result<()> { 13 | ctx.accounts.identity_account.identity = identity; 14 | Ok(()) 15 | } 16 | } 17 | 18 | #[derive(Accounts)] 19 | #[instruction(identity: Identity)] 20 | pub struct UpdateIdentity<'info> { 21 | #[account(mut)] 22 | payer: Signer<'info>, 23 | #[account(init_if_needed, payer = payer, space = identity.size(), seeds = [&[identity.ecosystem_id_as_u8()], payer.key().as_ref()], bump ) ] 24 | identity_account: Account<'info, IdentityAccount>, 25 | system_program: Program<'info, System>, 26 | } 27 | 28 | #[account] 29 | pub struct IdentityAccount { 30 | identity: Identity, 31 | } 32 | 33 | #[derive(AnchorDeserialize, AnchorSerialize, Clone)] 34 | pub enum Identity { 35 | Evm { pubkey: Option<[u8; 20]> }, 36 | } 37 | 38 | impl Identity { 39 | fn ecosystem_id_as_u8(&self) -> u8 { 40 | match self { 41 | Identity::Evm { .. } => 0, 42 | } 43 | } 44 | 45 | fn size(&self) -> usize { 46 | 8 + 1 47 | + match self { 48 | Identity::Evm { .. } => 21, 49 | } 50 | } 51 | } 52 | 53 | #[cfg(test)] 54 | pub mod tests { 55 | use { 56 | super::*, 57 | anchor_lang::Discriminator, 58 | }; 59 | 60 | #[test] 61 | fn check_size() { 62 | let evm_identity = Identity::Evm { 63 | pubkey: Some([0u8; 20]), 64 | }; 65 | 66 | assert_eq!( 67 | IdentityAccount::discriminator().len() + evm_identity.try_to_vec().unwrap().len(), 68 | evm_identity.size() 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /staking/programs/publisher-caps/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "publisher-caps" 3 | version = "1.0.0" 4 | description = "Created with Anchor" 5 | edition = "2018" 6 | 7 | [features] 8 | mock-clock = [] 9 | no-entrypoint = [] 10 | idl-build = ["anchor-lang/idl-build"] 11 | 12 | [lib] 13 | crate-type = ["cdylib", "lib"] 14 | name = "publisher_caps" 15 | 16 | [dependencies] 17 | anchor-lang = {workspace = true, features = ["init-if-needed"]} 18 | arrayref = "0.3.8" 19 | bytemuck = {version = "1.4.0", features = ["derive", "min_const_generics"]} 20 | pythnet-sdk = {version = "2.3.0", features = ["solana-program"]} 21 | wormhole-solana-vaas = {version="0.3.0-alpha.1", features = ["anchor", "encoded-vaa", "mainnet"]} 22 | -------------------------------------------------------------------------------- /staking/programs/staking/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pyth-staking-program" 3 | version = "2.1.0" 4 | description = "Created with Anchor" 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "staking" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | mock-clock = [] 17 | wasm = ["no-entrypoint", "js-sys", "bincode"] 18 | default = [] 19 | idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] 20 | 21 | [dependencies] 22 | anchor-lang = {workspace = true, features = ["init-if-needed"]} 23 | anchor-spl = {workspace = true} 24 | wasm-bindgen = "0.2.79" 25 | spl-governance = {version = "4.0.0", features = ["no-entrypoint"]} 26 | js-sys = { version = "0.3.56", optional = true } 27 | bincode = { version = "1.3.3", optional = true } 28 | solana-program = { workspace = true } 29 | bytemuck = {version = "1.4.0", features = ["derive", "min_const_generics"]} 30 | ahash = "=0.8.11" 31 | arrayref = "0.3.8" 32 | 33 | [dev-dependencies] 34 | rand = "0.8.5" 35 | quickcheck = "1" 36 | quickcheck_macros = "1" 37 | -------------------------------------------------------------------------------- /staking/programs/staking/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /staking/programs/staking/src/state/global_config.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::prelude::*, 3 | borsh::BorshSchema, 4 | }; 5 | 6 | #[account] 7 | #[derive(Default, BorshSchema)] 8 | pub struct GlobalConfig { 9 | pub bump: u8, 10 | pub governance_authority: Pubkey, 11 | pub pyth_token_mint: Pubkey, 12 | pub pyth_governance_realm: Pubkey, 13 | // unlocking_duration is deprecated, but we need to keep the space for account structure 14 | pub removed_unlocking_duration: u8, 15 | pub epoch_duration: u64, // epoch duration in seconds 16 | pub freeze: bool, 17 | pub pda_authority: Pubkey, /* Authority that can authorize the transfer of 18 | * locked 19 | * tokens */ 20 | pub governance_program: Pubkey, // Governance program id 21 | 22 | /// Once the pyth token is listed, governance can update the config to set this value. 23 | /// Once this value is set, vesting schedules that depend on the token list date can start 24 | /// vesting. 25 | pub pyth_token_list_time: Option, 26 | pub agreement_hash: [u8; 32], 27 | 28 | pub mock_clock_time: i64, /* this field needs to be greater than 0 otherwise the API 29 | * will use real time */ 30 | pub pool_authority: Pubkey, 31 | } 32 | 33 | impl GlobalConfig { 34 | pub const LEN: usize = 10240; 35 | } 36 | 37 | #[cfg(test)] 38 | pub mod tests { 39 | use crate::state::global_config::GlobalConfig; 40 | 41 | #[test] 42 | #[allow(deprecated)] 43 | fn check_size() { 44 | assert!( 45 | anchor_lang::solana_program::borsh::get_packed_len::() 46 | < GlobalConfig::LEN 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /staking/programs/staking/src/state/max_voter_weight_record.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::{ 2 | borsh::BorshSchema, 3 | *, 4 | }; 5 | 6 | pub const MAX_VOTER_WEIGHT: u64 = 10_000_000_000_000_000; // 10 Billion with 6 decimals 7 | 8 | /// Copied this struct from https://github.com/solana-labs/solana-program-library/blob/master/governance/addin-api/src/max_voter_weight.rs 9 | #[account] 10 | #[derive(BorshSchema)] 11 | pub struct MaxVoterWeightRecord { 12 | /// The Realm the MaxVoterWeightRecord belongs to 13 | pub realm: Pubkey, 14 | 15 | /// Governing Token Mint the MaxVoterWeightRecord is associated with 16 | /// Note: The addin can take deposits of any tokens and is not restricted to the community or 17 | /// council tokens only 18 | // The mint here is to link the record to either community or council mint of the realm 19 | pub governing_token_mint: Pubkey, 20 | 21 | /// Max voter weight 22 | /// The max voter weight provided by the addin for the given realm and governing_token_mint 23 | pub max_voter_weight: u64, 24 | 25 | /// The slot when the max voting weight expires 26 | /// It should be set to None if the weight never expires 27 | /// If the max vote weight decays with time, for example for time locked based weights, then 28 | /// the expiry must be set As a pattern Revise instruction to update the max weight should 29 | /// be invoked before governance instruction within the same transaction and the expiry set 30 | /// to the current slot to provide up to date weight 31 | pub max_voter_weight_expiry: Option, 32 | 33 | /// Reserved space for future versions 34 | pub reserved: [u8; 8], 35 | } 36 | 37 | impl MaxVoterWeightRecord { 38 | pub const LEN: usize = 8 + 32 + 32 + 8 + 9 + 8; 39 | } 40 | 41 | #[cfg(test)] 42 | pub mod tests { 43 | use { 44 | crate::state::max_voter_weight_record::MaxVoterWeightRecord, 45 | anchor_lang::Discriminator, 46 | }; 47 | 48 | #[test] 49 | #[allow(deprecated)] 50 | fn check_size() { 51 | assert_eq!( 52 | anchor_lang::solana_program::borsh::get_packed_len::() 53 | + MaxVoterWeightRecord::discriminator().len(), 54 | MaxVoterWeightRecord::LEN 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /staking/programs/staking/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod global_config; 2 | pub mod max_voter_weight_record; 3 | pub mod positions; 4 | pub mod split_request; 5 | pub mod stake_account; 6 | pub mod target; 7 | pub mod vesting; 8 | pub mod voter_weight_record; 9 | -------------------------------------------------------------------------------- /staking/programs/staking/src/state/split_request.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anchor_lang::prelude::*, 3 | borsh::BorshSchema, 4 | }; 5 | 6 | #[account] 7 | #[derive(Default, BorshSchema)] 8 | pub struct SplitRequest { 9 | pub amount: u64, 10 | pub recipient: Pubkey, 11 | } 12 | 13 | impl SplitRequest { 14 | pub const LEN: usize = 8 // Discriminant 15 | + 8 // Amount 16 | + 32; // Recipient 17 | } 18 | -------------------------------------------------------------------------------- /staking/programs/staking/src/utils/clock.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | error::ErrorCode, 4 | state::global_config::GlobalConfig, 5 | }, 6 | anchor_lang::{ 7 | prelude::*, 8 | solana_program::clock::UnixTimestamp, 9 | }, 10 | std::convert::TryInto, 11 | }; 12 | 13 | pub const UNLOCKING_DURATION: u64 = 1; // 1 epoch 14 | 15 | /// Computes Pyth clock. 16 | /// Right now it's just the current Unix timestamp divided by the epoch length 17 | pub fn get_current_epoch(global_config: &GlobalConfig) -> Result { 18 | let now_ts: i64 = get_current_time(global_config); 19 | time_to_epoch(global_config, now_ts) 20 | } 21 | 22 | pub fn time_to_epoch(global_config: &GlobalConfig, now_ts: UnixTimestamp) -> Result { 23 | TryInto::::try_into(now_ts) 24 | .map_err(|_| ErrorCode::GenericOverflow)? 25 | .checked_div(global_config.epoch_duration) 26 | .ok_or_else(|| error!(ErrorCode::ZeroEpochDuration)) 27 | } 28 | 29 | // As an extra form of defense to make sure we're not using the mock clock 30 | // in devnet or mainnet, we'd like to have an assert(localnet). There's not 31 | // an easy way to do that, but something that gets close is checking that the 32 | // number of slots that have passed is much smaller than anything possible on 33 | // mainnet or devnet. We set the threshold at 10 million slots, which is more 34 | // than a month. mainnet, devnet, and testnet are all > 140 million right now. 35 | #[cfg(feature = "mock-clock")] 36 | const MAX_LOCALNET_VALIDATOR_RUNTIME_SLOTS: u64 = 10_000_000; 37 | 38 | #[cfg(feature = "mock-clock")] 39 | pub fn get_current_time(global_config: &GlobalConfig) -> UnixTimestamp { 40 | assert!(Clock::get().unwrap().slot < MAX_LOCALNET_VALIDATOR_RUNTIME_SLOTS); 41 | global_config.mock_clock_time 42 | } 43 | #[cfg(not(feature = "mock-clock"))] 44 | pub fn get_current_time(_global_config: &GlobalConfig) -> UnixTimestamp { 45 | Clock::get().unwrap().unix_timestamp 46 | } 47 | -------------------------------------------------------------------------------- /staking/programs/staking/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod clock; 2 | pub mod risk; 3 | pub mod voter_weight; 4 | -------------------------------------------------------------------------------- /staking/programs/wallet-tester/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wallet-tester" 3 | version = "1.0.0" 4 | description = "Created with Anchor" 5 | edition = "2018" 6 | 7 | [features] 8 | mock-clock = [] 9 | idl-build = ["anchor-lang/idl-build"] 10 | 11 | [lib] 12 | crate-type = ["cdylib", "lib"] 13 | name = "wallet_tester" 14 | 15 | [dependencies] 16 | anchor-lang = {workspace = true, features = ["init-if-needed"]} 17 | -------------------------------------------------------------------------------- /staking/programs/wallet-tester/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Wallet tester program 2 | //! 3 | //! This is just program that is used to test that a wallet can send a transaction to a program 4 | //! It's used so that future recipients of locked tokens can test before they receive the tokens 5 | use anchor_lang::prelude::*; 6 | 7 | declare_id!("tstPARXbQ5yxVkRU2UcZRbYphzbUEW6t5ihzpLaafgz"); 8 | 9 | #[program] 10 | pub mod wallet_tester { 11 | use super::*; 12 | 13 | pub fn test(_ctx: Context) -> Result<()> { 14 | Ok(()) 15 | } 16 | } 17 | 18 | #[derive(Accounts)] 19 | pub struct Test<'info> { 20 | #[account(mut)] 21 | payer: Signer<'info>, 22 | /// CHECK: this is just a receipt account without any data 23 | #[account(init_if_needed, payer = payer, space = 0, seeds = [payer.key().as_ref()], bump ) ] 24 | test_receipt: AccountInfo<'info>, 25 | system_program: Program<'info, System>, 26 | } 27 | -------------------------------------------------------------------------------- /staking/rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.75.0" 3 | -------------------------------------------------------------------------------- /staking/scripts/build_wasm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd programs/staking/ 3 | wasm-pack build -d ../../../wasm/bundle -- --features wasm 4 | wasm-pack build -d ../../../wasm/node -t nodejs -- --features wasm 5 | -------------------------------------------------------------------------------- /staking/target/idl/wallet_tester.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "tstPARXbQ5yxVkRU2UcZRbYphzbUEW6t5ihzpLaafgz", 3 | "metadata": { 4 | "name": "wallet_tester", 5 | "version": "1.0.0", 6 | "spec": "0.1.0", 7 | "description": "Created with Anchor" 8 | }, 9 | "instructions": [ 10 | { 11 | "name": "test", 12 | "discriminator": [ 13 | 163, 14 | 36, 15 | 134, 16 | 53, 17 | 232, 18 | 223, 19 | 146, 20 | 222 21 | ], 22 | "accounts": [ 23 | { 24 | "name": "payer", 25 | "writable": true, 26 | "signer": true 27 | }, 28 | { 29 | "name": "test_receipt", 30 | "writable": true, 31 | "pda": { 32 | "seeds": [ 33 | { 34 | "kind": "account", 35 | "path": "payer" 36 | } 37 | ] 38 | } 39 | }, 40 | { 41 | "name": "system_program", 42 | "address": "11111111111111111111111111111111" 43 | } 44 | ], 45 | "args": [] 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /staking/target/types/wallet_tester.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Program IDL in camelCase format in order to be used in JS/TS. 3 | * 4 | * Note that this is only a type helper and is not the actual IDL. The original 5 | * IDL can be found at `target/idl/wallet_tester.json`. 6 | */ 7 | export type WalletTester = { 8 | "address": "tstPARXbQ5yxVkRU2UcZRbYphzbUEW6t5ihzpLaafgz", 9 | "metadata": { 10 | "name": "walletTester", 11 | "version": "1.0.0", 12 | "spec": "0.1.0", 13 | "description": "Created with Anchor" 14 | }, 15 | "instructions": [ 16 | { 17 | "name": "test", 18 | "discriminator": [ 19 | 163, 20 | 36, 21 | 134, 22 | 53, 23 | 232, 24 | 223, 25 | 146, 26 | 222 27 | ], 28 | "accounts": [ 29 | { 30 | "name": "payer", 31 | "writable": true, 32 | "signer": true 33 | }, 34 | { 35 | "name": "testReceipt", 36 | "writable": true, 37 | "pda": { 38 | "seeds": [ 39 | { 40 | "kind": "account", 41 | "path": "payer" 42 | } 43 | ] 44 | } 45 | }, 46 | { 47 | "name": "systemProgram", 48 | "address": "11111111111111111111111111111111" 49 | } 50 | ], 51 | "args": [] 52 | } 53 | ] 54 | }; 55 | -------------------------------------------------------------------------------- /staking/tests/create_product.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CustomAbortController, 3 | getPortNumber, 4 | standardSetup, 5 | } from "./utils/before"; 6 | import path from "path"; 7 | import { PublicKey } from "@solana/web3.js"; 8 | import { StakeConnection } from "../app"; 9 | import { BN, Program, utils } from "@coral-xyz/anchor"; 10 | import * as wasm from "@pythnetwork/staking-wasm"; 11 | import assert from "assert"; 12 | import { Staking } from "../target/types/staking"; 13 | import { abortUnlessDetached } from "./utils/after"; 14 | 15 | const portNumber = getPortNumber(path.basename(__filename)); 16 | 17 | describe("create_product", async () => { 18 | let stakeConnection: StakeConnection; 19 | let controller: CustomAbortController; 20 | let program: Program; 21 | let targetAccount: PublicKey; 22 | let bump: number; 23 | 24 | before(async () => { 25 | ({ controller, stakeConnection } = await standardSetup(portNumber)); 26 | 27 | program = stakeConnection.program; 28 | }); 29 | 30 | it("checks governance product", async () => { 31 | [targetAccount, bump] = await PublicKey.findProgramAddress( 32 | [ 33 | utils.bytes.utf8.encode(wasm.Constants.TARGET_SEED()), 34 | utils.bytes.utf8.encode(wasm.Constants.VOTING_TARGET_SEED()), 35 | ], 36 | program.programId 37 | ); 38 | 39 | const productAccountData = await program.account.targetMetadata.fetch( 40 | targetAccount 41 | ); 42 | 43 | assert.equal( 44 | JSON.stringify(productAccountData), 45 | JSON.stringify({ 46 | bump, 47 | lastUpdateAt: (await stakeConnection.getTime()).div( 48 | stakeConnection.config.epochDuration 49 | ), 50 | prevEpochLocked: new BN(0), 51 | locked: new BN(0), 52 | deltaLocked: new BN(0), 53 | }) 54 | ); 55 | }); 56 | 57 | after(async () => { 58 | await abortUnlessDetached(portNumber, controller); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /staking/tests/programs/chat.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/staking/tests/programs/chat.so -------------------------------------------------------------------------------- /staking/tests/programs/governance.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyth-network/governance/d94c5d95620aa993c53459240259c09821aebbbd/staking/tests/programs/governance.so -------------------------------------------------------------------------------- /staking/tests/utils/after.ts: -------------------------------------------------------------------------------- 1 | import { CustomAbortController } from "./before"; 2 | 3 | export async function abortUnlessDetached( 4 | portNumber: number, 5 | controller: CustomAbortController 6 | ) { 7 | if (process.env.DETACH) { 8 | console.log( 9 | `The validator is still running at url http://127.0.0.1:${portNumber}` 10 | ); 11 | while (true) { 12 | await new Promise((resolve) => setTimeout(resolve, 1000)); 13 | } 14 | } 15 | await controller.abort(); 16 | } 17 | -------------------------------------------------------------------------------- /staking/tests/utils/keys.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from "@solana/web3.js"; 2 | import fs from "fs"; 3 | 4 | export function loadKeypair(path: string) { 5 | return Keypair.fromSecretKey( 6 | new Uint8Array(JSON.parse(fs.readFileSync(path, "utf-8"))) 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /staking/tests/wallet_tester.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { StakeConnection } from "../app/StakeConnection"; 3 | import { 4 | standardSetup, 5 | getPortNumber, 6 | CustomAbortController, 7 | } from "./utils/before"; 8 | import path from "path"; 9 | import { abortUnlessDetached } from "./utils/after"; 10 | 11 | const portNumber = getPortNumber(path.basename(__filename)); 12 | 13 | describe("wallet tester", async () => { 14 | let stakeConnection: StakeConnection; 15 | let controller: CustomAbortController; 16 | 17 | after(async () => { 18 | await abortUnlessDetached(portNumber, controller); 19 | }); 20 | 21 | before(async () => { 22 | ({ controller, stakeConnection } = await standardSetup(portNumber)); 23 | }); 24 | 25 | it("tests a wallet", async () => { 26 | assert( 27 | !(await stakeConnection.walletHasTested(stakeConnection.userPublicKey())) 28 | ); 29 | await stakeConnection.testWallet(); 30 | assert( 31 | await stakeConnection.walletHasTested(stakeConnection.userPublicKey()) 32 | ); 33 | await stakeConnection.testWallet(); 34 | assert( 35 | await stakeConnection.walletHasTested(stakeConnection.userPublicKey()) 36 | ); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /staking/tsconfig.api.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "node"], 4 | "lib": ["es2015"], 5 | "module": "commonjs", 6 | "target": "es6", 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "outDir": "lib", 10 | "declaration": true, 11 | "resolveJsonModule": true 12 | }, 13 | "include": ["./app/*.ts"], 14 | "exclude": ["./tests", "./docker", "./migrations"] 15 | } 16 | -------------------------------------------------------------------------------- /staking/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "node"], 4 | "lib": ["es2015"], 5 | "module": "commonjs", 6 | "target": "es6", 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | }, 11 | "include": ["./app/*.ts", "./tests"], 12 | "exclude": ["./docker", "./migrations"] 13 | } 14 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": { 3 | "pages/api/v1/all_locked_accounts.ts": { 4 | "maxDuration": 30 5 | }, 6 | "pages/api/v1/cmc/supply.ts": { 7 | "maxDuration": 30 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /wasm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pythnetwork/staking-wasm", 3 | "description": "Web Assembly files for Pyth Network Staking SDK", 4 | "version": "0.3.4", 5 | "files": [ 6 | "bundle/staking_bg.wasm", 7 | "bundle/staking.js", 8 | "bundle/staking_bg.js", 9 | "bundle/staking.d.ts", 10 | "node/staking_bg.wasm", 11 | "node/staking.js", 12 | "node/staking.d.ts" 13 | ], 14 | "repository": "https://github.com/pyth-network/governance/tree/main/wasm", 15 | "main": "./node/staking.js", 16 | "browser": "./bundle/staking.js", 17 | "module": "./bundle/staking.js", 18 | "types": "./node/staking.d.ts" 19 | } 20 | --------------------------------------------------------------------------------