Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
|
77f29af900 | |
|
b84fc6f4a8 | |
|
fe239f6cc2 |
|
@ -15,4 +15,5 @@
|
||||||
|
|
||||||
.git
|
.git
|
||||||
|
|
||||||
!apps/hermes/src/state/cache.rs
|
hermes/wormhole
|
||||||
|
!hermes/src/state/cache.rs
|
||||||
|
|
|
@ -21,10 +21,10 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Download CLI
|
- name: Download CLI
|
||||||
run: wget https://github.com/aptos-labs/aptos-core/releases/download/aptos-cli-v3.1.0/aptos-cli-3.1.0-Ubuntu-22.04-x86_64.zip
|
run: wget https://github.com/aptos-labs/aptos-core/releases/download/aptos-cli-v1.0.4/aptos-cli-1.0.4-Ubuntu-22.04-x86_64.zip
|
||||||
|
|
||||||
- name: Unzip CLI
|
- name: Unzip CLI
|
||||||
run: unzip aptos-cli-3.1.0-Ubuntu-22.04-x86_64.zip
|
run: unzip aptos-cli-1.0.4-Ubuntu-22.04-x86_64.zip
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: ./aptos move test
|
run: ./aptos move test
|
||||||
|
|
|
@ -2,10 +2,10 @@ name: Check Fortuna
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths: [apps/fortuna/**]
|
paths: [fortuna/**]
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths: [apps/fortuna/**]
|
paths: [fortuna/**]
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -17,4 +17,4 @@ jobs:
|
||||||
toolchain: nightly-2023-07-23
|
toolchain: nightly-2023-07-23
|
||||||
override: true
|
override: true
|
||||||
- name: Run executor tests
|
- name: Run executor tests
|
||||||
run: cargo test --manifest-path ./apps/fortuna/Cargo.toml
|
run: cargo test --manifest-path ./fortuna/Cargo.toml
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
name: Test Fuel Contract
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- target_chains/fuel/**
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- target_chains/fuel/**
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: target_chains/fuel/contracts/
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Install Fuel toolchain
|
|
||||||
run: |
|
|
||||||
curl https://install.fuel.network | sh
|
|
||||||
echo "$HOME/.fuelup/bin" >> $GITHUB_PATH
|
|
||||||
- name: Build with Forc
|
|
||||||
run: forc build --verbose
|
|
||||||
- name: Run tests with Forc
|
|
||||||
run: forc test --verbose
|
|
||||||
- name: Build
|
|
||||||
run: cargo build --verbose
|
|
||||||
- name: Run tests
|
|
||||||
run: cargo test --verbose
|
|
|
@ -1,23 +0,0 @@
|
||||||
name: Check Hermes
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths: [apps/hermes/**]
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths: [apps/hermes/**]
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: nightly-2024-03-26
|
|
||||||
components: rustfmt, clippy
|
|
||||||
override: true
|
|
||||||
- name: Install protoc
|
|
||||||
uses: arduino/setup-protoc@v3
|
|
||||||
- name: Run executor tests
|
|
||||||
run: cargo test --manifest-path ./apps/hermes/Cargo.toml
|
|
|
@ -27,13 +27,6 @@ jobs:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: nightly-2023-07-23
|
toolchain: nightly-2023-07-23
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: nightly-2024-03-26
|
|
||||||
components: rustfmt, clippy
|
|
||||||
- name: Install protoc
|
|
||||||
uses: arduino/setup-protoc@v3
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Install poetry
|
- name: Install poetry
|
||||||
run: pipx install poetry
|
run: pipx install poetry
|
||||||
|
|
|
@ -32,4 +32,4 @@ jobs:
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo-test-sbf
|
run: cargo-test-sbf
|
||||||
- name: Run sdk tests
|
- name: Run sdk tests
|
||||||
run: cargo test --package pyth-solana-receiver-sdk
|
run: cargo test --package pyth-solana-receiver-state
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
name: Starknet contract
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- target_chains/starknet/contracts/**
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- target_chains/starknet/contracts/**
|
|
||||||
jobs:
|
|
||||||
check:
|
|
||||||
name: Starknet Foundry tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: target_chains/starknet/contracts/
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Install Scarb
|
|
||||||
uses: software-mansion/setup-scarb@v1
|
|
||||||
with:
|
|
||||||
tool-versions: target_chains/starknet/contracts/.tool-versions
|
|
||||||
- name: Install Starknet Foundry
|
|
||||||
uses: foundry-rs/setup-snfoundry@v3
|
|
||||||
with:
|
|
||||||
tool-versions: target_chains/starknet/contracts/.tool-versions
|
|
||||||
- name: Install Starkli
|
|
||||||
run: curl https://get.starkli.sh | sh && . ~/.config/.starkli/env && starkliup -v $(awk '/starkli/{print $2}' .tool-versions)
|
|
||||||
- name: Install Katana
|
|
||||||
run: curl -L https://install.dojoengine.org | bash && PATH="$PATH:$HOME/.config/.dojo/bin" dojoup -v $(awk '/dojo/{print $2}' .tool-versions)
|
|
||||||
- name: Check formatting
|
|
||||||
run: scarb fmt --check
|
|
||||||
- name: Run tests
|
|
||||||
run: snforge test
|
|
||||||
- name: Test local deployment script
|
|
||||||
run: bash -c 'PATH="$PATH:$HOME/.config/.dojo/bin" katana & . ~/.config/.starkli/env && deploy/local_deploy'
|
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "16"
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npx lerna run build --no-private
|
- run: npx lerna run build --no-private
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
name: Publish Pyth SDK for the Solana Receiver to crates.io
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- pyth-solana-receiver-sdk-v*
|
|
||||||
jobs:
|
|
||||||
publish-pyth-solana-receiver-sdk:
|
|
||||||
name: Publish Pyth SDK for the Solana Receiver
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout sources
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Install Rust
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
default: true
|
|
||||||
profile: minimal
|
|
||||||
|
|
||||||
- run: cargo +stable-x86_64-unknown-linux-gnu publish --token ${CARGO_REGISTRY_TOKEN}
|
|
||||||
env:
|
|
||||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
|
||||||
working-directory: "target_chains/solana/pyth_solana_receiver_sdk"
|
|
|
@ -1,18 +0,0 @@
|
||||||
name: Publish Pythnet SDK to crates.io
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- pythnet-sdk-v*
|
|
||||||
jobs:
|
|
||||||
publish-pythnet-sdk:
|
|
||||||
name: Publish Pythnet SDK
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout sources
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- run: cargo publish --token ${CARGO_REGISTRY_TOKEN}
|
|
||||||
env:
|
|
||||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
|
||||||
working-directory: "pythnet/pythnet_sdk"
|
|
|
@ -46,7 +46,7 @@ jobs:
|
||||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: "./apps/fortuna/Dockerfile"
|
file: "./fortuna/Dockerfile"
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.metadata_fortuna.outputs.tags }}
|
tags: ${{ steps.metadata_fortuna.outputs.tags }}
|
||||||
labels: ${{ steps.metadata_fortuna.outputs.labels }}
|
labels: ${{ steps.metadata_fortuna.outputs.labels }}
|
||||||
|
|
|
@ -37,7 +37,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
AWS_REGION: us-east-1
|
AWS_REGION: us-east-1
|
||||||
- run: |
|
- run: |
|
||||||
DOCKER_BUILDKIT=1 docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f apps/hermes/Dockerfile .
|
DOCKER_BUILDKIT=1 docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f hermes/Dockerfile .
|
||||||
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
|
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
|
||||||
env:
|
env:
|
||||||
ECR_REGISTRY: public.ecr.aws
|
ECR_REGISTRY: public.ecr.aws
|
||||||
|
|
|
@ -40,7 +40,7 @@ jobs:
|
||||||
id: ecr_login
|
id: ecr_login
|
||||||
- run: |
|
- run: |
|
||||||
DOCKER_BUILDKIT=1 docker build -t lerna -f Dockerfile.lerna .
|
DOCKER_BUILDKIT=1 docker build -t lerna -f Dockerfile.lerna .
|
||||||
DOCKER_BUILDKIT=1 docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f apps/price_pusher/Dockerfile .
|
DOCKER_BUILDKIT=1 docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f price_pusher/Dockerfile .
|
||||||
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
|
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
|
||||||
env:
|
env:
|
||||||
ECR_REGISTRY: public.ecr.aws
|
ECR_REGISTRY: public.ecr.aws
|
||||||
|
|
|
@ -6,12 +6,8 @@ on:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
packages: write
|
|
||||||
env:
|
|
||||||
REGISTRY: ghcr.io
|
|
||||||
IMAGE_NAME: pyth-network/xc-admin-frontend
|
|
||||||
jobs:
|
jobs:
|
||||||
xc-admin-frontend-image:
|
xc-admin-image:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
@ -20,17 +16,23 @@ jobs:
|
||||||
SHORT_HASH=$(echo ${{ github.sha }} | cut -c1-7)
|
SHORT_HASH=$(echo ${{ github.sha }} | cut -c1-7)
|
||||||
TIMESTAMP=$(date +%s)
|
TIMESTAMP=$(date +%s)
|
||||||
echo "IMAGE_TAG=${TIMESTAMP}-${SHORT_HASH}" >> "${GITHUB_ENV}"
|
echo "IMAGE_TAG=${TIMESTAMP}-${SHORT_HASH}" >> "${GITHUB_ENV}"
|
||||||
- name: Log in to the Container registry
|
- uses: aws-actions/configure-aws-credentials@8a84b07f2009032ade05a88a28750d733cc30db1
|
||||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
role-to-assume: arn:aws:iam::192824654885:role/github-actions-ecr
|
||||||
username: ${{ github.actor }}
|
aws-region: eu-west-2
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
- uses: aws-actions/amazon-ecr-login@v1
|
||||||
|
id: ecr_login
|
||||||
- name: Build docker image
|
- name: Build docker image
|
||||||
run: |
|
run: |
|
||||||
DOCKER_BUILDKIT=1 docker build -t lerna -f Dockerfile.lerna .
|
DOCKER_BUILDKIT=1 docker build -t lerna -f Dockerfile.lerna .
|
||||||
DOCKER_BUILDKIT=1 docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} -f governance/xc_admin/packages/xc_admin_frontend/Dockerfile .
|
DOCKER_BUILDKIT=1 docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f governance/xc_admin/packages/xc_admin_frontend/Dockerfile .
|
||||||
|
env:
|
||||||
|
ECR_REGISTRY: ${{ steps.ecr_login.outputs.registry }}
|
||||||
|
ECR_REPOSITORY: xc-admin-frontend
|
||||||
- name: Push docker image
|
- name: Push docker image
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
run: |
|
run: |
|
||||||
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
|
||||||
|
env:
|
||||||
|
ECR_REGISTRY: ${{ steps.ecr_login.outputs.registry }}
|
||||||
|
ECR_REPOSITORY: xc-admin-frontend
|
||||||
|
|
|
@ -6,10 +6,6 @@ on:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
packages: write
|
|
||||||
env:
|
|
||||||
REGISTRY: ghcr.io
|
|
||||||
IMAGE_NAME: pyth-network/xc-admin
|
|
||||||
jobs:
|
jobs:
|
||||||
xc-admin-image:
|
xc-admin-image:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -21,16 +17,16 @@ jobs:
|
||||||
PREFIX="refs/tags/xc-admin-"
|
PREFIX="refs/tags/xc-admin-"
|
||||||
VERSION="${GITHUB_REF:${#PREFIX}}"
|
VERSION="${GITHUB_REF:${#PREFIX}}"
|
||||||
echo "IMAGE_TAG=${VERSION}" >> "${GITHUB_ENV}"
|
echo "IMAGE_TAG=${VERSION}" >> "${GITHUB_ENV}"
|
||||||
- name: Log in to the Container registry
|
- uses: aws-actions/configure-aws-credentials@8a84b07f2009032ade05a88a28750d733cc30db1
|
||||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
role-to-assume: arn:aws:iam::192824654885:role/github-actions-ecr
|
||||||
username: ${{ github.actor }}
|
aws-region: eu-west-2
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
- uses: aws-actions/amazon-ecr-login@v1
|
||||||
- name: Build docker image
|
id: ecr_login
|
||||||
run: |
|
- run: |
|
||||||
DOCKER_BUILDKIT=1 docker build -t lerna -f Dockerfile.lerna .
|
DOCKER_BUILDKIT=1 docker build -t lerna -f Dockerfile.lerna .
|
||||||
DOCKER_BUILDKIT=1 docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} -f governance/xc_admin/Dockerfile .
|
DOCKER_BUILDKIT=1 docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f governance/xc_admin/Dockerfile .
|
||||||
- name: Push docker image
|
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
|
||||||
run: |
|
env:
|
||||||
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
ECR_REGISTRY: ${{ steps.ecr_login.outputs.registry }}
|
||||||
|
ECR_REPOSITORY: xc-admin
|
||||||
|
|
|
@ -5,17 +5,12 @@ on:
|
||||||
tags:
|
tags:
|
||||||
- "python-v*"
|
- "python-v*"
|
||||||
|
|
||||||
env:
|
|
||||||
PYTHON_VERSION: "3.11"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v2
|
||||||
with:
|
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install --upgrade poetry
|
python3 -m pip install --upgrade poetry
|
||||||
|
|
|
@ -30,6 +30,21 @@ repos:
|
||||||
entry: cargo +nightly-2023-03-01 clippy --manifest-path ./governance/remote_executor/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings
|
entry: cargo +nightly-2023-03-01 clippy --manifest-path ./governance/remote_executor/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: governance/remote_executor
|
files: governance/remote_executor
|
||||||
|
# Hooks for the attester
|
||||||
|
- id: cargo-fmt-attester
|
||||||
|
name: Cargo format for attester
|
||||||
|
language: "rust"
|
||||||
|
entry: cargo +nightly-2023-03-01 fmt --manifest-path ./wormhole_attester/Cargo.toml --all -- --config-path rustfmt.toml
|
||||||
|
pass_filenames: false
|
||||||
|
files: wormhole_attester
|
||||||
|
- id: cargo-clippy-attester
|
||||||
|
name: Cargo clippy for attester
|
||||||
|
language: "rust"
|
||||||
|
entry: |
|
||||||
|
bash -c 'EMITTER_ADDRESS=0 BRIDGE_ADDRESS=0 cargo +nightly-2023-03-01 clippy --manifest-path \
|
||||||
|
./wormhole_attester/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings'
|
||||||
|
pass_filenames: false
|
||||||
|
files: wormhole_attester
|
||||||
# Hooks for cosmwasm contract
|
# Hooks for cosmwasm contract
|
||||||
- id: cargo-fmt-cosmwasm
|
- id: cargo-fmt-cosmwasm
|
||||||
name: Cargo format for cosmwasm contract
|
name: Cargo format for cosmwasm contract
|
||||||
|
@ -45,24 +60,18 @@ repos:
|
||||||
files: target_chains/cosmwasm
|
files: target_chains/cosmwasm
|
||||||
# Hooks for Hermes
|
# Hooks for Hermes
|
||||||
- id: cargo-fmt-hermes
|
- id: cargo-fmt-hermes
|
||||||
name: Cargo format for Hermes
|
name: Cargo format for Pyth Hermes
|
||||||
language: "rust"
|
language: "rust"
|
||||||
entry: cargo +nightly-2024-03-26 fmt --manifest-path ./apps/hermes/Cargo.toml --all -- --config-path rustfmt.toml
|
entry: cargo +nightly-2023-07-23 fmt --manifest-path ./hermes/Cargo.toml --all -- --config-path rustfmt.toml
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: apps/hermes
|
files: hermes
|
||||||
- id: cargo-clippy-hermes
|
|
||||||
name: Cargo clippy for Hermes
|
|
||||||
language: "rust"
|
|
||||||
entry: cargo +nightly-2024-03-26 clippy --manifest-path ./apps/hermes/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings
|
|
||||||
pass_filenames: false
|
|
||||||
files: apps/hermes
|
|
||||||
# Hooks for Fortuna
|
# Hooks for Fortuna
|
||||||
- id: cargo-fmt-fortuna
|
- id: cargo-fmt-fortuna
|
||||||
name: Cargo format for Fortuna
|
name: Cargo format for Fortuna
|
||||||
language: "rust"
|
language: "rust"
|
||||||
entry: cargo +nightly-2023-07-23 fmt --manifest-path ./apps/fortuna/Cargo.toml --all -- --config-path rustfmt.toml
|
entry: cargo +nightly-2023-07-23 fmt --manifest-path ./fortuna/Cargo.toml --all -- --config-path rustfmt.toml
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: apps/fortuna
|
files: fortuna
|
||||||
# Hooks for message buffer contract
|
# Hooks for message buffer contract
|
||||||
- id: cargo-fmt-message-buffer
|
- id: cargo-fmt-message-buffer
|
||||||
name: Cargo format for message buffer contract
|
name: Cargo format for message buffer contract
|
||||||
|
@ -80,13 +89,13 @@ repos:
|
||||||
- id: cargo-fmt-pythnet-sdk
|
- id: cargo-fmt-pythnet-sdk
|
||||||
name: Cargo format for pythnet SDK
|
name: Cargo format for pythnet SDK
|
||||||
language: "rust"
|
language: "rust"
|
||||||
entry: cargo +nightly-2024-03-26 fmt --manifest-path ./pythnet/pythnet_sdk/Cargo.toml --all -- --config-path rustfmt.toml
|
entry: cargo +nightly-2023-07-23 fmt --manifest-path ./pythnet/pythnet_sdk/Cargo.toml --all -- --config-path rustfmt.toml
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: pythnet/pythnet_sdk
|
files: pythnet/pythnet_sdk
|
||||||
- id: cargo-clippy-pythnet-sdk
|
- id: cargo-clippy-pythnet-sdk
|
||||||
name: Cargo clippy for pythnet SDK
|
name: Cargo clippy for pythnet SDK
|
||||||
language: "rust"
|
language: "rust"
|
||||||
entry: cargo +nightly-2024-03-26 clippy --manifest-path ./pythnet/pythnet_sdk/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings
|
entry: cargo +nightly-2023-07-23 clippy --manifest-path ./pythnet/pythnet_sdk/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: pythnet/pythnet_sdk
|
files: pythnet/pythnet_sdk
|
||||||
# Hooks for solana receiver contract
|
# Hooks for solana receiver contract
|
||||||
|
@ -103,24 +112,18 @@ repos:
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: target_chains/solana
|
files: target_chains/solana
|
||||||
# For express relay python files
|
# For express relay python files
|
||||||
- id: poetry-install
|
|
||||||
name: poetry install
|
|
||||||
entry: poetry -C express_relay/sdk/python/express_relay install
|
|
||||||
pass_filenames: false
|
|
||||||
files: express_relay/sdk/python/express_relay
|
|
||||||
language: "system"
|
|
||||||
- id: black
|
- id: black
|
||||||
name: black
|
name: black
|
||||||
entry: poetry -C express_relay/sdk/python/express_relay run black
|
entry: poetry -C express_relay/sdk/python/express_relay run black express_relay/sdk/python/express_relay
|
||||||
files: express_relay/sdk/python/express_relay
|
pass_filenames: false
|
||||||
language: "system"
|
language: "system"
|
||||||
- id: pyflakes
|
- id: pyflakes
|
||||||
name: pyflakes
|
name: pyflakes
|
||||||
entry: poetry -C express_relay/sdk/python/express_relay run pyflakes
|
entry: poetry -C express_relay/sdk/python/express_relay run pyflakes express_relay/sdk/python/express_relay
|
||||||
files: express_relay/sdk/python/express_relay
|
pass_filenames: false
|
||||||
language: "system"
|
language: "system"
|
||||||
- id: mypy
|
- id: mypy
|
||||||
name: mypy
|
name: mypy
|
||||||
entry: poetry -C express_relay/sdk/python/express_relay run mypy
|
entry: poetry -C express_relay/sdk/python/express_relay run mypy express_relay/sdk/python/express_relay
|
||||||
files: express_relay/sdk/python/express_relay
|
pass_filenames: false
|
||||||
language: "system"
|
language: "system"
|
||||||
|
|
|
@ -16,7 +16,7 @@ contracts, SDKs, and examples.
|
||||||
|
|
||||||
## Hermes
|
## Hermes
|
||||||
|
|
||||||
> [hermes](./apps/hermes/)
|
> [hermes](./hermes/)
|
||||||
|
|
||||||
Hermes is an off-chain service which constantly observes Pythnet and the
|
Hermes is an off-chain service which constantly observes Pythnet and the
|
||||||
Wormhole network watching for price updates emitted from the Pyth contract. It
|
Wormhole network watching for price updates emitted from the Pyth contract. It
|
||||||
|
@ -79,11 +79,10 @@ Lerna has some common failure modes that you may encounter:
|
||||||
1. `npm ci` fails with a typescript compilation error about a missing package.
|
1. `npm ci` fails with a typescript compilation error about a missing package.
|
||||||
This error likely means that the failing package has a `prepare` entry compiling the typescript in its `package.json`.
|
This error likely means that the failing package has a `prepare` entry compiling the typescript in its `package.json`.
|
||||||
Fix this error by moving that logic to the `prepublishOnly` entry.
|
Fix this error by moving that logic to the `prepublishOnly` entry.
|
||||||
2. The software builds locally but fails in CI, or vice-versa.
|
1. The software builds locally but fails in CI, or vice-versa.
|
||||||
This error likely means that some local build caches need to be cleaned.
|
This error likely means that some local build caches need to be cleaned.
|
||||||
The build error may not indicate that this is a caching issue, e.g., it may appear that the packages are being built in the wrong order.
|
The build error may not indicate that this is a caching issue, e.g., it may appear that the packages are being built in the wrong order.
|
||||||
Delete `node_modules/`, `lib/` and `tsconfig.tsbuildinfo` from each package's subdirectory. then try again.
|
Delete `node_modules/`, `lib/` and `tsconfig.tsbuildinfo` from each package's subdirectory. then try again.
|
||||||
3. `npm ci` fails due to wrong node version. Make sure to be using `v18`. Node version `v21` is not supported and known to cause issues.
|
|
||||||
|
|
||||||
## Audit / Feature Status
|
## Audit / Feature Status
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
chains:
|
|
||||||
lightlink-pegasus:
|
|
||||||
commitments:
|
|
||||||
# prettier-ignore
|
|
||||||
- seed: [219,125,217,197,234,88,208,120,21,181,172,143,239,102,41,233,167,212,237,106,37,255,184,165,238,121,230,155,116,158,173,48]
|
|
||||||
chain_length: 10000
|
|
||||||
original_commitment_sequence_number: 104
|
|
|
@ -1 +0,0 @@
|
||||||
nightly-2023-07-23
|
|
|
@ -1,228 +0,0 @@
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
api::{
|
|
||||||
self,
|
|
||||||
BlockchainState,
|
|
||||||
ChainId,
|
|
||||||
},
|
|
||||||
chain::ethereum::PythContract,
|
|
||||||
command::register_provider::CommitmentMetadata,
|
|
||||||
config::{
|
|
||||||
Commitment,
|
|
||||||
Config,
|
|
||||||
ProviderConfig,
|
|
||||||
RunOptions,
|
|
||||||
},
|
|
||||||
keeper,
|
|
||||||
state::{
|
|
||||||
HashChainState,
|
|
||||||
PebbleHashChain,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
anyhow::{
|
|
||||||
anyhow,
|
|
||||||
Error,
|
|
||||||
Result,
|
|
||||||
},
|
|
||||||
axum::Router,
|
|
||||||
std::{
|
|
||||||
collections::HashMap,
|
|
||||||
net::SocketAddr,
|
|
||||||
sync::Arc,
|
|
||||||
},
|
|
||||||
tokio::{
|
|
||||||
spawn,
|
|
||||||
sync::watch,
|
|
||||||
},
|
|
||||||
tower_http::cors::CorsLayer,
|
|
||||||
utoipa::OpenApi,
|
|
||||||
utoipa_swagger_ui::SwaggerUi,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn run_api(
|
|
||||||
socket_addr: SocketAddr,
|
|
||||||
chains: HashMap<String, api::BlockchainState>,
|
|
||||||
mut rx_exit: watch::Receiver<bool>,
|
|
||||||
) -> Result<()> {
|
|
||||||
#[derive(OpenApi)]
|
|
||||||
#[openapi(
|
|
||||||
paths(
|
|
||||||
crate::api::revelation,
|
|
||||||
crate::api::chain_ids,
|
|
||||||
),
|
|
||||||
components(
|
|
||||||
schemas(
|
|
||||||
crate::api::GetRandomValueResponse,
|
|
||||||
crate::api::Blob,
|
|
||||||
crate::api::BinaryEncoding,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
tags(
|
|
||||||
(name = "fortuna", description = "Random number service for the Pyth Entropy protocol")
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
struct ApiDoc;
|
|
||||||
|
|
||||||
let metrics_registry = api::Metrics::new();
|
|
||||||
let api_state = api::ApiState {
|
|
||||||
chains: Arc::new(chains),
|
|
||||||
metrics: Arc::new(metrics_registry),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize Axum Router. Note the type here is a `Router<State>` due to the use of the
|
|
||||||
// `with_state` method which replaces `Body` with `State` in the type signature.
|
|
||||||
let app = Router::new();
|
|
||||||
let app = app
|
|
||||||
.merge(SwaggerUi::new("/docs").url("/docs/openapi.json", ApiDoc::openapi()))
|
|
||||||
.merge(api::routes(api_state))
|
|
||||||
// Permissive CORS layer to allow all origins
|
|
||||||
.layer(CorsLayer::permissive());
|
|
||||||
|
|
||||||
tracing::info!("Starting server on: {:?}", &socket_addr);
|
|
||||||
// Binds the axum's server to the configured address and port. This is a blocking call and will
|
|
||||||
// not return until the server is shutdown.
|
|
||||||
axum::Server::try_bind(&socket_addr)?
|
|
||||||
.serve(app.into_make_service())
|
|
||||||
.with_graceful_shutdown(async {
|
|
||||||
// It can return an error or an Ok(()). In both cases, we would shut down.
|
|
||||||
// As Ok(()) means, exit signal (ctrl + c) was received.
|
|
||||||
// And Err(e) means, the sender was dropped which should not be the case.
|
|
||||||
let _ = rx_exit.changed().await;
|
|
||||||
|
|
||||||
tracing::info!("Shutting down RPC server...");
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub async fn run_keeper(
|
|
||||||
chains: HashMap<String, api::BlockchainState>,
|
|
||||||
config: Config,
|
|
||||||
private_key: String,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut handles = Vec::new();
|
|
||||||
for (chain_id, chain_config) in chains {
|
|
||||||
let chain_eth_config = config
|
|
||||||
.chains
|
|
||||||
.get(&chain_id)
|
|
||||||
.expect("All chains should be present in the config file")
|
|
||||||
.clone();
|
|
||||||
let private_key = private_key.clone();
|
|
||||||
handles.push(spawn(keeper::run_keeper_threads(
|
|
||||||
private_key,
|
|
||||||
chain_eth_config,
|
|
||||||
chain_config.clone(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(opts: &RunOptions) -> Result<()> {
|
|
||||||
let config = Config::load(&opts.config.config)?;
|
|
||||||
let provider_config = opts
|
|
||||||
.provider_config
|
|
||||||
.provider_config
|
|
||||||
.as_ref()
|
|
||||||
.map(|path| ProviderConfig::load(&path).expect("Failed to load provider config"));
|
|
||||||
let secret = opts.randomness.load_secret()?;
|
|
||||||
let (tx_exit, rx_exit) = watch::channel(false);
|
|
||||||
|
|
||||||
let mut chains: HashMap<ChainId, BlockchainState> = HashMap::new();
|
|
||||||
for (chain_id, chain_config) in &config.chains {
|
|
||||||
let contract = Arc::new(PythContract::from_config(&chain_config)?);
|
|
||||||
let provider_chain_config = provider_config
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|c| c.get_chain_config(chain_id));
|
|
||||||
let mut provider_commitments = provider_chain_config
|
|
||||||
.as_ref()
|
|
||||||
.map(|c| c.get_sorted_commitments())
|
|
||||||
.unwrap_or_else(|| Vec::new());
|
|
||||||
|
|
||||||
let provider_info = contract.get_provider_info(opts.provider).call().await?;
|
|
||||||
let latest_metadata =
|
|
||||||
bincode::deserialize::<CommitmentMetadata>(&provider_info.commitment_metadata)
|
|
||||||
.map_err(|e| {
|
|
||||||
anyhow!(
|
|
||||||
"Chain: {} - Failed to deserialize commitment metadata: {}",
|
|
||||||
&chain_id,
|
|
||||||
e
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
provider_commitments.push(Commitment {
|
|
||||||
seed: latest_metadata.seed,
|
|
||||||
chain_length: latest_metadata.chain_length,
|
|
||||||
original_commitment_sequence_number: provider_info.original_commitment_sequence_number,
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: we may want to load the hash chain in a lazy/fault-tolerant way. If there are many blockchains,
|
|
||||||
// then it's more likely that some RPC fails. We should tolerate these faults and generate the hash chain
|
|
||||||
// later when a user request comes in for that chain.
|
|
||||||
|
|
||||||
let mut offsets = Vec::<usize>::new();
|
|
||||||
let mut hash_chains = Vec::<PebbleHashChain>::new();
|
|
||||||
|
|
||||||
for commitment in &provider_commitments {
|
|
||||||
let offset = commitment.original_commitment_sequence_number.try_into()?;
|
|
||||||
offsets.push(offset);
|
|
||||||
|
|
||||||
let pebble_hash_chain = PebbleHashChain::from_config(
|
|
||||||
&secret,
|
|
||||||
&chain_id,
|
|
||||||
&opts.provider,
|
|
||||||
&chain_config.contract_addr,
|
|
||||||
&commitment.seed,
|
|
||||||
commitment.chain_length,
|
|
||||||
)?;
|
|
||||||
hash_chains.push(pebble_hash_chain);
|
|
||||||
}
|
|
||||||
|
|
||||||
let chain_state = HashChainState {
|
|
||||||
offsets,
|
|
||||||
hash_chains,
|
|
||||||
};
|
|
||||||
|
|
||||||
if chain_state.reveal(provider_info.original_commitment_sequence_number)?
|
|
||||||
!= provider_info.original_commitment
|
|
||||||
{
|
|
||||||
return Err(anyhow!("The root of the generated hash chain for chain id {} does not match the commitment. Are the secret and chain length configured correctly?", &chain_id).into());
|
|
||||||
} else {
|
|
||||||
tracing::info!("Root of chain id {} matches commitment", &chain_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let state = api::BlockchainState {
|
|
||||||
id: chain_id.clone(),
|
|
||||||
state: Arc::new(chain_state),
|
|
||||||
contract,
|
|
||||||
provider_address: opts.provider,
|
|
||||||
reveal_delay_blocks: chain_config.reveal_delay_blocks,
|
|
||||||
confirmed_block_status: chain_config.confirmed_block_status,
|
|
||||||
};
|
|
||||||
|
|
||||||
chains.insert(chain_id.clone(), state);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Listen for Ctrl+C so we can set the exit flag and wait for a graceful shutdown.
|
|
||||||
spawn(async move {
|
|
||||||
tracing::info!("Registered shutdown signal handler...");
|
|
||||||
tokio::signal::ctrl_c().await.unwrap();
|
|
||||||
tracing::info!("Shut down signal received, waiting for tasks...");
|
|
||||||
// no need to handle error here, as it will only occur when all the
|
|
||||||
// receiver has been dropped and that's what we want to do
|
|
||||||
tx_exit.send(true)?;
|
|
||||||
|
|
||||||
Ok::<(), Error>(())
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(keeper_private_key) = opts.load_keeper_private_key()? {
|
|
||||||
spawn(run_keeper(chains.clone(), config, keeper_private_key));
|
|
||||||
}
|
|
||||||
|
|
||||||
run_api(opts.addr.clone(), chains, rx_exit).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
use {
|
|
||||||
crate::config::{
|
|
||||||
ConfigOptions,
|
|
||||||
ProviderConfigOptions,
|
|
||||||
RandomnessOptions,
|
|
||||||
},
|
|
||||||
anyhow::Result,
|
|
||||||
clap::Args,
|
|
||||||
ethers::types::Address,
|
|
||||||
std::{
|
|
||||||
fs,
|
|
||||||
net::SocketAddr,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Run the webservice
|
|
||||||
#[derive(Args, Clone, Debug)]
|
|
||||||
pub struct RunOptions {
|
|
||||||
#[command(flatten)]
|
|
||||||
pub config: ConfigOptions,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
|
||||||
pub provider_config: ProviderConfigOptions,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
|
||||||
pub randomness: RandomnessOptions,
|
|
||||||
|
|
||||||
/// Address and port the HTTP server will bind to.
|
|
||||||
#[arg(long = "rpc-listen-addr")]
|
|
||||||
#[arg(default_value = super::DEFAULT_RPC_ADDR)]
|
|
||||||
#[arg(env = "RPC_ADDR")]
|
|
||||||
pub addr: SocketAddr,
|
|
||||||
|
|
||||||
/// The public key of the provider whose requests the server will respond to.
|
|
||||||
#[arg(long = "provider")]
|
|
||||||
#[arg(env = "FORTUNA_PROVIDER")]
|
|
||||||
pub provider: Address,
|
|
||||||
|
|
||||||
/// If provided, the keeper will run alongside the Fortuna API service.
|
|
||||||
/// It should be a path to a file containing a 20-byte (40 char) hex encoded Ethereum private key.
|
|
||||||
/// This key is required to submit transactions for entropy callback requests.
|
|
||||||
/// This key should not be a registered provider.
|
|
||||||
#[arg(long = "keeper-private-key")]
|
|
||||||
#[arg(env = "KEEPER_PRIVATE_KEY")]
|
|
||||||
pub keeper_private_key_file: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RunOptions {
|
|
||||||
pub fn load_keeper_private_key(&self) -> Result<Option<String>> {
|
|
||||||
if let Some(ref keeper_private_key_file) = self.keeper_private_key_file {
|
|
||||||
return Ok(Some(fs::read_to_string(keeper_private_key_file)?));
|
|
||||||
}
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,487 +0,0 @@
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
api::{
|
|
||||||
self,
|
|
||||||
BlockchainState,
|
|
||||||
},
|
|
||||||
chain::{
|
|
||||||
ethereum::SignablePythContract,
|
|
||||||
reader::{
|
|
||||||
BlockNumber,
|
|
||||||
RequestedWithCallbackEvent,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
config::EthereumConfig,
|
|
||||||
},
|
|
||||||
anyhow::{
|
|
||||||
anyhow,
|
|
||||||
Result,
|
|
||||||
},
|
|
||||||
ethers::{
|
|
||||||
contract::ContractError,
|
|
||||||
providers::{
|
|
||||||
Middleware,
|
|
||||||
Provider,
|
|
||||||
Ws,
|
|
||||||
},
|
|
||||||
types::U256,
|
|
||||||
},
|
|
||||||
futures::StreamExt,
|
|
||||||
std::sync::Arc,
|
|
||||||
tokio::{
|
|
||||||
spawn,
|
|
||||||
sync::mpsc,
|
|
||||||
time::{
|
|
||||||
self,
|
|
||||||
Duration,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tracing::{
|
|
||||||
self,
|
|
||||||
Instrument,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BlockRange {
|
|
||||||
pub from: BlockNumber,
|
|
||||||
pub to: BlockNumber,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// How much to wait before retrying in case of an RPC error
|
|
||||||
const RETRY_INTERVAL: Duration = Duration::from_secs(5);
|
|
||||||
/// How many blocks to look back for events that might be missed when starting the keeper
|
|
||||||
const BACKLOG_RANGE: u64 = 1000;
|
|
||||||
/// How many blocks to fetch events for in a single rpc call
|
|
||||||
const BLOCK_BATCH_SIZE: u64 = 100;
|
|
||||||
/// How much to wait before polling the next latest block
|
|
||||||
const POLL_INTERVAL: Duration = Duration::from_secs(5);
|
|
||||||
|
|
||||||
|
|
||||||
/// Get the latest safe block number for the chain. Retry internally if there is an error.
|
|
||||||
async fn get_latest_safe_block(chain_state: &BlockchainState) -> BlockNumber {
|
|
||||||
loop {
|
|
||||||
match chain_state
|
|
||||||
.contract
|
|
||||||
.get_block_number(chain_state.confirmed_block_status)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(latest_confirmed_block) => {
|
|
||||||
tracing::info!(
|
|
||||||
"Fetched latest safe block {}",
|
|
||||||
latest_confirmed_block - chain_state.reveal_delay_blocks
|
|
||||||
);
|
|
||||||
return latest_confirmed_block - chain_state.reveal_delay_blocks;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Error while getting block number. error: {:?}", e);
|
|
||||||
time::sleep(RETRY_INTERVAL).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run threads to handle events for the last `BACKLOG_RANGE` blocks, watch for new blocks and
|
|
||||||
/// handle any events for the new blocks.
|
|
||||||
#[tracing::instrument(name="keeper", skip_all, fields(chain_id=chain_state.id))]
|
|
||||||
pub async fn run_keeper_threads(
|
|
||||||
private_key: String,
|
|
||||||
chain_eth_config: EthereumConfig,
|
|
||||||
chain_state: BlockchainState,
|
|
||||||
) {
|
|
||||||
tracing::info!("starting keeper");
|
|
||||||
let latest_safe_block = get_latest_safe_block(&chain_state).in_current_span().await;
|
|
||||||
tracing::info!("latest safe block: {}", &latest_safe_block);
|
|
||||||
|
|
||||||
let contract = Arc::new(
|
|
||||||
SignablePythContract::from_config(&chain_eth_config, &private_key)
|
|
||||||
.await
|
|
||||||
.expect("Chain config should be valid"),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Spawn a thread to handle the events from last BACKLOG_RANGE blocks.
|
|
||||||
spawn(
|
|
||||||
process_backlog(
|
|
||||||
BlockRange {
|
|
||||||
from: latest_safe_block.saturating_sub(BACKLOG_RANGE),
|
|
||||||
to: latest_safe_block,
|
|
||||||
},
|
|
||||||
contract.clone(),
|
|
||||||
chain_eth_config.gas_limit,
|
|
||||||
chain_state.clone(),
|
|
||||||
)
|
|
||||||
.in_current_span(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel::<BlockRange>(1000);
|
|
||||||
// Spawn a thread to watch for new blocks and send the range of blocks for which events has not been handled to the `tx` channel.
|
|
||||||
spawn(
|
|
||||||
watch_blocks_wrapper(
|
|
||||||
chain_state.clone(),
|
|
||||||
latest_safe_block,
|
|
||||||
tx,
|
|
||||||
chain_eth_config.geth_rpc_wss.clone(),
|
|
||||||
)
|
|
||||||
.in_current_span(),
|
|
||||||
);
|
|
||||||
// Spawn a thread that listens for block ranges on the `rx` channel and processes the events for those blocks.
|
|
||||||
spawn(
|
|
||||||
process_new_blocks(
|
|
||||||
chain_state.clone(),
|
|
||||||
rx,
|
|
||||||
Arc::clone(&contract),
|
|
||||||
chain_eth_config.gas_limit,
|
|
||||||
)
|
|
||||||
.in_current_span(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Process an event for a chain. It estimates the gas for the reveal with callback and
|
|
||||||
/// submits the transaction if the gas estimate is below the gas limit.
|
|
||||||
/// It will return an Error if the gas estimation failed with a provider error or if the
|
|
||||||
/// reveal with callback failed with a provider error.
|
|
||||||
pub async fn process_event(
|
|
||||||
event: RequestedWithCallbackEvent,
|
|
||||||
chain_config: &BlockchainState,
|
|
||||||
contract: &Arc<SignablePythContract>,
|
|
||||||
gas_limit: U256,
|
|
||||||
) -> Result<()> {
|
|
||||||
if chain_config.provider_address != event.provider_address {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let provider_revelation = match chain_config.state.reveal(event.sequence_number) {
|
|
||||||
Ok(result) => result,
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!(
|
|
||||||
sequence_number = &event.sequence_number,
|
|
||||||
"Error while revealing with error: {:?}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let gas_estimate_res = chain_config
|
|
||||||
.contract
|
|
||||||
.estimate_reveal_with_callback_gas(
|
|
||||||
event.provider_address,
|
|
||||||
event.sequence_number,
|
|
||||||
event.user_random_number,
|
|
||||||
provider_revelation,
|
|
||||||
)
|
|
||||||
.in_current_span()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match gas_estimate_res {
|
|
||||||
Ok(gas_estimate_option) => match gas_estimate_option {
|
|
||||||
Some(gas_estimate) => {
|
|
||||||
// Pad the gas estimate by 33%
|
|
||||||
let (gas_estimate, _) = gas_estimate
|
|
||||||
.saturating_mul(U256::from(4))
|
|
||||||
.div_mod(U256::from(3));
|
|
||||||
|
|
||||||
if gas_estimate > gas_limit {
|
|
||||||
tracing::error!(
|
|
||||||
sequence_number = &event.sequence_number,
|
|
||||||
"Gas estimate for reveal with callback is higher than the gas limit"
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let contract_call = contract
|
|
||||||
.reveal_with_callback(
|
|
||||||
event.provider_address,
|
|
||||||
event.sequence_number,
|
|
||||||
event.user_random_number,
|
|
||||||
provider_revelation,
|
|
||||||
)
|
|
||||||
.gas(gas_estimate);
|
|
||||||
|
|
||||||
let res = contract_call.send().await;
|
|
||||||
|
|
||||||
let pending_tx = match res {
|
|
||||||
Ok(pending_tx) => pending_tx,
|
|
||||||
Err(e) => match e {
|
|
||||||
// If there is a provider error, we weren't able to send the transaction.
|
|
||||||
// We will return an error. So, that the caller can decide what to do (retry).
|
|
||||||
ContractError::ProviderError { e } => return Err(e.into()),
|
|
||||||
// For all the other errors, it is likely the case we won't be able to reveal for
|
|
||||||
// ever. We will return an Ok(()) to signal that we have processed this reveal
|
|
||||||
// and concluded that its Ok to not reveal.
|
|
||||||
_ => {
|
|
||||||
tracing::error!(
|
|
||||||
sequence_number = &event.sequence_number,
|
|
||||||
"Error while revealing with error: {:?}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
match pending_tx.await {
|
|
||||||
Ok(res) => {
|
|
||||||
tracing::info!(
|
|
||||||
sequence_number = &event.sequence_number,
|
|
||||||
"Revealed with res: {:?}",
|
|
||||||
res
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!(
|
|
||||||
sequence_number = &event.sequence_number,
|
|
||||||
"Error while revealing with error: {:?}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
Err(e.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
tracing::info!(
|
|
||||||
sequence_number = &event.sequence_number,
|
|
||||||
"Not processing event"
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!(
|
|
||||||
sequence_number = &event.sequence_number,
|
|
||||||
"Error while simulating reveal with error: {:?}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Process a range of blocks in batches. It calls the `process_single_block_batch` method for each batch.
|
|
||||||
#[tracing::instrument(skip_all, fields(range_from_block=block_range.from, range_to_block=block_range.to))]
|
|
||||||
pub async fn process_block_range(
|
|
||||||
block_range: BlockRange,
|
|
||||||
contract: Arc<SignablePythContract>,
|
|
||||||
gas_limit: U256,
|
|
||||||
chain_state: api::BlockchainState,
|
|
||||||
) {
|
|
||||||
let BlockRange {
|
|
||||||
from: first_block,
|
|
||||||
to: last_block,
|
|
||||||
} = block_range;
|
|
||||||
let mut current_block = first_block;
|
|
||||||
while current_block <= last_block {
|
|
||||||
let mut to_block = current_block + BLOCK_BATCH_SIZE;
|
|
||||||
if to_block > last_block {
|
|
||||||
to_block = last_block;
|
|
||||||
}
|
|
||||||
|
|
||||||
process_single_block_batch(
|
|
||||||
BlockRange {
|
|
||||||
from: current_block,
|
|
||||||
to: to_block,
|
|
||||||
},
|
|
||||||
contract.clone(),
|
|
||||||
gas_limit,
|
|
||||||
chain_state.clone(),
|
|
||||||
)
|
|
||||||
.in_current_span()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
current_block = to_block + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Process a batch of blocks for a chain. It will fetch events for all the blocks in a single call for the provided batch
|
|
||||||
/// and then try to process them one by one. If the process fails, it will retry indefinitely.
|
|
||||||
#[tracing::instrument(name="batch", skip_all, fields(batch_from_block=block_range.from, batch_to_block=block_range.to))]
|
|
||||||
pub async fn process_single_block_batch(
|
|
||||||
block_range: BlockRange,
|
|
||||||
contract: Arc<SignablePythContract>,
|
|
||||||
gas_limit: U256,
|
|
||||||
chain_state: api::BlockchainState,
|
|
||||||
) {
|
|
||||||
loop {
|
|
||||||
let events_res = chain_state
|
|
||||||
.contract
|
|
||||||
.get_request_with_callback_events(block_range.from, block_range.to)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match events_res {
|
|
||||||
Ok(events) => {
|
|
||||||
tracing::info!(num_of_events = &events.len(), "Processing",);
|
|
||||||
for event in &events {
|
|
||||||
tracing::info!(sequence_number = &event.sequence_number, "Processing event",);
|
|
||||||
while let Err(e) =
|
|
||||||
process_event(event.clone(), &chain_state, &contract, gas_limit)
|
|
||||||
.in_current_span()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::error!(
|
|
||||||
sequence_number = &event.sequence_number,
|
|
||||||
"Error while processing event. Waiting for {} seconds before retry. error: {:?}",
|
|
||||||
RETRY_INTERVAL.as_secs(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
time::sleep(RETRY_INTERVAL).await;
|
|
||||||
}
|
|
||||||
tracing::info!(sequence_number = &event.sequence_number, "Processed event",);
|
|
||||||
}
|
|
||||||
tracing::info!(num_of_events = &events.len(), "Processed",);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!(
|
|
||||||
"Error while getting events. Waiting for {} seconds before retry. error: {:?}",
|
|
||||||
RETRY_INTERVAL.as_secs(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
time::sleep(RETRY_INTERVAL).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for the `watch_blocks` method. If there was an error while watching, it will retry after a delay.
|
|
||||||
/// It retries indefinitely.
|
|
||||||
#[tracing::instrument(name="watch_blocks", skip_all, fields(initial_safe_block=latest_safe_block))]
|
|
||||||
pub async fn watch_blocks_wrapper(
|
|
||||||
chain_state: BlockchainState,
|
|
||||||
latest_safe_block: BlockNumber,
|
|
||||||
tx: mpsc::Sender<BlockRange>,
|
|
||||||
geth_rpc_wss: Option<String>,
|
|
||||||
) {
|
|
||||||
let mut last_safe_block_processed = latest_safe_block;
|
|
||||||
loop {
|
|
||||||
if let Err(e) = watch_blocks(
|
|
||||||
chain_state.clone(),
|
|
||||||
&mut last_safe_block_processed,
|
|
||||||
tx.clone(),
|
|
||||||
geth_rpc_wss.clone(),
|
|
||||||
)
|
|
||||||
.in_current_span()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::error!("watching blocks. error: {:?}", e);
|
|
||||||
time::sleep(RETRY_INTERVAL).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Watch for new blocks and send the range of blocks for which events have not been handled to the `tx` channel.
|
|
||||||
/// We are subscribing to new blocks instead of events. If we miss some blocks, it will be fine as we are sending
|
|
||||||
/// block ranges to the `tx` channel. If we have subscribed to events, we could have missed those and won't even
|
|
||||||
/// know about it.
|
|
||||||
pub async fn watch_blocks(
|
|
||||||
chain_state: BlockchainState,
|
|
||||||
last_safe_block_processed: &mut BlockNumber,
|
|
||||||
tx: mpsc::Sender<BlockRange>,
|
|
||||||
geth_rpc_wss: Option<String>,
|
|
||||||
) -> Result<()> {
|
|
||||||
tracing::info!("Watching blocks to handle new events");
|
|
||||||
|
|
||||||
let provider_option = match geth_rpc_wss {
|
|
||||||
Some(wss) => Some(match Provider::<Ws>::connect(wss.clone()).await {
|
|
||||||
Ok(provider) => provider,
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Error while connecting to wss: {}. error: {:?}", wss, e);
|
|
||||||
return Err(e.into());
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
None => {
|
|
||||||
tracing::info!("No wss provided");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut stream_option = match provider_option {
|
|
||||||
Some(ref provider) => Some(match provider.subscribe_blocks().await {
|
|
||||||
Ok(client) => client,
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Error while subscribing to blocks. error {:?}", e);
|
|
||||||
return Err(e.into());
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match stream_option {
|
|
||||||
Some(ref mut stream) => {
|
|
||||||
if let None = stream.next().await {
|
|
||||||
tracing::error!("Error blocks subscription stream ended");
|
|
||||||
return Err(anyhow!("Error blocks subscription stream ended"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
time::sleep(POLL_INTERVAL).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let latest_safe_block = get_latest_safe_block(&chain_state).in_current_span().await;
|
|
||||||
if latest_safe_block > *last_safe_block_processed {
|
|
||||||
match tx
|
|
||||||
.send(BlockRange {
|
|
||||||
from: *last_safe_block_processed + 1,
|
|
||||||
to: latest_safe_block,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {
|
|
||||||
tracing::info!(
|
|
||||||
from_block = *last_safe_block_processed + 1,
|
|
||||||
to_block = &latest_safe_block,
|
|
||||||
"Block range sent to handle events",
|
|
||||||
);
|
|
||||||
*last_safe_block_processed = latest_safe_block;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!(
|
|
||||||
"Error while sending block range to handle events. These will be handled in next call. error: {:?}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// It waits on rx channel to receive block ranges and then calls process_block_range to process them.
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn process_new_blocks(
|
|
||||||
chain_state: BlockchainState,
|
|
||||||
mut rx: mpsc::Receiver<BlockRange>,
|
|
||||||
contract: Arc<SignablePythContract>,
|
|
||||||
gas_limit: U256,
|
|
||||||
) {
|
|
||||||
tracing::info!("Waiting for new block ranges to process");
|
|
||||||
loop {
|
|
||||||
if let Some(block_range) = rx.recv().await {
|
|
||||||
process_block_range(
|
|
||||||
block_range,
|
|
||||||
Arc::clone(&contract),
|
|
||||||
gas_limit,
|
|
||||||
chain_state.clone(),
|
|
||||||
)
|
|
||||||
.in_current_span()
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Processes the backlog_range for a chain.
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn process_backlog(
|
|
||||||
backlog_range: BlockRange,
|
|
||||||
contract: Arc<SignablePythContract>,
|
|
||||||
gas_limit: U256,
|
|
||||||
chain_state: BlockchainState,
|
|
||||||
) {
|
|
||||||
tracing::info!("Processing backlog");
|
|
||||||
process_block_range(backlog_range, contract, gas_limit, chain_state)
|
|
||||||
.in_current_span()
|
|
||||||
.await;
|
|
||||||
tracing::info!("Backlog processed");
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
# The rust version itself is not so important as we install a fixed
|
|
||||||
# nightly version. We use the latest stable version to get the latest
|
|
||||||
# updates and dependencies.
|
|
||||||
FROM rust:1.77.0 AS build
|
|
||||||
|
|
||||||
# Install OS packages
|
|
||||||
RUN apt-get update && apt-get install --yes \
|
|
||||||
build-essential curl clang libssl-dev protobuf-compiler
|
|
||||||
|
|
||||||
# Set default toolchain
|
|
||||||
RUN rustup default nightly-2024-03-26
|
|
||||||
|
|
||||||
# Build
|
|
||||||
WORKDIR /src
|
|
||||||
COPY apps/hermes apps/hermes
|
|
||||||
COPY pythnet/pythnet_sdk pythnet/pythnet_sdk
|
|
||||||
|
|
||||||
|
|
||||||
WORKDIR /src/apps/hermes
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cargo/registry cargo build --release
|
|
||||||
|
|
||||||
FROM rust:1.77.0
|
|
||||||
|
|
||||||
# Copy artifacts from other images
|
|
||||||
COPY --from=build /src/apps/hermes/target/release/hermes /usr/local/bin/
|
|
|
@ -1,2 +0,0 @@
|
||||||
[toolchain]
|
|
||||||
channel = "nightly-2024-03-26"
|
|
|
@ -1,25 +0,0 @@
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
api::ApiState,
|
|
||||||
state::aggregate::Aggregates,
|
|
||||||
},
|
|
||||||
axum::{
|
|
||||||
extract::State,
|
|
||||||
http::StatusCode,
|
|
||||||
response::{
|
|
||||||
IntoResponse,
|
|
||||||
Response,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn ready<S>(State(state): State<ApiState<S>>) -> Response
|
|
||||||
where
|
|
||||||
S: Aggregates,
|
|
||||||
{
|
|
||||||
let state = &*state.state;
|
|
||||||
match Aggregates::is_ready(state).await {
|
|
||||||
true => (StatusCode::OK, "OK").into_response(),
|
|
||||||
false => (StatusCode::SERVICE_UNAVAILABLE, "Service Unavailable").into_response(),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,235 +0,0 @@
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
api::{
|
|
||||||
rest::{
|
|
||||||
verify_price_ids_exist,
|
|
||||||
RestError,
|
|
||||||
},
|
|
||||||
types::{
|
|
||||||
BinaryPriceUpdate,
|
|
||||||
EncodingType,
|
|
||||||
ParsedPriceUpdate,
|
|
||||||
PriceIdInput,
|
|
||||||
PriceUpdate,
|
|
||||||
RpcPriceIdentifier,
|
|
||||||
},
|
|
||||||
ApiState,
|
|
||||||
},
|
|
||||||
state::aggregate::{
|
|
||||||
Aggregates,
|
|
||||||
AggregationEvent,
|
|
||||||
RequestTime,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
anyhow::Result,
|
|
||||||
axum::{
|
|
||||||
extract::State,
|
|
||||||
response::sse::{
|
|
||||||
Event,
|
|
||||||
KeepAlive,
|
|
||||||
Sse,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
futures::Stream,
|
|
||||||
pyth_sdk::PriceIdentifier,
|
|
||||||
serde::Deserialize,
|
|
||||||
serde_qs::axum::QsQuery,
|
|
||||||
std::convert::Infallible,
|
|
||||||
tokio::sync::broadcast,
|
|
||||||
tokio_stream::{
|
|
||||||
wrappers::BroadcastStream,
|
|
||||||
StreamExt as _,
|
|
||||||
},
|
|
||||||
utoipa::IntoParams,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, IntoParams)]
|
|
||||||
#[into_params(parameter_in = Query)]
|
|
||||||
pub struct StreamPriceUpdatesQueryParams {
|
|
||||||
/// Get the most recent price update for this set of price feed ids.
|
|
||||||
///
|
|
||||||
/// This parameter can be provided multiple times to retrieve multiple price updates,
|
|
||||||
/// for example see the following query string:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// ?ids[]=a12...&ids[]=b4c...
|
|
||||||
/// ```
|
|
||||||
#[param(rename = "ids[]")]
|
|
||||||
#[param(example = "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43")]
|
|
||||||
ids: Vec<PriceIdInput>,
|
|
||||||
|
|
||||||
/// If true, include the parsed price update in the `parsed` field of each returned feed. Default is `hex`.
|
|
||||||
#[serde(default)]
|
|
||||||
encoding: EncodingType,
|
|
||||||
|
|
||||||
/// If true, include the parsed price update in the `parsed` field of each returned feed. Default is `true`.
|
|
||||||
#[serde(default = "default_true")]
|
|
||||||
parsed: bool,
|
|
||||||
|
|
||||||
/// If true, allows unordered price updates to be included in the stream.
|
|
||||||
#[serde(default)]
|
|
||||||
allow_unordered: bool,
|
|
||||||
|
|
||||||
/// If true, only include benchmark prices that are the initial price updates at a given timestamp (i.e., prevPubTime != pubTime).
|
|
||||||
#[serde(default)]
|
|
||||||
benchmarks_only: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_true() -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[utoipa::path(
|
|
||||||
get,
|
|
||||||
path = "/v2/updates/price/stream",
|
|
||||||
responses(
|
|
||||||
(status = 200, description = "Price updates retrieved successfully", body = PriceUpdate),
|
|
||||||
(status = 404, description = "Price ids not found", body = String)
|
|
||||||
),
|
|
||||||
params(StreamPriceUpdatesQueryParams)
|
|
||||||
)]
|
|
||||||
/// SSE route handler for streaming price updates.
|
|
||||||
pub async fn price_stream_sse_handler<S>(
|
|
||||||
State(state): State<ApiState<S>>,
|
|
||||||
QsQuery(params): QsQuery<StreamPriceUpdatesQueryParams>,
|
|
||||||
) -> Result<Sse<impl Stream<Item = Result<Event, Infallible>>>, RestError>
|
|
||||||
where
|
|
||||||
S: Aggregates,
|
|
||||||
S: Sync,
|
|
||||||
S: Send,
|
|
||||||
S: 'static,
|
|
||||||
{
|
|
||||||
let price_ids: Vec<PriceIdentifier> = params.ids.into_iter().map(Into::into).collect();
|
|
||||||
|
|
||||||
verify_price_ids_exist(&state, &price_ids).await?;
|
|
||||||
|
|
||||||
// Clone the update_tx receiver to listen for new price updates
|
|
||||||
let update_rx: broadcast::Receiver<AggregationEvent> = Aggregates::subscribe(&*state.state);
|
|
||||||
|
|
||||||
// Convert the broadcast receiver into a Stream
|
|
||||||
let stream = BroadcastStream::new(update_rx);
|
|
||||||
|
|
||||||
let sse_stream = stream.then(move |message| {
|
|
||||||
let state_clone = state.clone(); // Clone again to use inside the async block
|
|
||||||
let price_ids_clone = price_ids.clone(); // Clone again for use inside the async block
|
|
||||||
async move {
|
|
||||||
match message {
|
|
||||||
Ok(event) => {
|
|
||||||
match handle_aggregation_event(
|
|
||||||
event,
|
|
||||||
state_clone,
|
|
||||||
price_ids_clone,
|
|
||||||
params.encoding,
|
|
||||||
params.parsed,
|
|
||||||
params.benchmarks_only,
|
|
||||||
params.allow_unordered,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(Some(update)) => Ok(Event::default()
|
|
||||||
.json_data(update)
|
|
||||||
.unwrap_or_else(|e| error_event(e))),
|
|
||||||
Ok(None) => Ok(Event::default().comment("No update available")),
|
|
||||||
Err(e) => Ok(error_event(e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => Ok(error_event(e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_aggregation_event<S>(
|
|
||||||
event: AggregationEvent,
|
|
||||||
state: ApiState<S>,
|
|
||||||
mut price_ids: Vec<PriceIdentifier>,
|
|
||||||
encoding: EncodingType,
|
|
||||||
parsed: bool,
|
|
||||||
benchmarks_only: bool,
|
|
||||||
allow_unordered: bool,
|
|
||||||
) -> Result<Option<PriceUpdate>>
|
|
||||||
where
|
|
||||||
S: Aggregates,
|
|
||||||
{
|
|
||||||
// Handle out-of-order events
|
|
||||||
if let AggregationEvent::OutOfOrder { .. } = event {
|
|
||||||
if !allow_unordered {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We check for available price feed ids to ensure that the price feed ids provided exists since price feeds can be removed.
|
|
||||||
let available_price_feed_ids = Aggregates::get_price_feed_ids(&*state.state).await;
|
|
||||||
|
|
||||||
price_ids.retain(|price_feed_id| available_price_feed_ids.contains(price_feed_id));
|
|
||||||
|
|
||||||
let mut price_feeds_with_update_data = Aggregates::get_price_feeds_with_update_data(
|
|
||||||
&*state.state,
|
|
||||||
&price_ids,
|
|
||||||
RequestTime::AtSlot(event.slot()),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut parsed_price_updates: Vec<ParsedPriceUpdate> = price_feeds_with_update_data
|
|
||||||
.price_feeds
|
|
||||||
.into_iter()
|
|
||||||
.map(|price_feed| price_feed.into())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
|
|
||||||
if benchmarks_only {
|
|
||||||
// Remove those with metadata.prev_publish_time != price.publish_time from parsed_price_updates
|
|
||||||
parsed_price_updates.retain(|price_feed| {
|
|
||||||
price_feed
|
|
||||||
.metadata
|
|
||||||
.prev_publish_time
|
|
||||||
.map_or(false, |prev_time| {
|
|
||||||
prev_time != price_feed.price.publish_time
|
|
||||||
})
|
|
||||||
});
|
|
||||||
// Retain price id in price_ids that are in parsed_price_updates
|
|
||||||
price_ids.retain(|price_id| {
|
|
||||||
parsed_price_updates
|
|
||||||
.iter()
|
|
||||||
.any(|price_feed| price_feed.id == RpcPriceIdentifier::from(*price_id))
|
|
||||||
});
|
|
||||||
price_feeds_with_update_data = Aggregates::get_price_feeds_with_update_data(
|
|
||||||
&*state.state,
|
|
||||||
&price_ids,
|
|
||||||
RequestTime::AtSlot(event.slot()),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if price_ids is empty after filtering and return None if it is
|
|
||||||
if price_ids.is_empty() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let price_update_data = price_feeds_with_update_data.update_data;
|
|
||||||
let encoded_data: Vec<String> = price_update_data
|
|
||||||
.into_iter()
|
|
||||||
.map(|data| encoding.encode_str(&data))
|
|
||||||
.collect();
|
|
||||||
let binary_price_update = BinaryPriceUpdate {
|
|
||||||
encoding,
|
|
||||||
data: encoded_data,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(PriceUpdate {
|
|
||||||
binary: binary_price_update,
|
|
||||||
parsed: if parsed {
|
|
||||||
Some(parsed_price_updates)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn error_event<E: std::fmt::Debug>(e: E) -> Event {
|
|
||||||
Event::default()
|
|
||||||
.event("error")
|
|
||||||
.data(format!("Error receiving update: {:?}", e))
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
api::types::{
|
|
||||||
AssetType,
|
|
||||||
PriceFeedMetadata,
|
|
||||||
},
|
|
||||||
state::State,
|
|
||||||
},
|
|
||||||
anyhow::Result,
|
|
||||||
tokio::sync::RwLock,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const DEFAULT_PRICE_FEEDS_CACHE_UPDATE_INTERVAL: u64 = 600;
|
|
||||||
|
|
||||||
pub struct PriceFeedMetaState {
|
|
||||||
pub data: RwLock<Vec<PriceFeedMetadata>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PriceFeedMetaState {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
data: RwLock::new(Vec::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allow downcasting State into CacheState for functions that depend on the `Cache` service.
|
|
||||||
impl<'a> From<&'a State> for &'a PriceFeedMetaState {
|
|
||||||
fn from(state: &'a State) -> &'a PriceFeedMetaState {
|
|
||||||
&state.price_feed_meta
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
pub trait PriceFeedMeta {
|
|
||||||
async fn retrieve_price_feeds_metadata(&self) -> Result<Vec<PriceFeedMetadata>>;
|
|
||||||
async fn store_price_feeds_metadata(
|
|
||||||
&self,
|
|
||||||
price_feeds_metadata: &[PriceFeedMetadata],
|
|
||||||
) -> Result<()>;
|
|
||||||
async fn get_price_feeds_metadata(
|
|
||||||
&self,
|
|
||||||
query: Option<String>,
|
|
||||||
asset_type: Option<AssetType>,
|
|
||||||
) -> Result<Vec<PriceFeedMetadata>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<T> PriceFeedMeta for T
|
|
||||||
where
|
|
||||||
for<'a> &'a T: Into<&'a PriceFeedMetaState>,
|
|
||||||
T: Sync,
|
|
||||||
{
|
|
||||||
async fn retrieve_price_feeds_metadata(&self) -> Result<Vec<PriceFeedMetadata>> {
|
|
||||||
let price_feeds_metadata = self.into().data.read().await;
|
|
||||||
Ok(price_feeds_metadata.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn store_price_feeds_metadata(
|
|
||||||
&self,
|
|
||||||
price_feeds_metadata: &[PriceFeedMetadata],
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut price_feeds_metadata_write_guard = self.into().data.write().await;
|
|
||||||
*price_feeds_metadata_write_guard = price_feeds_metadata.to_vec();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async fn get_price_feeds_metadata(
|
|
||||||
&self,
|
|
||||||
query: Option<String>,
|
|
||||||
asset_type: Option<AssetType>,
|
|
||||||
) -> Result<Vec<PriceFeedMetadata>> {
|
|
||||||
let mut price_feeds_metadata = self.retrieve_price_feeds_metadata().await?;
|
|
||||||
|
|
||||||
// Filter by query if provided
|
|
||||||
if let Some(query_str) = &query {
|
|
||||||
price_feeds_metadata.retain(|feed| {
|
|
||||||
feed.attributes.get("symbol").map_or(false, |symbol| {
|
|
||||||
symbol.to_lowercase().contains(&query_str.to_lowercase())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter by asset_type if provided
|
|
||||||
if let Some(asset_type) = &asset_type {
|
|
||||||
price_feeds_metadata.retain(|feed| {
|
|
||||||
feed.attributes.get("asset_type").map_or(false, |type_str| {
|
|
||||||
type_str.to_lowercase() == asset_type.to_string().to_lowercase()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(price_feeds_metadata)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"endpoint": "https://api.mainnet-beta.solana.com",
|
|
||||||
"keypair-file": "./id.json",
|
|
||||||
"shard-id": 1,
|
|
||||||
"jito-endpoint": "mainnet.block-engine.jito.wtf",
|
|
||||||
"jito-keypair-file": "./jito.json",
|
|
||||||
"jito-tip-lamports": "100000",
|
|
||||||
"jito-bundle-size": "5",
|
|
||||||
"price-config-file": "./price-config.yaml",
|
|
||||||
"price-service-endpoint": "https://hermes.pyth.network/",
|
|
||||||
"pyth-contract-address": "pythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT",
|
|
||||||
"pushing-frequency": "30"
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"endpoint": "https://api.devnet.solana.com",
|
|
||||||
"keypair-file": "./id.json",
|
|
||||||
"shard-id": 1,
|
|
||||||
"price-config-file": "./price-config.yaml",
|
|
||||||
"price-service-endpoint": "https://hermes.pyth.network/",
|
|
||||||
"pyth-contract-address": "pythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT",
|
|
||||||
"pushing-frequency": "30"
|
|
||||||
}
|
|
|
@ -1,177 +0,0 @@
|
||||||
import { Options } from "yargs";
|
|
||||||
import * as options from "../options";
|
|
||||||
import { readPriceConfigFile } from "../price-config";
|
|
||||||
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
|
|
||||||
import { PythPriceListener } from "../pyth-price-listener";
|
|
||||||
import {
|
|
||||||
SolanaPriceListener,
|
|
||||||
SolanaPricePusher,
|
|
||||||
SolanaPricePusherJito,
|
|
||||||
} from "./solana";
|
|
||||||
import { Controller } from "../controller";
|
|
||||||
import { PythSolanaReceiver } from "@pythnetwork/pyth-solana-receiver";
|
|
||||||
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
|
|
||||||
import { Keypair, Connection } from "@solana/web3.js";
|
|
||||||
import fs from "fs";
|
|
||||||
import { PublicKey } from "@solana/web3.js";
|
|
||||||
import {
|
|
||||||
SearcherClient,
|
|
||||||
searcherClient,
|
|
||||||
} from "jito-ts/dist/sdk/block-engine/searcher";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
command: "solana",
|
|
||||||
describe: "run price pusher for solana",
|
|
||||||
builder: {
|
|
||||||
endpoint: {
|
|
||||||
description: "Solana RPC API endpoint",
|
|
||||||
type: "string",
|
|
||||||
required: true,
|
|
||||||
} as Options,
|
|
||||||
"keypair-file": {
|
|
||||||
description: "Path to a keypair file",
|
|
||||||
type: "string",
|
|
||||||
required: true,
|
|
||||||
} as Options,
|
|
||||||
"shard-id": {
|
|
||||||
description: "Shard ID",
|
|
||||||
type: "number",
|
|
||||||
required: true,
|
|
||||||
} as Options,
|
|
||||||
"compute-unit-price-micro-lamports": {
|
|
||||||
description: "Priority fee per compute unit",
|
|
||||||
type: "number",
|
|
||||||
default: 50000,
|
|
||||||
} as Options,
|
|
||||||
"jito-endpoint": {
|
|
||||||
description: "Jito endpoint",
|
|
||||||
type: "string",
|
|
||||||
optional: true,
|
|
||||||
} as Options,
|
|
||||||
"jito-keypair-file": {
|
|
||||||
description:
|
|
||||||
"Path to the jito keypair file (need for grpc authentication)",
|
|
||||||
type: "string",
|
|
||||||
optional: true,
|
|
||||||
} as Options,
|
|
||||||
"jito-tip-lamports": {
|
|
||||||
description: "Lamports to tip the jito builder",
|
|
||||||
type: "number",
|
|
||||||
optional: true,
|
|
||||||
} as Options,
|
|
||||||
"jito-bundle-size": {
|
|
||||||
description: "Number of transactions in each bundle",
|
|
||||||
type: "number",
|
|
||||||
default: 2,
|
|
||||||
} as Options,
|
|
||||||
...options.priceConfigFile,
|
|
||||||
...options.priceServiceEndpoint,
|
|
||||||
...options.pythContractAddress,
|
|
||||||
...options.pollingFrequency,
|
|
||||||
...options.pushingFrequency,
|
|
||||||
},
|
|
||||||
handler: function (argv: any) {
|
|
||||||
const {
|
|
||||||
endpoint,
|
|
||||||
keypairFile,
|
|
||||||
shardId,
|
|
||||||
computeUnitPriceMicroLamports,
|
|
||||||
priceConfigFile,
|
|
||||||
priceServiceEndpoint,
|
|
||||||
pythContractAddress,
|
|
||||||
pushingFrequency,
|
|
||||||
pollingFrequency,
|
|
||||||
jitoEndpoint,
|
|
||||||
jitoKeypairFile,
|
|
||||||
jitoTipLamports,
|
|
||||||
jitoBundleSize,
|
|
||||||
} = argv;
|
|
||||||
|
|
||||||
const priceConfigs = readPriceConfigFile(priceConfigFile);
|
|
||||||
|
|
||||||
const priceServiceConnection = new PriceServiceConnection(
|
|
||||||
priceServiceEndpoint,
|
|
||||||
{
|
|
||||||
logger: {
|
|
||||||
// Log only warnings and errors from the price service client
|
|
||||||
info: () => undefined,
|
|
||||||
warn: console.warn,
|
|
||||||
error: console.error,
|
|
||||||
debug: () => undefined,
|
|
||||||
trace: () => undefined,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const priceItems = priceConfigs.map(({ id, alias }) => ({ id, alias }));
|
|
||||||
|
|
||||||
const pythListener = new PythPriceListener(
|
|
||||||
priceServiceConnection,
|
|
||||||
priceItems
|
|
||||||
);
|
|
||||||
|
|
||||||
const wallet = new NodeWallet(
|
|
||||||
Keypair.fromSecretKey(
|
|
||||||
Uint8Array.from(JSON.parse(fs.readFileSync(keypairFile, "ascii")))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const pythSolanaReceiver = new PythSolanaReceiver({
|
|
||||||
connection: new Connection(endpoint, "processed"),
|
|
||||||
wallet,
|
|
||||||
pushOracleProgramId: new PublicKey(pythContractAddress),
|
|
||||||
});
|
|
||||||
|
|
||||||
let solanaPricePusher;
|
|
||||||
if (jitoTipLamports) {
|
|
||||||
const jitoKeypair = Keypair.fromSecretKey(
|
|
||||||
Uint8Array.from(JSON.parse(fs.readFileSync(jitoKeypairFile, "ascii")))
|
|
||||||
);
|
|
||||||
|
|
||||||
const jitoClient = searcherClient(jitoEndpoint, jitoKeypair);
|
|
||||||
solanaPricePusher = new SolanaPricePusherJito(
|
|
||||||
pythSolanaReceiver,
|
|
||||||
priceServiceConnection,
|
|
||||||
shardId,
|
|
||||||
jitoTipLamports,
|
|
||||||
jitoClient,
|
|
||||||
jitoBundleSize
|
|
||||||
);
|
|
||||||
|
|
||||||
onBundleResult(jitoClient);
|
|
||||||
} else {
|
|
||||||
solanaPricePusher = new SolanaPricePusher(
|
|
||||||
pythSolanaReceiver,
|
|
||||||
priceServiceConnection,
|
|
||||||
shardId,
|
|
||||||
computeUnitPriceMicroLamports
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const solanaPriceListener = new SolanaPriceListener(
|
|
||||||
pythSolanaReceiver,
|
|
||||||
shardId,
|
|
||||||
priceItems,
|
|
||||||
{ pollingFrequency }
|
|
||||||
);
|
|
||||||
|
|
||||||
const controller = new Controller(
|
|
||||||
priceConfigs,
|
|
||||||
pythListener,
|
|
||||||
solanaPriceListener,
|
|
||||||
solanaPricePusher,
|
|
||||||
{ pushingFrequency }
|
|
||||||
);
|
|
||||||
|
|
||||||
controller.start();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const onBundleResult = (c: SearcherClient) => {
|
|
||||||
c.onBundleResult(
|
|
||||||
() => undefined,
|
|
||||||
(e) => {
|
|
||||||
console.log("Error in bundle result: ", e);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,180 +0,0 @@
|
||||||
import { PythSolanaReceiver } from "@pythnetwork/pyth-solana-receiver";
|
|
||||||
import {
|
|
||||||
ChainPriceListener,
|
|
||||||
IPricePusher,
|
|
||||||
PriceInfo,
|
|
||||||
PriceItem,
|
|
||||||
} from "../interface";
|
|
||||||
import { DurationInSeconds } from "../utils";
|
|
||||||
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
|
|
||||||
import {
|
|
||||||
sendTransactions,
|
|
||||||
sendTransactionsJito,
|
|
||||||
} from "@pythnetwork/solana-utils";
|
|
||||||
import { SearcherClient } from "jito-ts/dist/sdk/block-engine/searcher";
|
|
||||||
|
|
||||||
export class SolanaPriceListener extends ChainPriceListener {
|
|
||||||
constructor(
|
|
||||||
private pythSolanaReceiver: PythSolanaReceiver,
|
|
||||||
private shardId: number,
|
|
||||||
priceItems: PriceItem[],
|
|
||||||
config: {
|
|
||||||
pollingFrequency: DurationInSeconds;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
super("solana", config.pollingFrequency, priceItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOnChainPriceInfo(priceId: string): Promise<PriceInfo | undefined> {
|
|
||||||
try {
|
|
||||||
const priceFeedAccount =
|
|
||||||
await this.pythSolanaReceiver.fetchPriceFeedAccount(
|
|
||||||
this.shardId,
|
|
||||||
Buffer.from(priceId, "hex")
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`Polled a Solana on chain price for feed ${this.priceIdToAlias.get(
|
|
||||||
priceId
|
|
||||||
)} (${priceId}).`
|
|
||||||
);
|
|
||||||
if (priceFeedAccount) {
|
|
||||||
return {
|
|
||||||
conf: priceFeedAccount.priceMessage.conf.toString(),
|
|
||||||
price: priceFeedAccount.priceMessage.price.toString(),
|
|
||||||
publishTime: priceFeedAccount.priceMessage.publishTime.toNumber(),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Polling on-chain price for ${priceId} failed. Error:`);
|
|
||||||
console.error(e);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SolanaPricePusher implements IPricePusher {
|
|
||||||
constructor(
|
|
||||||
private pythSolanaReceiver: PythSolanaReceiver,
|
|
||||||
private priceServiceConnection: PriceServiceConnection,
|
|
||||||
private shardId: number,
|
|
||||||
private computeUnitPriceMicroLamports: number
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async updatePriceFeed(
|
|
||||||
priceIds: string[],
|
|
||||||
pubTimesToPush: number[]
|
|
||||||
): Promise<void> {
|
|
||||||
if (priceIds.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let priceFeedUpdateData;
|
|
||||||
try {
|
|
||||||
priceFeedUpdateData = await this.priceServiceConnection.getLatestVaas(
|
|
||||||
priceIds
|
|
||||||
);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(new Date(), "getPriceFeedsUpdateData failed:", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const transactionBuilder = this.pythSolanaReceiver.newTransactionBuilder({
|
|
||||||
closeUpdateAccounts: true,
|
|
||||||
});
|
|
||||||
await transactionBuilder.addUpdatePriceFeed(
|
|
||||||
priceFeedUpdateData,
|
|
||||||
this.shardId
|
|
||||||
);
|
|
||||||
|
|
||||||
const transactions = await transactionBuilder.buildVersionedTransactions({
|
|
||||||
computeUnitPriceMicroLamports: this.computeUnitPriceMicroLamports,
|
|
||||||
tightComputeBudget: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await sendTransactions(
|
|
||||||
transactions,
|
|
||||||
this.pythSolanaReceiver.connection,
|
|
||||||
this.pythSolanaReceiver.wallet
|
|
||||||
);
|
|
||||||
console.log(new Date(), "updatePriceFeed successful");
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(new Date(), "updatePriceFeed failed", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SolanaPricePusherJito implements IPricePusher {
|
|
||||||
constructor(
|
|
||||||
private pythSolanaReceiver: PythSolanaReceiver,
|
|
||||||
private priceServiceConnection: PriceServiceConnection,
|
|
||||||
private shardId: number,
|
|
||||||
private jitoTipLamports: number,
|
|
||||||
private searcherClient: SearcherClient,
|
|
||||||
private jitoBundleSize: number
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async updatePriceFeed(
|
|
||||||
priceIds: string[],
|
|
||||||
pubTimesToPush: number[]
|
|
||||||
): Promise<void> {
|
|
||||||
let priceFeedUpdateData;
|
|
||||||
try {
|
|
||||||
priceFeedUpdateData = await this.priceServiceConnection.getLatestVaas(
|
|
||||||
priceIds
|
|
||||||
);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(new Date(), "getPriceFeedsUpdateData failed:", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const transactionBuilder = this.pythSolanaReceiver.newTransactionBuilder({
|
|
||||||
closeUpdateAccounts: false,
|
|
||||||
});
|
|
||||||
await transactionBuilder.addUpdatePriceFeed(
|
|
||||||
priceFeedUpdateData,
|
|
||||||
this.shardId
|
|
||||||
);
|
|
||||||
await transactionBuilder.addClosePreviousEncodedVaasInstructions();
|
|
||||||
|
|
||||||
const transactions = await transactionBuilder.buildVersionedTransactions({
|
|
||||||
jitoTipLamports: this.jitoTipLamports,
|
|
||||||
tightComputeBudget: true,
|
|
||||||
jitoBundleSize: this.jitoBundleSize,
|
|
||||||
});
|
|
||||||
|
|
||||||
const firstSignature = await sendTransactionsJito(
|
|
||||||
transactions.slice(0, this.jitoBundleSize),
|
|
||||||
this.searcherClient,
|
|
||||||
this.pythSolanaReceiver.wallet
|
|
||||||
);
|
|
||||||
|
|
||||||
const blockhashResult =
|
|
||||||
await this.pythSolanaReceiver.connection.getLatestBlockhashAndContext({
|
|
||||||
commitment: "confirmed",
|
|
||||||
});
|
|
||||||
await this.pythSolanaReceiver.connection.confirmTransaction(
|
|
||||||
{
|
|
||||||
signature: firstSignature,
|
|
||||||
blockhash: blockhashResult.value.blockhash,
|
|
||||||
lastValidBlockHeight: blockhashResult.value.lastValidBlockHeight,
|
|
||||||
},
|
|
||||||
"confirmed"
|
|
||||||
);
|
|
||||||
|
|
||||||
for (
|
|
||||||
let i = this.jitoBundleSize;
|
|
||||||
i < transactions.length;
|
|
||||||
i += this.jitoBundleSize
|
|
||||||
) {
|
|
||||||
await sendTransactionsJito(
|
|
||||||
transactions.slice(i, i + this.jitoBundleSize),
|
|
||||||
this.searcherClient,
|
|
||||||
this.pythSolanaReceiver.wallet
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@pythnetwork/contract-manager",
|
"name": "contract_manager",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Set of tools to manage pyth contracts",
|
"description": "Set of tools to manage pyth contracts",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -21,9 +21,9 @@
|
||||||
"url": "git+https://github.com/pyth-network/pyth-crosschain.git"
|
"url": "git+https://github.com/pyth-network/pyth-crosschain.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certusone/wormhole-sdk": "^0.9.8",
|
|
||||||
"@coral-xyz/anchor": "^0.29.0",
|
"@coral-xyz/anchor": "^0.29.0",
|
||||||
"@injectivelabs/networks": "^1.14.6",
|
"@certusone/wormhole-sdk": "^0.9.8",
|
||||||
|
"@injectivelabs/networks": "1.0.68",
|
||||||
"@mysten/sui.js": "^0.49.1",
|
"@mysten/sui.js": "^0.49.1",
|
||||||
"@pythnetwork/cosmwasm-deploy-tools": "*",
|
"@pythnetwork/cosmwasm-deploy-tools": "*",
|
||||||
"@pythnetwork/entropy-sdk-solidity": "*",
|
"@pythnetwork/entropy-sdk-solidity": "*",
|
||||||
|
@ -31,7 +31,6 @@
|
||||||
"@pythnetwork/pyth-sui-js": "*",
|
"@pythnetwork/pyth-sui-js": "*",
|
||||||
"@types/yargs": "^17.0.32",
|
"@types/yargs": "^17.0.32",
|
||||||
"aptos": "^1.5.0",
|
"aptos": "^1.5.0",
|
||||||
"axios": "^0.24.0",
|
|
||||||
"bs58": "^5.0.0",
|
"bs58": "^5.0.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { createHash } from "crypto";
|
||||||
import { DefaultStore } from "../src/store";
|
import { DefaultStore } from "../src/store";
|
||||||
import {
|
import {
|
||||||
CosmosUpgradeContract,
|
CosmosUpgradeContract,
|
||||||
EvmExecute,
|
|
||||||
EvmSetWormholeAddress,
|
EvmSetWormholeAddress,
|
||||||
EvmUpgradeContract,
|
EvmUpgradeContract,
|
||||||
getProposalInstructions,
|
getProposalInstructions,
|
||||||
|
@ -20,10 +19,8 @@ import {
|
||||||
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
|
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
|
||||||
import { AccountMeta, Keypair, PublicKey } from "@solana/web3.js";
|
import { AccountMeta, Keypair, PublicKey } from "@solana/web3.js";
|
||||||
import {
|
import {
|
||||||
EvmEntropyContract,
|
|
||||||
EvmPriceFeedContract,
|
EvmPriceFeedContract,
|
||||||
getCodeDigestWithoutAddress,
|
WormholeEvmContract,
|
||||||
EvmWormholeContract,
|
|
||||||
} from "../src/contracts/evm";
|
} from "../src/contracts/evm";
|
||||||
import Web3 from "web3";
|
import Web3 from "web3";
|
||||||
|
|
||||||
|
@ -73,7 +70,7 @@ async function main() {
|
||||||
instruction.governanceAction.targetChainId
|
instruction.governanceAction.targetChainId
|
||||||
) {
|
) {
|
||||||
const address = instruction.governanceAction.address;
|
const address = instruction.governanceAction.address;
|
||||||
const contract = new EvmWormholeContract(chain, address);
|
const contract = new WormholeEvmContract(chain, address);
|
||||||
const currentIndex = await contract.getCurrentGuardianSetIndex();
|
const currentIndex = await contract.getCurrentGuardianSetIndex();
|
||||||
const guardianSet = await contract.getGuardianSet();
|
const guardianSet = await contract.getGuardianSet();
|
||||||
|
|
||||||
|
@ -137,70 +134,6 @@ async function main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (instruction.governanceAction instanceof EvmExecute) {
|
|
||||||
// Note: it only checks for upgrade entropy contracts right now
|
|
||||||
console.log(
|
|
||||||
`Verifying EVMExecute Contract on ${instruction.governanceAction.targetChainId}`
|
|
||||||
);
|
|
||||||
for (const chain of Object.values(DefaultStore.chains)) {
|
|
||||||
if (
|
|
||||||
chain instanceof EvmChain &&
|
|
||||||
chain.wormholeChainName ===
|
|
||||||
instruction.governanceAction.targetChainId
|
|
||||||
) {
|
|
||||||
const executorAddress =
|
|
||||||
instruction.governanceAction.executorAddress;
|
|
||||||
const callAddress = instruction.governanceAction.callAddress;
|
|
||||||
const calldata = instruction.governanceAction.calldata;
|
|
||||||
|
|
||||||
// currently executor is only being used by the entropy contract
|
|
||||||
const contract = new EvmEntropyContract(chain, callAddress);
|
|
||||||
const owner = await contract.getOwner();
|
|
||||||
|
|
||||||
if (
|
|
||||||
executorAddress.toUpperCase() !==
|
|
||||||
owner.replace("0x", "").toUpperCase()
|
|
||||||
) {
|
|
||||||
console.log(
|
|
||||||
`Executor Address: ${executorAddress.toUpperCase()} is not equal to Owner Address: ${owner
|
|
||||||
.replace("0x", "")
|
|
||||||
.toUpperCase()}`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const calldataHex = calldata.toString("hex");
|
|
||||||
const web3 = new Web3();
|
|
||||||
const methodSignature = web3.eth.abi
|
|
||||||
.encodeFunctionSignature("upgradeTo(address)")
|
|
||||||
.replace("0x", "");
|
|
||||||
|
|
||||||
let newImplementationAddress: string | undefined = undefined;
|
|
||||||
if (calldataHex.startsWith(methodSignature)) {
|
|
||||||
newImplementationAddress = web3.eth.abi.decodeParameter(
|
|
||||||
"address",
|
|
||||||
calldataHex.replace(methodSignature, "")
|
|
||||||
) as unknown as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newImplementationAddress === undefined) {
|
|
||||||
console.log(
|
|
||||||
`We couldn't parse the instruction for ${chain.getId()}`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newImplementationCode = await getCodeDigestWithoutAddress(
|
|
||||||
chain.getRpcUrl(),
|
|
||||||
newImplementationAddress
|
|
||||||
);
|
|
||||||
// this should be the same keccak256 of the deployedCode property generated by truffle
|
|
||||||
console.log(
|
|
||||||
`${chain.getId()} new implementation address:${newImplementationAddress} digest:${newImplementationCode}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
import {
|
import { DefaultStore, EvmChain, PrivateKey } from "../src";
|
||||||
DefaultStore,
|
|
||||||
EvmChain,
|
|
||||||
EvmEntropyContract,
|
|
||||||
EvmWormholeContract,
|
|
||||||
getDefaultDeploymentConfig,
|
|
||||||
PrivateKey,
|
|
||||||
} from "../src";
|
|
||||||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import Web3 from "web3";
|
import Web3 from "web3";
|
||||||
import { Contract } from "web3-eth-contract";
|
import { Contract } from "web3-eth-contract";
|
||||||
import { InferredOptionType } from "yargs";
|
import { InferredOptionType } from "yargs";
|
||||||
|
|
||||||
export interface BaseDeployConfig {
|
interface DeployConfig {
|
||||||
gasMultiplier: number;
|
gasMultiplier: number;
|
||||||
gasPriceMultiplier: number;
|
gasPriceMultiplier: number;
|
||||||
jsonOutputDir: string;
|
jsonOutputDir: string;
|
||||||
|
@ -26,7 +19,7 @@ export interface BaseDeployConfig {
|
||||||
export async function deployIfNotCached(
|
export async function deployIfNotCached(
|
||||||
cacheFile: string,
|
cacheFile: string,
|
||||||
chain: EvmChain,
|
chain: EvmChain,
|
||||||
config: BaseDeployConfig,
|
config: DeployConfig,
|
||||||
artifactName: string,
|
artifactName: string,
|
||||||
deployArgs: any[], // eslint-disable-line @typescript-eslint/no-explicit-any
|
deployArgs: any[], // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
cacheKey?: string
|
cacheKey?: string
|
||||||
|
@ -74,12 +67,12 @@ export const COMMON_DEPLOY_OPTIONS = {
|
||||||
"private-key": {
|
"private-key": {
|
||||||
type: "string",
|
type: "string",
|
||||||
demandOption: true,
|
demandOption: true,
|
||||||
desc: "Private key to sign the transactions with",
|
desc: "Private key to sign the trnasactions with",
|
||||||
},
|
},
|
||||||
chain: {
|
chain: {
|
||||||
type: "array",
|
type: "array",
|
||||||
demandOption: true,
|
demandOption: true,
|
||||||
desc: "Chain to upload the contract on. Can be one of the chains available in the store",
|
desc: "Chain to upload the contract on. Can be one of the evm chains available in the store",
|
||||||
},
|
},
|
||||||
"deployment-type": {
|
"deployment-type": {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
@ -188,149 +181,3 @@ export function getSelectedChains(argv: {
|
||||||
}
|
}
|
||||||
return selectedChains;
|
return selectedChains;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the entropy contract for a given EVM chain.
|
|
||||||
* @param {EvmChain} chain The EVM chain to find the entropy contract for.
|
|
||||||
* @returns The entropy contract for the given EVM chain.
|
|
||||||
* @throws {Error} an error if the entropy contract is not found for the given EVM chain.
|
|
||||||
*/
|
|
||||||
export function findEntropyContract(chain: EvmChain): EvmEntropyContract {
|
|
||||||
for (const contract of Object.values(DefaultStore.entropy_contracts)) {
|
|
||||||
if (contract.getChain().getId() === chain.getId()) {
|
|
||||||
return contract;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(`Entropy contract not found for chain ${chain.getId()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds an EVM chain by its name.
|
|
||||||
* @param {string} chainName The name of the chain to find.
|
|
||||||
* @returns The EVM chain instance.
|
|
||||||
* @throws {Error} an error if the chain is not found or is not an EVM chain.
|
|
||||||
*/
|
|
||||||
export function findEvmChain(chainName: string): EvmChain {
|
|
||||||
const chain = DefaultStore.chains[chainName];
|
|
||||||
if (!chain) {
|
|
||||||
throw new Error(`Chain ${chainName} not found`);
|
|
||||||
} else if (!(chain instanceof EvmChain)) {
|
|
||||||
throw new Error(`Chain ${chainName} is not an EVM chain`);
|
|
||||||
}
|
|
||||||
return chain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the wormhole contract for a given EVM chain.
|
|
||||||
* @param {EvmChain} chain The EVM chain to find the wormhole contract for.
|
|
||||||
* @returns If found, the wormhole contract for the given EVM chain. Else, undefined
|
|
||||||
*/
|
|
||||||
export function findWormholeContract(
|
|
||||||
chain: EvmChain
|
|
||||||
): EvmWormholeContract | undefined {
|
|
||||||
for (const contract of Object.values(DefaultStore.wormhole_contracts)) {
|
|
||||||
if (
|
|
||||||
contract instanceof EvmWormholeContract &&
|
|
||||||
contract.getChain().getId() === chain.getId()
|
|
||||||
) {
|
|
||||||
return contract;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DeployWormholeReceiverContractsConfig
|
|
||||||
extends BaseDeployConfig {
|
|
||||||
saveContract: boolean;
|
|
||||||
type: "stable" | "beta";
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Deploys the wormhole receiver contract for a given EVM chain.
|
|
||||||
* @param {EvmChain} chain The EVM chain to find the wormhole receiver contract for.
|
|
||||||
* @param {DeployWormholeReceiverContractsConfig} config The deployment configuration.
|
|
||||||
* @param {string} cacheFile The path to the cache file.
|
|
||||||
* @returns {EvmWormholeContract} The wormhole contract for the given EVM chain.
|
|
||||||
*/
|
|
||||||
export async function deployWormholeContract(
|
|
||||||
chain: EvmChain,
|
|
||||||
config: DeployWormholeReceiverContractsConfig,
|
|
||||||
cacheFile: string
|
|
||||||
): Promise<EvmWormholeContract> {
|
|
||||||
const receiverSetupAddr = await deployIfNotCached(
|
|
||||||
cacheFile,
|
|
||||||
chain,
|
|
||||||
config,
|
|
||||||
"ReceiverSetup",
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const receiverImplAddr = await deployIfNotCached(
|
|
||||||
cacheFile,
|
|
||||||
chain,
|
|
||||||
config,
|
|
||||||
"ReceiverImplementation",
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Craft the init data for the proxy contract
|
|
||||||
const setupContract = getWeb3Contract(
|
|
||||||
config.jsonOutputDir,
|
|
||||||
"ReceiverSetup",
|
|
||||||
receiverSetupAddr
|
|
||||||
);
|
|
||||||
|
|
||||||
const { wormholeConfig } = getDefaultDeploymentConfig(config.type);
|
|
||||||
|
|
||||||
const initData = setupContract.methods
|
|
||||||
.setup(
|
|
||||||
receiverImplAddr,
|
|
||||||
wormholeConfig.initialGuardianSet.map((addr: string) => "0x" + addr),
|
|
||||||
chain.getWormholeChainId(),
|
|
||||||
wormholeConfig.governanceChainId,
|
|
||||||
"0x" + wormholeConfig.governanceContract
|
|
||||||
)
|
|
||||||
.encodeABI();
|
|
||||||
|
|
||||||
const wormholeReceiverAddr = await deployIfNotCached(
|
|
||||||
cacheFile,
|
|
||||||
chain,
|
|
||||||
config,
|
|
||||||
"WormholeReceiver",
|
|
||||||
[receiverSetupAddr, initData]
|
|
||||||
);
|
|
||||||
|
|
||||||
const wormholeContract = new EvmWormholeContract(chain, wormholeReceiverAddr);
|
|
||||||
|
|
||||||
if (config.type === "stable") {
|
|
||||||
console.log(`Syncing mainnet guardian sets for ${chain.getId()}...`);
|
|
||||||
// TODO: Add a way to pass gas configs to this
|
|
||||||
await wormholeContract.syncMainnetGuardianSets(config.privateKey);
|
|
||||||
console.log(`✅ Synced mainnet guardian sets for ${chain.getId()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.saveContract) {
|
|
||||||
DefaultStore.wormhole_contracts[wormholeContract.getId()] =
|
|
||||||
wormholeContract;
|
|
||||||
DefaultStore.saveAllContracts();
|
|
||||||
}
|
|
||||||
|
|
||||||
return wormholeContract;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the wormhole contract for a given EVM chain.
|
|
||||||
* If there was no wormhole contract deployed for the given chain, it will deploy the wormhole contract and save it to the default store.
|
|
||||||
* @param {EvmChain} chain The EVM chain to find the wormhole contract for.
|
|
||||||
* @param {DeployWormholeReceiverContractsConfig} config The deployment configuration.
|
|
||||||
* @param {string} cacheFile The path to the cache file.
|
|
||||||
* @returns {EvmWormholeContract} The wormhole contract for the given EVM chain.
|
|
||||||
*/
|
|
||||||
export async function getOrDeployWormholeContract(
|
|
||||||
chain: EvmChain,
|
|
||||||
config: DeployWormholeReceiverContractsConfig,
|
|
||||||
cacheFile: string
|
|
||||||
): Promise<EvmWormholeContract> {
|
|
||||||
return (
|
|
||||||
findWormholeContract(chain) ??
|
|
||||||
(await deployWormholeContract(chain, config, cacheFile))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,23 +5,29 @@ import { DefaultStore } from "../src/store";
|
||||||
import {
|
import {
|
||||||
DeploymentType,
|
DeploymentType,
|
||||||
EvmEntropyContract,
|
EvmEntropyContract,
|
||||||
|
EvmPriceFeedContract,
|
||||||
getDefaultDeploymentConfig,
|
getDefaultDeploymentConfig,
|
||||||
|
PrivateKey,
|
||||||
toDeploymentType,
|
toDeploymentType,
|
||||||
toPrivateKey,
|
toPrivateKey,
|
||||||
|
WormholeEvmContract,
|
||||||
} from "../src";
|
} from "../src";
|
||||||
import {
|
import {
|
||||||
COMMON_DEPLOY_OPTIONS,
|
COMMON_DEPLOY_OPTIONS,
|
||||||
deployIfNotCached,
|
deployIfNotCached,
|
||||||
getWeb3Contract,
|
getWeb3Contract,
|
||||||
getOrDeployWormholeContract,
|
|
||||||
BaseDeployConfig,
|
|
||||||
} from "./common";
|
} from "./common";
|
||||||
import Web3 from "web3";
|
import Web3 from "web3";
|
||||||
|
|
||||||
interface DeploymentConfig extends BaseDeployConfig {
|
type DeploymentConfig = {
|
||||||
type: DeploymentType;
|
type: DeploymentType;
|
||||||
|
gasMultiplier: number;
|
||||||
|
gasPriceMultiplier: number;
|
||||||
|
privateKey: PrivateKey;
|
||||||
|
jsonOutputDir: string;
|
||||||
|
wormholeAddr: string;
|
||||||
saveContract: boolean;
|
saveContract: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
const CACHE_FILE = ".cache-deploy-evm-entropy-contracts";
|
const CACHE_FILE = ".cache-deploy-evm-entropy-contracts";
|
||||||
const ENTROPY_DEFAULT_PROVIDER = {
|
const ENTROPY_DEFAULT_PROVIDER = {
|
||||||
|
@ -45,8 +51,7 @@ const parser = yargs(hideBin(process.argv))
|
||||||
|
|
||||||
async function deployExecutorContracts(
|
async function deployExecutorContracts(
|
||||||
chain: EvmChain,
|
chain: EvmChain,
|
||||||
config: DeploymentConfig,
|
config: DeploymentConfig
|
||||||
wormholeAddr: string
|
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const executorImplAddr = await deployIfNotCached(
|
const executorImplAddr = await deployIfNotCached(
|
||||||
CACHE_FILE,
|
CACHE_FILE,
|
||||||
|
@ -67,7 +72,7 @@ async function deployExecutorContracts(
|
||||||
|
|
||||||
const executorInitData = executorImplContract.methods
|
const executorInitData = executorImplContract.methods
|
||||||
.initialize(
|
.initialize(
|
||||||
wormholeAddr,
|
config.wormholeAddr,
|
||||||
0, // lastExecutedSequence,
|
0, // lastExecutedSequence,
|
||||||
chain.getWormholeChainId(),
|
chain.getWormholeChainId(),
|
||||||
governanceDataSource.emitterChain,
|
governanceDataSource.emitterChain,
|
||||||
|
@ -156,6 +161,19 @@ async function topupProviderIfNecessary(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function findWormholeAddress(
|
||||||
|
chain: EvmChain
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
for (const contract of Object.values(DefaultStore.contracts)) {
|
||||||
|
if (
|
||||||
|
contract instanceof EvmPriceFeedContract &&
|
||||||
|
contract.getChain().getId() === chain.getId()
|
||||||
|
) {
|
||||||
|
return (await contract.getWormholeContract()).address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const argv = await parser.argv;
|
const argv = await parser.argv;
|
||||||
|
|
||||||
|
@ -167,6 +185,12 @@ async function main() {
|
||||||
throw new Error(`Chain ${chainName} is not an EVM chain`);
|
throw new Error(`Chain ${chainName} is not an EVM chain`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wormholeAddr = await findWormholeAddress(chain);
|
||||||
|
if (!wormholeAddr) {
|
||||||
|
// TODO: deploy wormhole if necessary and maintain a wormhole store
|
||||||
|
throw new Error(`Wormhole contract not found for chain ${chain.getId()}`);
|
||||||
|
}
|
||||||
|
|
||||||
const deploymentConfig: DeploymentConfig = {
|
const deploymentConfig: DeploymentConfig = {
|
||||||
type: toDeploymentType(argv.deploymentType),
|
type: toDeploymentType(argv.deploymentType),
|
||||||
gasMultiplier: argv.gasMultiplier,
|
gasMultiplier: argv.gasMultiplier,
|
||||||
|
@ -174,14 +198,18 @@ async function main() {
|
||||||
privateKey: toPrivateKey(argv.privateKey),
|
privateKey: toPrivateKey(argv.privateKey),
|
||||||
jsonOutputDir: argv.stdOutputDir,
|
jsonOutputDir: argv.stdOutputDir,
|
||||||
saveContract: argv.saveContract,
|
saveContract: argv.saveContract,
|
||||||
|
wormholeAddr,
|
||||||
};
|
};
|
||||||
|
const wormholeContract = new WormholeEvmContract(
|
||||||
const wormholeContract = await getOrDeployWormholeContract(
|
|
||||||
chain,
|
chain,
|
||||||
deploymentConfig,
|
deploymentConfig.wormholeAddr
|
||||||
CACHE_FILE
|
|
||||||
);
|
);
|
||||||
|
const wormholeChainId = await wormholeContract.getChainId();
|
||||||
|
if (chain.getWormholeChainId() != wormholeChainId) {
|
||||||
|
throw new Error(
|
||||||
|
`Wormhole chain id mismatch. Expected ${chain.getWormholeChainId()} but got ${wormholeChainId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
await topupProviderIfNecessary(chain, deploymentConfig);
|
await topupProviderIfNecessary(chain, deploymentConfig);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -190,11 +218,7 @@ async function main() {
|
||||||
|
|
||||||
console.log(`Deploying entropy contracts on ${chain.getId()}...`);
|
console.log(`Deploying entropy contracts on ${chain.getId()}...`);
|
||||||
|
|
||||||
const executorAddr = await deployExecutorContracts(
|
const executorAddr = await deployExecutorContracts(chain, deploymentConfig);
|
||||||
chain,
|
|
||||||
deploymentConfig,
|
|
||||||
wormholeContract.address
|
|
||||||
);
|
|
||||||
const entropyAddr = await deployEntropyContracts(
|
const entropyAddr = await deployEntropyContracts(
|
||||||
chain,
|
chain,
|
||||||
deploymentConfig,
|
deploymentConfig,
|
||||||
|
|
|
@ -6,23 +6,27 @@ import {
|
||||||
DeploymentType,
|
DeploymentType,
|
||||||
EvmPriceFeedContract,
|
EvmPriceFeedContract,
|
||||||
getDefaultDeploymentConfig,
|
getDefaultDeploymentConfig,
|
||||||
|
PrivateKey,
|
||||||
toDeploymentType,
|
toDeploymentType,
|
||||||
toPrivateKey,
|
toPrivateKey,
|
||||||
|
WormholeEvmContract,
|
||||||
} from "../src";
|
} from "../src";
|
||||||
import {
|
import {
|
||||||
COMMON_DEPLOY_OPTIONS,
|
COMMON_DEPLOY_OPTIONS,
|
||||||
deployIfNotCached,
|
deployIfNotCached,
|
||||||
getWeb3Contract,
|
getWeb3Contract,
|
||||||
getOrDeployWormholeContract,
|
|
||||||
BaseDeployConfig,
|
|
||||||
} from "./common";
|
} from "./common";
|
||||||
|
|
||||||
interface DeploymentConfig extends BaseDeployConfig {
|
type DeploymentConfig = {
|
||||||
type: DeploymentType;
|
type: DeploymentType;
|
||||||
validTimePeriodSeconds: number;
|
validTimePeriodSeconds: number;
|
||||||
singleUpdateFeeInWei: number;
|
singleUpdateFeeInWei: number;
|
||||||
|
gasMultiplier: number;
|
||||||
|
gasPriceMultiplier: number;
|
||||||
|
privateKey: PrivateKey;
|
||||||
|
jsonOutputDir: string;
|
||||||
saveContract: boolean;
|
saveContract: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
const CACHE_FILE = ".cache-deploy-evm";
|
const CACHE_FILE = ".cache-deploy-evm";
|
||||||
|
|
||||||
|
@ -47,6 +51,68 @@ const parser = yargs(hideBin(process.argv))
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function deployWormholeReceiverContracts(
|
||||||
|
chain: EvmChain,
|
||||||
|
config: DeploymentConfig
|
||||||
|
): Promise<string> {
|
||||||
|
const receiverSetupAddr = await deployIfNotCached(
|
||||||
|
CACHE_FILE,
|
||||||
|
chain,
|
||||||
|
config,
|
||||||
|
"ReceiverSetup",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const receiverImplAddr = await deployIfNotCached(
|
||||||
|
CACHE_FILE,
|
||||||
|
chain,
|
||||||
|
config,
|
||||||
|
"ReceiverImplementation",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Craft the init data for the proxy contract
|
||||||
|
const setupContract = getWeb3Contract(
|
||||||
|
config.jsonOutputDir,
|
||||||
|
"ReceiverSetup",
|
||||||
|
receiverSetupAddr
|
||||||
|
);
|
||||||
|
|
||||||
|
const { wormholeConfig } = getDefaultDeploymentConfig(config.type);
|
||||||
|
|
||||||
|
const initData = setupContract.methods
|
||||||
|
.setup(
|
||||||
|
receiverImplAddr,
|
||||||
|
wormholeConfig.initialGuardianSet.map((addr: string) => "0x" + addr),
|
||||||
|
chain.getWormholeChainId(),
|
||||||
|
wormholeConfig.governanceChainId,
|
||||||
|
"0x" + wormholeConfig.governanceContract
|
||||||
|
)
|
||||||
|
.encodeABI();
|
||||||
|
|
||||||
|
const wormholeReceiverAddr = await deployIfNotCached(
|
||||||
|
CACHE_FILE,
|
||||||
|
chain,
|
||||||
|
config,
|
||||||
|
"WormholeReceiver",
|
||||||
|
[receiverSetupAddr, initData]
|
||||||
|
);
|
||||||
|
|
||||||
|
const wormholeEvmContract = new WormholeEvmContract(
|
||||||
|
chain,
|
||||||
|
wormholeReceiverAddr
|
||||||
|
);
|
||||||
|
|
||||||
|
if (config.type === "stable") {
|
||||||
|
console.log(`Syncing mainnet guardian sets for ${chain.getId()}...`);
|
||||||
|
// TODO: Add a way to pass gas configs to this
|
||||||
|
await wormholeEvmContract.syncMainnetGuardianSets(config.privateKey);
|
||||||
|
console.log(`✅ Synced mainnet guardian sets for ${chain.getId()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wormholeReceiverAddr;
|
||||||
|
}
|
||||||
|
|
||||||
async function deployPriceFeedContracts(
|
async function deployPriceFeedContracts(
|
||||||
chain: EvmChain,
|
chain: EvmChain,
|
||||||
config: DeploymentConfig,
|
config: DeploymentConfig,
|
||||||
|
@ -120,16 +186,14 @@ async function main() {
|
||||||
|
|
||||||
console.log(`Deploying price feed contracts on ${chain.getId()}...`);
|
console.log(`Deploying price feed contracts on ${chain.getId()}...`);
|
||||||
|
|
||||||
const wormholeContract = await getOrDeployWormholeContract(
|
const wormholeAddr = await deployWormholeReceiverContracts(
|
||||||
chain,
|
chain,
|
||||||
deploymentConfig,
|
deploymentConfig
|
||||||
CACHE_FILE
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const priceFeedAddr = await deployPriceFeedContracts(
|
const priceFeedAddr = await deployPriceFeedContracts(
|
||||||
chain,
|
chain,
|
||||||
deploymentConfig,
|
deploymentConfig,
|
||||||
wormholeContract.address
|
wormholeAddr
|
||||||
);
|
);
|
||||||
|
|
||||||
if (deploymentConfig.saveContract) {
|
if (deploymentConfig.saveContract) {
|
||||||
|
|
|
@ -19,18 +19,6 @@ const parser = yargs(hideBin(process.argv))
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const argv = await parser.argv;
|
const argv = await parser.argv;
|
||||||
|
|
||||||
const prices: Record<string, number> = {};
|
|
||||||
for (const token of Object.values(DefaultStore.tokens)) {
|
|
||||||
const price = await token.getPriceForMinUnit();
|
|
||||||
// We're going to ignore the value of tokens that aren't configured
|
|
||||||
// in the store -- these are likely not worth much anyway.
|
|
||||||
if (price !== undefined) {
|
|
||||||
prices[token.id] = price;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalFeeUsd = 0;
|
|
||||||
for (const contract of Object.values(DefaultStore.contracts)) {
|
for (const contract of Object.values(DefaultStore.contracts)) {
|
||||||
if (contract.getChain().isMainnet() === argv.testnet) continue;
|
if (contract.getChain().isMainnet() === argv.testnet) continue;
|
||||||
if (
|
if (
|
||||||
|
@ -39,26 +27,12 @@ async function main() {
|
||||||
contract instanceof CosmWasmPriceFeedContract
|
contract instanceof CosmWasmPriceFeedContract
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const fee = await contract.getTotalFee();
|
console.log(`${contract.getId()} ${await contract.getTotalFee()}`);
|
||||||
let feeUsd = 0;
|
|
||||||
if (fee.denom !== undefined && prices[fee.denom] !== undefined) {
|
|
||||||
feeUsd = Number(fee.amount) * prices[fee.denom];
|
|
||||||
totalFeeUsd += feeUsd;
|
|
||||||
console.log(
|
|
||||||
`${contract.getId()} ${fee.amount} ${fee.denom} ($${feeUsd})`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`${contract.getId()} ${fee.amount} ${fee.denom} ($ value unknown)`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Error fetching fees for ${contract.getId()}`, e);
|
console.error(`Error fetching fees for ${contract.getId()}`, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Total fees in USD: $${totalFeeUsd}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
import yargs from "yargs";
|
|
||||||
import { hideBin } from "yargs/helpers";
|
|
||||||
import { DefaultStore } from "../src";
|
|
||||||
|
|
||||||
function deserializeCommitmentMetadata(data: Buffer) {
|
|
||||||
const seed = Uint8Array.from(data.subarray(0, 32));
|
|
||||||
const chainLength = data.readBigInt64LE(32);
|
|
||||||
|
|
||||||
return {
|
|
||||||
seed,
|
|
||||||
chainLength,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const parser = yargs(hideBin(process.argv))
|
|
||||||
.usage("Usage: $0")
|
|
||||||
.options({
|
|
||||||
testnet: {
|
|
||||||
type: "boolean",
|
|
||||||
default: false,
|
|
||||||
desc: "Fetch the provider registration data for the testnet contracts.",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const argv = await parser.argv;
|
|
||||||
|
|
||||||
for (const contract of Object.values(DefaultStore.entropy_contracts)) {
|
|
||||||
if (contract.getChain().isMainnet() === argv.testnet) continue;
|
|
||||||
let provider;
|
|
||||||
let providerInfo;
|
|
||||||
try {
|
|
||||||
provider = await contract.getDefaultProvider();
|
|
||||||
providerInfo = await contract.getProviderInfo(provider);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Error fetching info for ${contract.getId()}`, e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const commitmentMetadata = providerInfo.commitmentMetadata.replace(
|
|
||||||
"0x",
|
|
||||||
""
|
|
||||||
);
|
|
||||||
|
|
||||||
// const binaryData = hexToBytes(commitmentMetadata);
|
|
||||||
const metadata = deserializeCommitmentMetadata(
|
|
||||||
Buffer.from(commitmentMetadata, "hex")
|
|
||||||
);
|
|
||||||
console.log("=".repeat(100));
|
|
||||||
console.log(`Fetched info for ${contract.getId()}`);
|
|
||||||
|
|
||||||
console.log(`chain : ${contract.getChain().getId()}`);
|
|
||||||
console.log(`contract : ${contract.address}`);
|
|
||||||
console.log(`provider : ${provider}`);
|
|
||||||
console.log(`commitment data : ${commitmentMetadata}`);
|
|
||||||
console.log(`chainLength : ${metadata.chainLength}`);
|
|
||||||
console.log(`seed : [${metadata.seed}]`);
|
|
||||||
console.log(
|
|
||||||
`original seq no : ${providerInfo.originalCommitmentSequenceNumber}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
|
@ -1,66 +0,0 @@
|
||||||
import yargs from "yargs";
|
|
||||||
import { hideBin } from "yargs/helpers";
|
|
||||||
import { toPrivateKey } from "../src";
|
|
||||||
import {
|
|
||||||
COMMON_DEPLOY_OPTIONS,
|
|
||||||
findEntropyContract,
|
|
||||||
findEvmChain,
|
|
||||||
} from "./common";
|
|
||||||
|
|
||||||
const parser = yargs(hideBin(process.argv))
|
|
||||||
.usage(
|
|
||||||
"Requests and reveals a random number from an entropy contract while measuing the\n" +
|
|
||||||
"latency between request submission and availablity of the provider revelation from fortuna.\n" +
|
|
||||||
"Usage: $0 --chain <chain-id> --private-key <private-key>"
|
|
||||||
)
|
|
||||||
.options({
|
|
||||||
chain: {
|
|
||||||
type: "string",
|
|
||||||
demandOption: true,
|
|
||||||
desc: "test latency for the contract on this chain",
|
|
||||||
},
|
|
||||||
"private-key": COMMON_DEPLOY_OPTIONS["private-key"],
|
|
||||||
});
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const argv = await parser.argv;
|
|
||||||
const chain = findEvmChain(argv.chain);
|
|
||||||
const contract = findEntropyContract(chain);
|
|
||||||
|
|
||||||
const provider = await contract.getDefaultProvider();
|
|
||||||
const providerInfo = await contract.getProviderInfo(provider);
|
|
||||||
const userRandomNumber = contract.generateUserRandomNumber();
|
|
||||||
const privateKey = toPrivateKey(argv.privateKey);
|
|
||||||
const requestResponse = await contract.requestRandomness(
|
|
||||||
userRandomNumber,
|
|
||||||
provider,
|
|
||||||
privateKey
|
|
||||||
);
|
|
||||||
console.log("Request tx hash: ", requestResponse.transactionHash);
|
|
||||||
const startTime = Date.now();
|
|
||||||
const sequenceNumber = providerInfo.sequenceNumber;
|
|
||||||
const revealUrl = providerInfo.uri + `/revelations/${sequenceNumber}`;
|
|
||||||
console.log("Checking this url for revelation:", revealUrl);
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
while (true) {
|
|
||||||
const fortunaResponse = await fetch(revealUrl);
|
|
||||||
if (fortunaResponse.status === 200) {
|
|
||||||
const payload = await fortunaResponse.json();
|
|
||||||
const endTime = Date.now();
|
|
||||||
console.log(`Fortuna Latency: ${endTime - startTime}ms`);
|
|
||||||
const providerRevelation = "0x" + payload.value.data;
|
|
||||||
const revealResponse = await contract.revealRandomness(
|
|
||||||
userRandomNumber,
|
|
||||||
providerRevelation,
|
|
||||||
provider,
|
|
||||||
sequenceNumber,
|
|
||||||
privateKey
|
|
||||||
);
|
|
||||||
console.log("Reveal tx hash: ", revealResponse.transactionHash);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
|
@ -1,118 +0,0 @@
|
||||||
import yargs from "yargs";
|
|
||||||
import { hideBin } from "yargs/helpers";
|
|
||||||
import {
|
|
||||||
DefaultStore,
|
|
||||||
EvmEntropyContract,
|
|
||||||
PrivateKey,
|
|
||||||
toPrivateKey,
|
|
||||||
} from "../src";
|
|
||||||
import {
|
|
||||||
COMMON_DEPLOY_OPTIONS,
|
|
||||||
findEntropyContract,
|
|
||||||
findEvmChain,
|
|
||||||
} from "./common";
|
|
||||||
import Web3 from "web3";
|
|
||||||
|
|
||||||
const parser = yargs(hideBin(process.argv))
|
|
||||||
.usage(
|
|
||||||
"Requests a random number from an entropy contract and measures the\n" +
|
|
||||||
"latency between request submission and fulfillment by the Fortuna keeper service.\n" +
|
|
||||||
"Usage: $0 --private-key <private-key> --chain <chain-id> | --all-chains <testnet|mainnet>"
|
|
||||||
)
|
|
||||||
.options({
|
|
||||||
chain: {
|
|
||||||
type: "string",
|
|
||||||
desc: "test latency for the contract on this chain",
|
|
||||||
conflicts: "all-chains",
|
|
||||||
},
|
|
||||||
"all-chains": {
|
|
||||||
type: "string",
|
|
||||||
conflicts: "chain",
|
|
||||||
choices: ["testnet", "mainnet"],
|
|
||||||
desc: "test latency for all entropy contracts deployed either on mainnet or testnet",
|
|
||||||
},
|
|
||||||
"private-key": COMMON_DEPLOY_OPTIONS["private-key"],
|
|
||||||
});
|
|
||||||
|
|
||||||
async function testLatency(
|
|
||||||
contract: EvmEntropyContract,
|
|
||||||
privateKey: PrivateKey
|
|
||||||
) {
|
|
||||||
const provider = await contract.getDefaultProvider();
|
|
||||||
const userRandomNumber = contract.generateUserRandomNumber();
|
|
||||||
const requestResponse = await contract.requestRandomness(
|
|
||||||
userRandomNumber,
|
|
||||||
provider,
|
|
||||||
privateKey,
|
|
||||||
true // with callback
|
|
||||||
);
|
|
||||||
console.log(`Request tx hash : ${requestResponse.transactionHash}`);
|
|
||||||
// Read the sequence number for the request from the transaction events.
|
|
||||||
const sequenceNumber =
|
|
||||||
requestResponse.events.RequestedWithCallback.returnValues.sequenceNumber;
|
|
||||||
console.log(`sequence : ${sequenceNumber}`);
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
let fromBlock = requestResponse.blockNumber;
|
|
||||||
const web3 = new Web3(contract.chain.getRpcUrl());
|
|
||||||
const entropyContract = contract.getContract();
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
while (true) {
|
|
||||||
const currentBlock = await web3.eth.getBlockNumber();
|
|
||||||
|
|
||||||
if (fromBlock > currentBlock) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const events = await entropyContract.getPastEvents("RevealedWithCallback", {
|
|
||||||
fromBlock: fromBlock,
|
|
||||||
toBlock: currentBlock,
|
|
||||||
});
|
|
||||||
fromBlock = currentBlock + 1;
|
|
||||||
|
|
||||||
const event = events.find(
|
|
||||||
(event) => event.returnValues.request[1] == sequenceNumber
|
|
||||||
);
|
|
||||||
|
|
||||||
if (event !== undefined) {
|
|
||||||
console.log(`Random number : ${event.returnValues.randomNumber}`);
|
|
||||||
const endTime = Date.now();
|
|
||||||
console.log(`Fortuna Latency : ${endTime - startTime}ms`);
|
|
||||||
console.log(
|
|
||||||
`Revealed after : ${
|
|
||||||
currentBlock - requestResponse.blockNumber
|
|
||||||
} blocks`
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const argv = await parser.argv;
|
|
||||||
if (!argv.chain && !argv["all-chains"]) {
|
|
||||||
throw new Error("Must specify either --chain or --all-chains");
|
|
||||||
}
|
|
||||||
const privateKey = toPrivateKey(argv.privateKey);
|
|
||||||
if (argv["all-chains"]) {
|
|
||||||
for (const contract of Object.values(DefaultStore.entropy_contracts)) {
|
|
||||||
if (
|
|
||||||
contract.getChain().isMainnet() ===
|
|
||||||
(argv["all-chains"] === "mainnet")
|
|
||||||
) {
|
|
||||||
console.log(`Testing latency for ${contract.getId()}...`);
|
|
||||||
await testLatency(contract, privateKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (argv.chain) {
|
|
||||||
const chain = findEvmChain(argv.chain);
|
|
||||||
const contract = findEntropyContract(chain);
|
|
||||||
await testLatency(contract, privateKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
|
@ -13,22 +13,15 @@ const parser = yargs(hideBin(process.argv))
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const KEEPER_ADDRESS = {
|
|
||||||
mainnet: "0xBcAb779fCa45290288C35F5E231c37F9fA87b130",
|
|
||||||
testnet: "0xa5A68ed167431Afe739846A22597786ba2da85df",
|
|
||||||
};
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const argv = await parser.argv;
|
const argv = await parser.argv;
|
||||||
const entries = [];
|
const entries = [];
|
||||||
const keeperAddress = KEEPER_ADDRESS[argv.testnet ? "testnet" : "mainnet"];
|
|
||||||
for (const contract of Object.values(DefaultStore.entropy_contracts)) {
|
for (const contract of Object.values(DefaultStore.entropy_contracts)) {
|
||||||
if (contract.getChain().isMainnet() === argv.testnet) continue;
|
if (contract.getChain().isMainnet() === argv.testnet) continue;
|
||||||
try {
|
try {
|
||||||
const provider = await contract.getDefaultProvider();
|
const provider = await contract.getDefaultProvider();
|
||||||
const w3 = new Web3(contract.getChain().getRpcUrl());
|
const w3 = new Web3(contract.getChain().getRpcUrl());
|
||||||
const balance = await w3.eth.getBalance(provider);
|
const balance = await w3.eth.getBalance(provider);
|
||||||
const keeperBalance = await w3.eth.getBalance(keeperAddress);
|
|
||||||
let version = "unknown";
|
let version = "unknown";
|
||||||
try {
|
try {
|
||||||
version = await contract.getVersion();
|
version = await contract.getVersion();
|
||||||
|
@ -41,7 +34,6 @@ async function main() {
|
||||||
contract: contract.address,
|
contract: contract.address,
|
||||||
provider: providerInfo.uri,
|
provider: providerInfo.uri,
|
||||||
balance,
|
balance,
|
||||||
keeperBalance,
|
|
||||||
seq: providerInfo.sequenceNumber,
|
seq: providerInfo.sequenceNumber,
|
||||||
version,
|
version,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
import yargs from "yargs";
|
|
||||||
import { hideBin } from "yargs/helpers";
|
|
||||||
import {
|
|
||||||
CosmWasmPriceFeedContract,
|
|
||||||
DefaultStore,
|
|
||||||
EvmPriceFeedContract,
|
|
||||||
toPrivateKey,
|
|
||||||
} from "../src";
|
|
||||||
|
|
||||||
const parser = yargs(hideBin(process.argv))
|
|
||||||
.usage("Update the guardian set in stable networks. Usage: $0")
|
|
||||||
.options({
|
|
||||||
"private-key": {
|
|
||||||
type: "string",
|
|
||||||
demandOption: true,
|
|
||||||
desc: "Private key to sign the transactions with",
|
|
||||||
},
|
|
||||||
chain: {
|
|
||||||
type: "array",
|
|
||||||
desc: "Can be one of the chains available in the store",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const argv = await parser.argv;
|
|
||||||
|
|
||||||
const privateKey = toPrivateKey(argv.privateKey);
|
|
||||||
const chains = argv.chain;
|
|
||||||
|
|
||||||
for (const contract of Object.values(DefaultStore.contracts)) {
|
|
||||||
// We are currently only managing wormhole receiver contracts in EVM and
|
|
||||||
// CosmWasm and Solana-based networks. The rest of the networks are
|
|
||||||
// managed by the guardians themselves and they should be the ones updating
|
|
||||||
// the guardian set.
|
|
||||||
// TODO: Solana-based receivers have their script in their rust cli. Add
|
|
||||||
// support for Solana-based networks here once they are added to the
|
|
||||||
// contract manager.
|
|
||||||
if (
|
|
||||||
contract instanceof CosmWasmPriceFeedContract ||
|
|
||||||
contract instanceof EvmPriceFeedContract
|
|
||||||
) {
|
|
||||||
if (chains && !chains.includes(contract.getChain().getId())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log("------------------------------------");
|
|
||||||
const wormhole = await contract.getWormholeContract();
|
|
||||||
|
|
||||||
// TODO: This is a temporary workaround to skip contracts that are in beta channel
|
|
||||||
// We should have a better way to handle this
|
|
||||||
if ((await wormhole.getCurrentGuardianSetIndex()) === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Current Guardianset for ${contract.getId()}: ${await wormhole.getCurrentGuardianSetIndex()}`
|
|
||||||
);
|
|
||||||
|
|
||||||
await wormhole.syncMainnetGuardianSets(privateKey);
|
|
||||||
console.log(`Updated Guardianset for ${contract.getId()}`);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Error updating Guardianset for ${contract.getId()}`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
|
@ -9,33 +9,19 @@ import {
|
||||||
makeCacheFunction,
|
makeCacheFunction,
|
||||||
} from "./common";
|
} from "./common";
|
||||||
|
|
||||||
const EXECUTOR_CACHE_FILE = ".cache-upgrade-evm-executor-contract";
|
const CACHE_FILE = ".cache-upgrade-evm-executor-contract";
|
||||||
const ENTROPY_CACHE_FILE = ".cache-upgrade-evm-entropy-contract";
|
const runIfNotCached = makeCacheFunction(CACHE_FILE);
|
||||||
|
|
||||||
const parser = yargs(hideBin(process.argv))
|
const parser = yargs(hideBin(process.argv))
|
||||||
.usage(
|
.usage(
|
||||||
"Deploys a new Upgradeable contract for Executor or Entropy to a set of chains where Entropy is deployed and creates a governance proposal for it.\n" +
|
"Deploys a new ExecutorUpgradeable contract to a set of chains where Entropy is deployed and creates a governance proposal for it.\n" +
|
||||||
`Uses a cache file to avoid deploying contracts twice\n` +
|
`Uses a cache file (${CACHE_FILE}) to avoid deploying contracts twice\n` +
|
||||||
"Usage: $0 --chain <chain_1> --chain <chain_2> --private-key <private_key> --ops-key-path <ops_key_path> --std-output <std_output>"
|
"Usage: $0 --chain <chain_1> --chain <chain_2> --private-key <private_key> --ops-key-path <ops_key_path> --std-output <std_output>"
|
||||||
)
|
)
|
||||||
.options({
|
.options(COMMON_UPGRADE_OPTIONS);
|
||||||
...COMMON_UPGRADE_OPTIONS,
|
|
||||||
"contract-type": {
|
|
||||||
type: "string",
|
|
||||||
choices: ["executor", "entropy"],
|
|
||||||
demandOption: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const argv = await parser.argv;
|
const argv = await parser.argv;
|
||||||
const cacheFile =
|
|
||||||
argv["contract-type"] === "executor"
|
|
||||||
? EXECUTOR_CACHE_FILE
|
|
||||||
: ENTROPY_CACHE_FILE;
|
|
||||||
|
|
||||||
const runIfNotCached = makeCacheFunction(cacheFile);
|
|
||||||
|
|
||||||
const selectedChains = getSelectedChains(argv);
|
const selectedChains = getSelectedChains(argv);
|
||||||
|
|
||||||
const vault =
|
const vault =
|
||||||
|
@ -43,7 +29,7 @@ async function main() {
|
||||||
"mainnet-beta_FVQyHcooAtThJ83XFrNnv74BcinbRH3bRmfFamAHBfuj"
|
"mainnet-beta_FVQyHcooAtThJ83XFrNnv74BcinbRH3bRmfFamAHBfuj"
|
||||||
];
|
];
|
||||||
|
|
||||||
console.log("Using cache file", cacheFile);
|
console.log("Using cache file", CACHE_FILE);
|
||||||
|
|
||||||
const payloads: Buffer[] = [];
|
const payloads: Buffer[] = [];
|
||||||
for (const contract of Object.values(DefaultStore.entropy_contracts)) {
|
for (const contract of Object.values(DefaultStore.entropy_contracts)) {
|
||||||
|
@ -65,11 +51,9 @@ async function main() {
|
||||||
console.log(
|
console.log(
|
||||||
`Deployed contract at ${address} on ${contract.chain.getId()}`
|
`Deployed contract at ${address} on ${contract.chain.getId()}`
|
||||||
);
|
);
|
||||||
const payload =
|
const payload = await contract.generateUpgradeExecutorContractsPayload(
|
||||||
argv["contract-type"] === "executor"
|
address
|
||||||
? await contract.generateUpgradeExecutorContractsPayload(address)
|
);
|
||||||
: await contract.generateUpgradeEntropyContractPayload(address);
|
|
||||||
|
|
||||||
console.log(payload.toString("hex"));
|
console.log(payload.toString("hex"));
|
||||||
payloads.push(payload);
|
payloads.push(payload);
|
||||||
}
|
}
|
|
@ -22,13 +22,10 @@ import {
|
||||||
import { Network } from "@injectivelabs/networks";
|
import { Network } from "@injectivelabs/networks";
|
||||||
import { SuiClient } from "@mysten/sui.js/client";
|
import { SuiClient } from "@mysten/sui.js/client";
|
||||||
import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
|
import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
|
||||||
import { TransactionObject } from "web3/eth/types";
|
|
||||||
import { TokenId } from "./token";
|
|
||||||
|
|
||||||
export type ChainConfig = Record<string, string> & {
|
export type ChainConfig = Record<string, string> & {
|
||||||
mainnet: boolean;
|
mainnet: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
nativeToken: TokenId;
|
|
||||||
};
|
};
|
||||||
export abstract class Chain extends Storable {
|
export abstract class Chain extends Storable {
|
||||||
public wormholeChainName: ChainName;
|
public wormholeChainName: ChainName;
|
||||||
|
@ -39,14 +36,12 @@ export abstract class Chain extends Storable {
|
||||||
* @param mainnet whether this chain is mainnet or testnet/devnet
|
* @param mainnet whether this chain is mainnet or testnet/devnet
|
||||||
* @param wormholeChainName the name of the wormhole chain that this chain is associated with.
|
* @param wormholeChainName the name of the wormhole chain that this chain is associated with.
|
||||||
* Note that pyth has included additional chain names and ids to the wormhole spec.
|
* Note that pyth has included additional chain names and ids to the wormhole spec.
|
||||||
* @param nativeToken the id of the token used to pay gas on this chain
|
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
protected constructor(
|
protected constructor(
|
||||||
protected id: string,
|
protected id: string,
|
||||||
protected mainnet: boolean,
|
protected mainnet: boolean,
|
||||||
wormholeChainName: string,
|
wormholeChainName: string
|
||||||
protected nativeToken: TokenId | undefined
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.wormholeChainName = wormholeChainName as ChainName;
|
this.wormholeChainName = wormholeChainName as ChainName;
|
||||||
|
@ -69,10 +64,6 @@ export abstract class Chain extends Storable {
|
||||||
return this.mainnet;
|
return this.mainnet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNativeToken(): TokenId | undefined {
|
|
||||||
return this.nativeToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the payload for a governance SetFee instruction for contracts deployed on this chain
|
* Returns the payload for a governance SetFee instruction for contracts deployed on this chain
|
||||||
* @param fee the new fee to set
|
* @param fee the new fee to set
|
||||||
|
@ -133,7 +124,7 @@ export abstract class Chain extends Storable {
|
||||||
export class GlobalChain extends Chain {
|
export class GlobalChain extends Chain {
|
||||||
static type = "GlobalChain";
|
static type = "GlobalChain";
|
||||||
constructor() {
|
constructor() {
|
||||||
super("global", true, "unset", undefined);
|
super("global", true, "unset");
|
||||||
}
|
}
|
||||||
|
|
||||||
generateGovernanceUpgradePayload(): Buffer {
|
generateGovernanceUpgradePayload(): Buffer {
|
||||||
|
@ -171,13 +162,12 @@ export class CosmWasmChain extends Chain {
|
||||||
id: string,
|
id: string,
|
||||||
mainnet: boolean,
|
mainnet: boolean,
|
||||||
wormholeChainName: string,
|
wormholeChainName: string,
|
||||||
nativeToken: TokenId | undefined,
|
|
||||||
public endpoint: string,
|
public endpoint: string,
|
||||||
public gasPrice: string,
|
public gasPrice: string,
|
||||||
public prefix: string,
|
public prefix: string,
|
||||||
public feeDenom: string
|
public feeDenom: string
|
||||||
) {
|
) {
|
||||||
super(id, mainnet, wormholeChainName, nativeToken);
|
super(id, mainnet, wormholeChainName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(parsed: ChainConfig): CosmWasmChain {
|
static fromJson(parsed: ChainConfig): CosmWasmChain {
|
||||||
|
@ -186,7 +176,6 @@ export class CosmWasmChain extends Chain {
|
||||||
parsed.id,
|
parsed.id,
|
||||||
parsed.mainnet,
|
parsed.mainnet,
|
||||||
parsed.wormholeChainName,
|
parsed.wormholeChainName,
|
||||||
parsed.nativeToken,
|
|
||||||
parsed.endpoint,
|
parsed.endpoint,
|
||||||
parsed.gasPrice,
|
parsed.gasPrice,
|
||||||
parsed.prefix,
|
parsed.prefix,
|
||||||
|
@ -258,10 +247,9 @@ export class SuiChain extends Chain {
|
||||||
id: string,
|
id: string,
|
||||||
mainnet: boolean,
|
mainnet: boolean,
|
||||||
wormholeChainName: string,
|
wormholeChainName: string,
|
||||||
nativeToken: TokenId | undefined,
|
|
||||||
public rpcUrl: string
|
public rpcUrl: string
|
||||||
) {
|
) {
|
||||||
super(id, mainnet, wormholeChainName, nativeToken);
|
super(id, mainnet, wormholeChainName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(parsed: ChainConfig): SuiChain {
|
static fromJson(parsed: ChainConfig): SuiChain {
|
||||||
|
@ -270,7 +258,6 @@ export class SuiChain extends Chain {
|
||||||
parsed.id,
|
parsed.id,
|
||||||
parsed.mainnet,
|
parsed.mainnet,
|
||||||
parsed.wormholeChainName,
|
parsed.wormholeChainName,
|
||||||
parsed.nativeToken,
|
|
||||||
parsed.rpcUrl
|
parsed.rpcUrl
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -326,12 +313,11 @@ export class EvmChain extends Chain {
|
||||||
constructor(
|
constructor(
|
||||||
id: string,
|
id: string,
|
||||||
mainnet: boolean,
|
mainnet: boolean,
|
||||||
nativeToken: TokenId | undefined,
|
|
||||||
private rpcUrl: string,
|
private rpcUrl: string,
|
||||||
private networkId: number
|
private networkId: number
|
||||||
) {
|
) {
|
||||||
// On EVM networks we use the chain id as the wormhole chain name
|
// On EVM networks we use the chain id as the wormhole chain name
|
||||||
super(id, mainnet, id, nativeToken);
|
super(id, mainnet, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(parsed: ChainConfig & { networkId: number }): EvmChain {
|
static fromJson(parsed: ChainConfig & { networkId: number }): EvmChain {
|
||||||
|
@ -339,7 +325,6 @@ export class EvmChain extends Chain {
|
||||||
return new EvmChain(
|
return new EvmChain(
|
||||||
parsed.id,
|
parsed.id,
|
||||||
parsed.mainnet,
|
parsed.mainnet,
|
||||||
parsed.nativeToken,
|
|
||||||
parsed.rpcUrl,
|
parsed.rpcUrl,
|
||||||
parsed.networkId
|
parsed.networkId
|
||||||
);
|
);
|
||||||
|
@ -401,21 +386,6 @@ export class EvmChain extends Chain {
|
||||||
return gasPrice;
|
return gasPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
async estiamteAndSendTransaction(
|
|
||||||
transactionObject: TransactionObject<any>,
|
|
||||||
txParams: { from?: string; value?: string }
|
|
||||||
) {
|
|
||||||
const GAS_ESTIMATE_MULTIPLIER = 2;
|
|
||||||
const gasEstimate = await transactionObject.estimateGas(txParams);
|
|
||||||
// Some networks like Filecoin do not support the normal transaction type and need a type 2 transaction.
|
|
||||||
// To send a type 2 transaction, remove the ``gasPrice`` field.
|
|
||||||
return transactionObject.send({
|
|
||||||
gas: gasEstimate * GAS_ESTIMATE_MULTIPLIER,
|
|
||||||
gasPrice: Number(await this.getGasPrice()),
|
|
||||||
...txParams,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deploys a contract on this chain
|
* Deploys a contract on this chain
|
||||||
* @param privateKey hex string of the 32 byte private key without the 0x prefix
|
* @param privateKey hex string of the 32 byte private key without the 0x prefix
|
||||||
|
@ -481,10 +451,9 @@ export class AptosChain extends Chain {
|
||||||
id: string,
|
id: string,
|
||||||
mainnet: boolean,
|
mainnet: boolean,
|
||||||
wormholeChainName: string,
|
wormholeChainName: string,
|
||||||
nativeToken: TokenId | undefined,
|
|
||||||
public rpcUrl: string
|
public rpcUrl: string
|
||||||
) {
|
) {
|
||||||
super(id, mainnet, wormholeChainName, nativeToken);
|
super(id, mainnet, wormholeChainName);
|
||||||
}
|
}
|
||||||
|
|
||||||
getClient(): AptosClient {
|
getClient(): AptosClient {
|
||||||
|
@ -522,7 +491,6 @@ export class AptosChain extends Chain {
|
||||||
parsed.id,
|
parsed.id,
|
||||||
parsed.mainnet,
|
parsed.mainnet,
|
||||||
parsed.wormholeChainName,
|
parsed.wormholeChainName,
|
||||||
parsed.nativeToken,
|
|
||||||
parsed.rpcUrl
|
parsed.rpcUrl
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { ApiError, BCS, CoinClient, TxnBuilderTypes } from "aptos";
|
||||||
import { AptosChain, Chain } from "../chains";
|
import { AptosChain, Chain } from "../chains";
|
||||||
import { DataSource } from "xc_admin_common";
|
import { DataSource } from "xc_admin_common";
|
||||||
import { WormholeContract } from "./wormhole";
|
import { WormholeContract } from "./wormhole";
|
||||||
import { TokenQty } from "../token";
|
|
||||||
|
|
||||||
type WormholeState = {
|
type WormholeState = {
|
||||||
chain_id: { number: string };
|
chain_id: { number: string };
|
||||||
|
@ -17,39 +16,7 @@ type GuardianSet = {
|
||||||
index: { number: string };
|
index: { number: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export class AptosWormholeContract extends WormholeContract {
|
export class WormholeAptosContract extends WormholeContract {
|
||||||
static type = "AptosWormholeContract";
|
|
||||||
|
|
||||||
getId(): string {
|
|
||||||
return `${this.chain.getId()}_${this.address}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
getType(): string {
|
|
||||||
return AptosWormholeContract.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJson() {
|
|
||||||
return {
|
|
||||||
chain: this.chain.getId(),
|
|
||||||
address: this.address,
|
|
||||||
type: AptosWormholeContract.type,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(
|
|
||||||
chain: Chain,
|
|
||||||
parsed: {
|
|
||||||
type: string;
|
|
||||||
address: string;
|
|
||||||
}
|
|
||||||
): AptosWormholeContract {
|
|
||||||
if (parsed.type !== AptosWormholeContract.type)
|
|
||||||
throw new Error("Invalid type");
|
|
||||||
if (!(chain instanceof AptosChain))
|
|
||||||
throw new Error(`Wrong chain type ${chain}`);
|
|
||||||
return new AptosWormholeContract(chain, parsed.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(public chain: AptosChain, public address: string) {
|
constructor(public chain: AptosChain, public address: string) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -124,11 +91,7 @@ export class AptosPriceFeedContract extends PriceFeedContract {
|
||||||
|
|
||||||
static fromJson(
|
static fromJson(
|
||||||
chain: Chain,
|
chain: Chain,
|
||||||
parsed: {
|
parsed: { type: string; stateId: string; wormholeStateId: string }
|
||||||
type: string;
|
|
||||||
stateId: string;
|
|
||||||
wormholeStateId: string;
|
|
||||||
}
|
|
||||||
): AptosPriceFeedContract {
|
): AptosPriceFeedContract {
|
||||||
if (parsed.type !== AptosPriceFeedContract.type)
|
if (parsed.type !== AptosPriceFeedContract.type)
|
||||||
throw new Error("Invalid type");
|
throw new Error("Invalid type");
|
||||||
|
@ -156,8 +119,8 @@ export class AptosPriceFeedContract extends PriceFeedContract {
|
||||||
return this.chain.sendTransaction(senderPrivateKey, txPayload);
|
return this.chain.sendTransaction(senderPrivateKey, txPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getWormholeContract(): AptosWormholeContract {
|
public getWormholeContract(): WormholeAptosContract {
|
||||||
return new AptosWormholeContract(this.chain, this.wormholeStateId);
|
return new WormholeAptosContract(this.chain, this.wormholeStateId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeUpdatePriceFeed(
|
async executeUpdatePriceFeed(
|
||||||
|
@ -297,13 +260,9 @@ export class AptosPriceFeedContract extends PriceFeedContract {
|
||||||
return AptosPriceFeedContract.type;
|
return AptosPriceFeedContract.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTotalFee(): Promise<TokenQty> {
|
async getTotalFee(): Promise<bigint> {
|
||||||
const client = new CoinClient(this.chain.getClient());
|
const client = new CoinClient(this.chain.getClient());
|
||||||
const amount = await client.checkBalance(this.stateId);
|
return await client.checkBalance(this.stateId);
|
||||||
return {
|
|
||||||
amount,
|
|
||||||
denom: this.chain.getNativeToken(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getValidTimePeriod() {
|
async getValidTimePeriod() {
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
TxResult,
|
TxResult,
|
||||||
} from "../base";
|
} from "../base";
|
||||||
import { WormholeContract } from "./wormhole";
|
import { WormholeContract } from "./wormhole";
|
||||||
import { TokenQty } from "../token";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variables here need to be snake case to match the on-chain contract configs
|
* Variables here need to be snake case to match the on-chain contract configs
|
||||||
|
@ -38,36 +37,7 @@ export interface DeploymentConfig {
|
||||||
fee: { amount: string; denom: string };
|
fee: { amount: string; denom: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CosmWasmWormholeContract extends WormholeContract {
|
export class WormholeCosmWasmContract extends WormholeContract {
|
||||||
static type = "CosmWasmWormholeContract";
|
|
||||||
|
|
||||||
getId(): string {
|
|
||||||
return `${this.chain.getId()}_${this.address}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
getType(): string {
|
|
||||||
return CosmWasmWormholeContract.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJson() {
|
|
||||||
return {
|
|
||||||
chain: this.chain.getId(),
|
|
||||||
address: this.address,
|
|
||||||
type: CosmWasmWormholeContract.type,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(
|
|
||||||
chain: Chain,
|
|
||||||
parsed: { type: string; address: string }
|
|
||||||
): CosmWasmWormholeContract {
|
|
||||||
if (parsed.type !== CosmWasmWormholeContract.type)
|
|
||||||
throw new Error("Invalid type");
|
|
||||||
if (!(chain instanceof CosmWasmChain))
|
|
||||||
throw new Error(`Wrong chain type ${chain}`);
|
|
||||||
return new CosmWasmWormholeContract(chain, parsed.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(public chain: CosmWasmChain, public address: string) {
|
constructor(public chain: CosmWasmChain, public address: string) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -240,9 +210,7 @@ export class CosmWasmPriceFeedContract extends PriceFeedContract {
|
||||||
})) as Record<string, string>;
|
})) as Record<string, string>;
|
||||||
const config = {
|
const config = {
|
||||||
config_v1: JSON.parse(allStates["\x00\tconfig_v1"]),
|
config_v1: JSON.parse(allStates["\x00\tconfig_v1"]),
|
||||||
contract_version: allStates["\x00\x10contract_version"]
|
contract_version: JSON.parse(allStates["\x00\x10contract_version"]),
|
||||||
? JSON.parse(allStates["\x00\x10contract_version"])
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
@ -339,10 +307,10 @@ export class CosmWasmPriceFeedContract extends PriceFeedContract {
|
||||||
return { id: result.txHash, info: result };
|
return { id: result.txHash, info: result };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWormholeContract(): Promise<CosmWasmWormholeContract> {
|
async getWormholeContract(): Promise<WormholeCosmWasmContract> {
|
||||||
const config = await this.getConfig();
|
const config = await this.getConfig();
|
||||||
const wormholeAddress = config.config_v1.wormhole_contract;
|
const wormholeAddress = config.config_v1.wormhole_contract;
|
||||||
return new CosmWasmWormholeContract(this.chain, wormholeAddress);
|
return new WormholeCosmWasmContract(this.chain, wormholeAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUpdateFee(msgs: string[]): Promise<Coin> {
|
async getUpdateFee(msgs: string[]): Promise<Coin> {
|
||||||
|
@ -364,16 +332,13 @@ export class CosmWasmPriceFeedContract extends PriceFeedContract {
|
||||||
return this.chain;
|
return this.chain;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTotalFee(): Promise<TokenQty> {
|
async getTotalFee(): Promise<bigint> {
|
||||||
const client = await CosmWasmClient.connect(this.chain.endpoint);
|
const client = await CosmWasmClient.connect(this.chain.endpoint);
|
||||||
const coin = await client.getBalance(
|
const coin = await client.getBalance(
|
||||||
this.address,
|
this.address,
|
||||||
this.getChain().feeDenom
|
this.getChain().feeDenom
|
||||||
);
|
);
|
||||||
return {
|
return BigInt(coin.amount);
|
||||||
amount: BigInt(coin.amount),
|
|
||||||
denom: this.chain.getNativeToken(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getValidTimePeriod() {
|
async getValidTimePeriod() {
|
||||||
|
|
|
@ -5,9 +5,9 @@ import { PriceFeedContract, PrivateKey, Storable } from "../base";
|
||||||
import { Chain, EvmChain } from "../chains";
|
import { Chain, EvmChain } from "../chains";
|
||||||
import { DataSource, EvmExecute } from "xc_admin_common";
|
import { DataSource, EvmExecute } from "xc_admin_common";
|
||||||
import { WormholeContract } from "./wormhole";
|
import { WormholeContract } from "./wormhole";
|
||||||
import { TokenQty } from "../token";
|
|
||||||
|
|
||||||
// Just to make sure tx gas limit is enough
|
// Just to make sure tx gas limit is enough
|
||||||
|
const GAS_ESTIMATE_MULTIPLIER = 2;
|
||||||
const EXTENDED_ENTROPY_ABI = [
|
const EXTENDED_ENTROPY_ABI = [
|
||||||
{
|
{
|
||||||
inputs: [],
|
inputs: [],
|
||||||
|
@ -62,19 +62,6 @@ const EXTENDED_ENTROPY_ABI = [
|
||||||
stateMutability: "pure",
|
stateMutability: "pure",
|
||||||
type: "function",
|
type: "function",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
internalType: "address",
|
|
||||||
name: "newImplementation",
|
|
||||||
type: "address",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
name: "upgradeTo",
|
|
||||||
outputs: [],
|
|
||||||
stateMutability: "nonpayable",
|
|
||||||
type: "function",
|
|
||||||
},
|
|
||||||
...EntropyAbi,
|
...EntropyAbi,
|
||||||
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
const EXTENDED_PYTH_ABI = [
|
const EXTENDED_PYTH_ABI = [
|
||||||
|
@ -367,60 +354,7 @@ const EXECUTOR_ABI = [
|
||||||
type: "function",
|
type: "function",
|
||||||
},
|
},
|
||||||
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
] as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
export class WormholeEvmContract extends WormholeContract {
|
||||||
/**
|
|
||||||
* Returns the keccak256 digest of the contract bytecode at the given address after replacing
|
|
||||||
* any occurrences of the contract addr in the bytecode with 0.The bytecode stores the deployment
|
|
||||||
* address as an immutable variable. This behavior is inherited from OpenZeppelin's implementation
|
|
||||||
* of UUPSUpgradeable contract. You can read more about verification with immutable variables here:
|
|
||||||
* https://docs.sourcify.dev/docs/immutables/
|
|
||||||
* This function can be used to verify that the contract code is the same on all chains and matches
|
|
||||||
* with the deployedCode property generated by truffle builds
|
|
||||||
*/
|
|
||||||
export async function getCodeDigestWithoutAddress(
|
|
||||||
rpcUrl: string,
|
|
||||||
address: string
|
|
||||||
): Promise<string> {
|
|
||||||
const web3 = new Web3(rpcUrl);
|
|
||||||
const code = await web3.eth.getCode(address);
|
|
||||||
const strippedCode = code.replaceAll(
|
|
||||||
address.toLowerCase().replace("0x", ""),
|
|
||||||
"0000000000000000000000000000000000000000"
|
|
||||||
);
|
|
||||||
return Web3.utils.keccak256(strippedCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EvmWormholeContract extends WormholeContract {
|
|
||||||
static type = "EvmWormholeContract";
|
|
||||||
|
|
||||||
getId(): string {
|
|
||||||
return `${this.chain.getId()}_${this.address}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
getChain(): EvmChain {
|
|
||||||
return this.chain;
|
|
||||||
}
|
|
||||||
|
|
||||||
getType(): string {
|
|
||||||
return EvmWormholeContract.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getVersion(): Promise<string> {
|
|
||||||
const contract = this.getContract();
|
|
||||||
return contract.methods.version().call();
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(
|
|
||||||
chain: Chain,
|
|
||||||
parsed: { type: string; address: string }
|
|
||||||
): EvmWormholeContract {
|
|
||||||
if (parsed.type !== EvmWormholeContract.type)
|
|
||||||
throw new Error("Invalid type");
|
|
||||||
if (!(chain instanceof EvmChain))
|
|
||||||
throw new Error(`Wrong chain type ${chain}`);
|
|
||||||
return new EvmWormholeContract(chain, parsed.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(public chain: EvmChain, public address: string) {
|
constructor(public chain: EvmChain, public address: string) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -460,20 +394,20 @@ export class EvmWormholeContract extends WormholeContract {
|
||||||
const transactionObject = wormholeContract.methods.submitNewGuardianSet(
|
const transactionObject = wormholeContract.methods.submitNewGuardianSet(
|
||||||
"0x" + vaa.toString("hex")
|
"0x" + vaa.toString("hex")
|
||||||
);
|
);
|
||||||
const result = await this.chain.estiamteAndSendTransaction(
|
const gasEstiamte = await transactionObject.estimateGas({
|
||||||
transactionObject,
|
from: address,
|
||||||
{ from: address }
|
gas: 15000000,
|
||||||
);
|
});
|
||||||
|
// Some networks like Filecoin do not support the normal transaction type and need a type 2 transaction.
|
||||||
|
// To send a type 2 transaction, remove the ``gasPrice`` field and add the `type` field with the value
|
||||||
|
// `0x2` to the transaction configuration parameters.
|
||||||
|
const result = await transactionObject.send({
|
||||||
|
from: address,
|
||||||
|
gas: gasEstiamte * GAS_ESTIMATE_MULTIPLIER,
|
||||||
|
gasPrice: await this.chain.getGasPrice(),
|
||||||
|
});
|
||||||
return { id: result.transactionHash, info: result };
|
return { id: result.transactionHash, info: result };
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
|
||||||
return {
|
|
||||||
chain: this.chain.getId(),
|
|
||||||
address: this.address,
|
|
||||||
type: EvmWormholeContract.type,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EntropyProviderInfo {
|
interface EntropyProviderInfo {
|
||||||
|
@ -554,18 +488,6 @@ export class EvmEntropyContract extends Storable {
|
||||||
return this.generateExecutorPayload(newOwner, this.address, data);
|
return this.generateExecutorPayload(newOwner, this.address, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateUpgradeEntropyContractPayload(
|
|
||||||
newImplementation: string
|
|
||||||
): Promise<Buffer> {
|
|
||||||
const contract = this.getContract();
|
|
||||||
const data = contract.methods.upgradeTo(newImplementation).encodeABI();
|
|
||||||
return this.generateExecutorPayload(
|
|
||||||
await this.getOwner(),
|
|
||||||
this.address,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates a payload to upgrade the executor contract, the owner of entropy contracts
|
// Generates a payload to upgrade the executor contract, the owner of entropy contracts
|
||||||
async generateUpgradeExecutorContractsPayload(
|
async generateUpgradeExecutorContractsPayload(
|
||||||
newImplementation: string
|
newImplementation: string
|
||||||
|
@ -621,65 +543,6 @@ export class EvmEntropyContract extends Storable {
|
||||||
uri: Web3.utils.toAscii(info.uri),
|
uri: Web3.utils.toAscii(info.uri),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
generateUserRandomNumber() {
|
|
||||||
const web3 = new Web3(this.chain.getRpcUrl());
|
|
||||||
return web3.utils.randomHex(32);
|
|
||||||
}
|
|
||||||
|
|
||||||
async requestRandomness(
|
|
||||||
userRandomNumber: string,
|
|
||||||
provider: string,
|
|
||||||
senderPrivateKey: PrivateKey,
|
|
||||||
withCallback?: boolean
|
|
||||||
) {
|
|
||||||
const web3 = new Web3(this.chain.getRpcUrl());
|
|
||||||
const userCommitment = web3.utils.keccak256(userRandomNumber);
|
|
||||||
const contract = new web3.eth.Contract(EXTENDED_ENTROPY_ABI, this.address);
|
|
||||||
const fee = await contract.methods.getFee(provider).call();
|
|
||||||
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
|
||||||
|
|
||||||
let transactionObject;
|
|
||||||
if (withCallback) {
|
|
||||||
transactionObject = contract.methods.requestWithCallback(
|
|
||||||
provider,
|
|
||||||
userCommitment
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const useBlockHash = false;
|
|
||||||
transactionObject = contract.methods.request(
|
|
||||||
provider,
|
|
||||||
userCommitment,
|
|
||||||
useBlockHash
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.chain.estiamteAndSendTransaction(transactionObject, {
|
|
||||||
from: address,
|
|
||||||
value: fee,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async revealRandomness(
|
|
||||||
userRevelation: string,
|
|
||||||
providerRevelation: string,
|
|
||||||
provider: string,
|
|
||||||
sequenceNumber: string,
|
|
||||||
senderPrivateKey: PrivateKey
|
|
||||||
) {
|
|
||||||
const web3 = new Web3(this.chain.getRpcUrl());
|
|
||||||
const contract = new web3.eth.Contract(EXTENDED_ENTROPY_ABI, this.address);
|
|
||||||
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
|
|
||||||
const transactionObject = contract.methods.reveal(
|
|
||||||
provider,
|
|
||||||
sequenceNumber,
|
|
||||||
userRevelation,
|
|
||||||
providerRevelation
|
|
||||||
);
|
|
||||||
return this.chain.estiamteAndSendTransaction(transactionObject, {
|
|
||||||
from: address,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EvmExecutorContract {
|
export class EvmExecutorContract {
|
||||||
|
@ -689,13 +552,13 @@ export class EvmExecutorContract {
|
||||||
return `${this.chain.getId()}_${this.address}`;
|
return `${this.chain.getId()}_${this.address}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWormholeContract(): Promise<EvmWormholeContract> {
|
async getWormholeContract(): Promise<WormholeEvmContract> {
|
||||||
const web3 = new Web3(this.chain.getRpcUrl());
|
const web3 = new Web3(this.chain.getRpcUrl());
|
||||||
//Unfortunately, there is no public method to get the wormhole address
|
//Unfortunately, there is no public method to get the wormhole address
|
||||||
//Found 251 by using `forge build --extra-output storageLayout` and finding the slot for the wormhole variable.
|
//Found 251 by using `forge build --extra-output storageLayout` and finding the slot for the wormhole variable.
|
||||||
let address = await web3.eth.getStorageAt(this.address, 251);
|
let address = await web3.eth.getStorageAt(this.address, 251);
|
||||||
address = "0x" + address.slice(26);
|
address = "0x" + address.slice(26);
|
||||||
return new EvmWormholeContract(this.chain, address);
|
return new WormholeEvmContract(this.chain, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
getContract() {
|
getContract() {
|
||||||
|
@ -739,10 +602,15 @@ export class EvmExecutorContract {
|
||||||
const transactionObject = executorContract.methods.execute(
|
const transactionObject = executorContract.methods.execute(
|
||||||
"0x" + vaa.toString("hex")
|
"0x" + vaa.toString("hex")
|
||||||
);
|
);
|
||||||
const result = await this.chain.estiamteAndSendTransaction(
|
const gasEstimate = await transactionObject.estimateGas({
|
||||||
transactionObject,
|
from: address,
|
||||||
{ from: address }
|
gas: 100000000,
|
||||||
);
|
});
|
||||||
|
const result = await transactionObject.send({
|
||||||
|
from: address,
|
||||||
|
gas: gasEstimate * GAS_ESTIMATE_MULTIPLIER,
|
||||||
|
gasPrice: await this.chain.getGasPrice(),
|
||||||
|
});
|
||||||
return { id: result.transactionHash, info: result };
|
return { id: result.transactionHash, info: result };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -805,19 +673,26 @@ export class EvmPriceFeedContract extends PriceFeedContract {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the keccak256 digest of the contract bytecode
|
* Returns the keccak256 digest of the contract bytecode after replacing any occurrences of the contract addr in
|
||||||
|
* the bytecode with 0.The bytecode stores the deployment address as an immutable variable.
|
||||||
|
* This behavior is inherited from OpenZeppelin's implementation of UUPSUpgradeable contract.
|
||||||
|
* You can read more about verification with immutable variables here:
|
||||||
|
* https://docs.sourcify.dev/docs/immutables/
|
||||||
|
* This function can be used to verify that the contract code is the same on all chains and matches
|
||||||
|
* with the deployedCode property generated by truffle builds
|
||||||
*/
|
*/
|
||||||
async getCodeDigestWithoutAddress(): Promise<string> {
|
async getCodeDigestWithoutAddress(): Promise<string> {
|
||||||
return getCodeDigestWithoutAddress(this.chain.getRpcUrl(), this.address);
|
const code = await this.getCode();
|
||||||
|
const strippedCode = code.replaceAll(
|
||||||
|
this.address.toLowerCase().replace("0x", ""),
|
||||||
|
"0000000000000000000000000000000000000000"
|
||||||
|
);
|
||||||
|
return Web3.utils.keccak256(strippedCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTotalFee(): Promise<TokenQty> {
|
async getTotalFee(): Promise<bigint> {
|
||||||
const web3 = new Web3(this.chain.getRpcUrl());
|
const web3 = new Web3(this.chain.getRpcUrl());
|
||||||
const amount = BigInt(await web3.eth.getBalance(this.address));
|
return BigInt(await web3.eth.getBalance(this.address));
|
||||||
return {
|
|
||||||
amount,
|
|
||||||
denom: this.chain.getNativeToken(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLastExecutedGovernanceSequence() {
|
async getLastExecutedGovernanceSequence() {
|
||||||
|
@ -860,10 +735,10 @@ export class EvmPriceFeedContract extends PriceFeedContract {
|
||||||
/**
|
/**
|
||||||
* Returns the wormhole contract which is being used for VAA verification
|
* Returns the wormhole contract which is being used for VAA verification
|
||||||
*/
|
*/
|
||||||
async getWormholeContract(): Promise<EvmWormholeContract> {
|
async getWormholeContract(): Promise<WormholeEvmContract> {
|
||||||
const pythContract = this.getContract();
|
const pythContract = this.getContract();
|
||||||
const address = await pythContract.methods.wormhole().call();
|
const address = await pythContract.methods.wormhole().call();
|
||||||
return new EvmWormholeContract(this.chain, address);
|
return new WormholeEvmContract(this.chain, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBaseUpdateFee() {
|
async getBaseUpdateFee() {
|
||||||
|
@ -912,10 +787,17 @@ export class EvmPriceFeedContract extends PriceFeedContract {
|
||||||
.call();
|
.call();
|
||||||
const transactionObject =
|
const transactionObject =
|
||||||
pythContract.methods.updatePriceFeeds(priceFeedUpdateData);
|
pythContract.methods.updatePriceFeeds(priceFeedUpdateData);
|
||||||
const result = await this.chain.estiamteAndSendTransaction(
|
const gasEstimate = await transactionObject.estimateGas({
|
||||||
transactionObject,
|
from: address,
|
||||||
{ from: address, value: updateFee }
|
gas: 15000000,
|
||||||
);
|
value: updateFee,
|
||||||
|
});
|
||||||
|
const result = await transactionObject.send({
|
||||||
|
from: address,
|
||||||
|
value: updateFee,
|
||||||
|
gas: gasEstimate * GAS_ESTIMATE_MULTIPLIER,
|
||||||
|
gasPrice: await this.chain.getGasPrice(),
|
||||||
|
});
|
||||||
return { id: result.transactionHash, info: result };
|
return { id: result.transactionHash, info: result };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -929,10 +811,15 @@ export class EvmPriceFeedContract extends PriceFeedContract {
|
||||||
const transactionObject = pythContract.methods.executeGovernanceInstruction(
|
const transactionObject = pythContract.methods.executeGovernanceInstruction(
|
||||||
"0x" + vaa.toString("hex")
|
"0x" + vaa.toString("hex")
|
||||||
);
|
);
|
||||||
const result = await this.chain.estiamteAndSendTransaction(
|
const gasEstiamte = await transactionObject.estimateGas({
|
||||||
transactionObject,
|
from: address,
|
||||||
{ from: address }
|
gas: 15000000,
|
||||||
);
|
});
|
||||||
|
const result = await transactionObject.send({
|
||||||
|
from: address,
|
||||||
|
gas: gasEstiamte * GAS_ESTIMATE_MULTIPLIER,
|
||||||
|
gasPrice: await this.chain.getGasPrice(),
|
||||||
|
});
|
||||||
return { id: result.transactionHash, info: result };
|
return { id: result.transactionHash, info: result };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { PrivateKey, Storable, TxResult } from "../base";
|
import { PrivateKey, TxResult } from "../base";
|
||||||
|
|
||||||
export abstract class WormholeContract extends Storable {
|
export abstract class WormholeContract {
|
||||||
abstract getCurrentGuardianSetIndex(): Promise<number>;
|
abstract getCurrentGuardianSetIndex(): Promise<number>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,7 +33,6 @@ export abstract class WormholeContract extends Storable {
|
||||||
"010000000001007ac31b282c2aeeeb37f3385ee0de5f8e421d30b9e5ae8ba3d4375c1c77a86e77159bb697d9c456d6f8c02d22a94b1279b65b0d6a9957e7d3857423845ac758e300610ac1d2000000030001000000000000000000000000000000000000000000000000000000000000000400000000000005390000000000000000000000000000000000000000000000000000000000436f7265020000000000011358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cdeb5f7389fa26941519f0863349c223b73a6ddee774a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d",
|
"010000000001007ac31b282c2aeeeb37f3385ee0de5f8e421d30b9e5ae8ba3d4375c1c77a86e77159bb697d9c456d6f8c02d22a94b1279b65b0d6a9957e7d3857423845ac758e300610ac1d2000000030001000000000000000000000000000000000000000000000000000000000000000400000000000005390000000000000000000000000000000000000000000000000000000000436f7265020000000000011358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cdeb5f7389fa26941519f0863349c223b73a6ddee774a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d",
|
||||||
"01000000010d0012e6b39c6da90c5dfd3c228edbb78c7a4c97c488ff8a346d161a91db067e51d638c17216f368aa9bdf4836b8645a98018ca67d2fec87d769cabfdf2406bf790a0002ef42b288091a670ef3556596f4f47323717882881eaf38e03345078d07a156f312b785b64dae6e9a87e3d32872f59cb1931f728cecf511762981baf48303668f0103cef2616b84c4e511ff03329e0853f1bd7ee9ac5ba71d70a4d76108bddf94f69c2a8a84e4ee94065e8003c334e899184943634e12043d0dda78d93996da073d190104e76d166b9dac98f602107cc4b44ac82868faf00b63df7d24f177aa391e050902413b71046434e67c770b19aecdf7fce1d1435ea0be7262e3e4c18f50ddc8175c0105d9450e8216d741e0206a50f93b750a47e0a258b80eb8fed1314cc300b3d905092de25cd36d366097b7103ae2d184121329ba3aa2d7c6cc53273f11af14798110010687477c8deec89d36a23e7948feb074df95362fc8dcbd8ae910ac556a1dee1e755c56b9db5d710c940938ed79bc1895a3646523a58bc55f475a23435a373ecfdd0107fb06734864f79def4e192497362513171530daea81f07fbb9f698afe7e66c6d44db21323144f2657d4a5386a954bb94eef9f64148c33aef6e477eafa2c5c984c01088769e82216310d1827d9bd48645ec23e90de4ef8a8de99e2d351d1df318608566248d80cdc83bdcac382b3c30c670352be87f9069aab5037d0b747208eae9c650109e9796497ff9106d0d1c62e184d83716282870cef61a1ee13d6fc485b521adcce255c96f7d1bca8d8e7e7d454b65783a830bddc9d94092091a268d311ecd84c26010c468c9fb6d41026841ff9f8d7368fa309d4dbea3ea4bbd2feccf94a92cc8a20a226338a8e2126cd16f70eaf15b4fc9be2c3fa19def14e071956a605e9d1ac4162010e23fcb6bd445b7c25afb722250c1acbc061ed964ba9de1326609ae012acdfb96942b2a102a2de99ab96327859a34a2b49a767dbdb62e0a1fb26af60fe44fd496a00106bb0bac77ac68b347645f2fb1ad789ea9bd76fb9b2324f25ae06f97e65246f142df717f662e73948317182c62ce87d79c73def0dba12e5242dfc038382812cfe00126da03c5e56cb15aeeceadc1e17a45753ab4dc0ec7bf6a75ca03143ed4a294f6f61bc3f478a457833e43084ecd7c985bf2f55a55f168aac0e030fc49e845e497101626e9d9a5d9e343f00010000000000000000000000000000000000000000000000000000000000000004c1759167c43f501c2000000000000000000000000000000000000000000000000000000000436f7265020000000000021358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd66b9590e1c41e0b226937bf9217d1d67fd4e91f574a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d",
|
"01000000010d0012e6b39c6da90c5dfd3c228edbb78c7a4c97c488ff8a346d161a91db067e51d638c17216f368aa9bdf4836b8645a98018ca67d2fec87d769cabfdf2406bf790a0002ef42b288091a670ef3556596f4f47323717882881eaf38e03345078d07a156f312b785b64dae6e9a87e3d32872f59cb1931f728cecf511762981baf48303668f0103cef2616b84c4e511ff03329e0853f1bd7ee9ac5ba71d70a4d76108bddf94f69c2a8a84e4ee94065e8003c334e899184943634e12043d0dda78d93996da073d190104e76d166b9dac98f602107cc4b44ac82868faf00b63df7d24f177aa391e050902413b71046434e67c770b19aecdf7fce1d1435ea0be7262e3e4c18f50ddc8175c0105d9450e8216d741e0206a50f93b750a47e0a258b80eb8fed1314cc300b3d905092de25cd36d366097b7103ae2d184121329ba3aa2d7c6cc53273f11af14798110010687477c8deec89d36a23e7948feb074df95362fc8dcbd8ae910ac556a1dee1e755c56b9db5d710c940938ed79bc1895a3646523a58bc55f475a23435a373ecfdd0107fb06734864f79def4e192497362513171530daea81f07fbb9f698afe7e66c6d44db21323144f2657d4a5386a954bb94eef9f64148c33aef6e477eafa2c5c984c01088769e82216310d1827d9bd48645ec23e90de4ef8a8de99e2d351d1df318608566248d80cdc83bdcac382b3c30c670352be87f9069aab5037d0b747208eae9c650109e9796497ff9106d0d1c62e184d83716282870cef61a1ee13d6fc485b521adcce255c96f7d1bca8d8e7e7d454b65783a830bddc9d94092091a268d311ecd84c26010c468c9fb6d41026841ff9f8d7368fa309d4dbea3ea4bbd2feccf94a92cc8a20a226338a8e2126cd16f70eaf15b4fc9be2c3fa19def14e071956a605e9d1ac4162010e23fcb6bd445b7c25afb722250c1acbc061ed964ba9de1326609ae012acdfb96942b2a102a2de99ab96327859a34a2b49a767dbdb62e0a1fb26af60fe44fd496a00106bb0bac77ac68b347645f2fb1ad789ea9bd76fb9b2324f25ae06f97e65246f142df717f662e73948317182c62ce87d79c73def0dba12e5242dfc038382812cfe00126da03c5e56cb15aeeceadc1e17a45753ab4dc0ec7bf6a75ca03143ed4a294f6f61bc3f478a457833e43084ecd7c985bf2f55a55f168aac0e030fc49e845e497101626e9d9a5d9e343f00010000000000000000000000000000000000000000000000000000000000000004c1759167c43f501c2000000000000000000000000000000000000000000000000000000000436f7265020000000000021358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd66b9590e1c41e0b226937bf9217d1d67fd4e91f574a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d",
|
||||||
"01000000020d00ce45474d9e1b1e7790a2d210871e195db53a70ffd6f237cfe70e2686a32859ac43c84a332267a8ef66f59719cf91cc8df0101fd7c36aa1878d5139241660edc0010375cc906156ae530786661c0cd9aef444747bc3d8d5aa84cac6a6d2933d4e1a031cffa30383d4af8131e929d9f203f460b07309a647d6cd32ab1cc7724089392c000452305156cfc90343128f97e499311b5cae174f488ff22fbc09591991a0a73d8e6af3afb8a5968441d3ab8437836407481739e9850ad5c95e6acfcc871e951bc30105a7956eefc23e7c945a1966d5ddbe9e4be376c2f54e45e3d5da88c2f8692510c7429b1ea860ae94d929bd97e84923a18187e777aa3db419813a80deb84cc8d22b00061b2a4f3d2666608e0aa96737689e3ba5793810ff3a52ff28ad57d8efb20967735dc5537a2e43ef10f583d144c12a1606542c207f5b79af08c38656d3ac40713301086b62c8e130af3411b3c0d91b5b50dcb01ed5f293963f901fc36e7b0e50114dce203373b32eb45971cef8288e5d928d0ed51cd86e2a3006b0af6a65c396c009080009e93ab4d2c8228901a5f4525934000b2c26d1dc679a05e47fdf0ff3231d98fbc207103159ff4116df2832eea69b38275283434e6cd4a4af04d25fa7a82990b707010aa643f4cf615dfff06ffd65830f7f6cf6512dabc3690d5d9e210fdc712842dc2708b8b2c22e224c99280cd25e5e8bfb40e3d1c55b8c41774e287c1e2c352aecfc010b89c1e85faa20a30601964ccc6a79c0ae53cfd26fb10863db37783428cd91390a163346558239db3cd9d420cfe423a0df84c84399790e2e308011b4b63e6b8015010ca31dcb564ac81a053a268d8090e72097f94f366711d0c5d13815af1ec7d47e662e2d1bde22678113d15963da100b668ba26c0c325970d07114b83c5698f46097010dc9fda39c0d592d9ed92cd22b5425cc6b37430e236f02d0d1f8a2ef45a00bde26223c0a6eb363c8b25fd3bf57234a1d9364976cefb8360e755a267cbbb674b39501108db01e444ab1003dd8b6c96f8eb77958b40ba7a85fefecf32ad00b7a47c0ae7524216262495977e09c0989dd50f280c21453d3756843608eacd17f4fdfe47600001261025228ef5af837cb060bcd986fcfa84ccef75b3fa100468cfd24e7fadf99163938f3b841a33496c2706d0208faab088bd155b2e20fd74c625bb1cc8c43677a0163c53c409e0c5dfa000100000000000000000000000000000000000000000000000000000000000000046c5a054d7833d1e42000000000000000000000000000000000000000000000000000000000436f7265020000000000031358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd15e7caf07c4e3dc8e7c469f92c8cd88fb8005a2074a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d",
|
"01000000020d00ce45474d9e1b1e7790a2d210871e195db53a70ffd6f237cfe70e2686a32859ac43c84a332267a8ef66f59719cf91cc8df0101fd7c36aa1878d5139241660edc0010375cc906156ae530786661c0cd9aef444747bc3d8d5aa84cac6a6d2933d4e1a031cffa30383d4af8131e929d9f203f460b07309a647d6cd32ab1cc7724089392c000452305156cfc90343128f97e499311b5cae174f488ff22fbc09591991a0a73d8e6af3afb8a5968441d3ab8437836407481739e9850ad5c95e6acfcc871e951bc30105a7956eefc23e7c945a1966d5ddbe9e4be376c2f54e45e3d5da88c2f8692510c7429b1ea860ae94d929bd97e84923a18187e777aa3db419813a80deb84cc8d22b00061b2a4f3d2666608e0aa96737689e3ba5793810ff3a52ff28ad57d8efb20967735dc5537a2e43ef10f583d144c12a1606542c207f5b79af08c38656d3ac40713301086b62c8e130af3411b3c0d91b5b50dcb01ed5f293963f901fc36e7b0e50114dce203373b32eb45971cef8288e5d928d0ed51cd86e2a3006b0af6a65c396c009080009e93ab4d2c8228901a5f4525934000b2c26d1dc679a05e47fdf0ff3231d98fbc207103159ff4116df2832eea69b38275283434e6cd4a4af04d25fa7a82990b707010aa643f4cf615dfff06ffd65830f7f6cf6512dabc3690d5d9e210fdc712842dc2708b8b2c22e224c99280cd25e5e8bfb40e3d1c55b8c41774e287c1e2c352aecfc010b89c1e85faa20a30601964ccc6a79c0ae53cfd26fb10863db37783428cd91390a163346558239db3cd9d420cfe423a0df84c84399790e2e308011b4b63e6b8015010ca31dcb564ac81a053a268d8090e72097f94f366711d0c5d13815af1ec7d47e662e2d1bde22678113d15963da100b668ba26c0c325970d07114b83c5698f46097010dc9fda39c0d592d9ed92cd22b5425cc6b37430e236f02d0d1f8a2ef45a00bde26223c0a6eb363c8b25fd3bf57234a1d9364976cefb8360e755a267cbbb674b39501108db01e444ab1003dd8b6c96f8eb77958b40ba7a85fefecf32ad00b7a47c0ae7524216262495977e09c0989dd50f280c21453d3756843608eacd17f4fdfe47600001261025228ef5af837cb060bcd986fcfa84ccef75b3fa100468cfd24e7fadf99163938f3b841a33496c2706d0208faab088bd155b2e20fd74c625bb1cc8c43677a0163c53c409e0c5dfa000100000000000000000000000000000000000000000000000000000000000000046c5a054d7833d1e42000000000000000000000000000000000000000000000000000000000436f7265020000000000031358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd15e7caf07c4e3dc8e7c469f92c8cd88fb8005a2074a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d",
|
||||||
"01000000030d03d4a37a6ff4361d91714730831e9d49785f61624c8f348a9c6c1d82bc1d98cadc5e936338204445c6250bb4928f3f3e165ad47ca03a5d63111168a2de4576856301049a5df10464ea4e1961589fd30fc18d1970a7a2ffaad617e56a0f7777f25275253af7d10a0f0f2494dc6e99fc80e444ab9ebbbee252ded2d5dcb50cbf7a54bb5a01055f4603b553b9ba9e224f9c55c7bca3da00abb10abd19e0081aecd3b352be061a70f79f5f388ebe5190838ef3cd13a2f22459c9a94206883b739c90b40d5d74640006a8fade3997f650a36e46bceb1f609edff201ab32362266f166c5c7da713f6a19590c20b68ed3f0119cb24813c727560ede086b3d610c2d7a1efa66f655bad90900080f5e495a75ea52241c59d145c616bfac01e57182ad8d784cbcc9862ed3afb60c0983ccbc690553961ffcf115a0c917367daada8e60be2cbb8b8008bac6341a8c010935ab11e0eea28b87a1edc5ccce3f1fac25f75b5f640fe6b0673a7cd74513c9dc01c544216cf364cc9993b09fda612e0cd1ced9c00fb668b872a16a64ebb55d27010ab2bc39617a2396e7defa24cd7c22f42dc31f3c42ffcd9d1472b02df8468a4d0563911e8fb6a4b5b0ce0bd505daa53779b08ff660967b31f246126ed7f6f29a7e000bdb6d3fd7b33bdc9ac3992916eb4aacb97e7e21d19649e7fa28d2dd6e337937e4274516a96c13ac7a8895da9f91948ea3a09c25f44b982c62ce8842b58e20c8a9000d3d1b19c8bb000856b6610b9d28abde6c35cb7705c6ca5db711f7be96d60eed9d72cfa402a6bfe8bf0496dbc7af35796fc768da51a067b95941b3712dce8ae1e7010ec80085033157fd1a5628fc0c56267469a86f0e5a66d7dede1ad4ce74ecc3dff95b60307a39c3bfbeedc915075070da30d0395def9635130584f709b3885e1bdc0010fc480eb9ee715a2d151b23722b48b42581d7f4001fc1696c75425040bfc1ffc5394fe418adb2b64bd3dc692efda4cc408163677dbe233b16bcdabb853a20843301118ee9e115e1a0c981f19d0772b850e666591322da742a9a12cce9f52a5665bd474abdd59c580016bee8aae67fdf39b315be2528d12eec3a652910e03cc4c6fa3801129d0d1e2e429e969918ec163d16a7a5b2c6729aa44af5dccad07d25d19891556a79b574f42d9adbd9e2a9ae5a6b8750331d2fccb328dd94c3bf8791ee1bfe85aa00661e99781981faea00010000000000000000000000000000000000000000000000000000000000000004fd4c6c55ec8dfd342000000000000000000000000000000000000000000000000000000000436f726502000000000004135893b5a76c3f739645648885bdccc06cd70a3cd3ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd15e7caf07c4e3dc8e7c469f92c8cd88fb8005a2074a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d",
|
|
||||||
];
|
];
|
||||||
const currentIndex = await this.getCurrentGuardianSetIndex();
|
const currentIndex = await this.getCurrentGuardianSetIndex();
|
||||||
for (let i = currentIndex; i < MAINNET_UPGRADE_VAAS.length; i++) {
|
for (let i = currentIndex; i < MAINNET_UPGRADE_VAAS.length; i++) {
|
||||||
|
|
|
@ -33,7 +33,6 @@ import {
|
||||||
deriveWormholeBridgeDataKey,
|
deriveWormholeBridgeDataKey,
|
||||||
} from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
|
} from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
|
||||||
import { KeyValueConfig, Storable } from "./base";
|
import { KeyValueConfig, Storable } from "./base";
|
||||||
import { PriorityFeeConfig } from "@pythnetwork/solana-utils";
|
|
||||||
|
|
||||||
class InvalidTransactionError extends Error {
|
class InvalidTransactionError extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
|
@ -344,8 +343,7 @@ export class Vault extends Storable {
|
||||||
*/
|
*/
|
||||||
public async proposeWormholeMessage(
|
public async proposeWormholeMessage(
|
||||||
payloads: Buffer[],
|
payloads: Buffer[],
|
||||||
proposalAddress?: PublicKey,
|
proposalAddress?: PublicKey
|
||||||
priorityFeeConfig: PriorityFeeConfig = {}
|
|
||||||
): Promise<WormholeMultisigProposal> {
|
): Promise<WormholeMultisigProposal> {
|
||||||
const squad = this.getSquadOrThrow();
|
const squad = this.getSquadOrThrow();
|
||||||
const multisigVault = new MultisigVault(
|
const multisigVault = new MultisigVault(
|
||||||
|
@ -358,8 +356,7 @@ export class Vault extends Storable {
|
||||||
await multisigVault.proposeWormholeMultipleMessagesWithPayer(
|
await multisigVault.proposeWormholeMultipleMessagesWithPayer(
|
||||||
payloads,
|
payloads,
|
||||||
squad.wallet.publicKey,
|
squad.wallet.publicKey,
|
||||||
proposalAddress,
|
proposalAddress
|
||||||
priorityFeeConfig
|
|
||||||
);
|
);
|
||||||
return new WormholeMultisigProposal(txAccount, squad, this.cluster);
|
return new WormholeMultisigProposal(txAccount, squad, this.cluster);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,9 @@ repl.evalCode(
|
||||||
"import { loadHotWallet, Vault } from './src/governance';" +
|
"import { loadHotWallet, Vault } from './src/governance';" +
|
||||||
"import { SuiChain, CosmWasmChain, AptosChain, EvmChain } from './src/chains';" +
|
"import { SuiChain, CosmWasmChain, AptosChain, EvmChain } from './src/chains';" +
|
||||||
"import { SuiPriceFeedContract } from './src/contracts/sui';" +
|
"import { SuiPriceFeedContract } from './src/contracts/sui';" +
|
||||||
"import { CosmWasmWormholeContract, CosmWasmPriceFeedContract } from './src/contracts/cosmwasm';" +
|
"import { WormholeCosmWasmContract, CosmWasmPriceFeedContract } from './src/contracts/cosmwasm';" +
|
||||||
"import { EvmWormholeContract, EvmPriceFeedContract } from './src/contracts/evm';" +
|
"import { WormholeEvmContract, EvmPriceFeedContract } from './src/contracts/evm';" +
|
||||||
"import { AptosWormholeContract, AptosPriceFeedContract } from './src/contracts/aptos';" +
|
"import { WormholeAptosContract, AptosPriceFeedContract } from './src/contracts/aptos';" +
|
||||||
"import { DefaultStore } from './src/store';" +
|
"import { DefaultStore } from './src/store';" +
|
||||||
"import { toPrivateKey } from './src/base';" +
|
"import { toPrivateKey } from './src/base';" +
|
||||||
"DefaultStore"
|
"DefaultStore"
|
||||||
|
|
|
@ -8,16 +8,11 @@ import {
|
||||||
} from "./chains";
|
} from "./chains";
|
||||||
import {
|
import {
|
||||||
AptosPriceFeedContract,
|
AptosPriceFeedContract,
|
||||||
AptosWormholeContract,
|
|
||||||
CosmWasmPriceFeedContract,
|
CosmWasmPriceFeedContract,
|
||||||
CosmWasmWormholeContract,
|
|
||||||
EvmEntropyContract,
|
EvmEntropyContract,
|
||||||
EvmPriceFeedContract,
|
EvmPriceFeedContract,
|
||||||
EvmWormholeContract,
|
|
||||||
SuiPriceFeedContract,
|
SuiPriceFeedContract,
|
||||||
WormholeContract,
|
|
||||||
} from "./contracts";
|
} from "./contracts";
|
||||||
import { Token } from "./token";
|
|
||||||
import { PriceFeedContract, Storable } from "./base";
|
import { PriceFeedContract, Storable } from "./base";
|
||||||
import { parse, stringify } from "yaml";
|
import { parse, stringify } from "yaml";
|
||||||
import { readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
import { readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
||||||
|
@ -27,14 +22,11 @@ export class Store {
|
||||||
public chains: Record<string, Chain> = { global: new GlobalChain() };
|
public chains: Record<string, Chain> = { global: new GlobalChain() };
|
||||||
public contracts: Record<string, PriceFeedContract> = {};
|
public contracts: Record<string, PriceFeedContract> = {};
|
||||||
public entropy_contracts: Record<string, EvmEntropyContract> = {};
|
public entropy_contracts: Record<string, EvmEntropyContract> = {};
|
||||||
public wormhole_contracts: Record<string, WormholeContract> = {};
|
|
||||||
public tokens: Record<string, Token> = {};
|
|
||||||
public vaults: Record<string, Vault> = {};
|
public vaults: Record<string, Vault> = {};
|
||||||
|
|
||||||
constructor(public path: string) {
|
constructor(public path: string) {
|
||||||
this.loadAllChains();
|
this.loadAllChains();
|
||||||
this.loadAllContracts();
|
this.loadAllContracts();
|
||||||
this.loadAllTokens();
|
|
||||||
this.loadAllVaults();
|
this.loadAllVaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +78,6 @@ export class Store {
|
||||||
const contractsByType: Record<string, Storable[]> = {};
|
const contractsByType: Record<string, Storable[]> = {};
|
||||||
const contracts: Storable[] = Object.values(this.contracts);
|
const contracts: Storable[] = Object.values(this.contracts);
|
||||||
contracts.push(...Object.values(this.entropy_contracts));
|
contracts.push(...Object.values(this.entropy_contracts));
|
||||||
contracts.push(...Object.values(this.wormhole_contracts));
|
|
||||||
for (const contract of contracts) {
|
for (const contract of contracts) {
|
||||||
if (!contractsByType[contract.getType()]) {
|
if (!contractsByType[contract.getType()]) {
|
||||||
contractsByType[contract.getType()] = [];
|
contractsByType[contract.getType()] = [];
|
||||||
|
@ -120,13 +111,10 @@ export class Store {
|
||||||
loadAllContracts() {
|
loadAllContracts() {
|
||||||
const allContractClasses = {
|
const allContractClasses = {
|
||||||
[CosmWasmPriceFeedContract.type]: CosmWasmPriceFeedContract,
|
[CosmWasmPriceFeedContract.type]: CosmWasmPriceFeedContract,
|
||||||
[CosmWasmWormholeContract.type]: CosmWasmWormholeContract,
|
|
||||||
[SuiPriceFeedContract.type]: SuiPriceFeedContract,
|
[SuiPriceFeedContract.type]: SuiPriceFeedContract,
|
||||||
[EvmPriceFeedContract.type]: EvmPriceFeedContract,
|
[EvmPriceFeedContract.type]: EvmPriceFeedContract,
|
||||||
[AptosPriceFeedContract.type]: AptosPriceFeedContract,
|
[AptosPriceFeedContract.type]: AptosPriceFeedContract,
|
||||||
[AptosWormholeContract.type]: AptosWormholeContract,
|
|
||||||
[EvmEntropyContract.type]: EvmEntropyContract,
|
[EvmEntropyContract.type]: EvmEntropyContract,
|
||||||
[EvmWormholeContract.type]: EvmWormholeContract,
|
|
||||||
};
|
};
|
||||||
this.getYamlFiles(`${this.path}/contracts/`).forEach((yamlFile) => {
|
this.getYamlFiles(`${this.path}/contracts/`).forEach((yamlFile) => {
|
||||||
const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
|
const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
|
||||||
|
@ -141,16 +129,13 @@ export class Store {
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
this.contracts[chainContract.getId()] ||
|
this.contracts[chainContract.getId()] ||
|
||||||
this.entropy_contracts[chainContract.getId()] ||
|
this.entropy_contracts[chainContract.getId()]
|
||||||
this.wormhole_contracts[chainContract.getId()]
|
|
||||||
)
|
)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Multiple contracts with id ${chainContract.getId()} found`
|
`Multiple contracts with id ${chainContract.getId()} found`
|
||||||
);
|
);
|
||||||
if (chainContract instanceof EvmEntropyContract) {
|
if (chainContract instanceof EvmEntropyContract) {
|
||||||
this.entropy_contracts[chainContract.getId()] = chainContract;
|
this.entropy_contracts[chainContract.getId()] = chainContract;
|
||||||
} else if (chainContract instanceof WormholeContract) {
|
|
||||||
this.wormhole_contracts[chainContract.getId()] = chainContract;
|
|
||||||
} else {
|
} else {
|
||||||
this.contracts[chainContract.getId()] = chainContract;
|
this.contracts[chainContract.getId()] = chainContract;
|
||||||
}
|
}
|
||||||
|
@ -158,20 +143,6 @@ export class Store {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAllTokens() {
|
|
||||||
this.getYamlFiles(`${this.path}/tokens/`).forEach((yamlFile) => {
|
|
||||||
const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
|
|
||||||
for (const parsed of parsedArray) {
|
|
||||||
if (parsed.type !== Token.type) return;
|
|
||||||
|
|
||||||
const token = Token.fromJson(parsed);
|
|
||||||
if (this.tokens[token.getId()])
|
|
||||||
throw new Error(`Multiple tokens with id ${token.getId()} found`);
|
|
||||||
this.tokens[token.getId()] = token;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadAllVaults() {
|
loadAllVaults() {
|
||||||
this.getYamlFiles(`${this.path}/vaults/`).forEach((yamlFile) => {
|
this.getYamlFiles(`${this.path}/vaults/`).forEach((yamlFile) => {
|
||||||
const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
|
const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
import axios from "axios";
|
|
||||||
import { KeyValueConfig, Storable } from "./base";
|
|
||||||
|
|
||||||
export type TokenId = string;
|
|
||||||
/**
|
|
||||||
* A quantity of a token, represented as an integer number of the minimum denomination of the token.
|
|
||||||
* This can also represent a quantity of an unknown token (represented by an undefined denom).
|
|
||||||
*/
|
|
||||||
export type TokenQty = {
|
|
||||||
amount: bigint;
|
|
||||||
denom: TokenId | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A token represents a cryptocurrency like ETH or BTC.
|
|
||||||
* The main use of this class is to calculate the dollar value of accrued fees.
|
|
||||||
*/
|
|
||||||
export class Token extends Storable {
|
|
||||||
static type = "token";
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
public id: TokenId,
|
|
||||||
// The hexadecimal pyth id of the tokens X/USD price feed
|
|
||||||
// (get this from hermes or the Pyth docs page)
|
|
||||||
public pythId: string | undefined,
|
|
||||||
public decimals: number
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
getId(): TokenId {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
getType(): string {
|
|
||||||
return Token.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the dollar value of 1 token. Returns undefined for tokens that do
|
|
||||||
* not have a configured pricing method.
|
|
||||||
*/
|
|
||||||
async getPrice(): Promise<number | undefined> {
|
|
||||||
if (this.pythId) {
|
|
||||||
const url = `https://hermes.pyth.network/v2/updates/price/latest?ids%5B%5D=${this.pythId}&parsed=true`;
|
|
||||||
const response = await axios.get(url);
|
|
||||||
const price = response.data.parsed[0].price;
|
|
||||||
|
|
||||||
// Note that this conversion can lose some precision.
|
|
||||||
// We don't really care about that in this application.
|
|
||||||
return parseInt(price.price) * Math.pow(10, price.expo);
|
|
||||||
} else {
|
|
||||||
// We may support other pricing methodologies in the future but whatever.
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the dollar value of the minimum representable quantity of this token.
|
|
||||||
* E.g., for ETH, this method will return the dollar value of 1 wei.
|
|
||||||
*/
|
|
||||||
async getPriceForMinUnit(): Promise<number | undefined> {
|
|
||||||
const price = await this.getPrice();
|
|
||||||
return price ? price / Math.pow(10, this.decimals) : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJson(): KeyValueConfig {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
...(this.pythId !== undefined ? { pythId: this.pythId } : {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(parsed: {
|
|
||||||
id: string;
|
|
||||||
pythId?: string;
|
|
||||||
decimals: number;
|
|
||||||
}): Token {
|
|
||||||
return new Token(parsed.id, parsed.pythId, parsed.decimals);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://fullnode.mainnet.aptoslabs.com/v1
|
rpcUrl: https://fullnode.mainnet.aptoslabs.com/v1
|
||||||
type: AptosChain
|
type: AptosChain
|
||||||
nativeToken: APT
|
|
||||||
- id: movement_move_devnet
|
- id: movement_move_devnet
|
||||||
wormholeChainName: movement_move_devnet
|
wormholeChainName: movement_move_devnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
|
|
|
@ -66,19 +66,3 @@
|
||||||
prefix: juno
|
prefix: juno
|
||||||
feeDenom: ujunox
|
feeDenom: ujunox
|
||||||
type: CosmWasmChain
|
type: CosmWasmChain
|
||||||
- endpoint: http://18.199.53.161:26657
|
|
||||||
id: rol_testnet
|
|
||||||
wormholeChainName: rol_testnet
|
|
||||||
mainnet: false
|
|
||||||
gasPrice: "0.025"
|
|
||||||
prefix: rol
|
|
||||||
feeDenom: urax
|
|
||||||
type: CosmWasmChain
|
|
||||||
- endpoint: https://testnet-burnt-rpc.lavenderfive.com
|
|
||||||
id: xion_testnet
|
|
||||||
wormholeChainName: xion_testnet
|
|
||||||
mainnet: false
|
|
||||||
gasPrice: "0.025"
|
|
||||||
prefix: xion
|
|
||||||
feeDenom: uxion
|
|
||||||
type: CosmWasmChain
|
|
||||||
|
|
|
@ -3,11 +3,6 @@
|
||||||
rpcUrl: https://linea-goerli.blastapi.io/$ENV_BLAST_API_KEY
|
rpcUrl: https://linea-goerli.blastapi.io/$ENV_BLAST_API_KEY
|
||||||
networkId: 59140
|
networkId: 59140
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: linea_sepolia
|
|
||||||
mainnet: false
|
|
||||||
rpcUrl: https://rpc.sepolia.linea.build
|
|
||||||
networkId: 59141
|
|
||||||
type: EvmChain
|
|
||||||
- id: kava
|
- id: kava
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://kava-evm.publicnode.com
|
rpcUrl: https://kava-evm.publicnode.com
|
||||||
|
@ -18,7 +13,6 @@
|
||||||
rpcUrl: https://evmos-evm.publicnode.com
|
rpcUrl: https://evmos-evm.publicnode.com
|
||||||
networkId: 9001
|
networkId: 9001
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: EVMOS
|
|
||||||
- id: canto
|
- id: canto
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://canto.slingshot.finance
|
rpcUrl: https://canto.slingshot.finance
|
||||||
|
@ -39,6 +33,11 @@
|
||||||
rpcUrl: https://evm-t3.cronos.org
|
rpcUrl: https://evm-t3.cronos.org
|
||||||
networkId: 338
|
networkId: 338
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
|
- id: zksync_goerli
|
||||||
|
mainnet: false
|
||||||
|
rpcUrl: https://zksync2-testnet.zksync.dev
|
||||||
|
networkId: 280
|
||||||
|
type: EvmChain
|
||||||
- id: canto_testnet
|
- id: canto_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://canto-testnet.plexnode.wtf
|
rpcUrl: https://canto-testnet.plexnode.wtf
|
||||||
|
@ -46,14 +45,9 @@
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: polygon_zkevm_testnet
|
- id: polygon_zkevm_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://rpc.public.zkevm-test.net
|
rpcUrl: https://rpc.public.zkevm-test.net/
|
||||||
networkId: 1442
|
networkId: 1442
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: polygon_blackberry
|
|
||||||
mainnet: false
|
|
||||||
rpcUrl: https://rpc.polygon-blackberry.gelato.digital
|
|
||||||
networkId: 94204209
|
|
||||||
type: EvmChain
|
|
||||||
- id: aurora_testnet
|
- id: aurora_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://testnet.aurora.dev
|
rpcUrl: https://testnet.aurora.dev
|
||||||
|
@ -69,7 +63,6 @@
|
||||||
rpcUrl: https://rpc.gnosischain.com
|
rpcUrl: https://rpc.gnosischain.com
|
||||||
networkId: 100
|
networkId: 100
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: DAI
|
|
||||||
- id: fantom_testnet
|
- id: fantom_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://fantom-testnet.blastapi.io/$ENV_BLAST_API_KEY
|
rpcUrl: https://fantom-testnet.blastapi.io/$ENV_BLAST_API_KEY
|
||||||
|
@ -77,7 +70,7 @@
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: neon
|
- id: neon
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://neon-evm.drpc.org
|
rpcUrl: https://neon-proxy-mainnet.solana.p2p.org
|
||||||
networkId: 245022934
|
networkId: 245022934
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: fantom
|
- id: fantom
|
||||||
|
@ -85,7 +78,6 @@
|
||||||
rpcUrl: https://rpc.ankr.com/fantom
|
rpcUrl: https://rpc.ankr.com/fantom
|
||||||
networkId: 250
|
networkId: 250
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: FTM
|
|
||||||
- id: mumbai
|
- id: mumbai
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://polygon-testnet.blastapi.io/$ENV_BLAST_API_KEY
|
rpcUrl: https://polygon-testnet.blastapi.io/$ENV_BLAST_API_KEY
|
||||||
|
@ -111,7 +103,6 @@
|
||||||
rpcUrl: https://rpc.mantle.xyz/
|
rpcUrl: https://rpc.mantle.xyz/
|
||||||
networkId: 5000
|
networkId: 5000
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: MNT
|
|
||||||
- id: kava_testnet
|
- id: kava_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://evm.testnet.kava.io
|
rpcUrl: https://evm.testnet.kava.io
|
||||||
|
@ -132,7 +123,6 @@
|
||||||
rpcUrl: https://eth-mainnet.blastapi.io/$ENV_BLAST_API_KEY
|
rpcUrl: https://eth-mainnet.blastapi.io/$ENV_BLAST_API_KEY
|
||||||
networkId: 1
|
networkId: 1
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: ETH
|
|
||||||
- id: bsc_testnet
|
- id: bsc_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://rpc.ankr.com/bsc_testnet_chapel
|
rpcUrl: https://rpc.ankr.com/bsc_testnet_chapel
|
||||||
|
@ -148,7 +138,6 @@
|
||||||
rpcUrl: https://mainnet.aurora.dev
|
rpcUrl: https://mainnet.aurora.dev
|
||||||
networkId: 1313161554
|
networkId: 1313161554
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: NEAR
|
|
||||||
- id: bsc
|
- id: bsc
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://rpc.ankr.com/bsc
|
rpcUrl: https://rpc.ankr.com/bsc
|
||||||
|
@ -169,6 +158,11 @@
|
||||||
rpcUrl: https://evm.confluxrpc.org
|
rpcUrl: https://evm.confluxrpc.org
|
||||||
networkId: 1030
|
networkId: 1030
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
|
- id: optimism_goerli
|
||||||
|
mainnet: false
|
||||||
|
rpcUrl: https://rpc.ankr.com/optimism_testnet
|
||||||
|
networkId: 420
|
||||||
|
type: EvmChain
|
||||||
- id: celo
|
- id: celo
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://forno.celo.org
|
rpcUrl: https://forno.celo.org
|
||||||
|
@ -179,7 +173,6 @@
|
||||||
rpcUrl: https://polygon-rpc.com
|
rpcUrl: https://polygon-rpc.com
|
||||||
networkId: 137
|
networkId: 137
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: MATIC
|
|
||||||
- id: wemix_testnet
|
- id: wemix_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://api.test.wemix.com
|
rpcUrl: https://api.test.wemix.com
|
||||||
|
@ -190,13 +183,11 @@
|
||||||
rpcUrl: https://rpc-mainnet.kcc.network
|
rpcUrl: https://rpc-mainnet.kcc.network
|
||||||
networkId: 321
|
networkId: 321
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: KCS
|
|
||||||
- id: polygon_zkevm
|
- id: polygon_zkevm
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://zkevm-rpc.com
|
rpcUrl: https://zkevm-rpc.com
|
||||||
networkId: 1101
|
networkId: 1101
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: ETH
|
|
||||||
- id: celo_alfajores_testnet
|
- id: celo_alfajores_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://alfajores-forno.celo-testnet.org
|
rpcUrl: https://alfajores-forno.celo-testnet.org
|
||||||
|
@ -212,25 +203,21 @@
|
||||||
rpcUrl: https://zksync2-mainnet.zksync.io
|
rpcUrl: https://zksync2-mainnet.zksync.io
|
||||||
networkId: 324
|
networkId: 324
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: ETH
|
|
||||||
- id: base
|
- id: base
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://developer-access-mainnet.base.org/
|
rpcUrl: https://developer-access-mainnet.base.org/
|
||||||
networkId: 8453
|
networkId: 8453
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: ETH
|
|
||||||
- id: arbitrum
|
- id: arbitrum
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://arb1.arbitrum.io/rpc
|
rpcUrl: https://arb1.arbitrum.io/rpc
|
||||||
networkId: 42161
|
networkId: 42161
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: ETH
|
|
||||||
- id: optimism
|
- id: optimism
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://rpc.ankr.com/optimism
|
rpcUrl: https://rpc.ankr.com/optimism
|
||||||
networkId: 10
|
networkId: 10
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: ETH
|
|
||||||
- id: kcc_testnet
|
- id: kcc_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://rpc-testnet.kcc.network
|
rpcUrl: https://rpc-testnet.kcc.network
|
||||||
|
@ -251,7 +238,6 @@
|
||||||
rpcUrl: https://linea.rpc.thirdweb.com
|
rpcUrl: https://linea.rpc.thirdweb.com
|
||||||
networkId: 59144
|
networkId: 59144
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: ETH
|
|
||||||
- id: shimmer_testnet
|
- id: shimmer_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://json-rpc.evm.testnet.shimmer.network
|
rpcUrl: https://json-rpc.evm.testnet.shimmer.network
|
||||||
|
@ -279,7 +265,7 @@
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: horizen_eon
|
- id: horizen_eon
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://rpc.ankr.com/horizen_eon
|
rpcUrl: https://eon-rpc.horizenlabs.io/ethv1
|
||||||
networkId: 7332
|
networkId: 7332
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: horizen_gobi
|
- id: horizen_gobi
|
||||||
|
@ -302,11 +288,6 @@
|
||||||
rpcUrl: https://sepolia-rollup.arbitrum.io/rpc
|
rpcUrl: https://sepolia-rollup.arbitrum.io/rpc
|
||||||
networkId: 421614
|
networkId: 421614
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: arbitrum_blueberry
|
|
||||||
mainnet: false
|
|
||||||
rpcUrl: https://rpc.arb-blueberry.gelato.digital
|
|
||||||
networkId: 88153591557
|
|
||||||
type: EvmChain
|
|
||||||
- id: boba
|
- id: boba
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://replica.boba.network
|
rpcUrl: https://replica.boba.network
|
||||||
|
@ -317,11 +298,6 @@
|
||||||
rpcUrl: https://goerli.boba.network
|
rpcUrl: https://goerli.boba.network
|
||||||
networkId: 2888
|
networkId: 2888
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: boba_sepolia
|
|
||||||
mainnet: false
|
|
||||||
rpcUrl: https://sepolia.boba.network
|
|
||||||
networkId: 28882
|
|
||||||
type: EvmChain
|
|
||||||
- id: manta
|
- id: manta
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://pacific-rpc.manta.network/http
|
rpcUrl: https://pacific-rpc.manta.network/http
|
||||||
|
@ -329,7 +305,7 @@
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: manta_testnet
|
- id: manta_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://manta-pacific-testnet.drpc.org
|
rpcUrl: https://pacific-rpc.testnet.manta.network/http
|
||||||
networkId: 3441005
|
networkId: 3441005
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: manta_sepolia
|
- id: manta_sepolia
|
||||||
|
@ -342,11 +318,6 @@
|
||||||
rpcUrl: https://sepolia.optimism.io
|
rpcUrl: https://sepolia.optimism.io
|
||||||
networkId: 11155420
|
networkId: 11155420
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: optimism_celestia_raspberry
|
|
||||||
mainnet: false
|
|
||||||
rpcUrl: https://rpc.opcelestia-raspberry.gelato.digital
|
|
||||||
networkId: 123420111
|
|
||||||
type: EvmChain
|
|
||||||
- id: chiliz_spicy
|
- id: chiliz_spicy
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://spicy-rpc.chiliz.com
|
rpcUrl: https://spicy-rpc.chiliz.com
|
||||||
|
@ -372,11 +343,6 @@
|
||||||
rpcUrl: https://rpc.zkatana.gelato.digital
|
rpcUrl: https://rpc.zkatana.gelato.digital
|
||||||
networkId: 1261120
|
networkId: 1261120
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: astar_zkyoto_testnet
|
|
||||||
mainnet: false
|
|
||||||
rpcUrl: https://rpc.startale.com/zkyoto
|
|
||||||
networkId: 6038361
|
|
||||||
type: EvmChain
|
|
||||||
- id: astar_zkevm
|
- id: astar_zkevm
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://rpc.startale.com/astar-zkevm
|
rpcUrl: https://rpc.startale.com/astar-zkevm
|
||||||
|
@ -392,15 +358,14 @@
|
||||||
rpcUrl: https://rpc.coredao.org
|
rpcUrl: https://rpc.coredao.org
|
||||||
networkId: 1116
|
networkId: 1116
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: CORE
|
- id: tomochain
|
||||||
- id: viction
|
|
||||||
mainnet: true
|
mainnet: true
|
||||||
rpcUrl: https://viction.blockpi.network/v1/rpc/public
|
rpcUrl: https://rpc.tomochain.com
|
||||||
networkId: 88
|
networkId: 88
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: viction_testnet
|
- id: tomochain_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://rpc-testnet.viction.xyz
|
rpcUrl: https://rpc.testnet.tomochain.com
|
||||||
networkId: 89
|
networkId: 89
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: mode_testnet
|
- id: mode_testnet
|
||||||
|
@ -450,7 +415,7 @@
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: blast_s2_testnet
|
- id: blast_s2_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://sepolia.blast.io
|
rpcUrl: https://rpc.s2.testblast.io/$ENV_BLAST_S2_TESTNET_API_KEY
|
||||||
networkId: 168587773
|
networkId: 168587773
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: hedera_testnet
|
- id: hedera_testnet
|
||||||
|
@ -463,7 +428,6 @@
|
||||||
rpcUrl: https://mainnet.hashio.io/api
|
rpcUrl: https://mainnet.hashio.io/api
|
||||||
networkId: 295
|
networkId: 295
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
nativeToken: HBAR
|
|
||||||
- id: filecoin_calibration
|
- id: filecoin_calibration
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://rpc.ankr.com/filecoin_testnet
|
rpcUrl: https://rpc.ankr.com/filecoin_testnet
|
||||||
|
@ -486,7 +450,7 @@
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: sei_evm_devnet
|
- id: sei_evm_devnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://evm-rpc-arctic-1.sei-apis.com
|
rpcUrl: https://evm-devnet.seinetwork.io
|
||||||
networkId: 713715
|
networkId: 713715
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: fantom_sonic_testnet
|
- id: fantom_sonic_testnet
|
||||||
|
@ -502,7 +466,7 @@
|
||||||
- id: idex_xchain_testnet
|
- id: idex_xchain_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
rpcUrl: https://xchain-testnet-rpc.idex.io
|
rpcUrl: https://xchain-testnet-rpc.idex.io
|
||||||
networkId: 64002
|
networkId: 671276500
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: injective_inevm_testnet
|
- id: injective_inevm_testnet
|
||||||
mainnet: false
|
mainnet: false
|
||||||
|
@ -534,43 +498,3 @@
|
||||||
rpcUrl: https://rpc.merlinchain.io
|
rpcUrl: https://rpc.merlinchain.io
|
||||||
networkId: 4200
|
networkId: 4200
|
||||||
type: EvmChain
|
type: EvmChain
|
||||||
- id: parallel_testnet
|
|
||||||
mainnet: false
|
|
||||||
rpcUrl: https://rpc-accused-coffee-koala-b9fn1dik76.t.conduit.xyz
|
|
||||||
networkId: 9659
|
|
||||||
type: EvmChain
|
|
||||||
- id: parallel
|
|
||||||
mainnet: true
|
|
||||||
rpcUrl: https://rpc.parallel.fi/
|
|
||||||
networkId: 1024
|
|
||||||
type: EvmChain
|
|
||||||
- id: polynomial_testnet
|
|
||||||
mainnet: false
|
|
||||||
rpcUrl: https://rpc-polynomial-network-testnet-x0tryg8u1c.t.conduit.xyz
|
|
||||||
networkId: 80008
|
|
||||||
type: EvmChain
|
|
||||||
- id: morph_testnet
|
|
||||||
mainnet: false
|
|
||||||
rpcUrl: https://rpc-testnet.morphl2.io
|
|
||||||
networkId: 2710
|
|
||||||
type: EvmChain
|
|
||||||
- id: iota
|
|
||||||
mainnet: true
|
|
||||||
rpcUrl: https://json-rpc.evm.iotaledger.net
|
|
||||||
networkId: 8822
|
|
||||||
type: EvmChain
|
|
||||||
- id: flow_previewnet
|
|
||||||
mainnet: true
|
|
||||||
rpcUrl: https://previewnet.evm.nodes.onflow.org
|
|
||||||
networkId: 646
|
|
||||||
type: EvmChain
|
|
||||||
- id: olive_testnet
|
|
||||||
mainnet: false
|
|
||||||
rpcUrl: https://olive-network-testnet.rpc.caldera.xyz/http
|
|
||||||
networkId: 8101902
|
|
||||||
type: EvmChain
|
|
||||||
- id: taiko_hekla
|
|
||||||
mainnet: false
|
|
||||||
rpcUrl: https://rpc.hekla.taiko.xyz/
|
|
||||||
networkId: 167009
|
|
||||||
type: EvmChain
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
- chain: aptos_mainnet
|
|
||||||
address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"
|
|
||||||
type: AptosWormholeContract
|
|
||||||
- chain: aptos_testnet
|
|
||||||
address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"
|
|
||||||
type: AptosWormholeContract
|
|
||||||
- chain: movement_move_devnet
|
|
||||||
address: "0x9236893d6444b208b7e0b3e8d4be4ace90b6d17817ab7d1584e46a33ef5c50c9"
|
|
||||||
type: AptosWormholeContract
|
|
|
@ -40,9 +40,3 @@
|
||||||
- chain: neutron_testnet_pion_1
|
- chain: neutron_testnet_pion_1
|
||||||
address: neutron16zwrmx3zgggmxhzau86xfycm42cr4sj888hdvzsxya3qarp6zhhqzhlkvz
|
address: neutron16zwrmx3zgggmxhzau86xfycm42cr4sj888hdvzsxya3qarp6zhhqzhlkvz
|
||||||
type: CosmWasmPriceFeedContract
|
type: CosmWasmPriceFeedContract
|
||||||
- chain: rol_testnet
|
|
||||||
address: rol1pvrwmjuusn9wh34j7y520g8gumuy9xtl3gvprlljfdpwju3x7ucszdyfs8
|
|
||||||
type: CosmWasmPriceFeedContract
|
|
||||||
- chain: xion_testnet
|
|
||||||
address: xion1w39ctwxxhxxc2kxarycjxj9rndn65gf8daek7ggarwh3rq3zl0lqqllnmt
|
|
||||||
type: CosmWasmPriceFeedContract
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
- chain: rol_testnet
|
|
||||||
address: rol17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgss2u902
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: osmosis
|
|
||||||
address: osmo1t7qham5kle36rs28se2xd7cckm9mpwzgt65t40lrdf8fcq3837qqjvw80s
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: sei_testnet_atlantic_2
|
|
||||||
address: sei14utt2wp7hamd2qmuz0e5yj728y4u08cm7etujxkc6qprnrla3uwq95jz86
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: juno_testnet
|
|
||||||
address: juno1h7m0xwgu4qh0nrthahpydxzw7klvyd5w8d7jjl675p944ds7jr4sf3ta4l
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: sei_testnet_atlantic_2
|
|
||||||
address: sei1cn8ygrvqk03p5zce3c6rrst7j97qarm33d23rxgme7rzmasddfusw7cpxw
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: neutron_testnet_pion_1
|
|
||||||
address: neutron1nxs2ajn4ejrggfuvqczfx4txghrendcpy3526avg2tsngjktedtspgla8t
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: neutron_testnet_pion_1
|
|
||||||
address: neutron1wtuuak4yt4vyhtv7gt4xnv0m8zfakad5lnz6r7dx8alyydu0sgns67kmvy
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: juno_testnet
|
|
||||||
address: juno1g9xhl5jzhlm6lqc2earxkzyazwl2cshr5cnemxtjy0le64s4w22skukkxj
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: osmosis_testnet_5
|
|
||||||
address: osmo19ah8ak7rgmds40te22xnz7zsdmx5twjulv3sypqm79skkl2ajm4skuhwmf
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: sei_pacific_1
|
|
||||||
address: sei12qq3cufehhsaprjfjrwpx5ltyr43lcrxvf6eaqf0p4jsjpc7semq8p6ewa
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: injective_testnet
|
|
||||||
address: inj1hglkee95shfsl5xxky26hdqxj0mqp54lh7xm59
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: neutron
|
|
||||||
address: neutron178ruq7gf6gk3uus5n8xztj5tsrt5xwxfelw88mc9egfw5d99ktksnk5rsh
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: osmosis_testnet_5
|
|
||||||
address: osmo1llum0y8zc4h2f0rhcdn63xje4mrkdljrve9l40lun9lpeyu2l7cq4phaw6
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: injective_testnet
|
|
||||||
address: inj17sy3vx5dfeva9wx33d09yqdwruntpccnjyw0hj
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: injective
|
|
||||||
address: inj17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9l2q74d
|
|
||||||
type: CosmWasmWormholeContract
|
|
||||||
- chain: xion_testnet
|
|
||||||
address: xion14ycw3tx0hpz3aawmzm6cufs6hx94d64ht5qawd0ej9ug9j2ffzsqmpecys
|
|
||||||
type: CosmWasmWormholeContract
|
|
|
@ -19,6 +19,9 @@
|
||||||
- chain: blast_s2_testnet
|
- chain: blast_s2_testnet
|
||||||
address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603"
|
address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603"
|
||||||
type: EvmEntropyContract
|
type: EvmEntropyContract
|
||||||
|
- chain: sei_evm_devnet
|
||||||
|
address: "0x6E3A2a644eeDCf6007d3c7d85F0094Cc1B25B2AE"
|
||||||
|
type: EvmEntropyContract
|
||||||
- chain: lightlink_phoenix
|
- chain: lightlink_phoenix
|
||||||
address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603"
|
address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603"
|
||||||
type: EvmEntropyContract
|
type: EvmEntropyContract
|
||||||
|
@ -55,12 +58,3 @@
|
||||||
- chain: zetachain
|
- chain: zetachain
|
||||||
address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320"
|
address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320"
|
||||||
type: EvmEntropyContract
|
type: EvmEntropyContract
|
||||||
- chain: base
|
|
||||||
address: "0x6E7D74FA7d5c90FEF9F0512987605a6d546181Bb"
|
|
||||||
type: EvmEntropyContract
|
|
||||||
- chain: sei_evm_devnet
|
|
||||||
address: "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509"
|
|
||||||
type: EvmEntropyContract
|
|
||||||
- chain: taiko_hekla
|
|
||||||
address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603"
|
|
||||||
type: EvmEntropyContract
|
|
||||||
|
|
|
@ -97,7 +97,7 @@
|
||||||
- chain: coredao
|
- chain: coredao
|
||||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
- chain: viction
|
- chain: tomochain
|
||||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
- chain: arbitrum_sepolia
|
- chain: arbitrum_sepolia
|
||||||
|
@ -142,6 +142,9 @@
|
||||||
- chain: meter_testnet
|
- chain: meter_testnet
|
||||||
address: "0x5a71C07a0588074443545eE0c08fb0375564c3E4"
|
address: "0x5a71C07a0588074443545eE0c08fb0375564c3E4"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
|
- chain: optimism_goerli
|
||||||
|
address: "0xDd24F84d36BF92C65F92307595335bdFab5Bbd21"
|
||||||
|
type: EvmPriceFeedContract
|
||||||
- chain: shimmer_testnet
|
- chain: shimmer_testnet
|
||||||
address: "0x8D254a21b3C86D32F7179855531CE99164721933"
|
address: "0x8D254a21b3C86D32F7179855531CE99164721933"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
|
@ -166,6 +169,9 @@
|
||||||
- chain: coredao_testnet
|
- chain: coredao_testnet
|
||||||
address: "0x8D254a21b3C86D32F7179855531CE99164721933"
|
address: "0x8D254a21b3C86D32F7179855531CE99164721933"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
|
- chain: tomochain_testnet
|
||||||
|
address: "0x5D289Ad1CE59fCC25b6892e7A303dfFf3a9f7167"
|
||||||
|
type: EvmPriceFeedContract
|
||||||
- chain: cronos_testnet
|
- chain: cronos_testnet
|
||||||
address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320"
|
address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
|
@ -196,6 +202,9 @@
|
||||||
- chain: neon_devnet
|
- chain: neon_devnet
|
||||||
address: "0x0708325268dF9F66270F1401206434524814508b"
|
address: "0x0708325268dF9F66270F1401206434524814508b"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
|
- chain: zksync_goerli
|
||||||
|
address: "0x8739d5024B5143278E2b15Bd9e7C26f6CEc658F1"
|
||||||
|
type: EvmPriceFeedContract
|
||||||
- chain: optimism_sepolia
|
- chain: optimism_sepolia
|
||||||
address: "0x0708325268dF9F66270F1401206434524814508b"
|
address: "0x0708325268dF9F66270F1401206434524814508b"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
|
@ -239,7 +248,7 @@
|
||||||
address: "0x2880aB155794e7179c9eE2e38200202908C17B43"
|
address: "0x2880aB155794e7179c9eE2e38200202908C17B43"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
- chain: sei_evm_devnet
|
- chain: sei_evm_devnet
|
||||||
address: "0xe9d69CdD6Fe41e7B621B4A688C5D1a68cB5c8ADc"
|
address: "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
- chain: lightlink_pegasus_testnet
|
- chain: lightlink_pegasus_testnet
|
||||||
address: "0x5D289Ad1CE59fCC25b6892e7A303dfFf3a9f7167"
|
address: "0x5D289Ad1CE59fCC25b6892e7A303dfFf3a9f7167"
|
||||||
|
@ -254,7 +263,7 @@
|
||||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
- chain: idex_xchain_testnet
|
- chain: idex_xchain_testnet
|
||||||
address: "0x2880aB155794e7179c9eE2e38200202908C17B43"
|
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
- chain: injective_inevm_testnet
|
- chain: injective_inevm_testnet
|
||||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
||||||
|
@ -289,36 +298,3 @@
|
||||||
- chain: manta_sepolia
|
- chain: manta_sepolia
|
||||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
||||||
type: EvmPriceFeedContract
|
type: EvmPriceFeedContract
|
||||||
- chain: polygon_blackberry
|
|
||||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
|
||||||
type: EvmPriceFeedContract
|
|
||||||
- chain: arbitrum_blueberry
|
|
||||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
|
||||||
type: EvmPriceFeedContract
|
|
||||||
- chain: optimism_celestia_raspberry
|
|
||||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
|
||||||
type: EvmPriceFeedContract
|
|
||||||
- chain: polynomial_testnet
|
|
||||||
address: "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509"
|
|
||||||
type: EvmPriceFeedContract
|
|
||||||
- chain: parallel_testnet
|
|
||||||
address: "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509"
|
|
||||||
type: EvmPriceFeedContract
|
|
||||||
- chain: parallel
|
|
||||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
|
||||||
type: EvmPriceFeedContract
|
|
||||||
- chain: linea_sepolia
|
|
||||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
|
||||||
type: EvmPriceFeedContract
|
|
||||||
- chain: morph_testnet
|
|
||||||
address: "0xA2aa501b19aff244D90cc15a4Cf739D2725B5729"
|
|
||||||
type: EvmPriceFeedContract
|
|
||||||
- chain: flow_previewnet
|
|
||||||
address: "0x2880aB155794e7179c9eE2e38200202908C17B43"
|
|
||||||
type: EvmPriceFeedContract
|
|
||||||
- chain: taiko_hekla
|
|
||||||
address: "0x2880aB155794e7179c9eE2e38200202908C17B43"
|
|
||||||
type: EvmPriceFeedContract
|
|
||||||
- chain: olive_testnet
|
|
||||||
address: "0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c"
|
|
||||||
type: EvmPriceFeedContract
|
|
||||||
|
|
|
@ -1,303 +0,0 @@
|
||||||
- chain: polygon
|
|
||||||
address: "0x35a58BeeE77a2Ad547FcDed7e8CB1c6e19746b13"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: aurora
|
|
||||||
address: "0x41955476936DdA8d0fA98b8d1778172F7E4fCcA1"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: fantom
|
|
||||||
address: "0x35a58BeeE77a2Ad547FcDed7e8CB1c6e19746b13"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: optimism
|
|
||||||
address: "0x87047526937246727E4869C5f76A347160e08672"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: arbitrum
|
|
||||||
address: "0xEbe57e8045F2F230872523bbff7374986E45C486"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: gnosis
|
|
||||||
address: "0x26DD80569a8B23768A1d80869Ed7339e07595E85"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: polygon_zkevm
|
|
||||||
address: "0x41955476936DdA8d0fA98b8d1778172F7E4fCcA1"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: conflux_espace
|
|
||||||
address: "0xDd24F84d36BF92C65F92307595335bdFab5Bbd21"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: bsc
|
|
||||||
address: "0x41955476936DdA8d0fA98b8d1778172F7E4fCcA1"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: kava
|
|
||||||
address: "0x0708325268dF9F66270F1401206434524814508b"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: avalanche
|
|
||||||
address: "0x41955476936DdA8d0fA98b8d1778172F7E4fCcA1"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: canto
|
|
||||||
address: "0xf0a1b566B55e0A0CB5BeF52Eb2a57142617Bee67"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: linea
|
|
||||||
address: "0x0708325268dF9F66270F1401206434524814508b"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: neon
|
|
||||||
address: "0xCd76c50c3210C5AaA9c39D53A4f95BFd8b1a3a19"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: mantle
|
|
||||||
address: "0xf0a1b566B55e0A0CB5BeF52Eb2a57142617Bee67"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: meter
|
|
||||||
address: "0xfA133831D350A2A5997d6db182B6Ca9e8ad4191B"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: kcc
|
|
||||||
address: "0x41955476936DdA8d0fA98b8d1778172F7E4fCcA1"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: eos
|
|
||||||
address: "0xEbe57e8045F2F230872523bbff7374986E45C486"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: celo
|
|
||||||
address: "0x41955476936DdA8d0fA98b8d1778172F7E4fCcA1"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: wemix
|
|
||||||
address: "0xEbe57e8045F2F230872523bbff7374986E45C486"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: base
|
|
||||||
address: "0x87047526937246727E4869C5f76A347160e08672"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: zksync
|
|
||||||
address: "0x53cD6960888cA09361506678adfE267b4CE81A08"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: horizen_eon
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: shimmer
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: boba
|
|
||||||
address: "0x26DD80569a8B23768A1d80869Ed7339e07595E85"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: manta
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: scroll
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: chiliz
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: coredao
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: viction
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: arbitrum_sepolia
|
|
||||||
address: "0xfA25E653b44586dBbe27eE9d252192F0e4956683"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: fuji
|
|
||||||
address: "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: canto_testnet
|
|
||||||
address: "0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: aurora_testnet
|
|
||||||
address: "0x2880aB155794e7179c9eE2e38200202908C17B43"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: chiado
|
|
||||||
address: "0x87047526937246727E4869C5f76A347160e08672"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: kava_testnet
|
|
||||||
address: "0xD458261E832415CFd3BAE5E416FdF3230ce6F134"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: conflux_espace_testnet
|
|
||||||
address: "0xEbe57e8045F2F230872523bbff7374986E45C486"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: celo_alfajores_testnet
|
|
||||||
address: "0x2880aB155794e7179c9eE2e38200202908C17B43"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: bsc_testnet
|
|
||||||
address: "0xe9d69CdD6Fe41e7B621B4A688C5D1a68cB5c8ADc"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: kcc_testnet
|
|
||||||
address: "0x2880aB155794e7179c9eE2e38200202908C17B43"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: eos_testnet
|
|
||||||
address: "0x8D254a21b3C86D32F7179855531CE99164721933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: meter_testnet
|
|
||||||
address: "0x257c3B61102442C1c3286Efbd24242322d002920"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: shimmer_testnet
|
|
||||||
address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: scroll_sepolia
|
|
||||||
address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: boba_goerli
|
|
||||||
address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: manta_testnet
|
|
||||||
address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: chiliz_spicy
|
|
||||||
address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: coredao_testnet
|
|
||||||
address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: cronos_testnet
|
|
||||||
address: "0x74f09cb3c7e2A01865f424FD14F6dc9A14E3e94E"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: wemix_testnet
|
|
||||||
address: "0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: evmos_testnet
|
|
||||||
address: "0x2880aB155794e7179c9eE2e38200202908C17B43"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: zetachain_testnet
|
|
||||||
address: "0x8D254a21b3C86D32F7179855531CE99164721933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: neon_devnet
|
|
||||||
address: "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: optimism_sepolia
|
|
||||||
address: "0x8D254a21b3C86D32F7179855531CE99164721933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: mode
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: mode_testnet
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: bttc_testnet
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: bttc
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: zksync_sepolia
|
|
||||||
address: "0xc10F5BE78E464BB0E1f534D66E5A6ecaB150aEFa"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: base_sepolia
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: movement_evm_devnet
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: zkfair_testnet
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: blast_s2_testnet
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: zkfair
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: filecoin_calibration
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: filecoin
|
|
||||||
address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: zetachain
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: sei_evm_devnet
|
|
||||||
address: "0x66E9cBa5529824a03B5Bc9931d9c63637101D0F7"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: lightlink_pegasus_testnet
|
|
||||||
address: "0x5f3c61944CEb01B3eAef861251Fb1E0f14b848fb"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: fantom_sonic_testnet
|
|
||||||
address: "0x74f09cb3c7e2A01865f424FD14F6dc9A14E3e94E"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: dela_deperp_testnet
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: lightlink_phoenix
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: injective_inevm_testnet
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: injective_inevm
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: hedera_testnet
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: hedera
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: berachain_testnet
|
|
||||||
address: "0x74f09cb3c7e2A01865f424FD14F6dc9A14E3e94E"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: blast
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: astar_zkevm
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: merlin_testnet
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: mantle_sepolia
|
|
||||||
address: "0x66E9cBa5529824a03B5Bc9931d9c63637101D0F7"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: merlin
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: manta_sepolia
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: polygon_blackberry
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: arbitrum_blueberry
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: optimism_celestia_raspberry
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: polynomial_testnet
|
|
||||||
address: "0x87047526937246727E4869C5f76A347160e08672"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: parallel_testnet
|
|
||||||
address: "0x87047526937246727E4869C5f76A347160e08672"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: parallel
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: linea_sepolia
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: morph_testnet
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: cronos
|
|
||||||
address: "0x41955476936DdA8d0fA98b8d1778172F7E4fCcA1"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: ronin
|
|
||||||
address: "0x41955476936DdA8d0fA98b8d1778172F7E4fCcA1"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: saigon
|
|
||||||
address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: ethereum
|
|
||||||
address: "0x74f09cb3c7e2A01865f424FD14F6dc9A14E3e94E"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: mumbai
|
|
||||||
address: "0x876A4e56A51386aBb1a5ab5d62f77E814372f0C7"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: fantom_testnet
|
|
||||||
address: "0xe9d69CdD6Fe41e7B621B4A688C5D1a68cB5c8ADc"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: sepolia
|
|
||||||
address: "0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: linea_goerli
|
|
||||||
address: "0xfA25E653b44586dBbe27eE9d252192F0e4956683"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: taiko_hekla
|
|
||||||
address: "0xb27e5ca259702f209a29225d0eDdC131039C9933"
|
|
||||||
type: EvmWormholeContract
|
|
||||||
- chain: olive_testnet
|
|
||||||
address: "0x74f09cb3c7e2A01865f424FD14F6dc9A14E3e94E"
|
|
||||||
type: EvmWormholeContract
|
|
|
@ -1,44 +0,0 @@
|
||||||
- id: ETH
|
|
||||||
pythId: ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace
|
|
||||||
decimals: 18
|
|
||||||
type: token
|
|
||||||
- id: APT
|
|
||||||
pythId: 03ae4db29ed4ae33d323568895aa00337e658e348b37509f5372ae51f0af00d5
|
|
||||||
decimals: 8
|
|
||||||
type: token
|
|
||||||
- id: EVMOS
|
|
||||||
pythId: c19405e4c8bdcbf2a66c37ae05a27d385c8309e9d648ed20dc6ee717e7d30e17
|
|
||||||
decimals: 18
|
|
||||||
type: token
|
|
||||||
- id: MATIC
|
|
||||||
pythId: 5de33a9112c2b700b8d30b8a3402c103578ccfa2765696471cc672bd5cf6ac52
|
|
||||||
decimals: 18
|
|
||||||
type: token
|
|
||||||
- id: NEAR
|
|
||||||
pythId: c415de8d2eba7db216527dff4b60e8f3a5311c740dadb233e13e12547e226750
|
|
||||||
decimals: 18
|
|
||||||
type: token
|
|
||||||
- id: FTM
|
|
||||||
pythId: 5c6c0d2386e3352356c3ab84434fafb5ea067ac2678a38a338c4a69ddc4bdb0c
|
|
||||||
decimals: 18
|
|
||||||
type: token
|
|
||||||
- id: DAI
|
|
||||||
pythId: b0948a5e5313200c632b51bb5ca32f6de0d36e9950a942d19751e833f70dabfd
|
|
||||||
decimals: 18
|
|
||||||
type: token
|
|
||||||
- id: KCS
|
|
||||||
pythId: c8acad81438490d4ebcac23b3e93f31cdbcb893fcba746ea1c66b89684faae2f
|
|
||||||
decimals: 18
|
|
||||||
type: token
|
|
||||||
- id: MNT
|
|
||||||
pythId: 4e3037c822d852d79af3ac80e35eb420ee3b870dca49f9344a38ef4773fb0585
|
|
||||||
decimals: 18
|
|
||||||
type: token
|
|
||||||
- id: HBAR
|
|
||||||
pythId: 3728e591097635310e6341af53db8b7ee42da9b3a8d918f9463ce9cca886dfbd
|
|
||||||
decimals: 8
|
|
||||||
type: token
|
|
||||||
- id: CORE
|
|
||||||
pythId: 9b4503710cc8c53f75c30e6e4fda1a7064680ef2e0ee97acd2e3a7c37b3c830c
|
|
||||||
decimals: 18
|
|
||||||
type: token
|
|
|
@ -1,6 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
plugins: ["@typescript-eslint"],
|
|
||||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
|
||||||
};
|
|
|
@ -1,5 +0,0 @@
|
||||||
lib/*
|
|
||||||
out
|
|
||||||
cache
|
|
||||||
tslib
|
|
||||||
!lib/README.md
|
|
|
@ -1,20 +0,0 @@
|
||||||
# EasyLend Protocol
|
|
||||||
|
|
||||||
EasyLend is a simplified lending protocol that uses Express Relay for avoiding value leakage on liquidations.
|
|
||||||
It uses Pyth price feeds to calculate the asset values and the liquidation thresholds.
|
|
||||||
|
|
||||||
This project illustrates how to use the Express Relay SDK for contract integration and publishing opportunities.
|
|
||||||
|
|
||||||
## Contracts
|
|
||||||
|
|
||||||
The contracts are located in the `contracts` directory. The `EasyLend.sol` file contains the main contract logic.
|
|
||||||
The protocol can allow creation of undercollateralized vaults that are liquidatable upon creation. This is solely
|
|
||||||
for ease of testing and demonstration purposes.
|
|
||||||
|
|
||||||
## Monitoring script
|
|
||||||
|
|
||||||
The script in `src/monitor.ts` is used to monitor the vaults health and publish the liquidation opportunities:
|
|
||||||
|
|
||||||
- It subscribes to Pyth price feeds to get the latest prices for the assets used in the protocol.
|
|
||||||
- It periodically checks for new vaults using the chain rpc.
|
|
||||||
- Upon finding a vault that is below the liquidation threshold, it publishes a liquidation opportunity using the Express Relay SDK.
|
|
|
@ -1,358 +0,0 @@
|
||||||
// Copyright (C) 2024 Lavra Holdings Limited - All Rights Reserved
|
|
||||||
pragma solidity ^0.8.13;
|
|
||||||
|
|
||||||
import "./EasyLendStructs.sol";
|
|
||||||
import "./EasyLendErrors.sol";
|
|
||||||
import "forge-std/StdMath.sol";
|
|
||||||
|
|
||||||
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
||||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
||||||
import "@openzeppelin/contracts/utils/Strings.sol";
|
|
||||||
|
|
||||||
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
|
|
||||||
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
|
|
||||||
import "@pythnetwork/express-relay-sdk-solidity/IExpressRelayFeeReceiver.sol";
|
|
||||||
import "@pythnetwork/express-relay-sdk-solidity/IExpressRelay.sol";
|
|
||||||
|
|
||||||
contract EasyLend is IExpressRelayFeeReceiver {
|
|
||||||
using SafeERC20 for IERC20;
|
|
||||||
|
|
||||||
event VaultReceivedETH(address sender, uint256 amount, bytes permissionKey);
|
|
||||||
|
|
||||||
uint256 _nVaults;
|
|
||||||
address public immutable expressRelay;
|
|
||||||
mapping(uint256 => Vault) _vaults;
|
|
||||||
address _oracle;
|
|
||||||
bool _allowUndercollateralized;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice EasyLend constructor - Initializes a new token vault contract with given parameters
|
|
||||||
*
|
|
||||||
* @param expressRelayAddress: address of the express relay
|
|
||||||
* @param oracleAddress: address of the oracle contract
|
|
||||||
* @param allowUndercollateralized: boolean to allow undercollateralized vaults to be created and updated. Can be set to true for testing.
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
address expressRelayAddress,
|
|
||||||
address oracleAddress,
|
|
||||||
bool allowUndercollateralized
|
|
||||||
) {
|
|
||||||
_nVaults = 0;
|
|
||||||
expressRelay = expressRelayAddress;
|
|
||||||
_oracle = oracleAddress;
|
|
||||||
_allowUndercollateralized = allowUndercollateralized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice getLastVaultId function - getter function to get the id of the next vault to be created
|
|
||||||
* Ids are sequential and start from 0
|
|
||||||
*/
|
|
||||||
function getLastVaultId() public view returns (uint256) {
|
|
||||||
return _nVaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice convertToUint function - converts a Pyth price struct to a uint256 representing the price of an asset
|
|
||||||
*
|
|
||||||
* @param price: Pyth price struct to be converted
|
|
||||||
* @param targetDecimals: target number of decimals for the output
|
|
||||||
*/
|
|
||||||
function convertToUint(
|
|
||||||
PythStructs.Price memory price,
|
|
||||||
uint8 targetDecimals
|
|
||||||
) private pure returns (uint256) {
|
|
||||||
if (price.price < 0 || price.expo > 0 || price.expo < -255) {
|
|
||||||
revert InvalidPriceExponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8 priceDecimals = uint8(uint32(-1 * price.expo));
|
|
||||||
|
|
||||||
if (targetDecimals >= priceDecimals) {
|
|
||||||
return
|
|
||||||
uint(uint64(price.price)) *
|
|
||||||
10 ** uint32(targetDecimals - priceDecimals);
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
uint(uint64(price.price)) /
|
|
||||||
10 ** uint32(priceDecimals - targetDecimals);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice getPrice function - retrieves price of a given token from the oracle
|
|
||||||
*
|
|
||||||
* @param id: price feed Id of the token
|
|
||||||
*/
|
|
||||||
function _getPrice(bytes32 id) internal view returns (uint256) {
|
|
||||||
IPyth oracle = IPyth(payable(_oracle));
|
|
||||||
return convertToUint(oracle.getPrice(id), 18);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAllowUndercollateralized() public view returns (bool) {
|
|
||||||
return _allowUndercollateralized;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOracle() public view returns (address) {
|
|
||||||
return _oracle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice getVaultHealth function - calculates vault collateral/debt ratio
|
|
||||||
*
|
|
||||||
* @param vaultId: Id of the vault for which to calculate health
|
|
||||||
*/
|
|
||||||
function getVaultHealth(uint256 vaultId) public view returns (uint256) {
|
|
||||||
Vault memory vault = _vaults[vaultId];
|
|
||||||
return _getVaultHealth(vault);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice _getVaultHealth function - calculates vault collateral/debt ratio using the on-chain price feeds.
|
|
||||||
* In a real world scenario, caller should ensure that the price feeds are up to date before calling this function.
|
|
||||||
*
|
|
||||||
* @param vault: vault struct containing vault parameters
|
|
||||||
*/
|
|
||||||
function _getVaultHealth(
|
|
||||||
Vault memory vault
|
|
||||||
) internal view returns (uint256) {
|
|
||||||
uint256 priceCollateral = _getPrice(vault.tokenIdCollateral);
|
|
||||||
uint256 priceDebt = _getPrice(vault.tokenIdDebt);
|
|
||||||
|
|
||||||
if (priceCollateral < 0) {
|
|
||||||
revert NegativePrice();
|
|
||||||
}
|
|
||||||
if (priceDebt < 0) {
|
|
||||||
revert NegativePrice();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 valueCollateral = priceCollateral * vault.amountCollateral;
|
|
||||||
uint256 valueDebt = priceDebt * vault.amountDebt;
|
|
||||||
|
|
||||||
return (valueCollateral * 1_000_000_000_000_000_000) / valueDebt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice createVault function - creates a vault
|
|
||||||
*
|
|
||||||
* @param tokenCollateral: address of the collateral token of the vault
|
|
||||||
* @param tokenDebt: address of the debt token of the vault
|
|
||||||
* @param amountCollateral: amount of collateral tokens in the vault
|
|
||||||
* @param amountDebt: amount of debt tokens in the vault
|
|
||||||
* @param minHealthRatio: minimum health ratio of the vault, 10**18 is 100%
|
|
||||||
* @param minPermissionlessHealthRatio: minimum health ratio of the vault before permissionless liquidations are allowed. This should be less than minHealthRatio
|
|
||||||
* @param tokenIdCollateral: price feed Id of the collateral token
|
|
||||||
* @param tokenIdDebt: price feed Id of the debt token
|
|
||||||
* @param updateData: data to update price feeds with
|
|
||||||
*/
|
|
||||||
function createVault(
|
|
||||||
address tokenCollateral,
|
|
||||||
address tokenDebt,
|
|
||||||
uint256 amountCollateral,
|
|
||||||
uint256 amountDebt,
|
|
||||||
uint256 minHealthRatio,
|
|
||||||
uint256 minPermissionlessHealthRatio,
|
|
||||||
bytes32 tokenIdCollateral,
|
|
||||||
bytes32 tokenIdDebt,
|
|
||||||
bytes[] calldata updateData
|
|
||||||
) public payable returns (uint256) {
|
|
||||||
_updatePriceFeeds(updateData);
|
|
||||||
Vault memory vault = Vault(
|
|
||||||
tokenCollateral,
|
|
||||||
tokenDebt,
|
|
||||||
amountCollateral,
|
|
||||||
amountDebt,
|
|
||||||
minHealthRatio,
|
|
||||||
minPermissionlessHealthRatio,
|
|
||||||
tokenIdCollateral,
|
|
||||||
tokenIdDebt
|
|
||||||
);
|
|
||||||
if (minPermissionlessHealthRatio > minHealthRatio) {
|
|
||||||
revert InvalidHealthRatios();
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!_allowUndercollateralized &&
|
|
||||||
_getVaultHealth(vault) < vault.minHealthRatio
|
|
||||||
) {
|
|
||||||
revert UncollateralizedVaultCreation();
|
|
||||||
}
|
|
||||||
|
|
||||||
IERC20(vault.tokenCollateral).safeTransferFrom(
|
|
||||||
msg.sender,
|
|
||||||
address(this),
|
|
||||||
vault.amountCollateral
|
|
||||||
);
|
|
||||||
IERC20(vault.tokenDebt).safeTransfer(msg.sender, vault.amountDebt);
|
|
||||||
|
|
||||||
_vaults[_nVaults] = vault;
|
|
||||||
_nVaults += 1;
|
|
||||||
|
|
||||||
return _nVaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice updateVault function - updates a vault's collateral and debt amounts
|
|
||||||
*
|
|
||||||
* @param vaultId: Id of the vault to be updated
|
|
||||||
* @param deltaCollateral: delta change to collateral amount (+ means adding collateral tokens, - means removing collateral tokens)
|
|
||||||
* @param deltaDebt: delta change to debt amount (+ means withdrawing debt tokens from protocol, - means resending debt tokens to protocol)
|
|
||||||
*/
|
|
||||||
function updateVault(
|
|
||||||
uint256 vaultId,
|
|
||||||
int256 deltaCollateral,
|
|
||||||
int256 deltaDebt
|
|
||||||
) public {
|
|
||||||
Vault memory vault = _vaults[vaultId];
|
|
||||||
|
|
||||||
uint256 qCollateral = stdMath.abs(deltaCollateral);
|
|
||||||
uint256 qDebt = stdMath.abs(deltaDebt);
|
|
||||||
|
|
||||||
bool withdrawExcessiveCollateral = (deltaCollateral < 0) &&
|
|
||||||
(qCollateral > vault.amountCollateral);
|
|
||||||
|
|
||||||
if (withdrawExcessiveCollateral) {
|
|
||||||
revert InvalidVaultUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 futureCollateral = (deltaCollateral >= 0)
|
|
||||||
? (vault.amountCollateral + qCollateral)
|
|
||||||
: (vault.amountCollateral - qCollateral);
|
|
||||||
uint256 futureDebt = (deltaDebt >= 0)
|
|
||||||
? (vault.amountDebt + qDebt)
|
|
||||||
: (vault.amountDebt - qDebt);
|
|
||||||
|
|
||||||
vault.amountCollateral = futureCollateral;
|
|
||||||
vault.amountDebt = futureDebt;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!_allowUndercollateralized &&
|
|
||||||
_getVaultHealth(vault) < vault.minHealthRatio
|
|
||||||
) {
|
|
||||||
revert InvalidVaultUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// update collateral position
|
|
||||||
if (deltaCollateral >= 0) {
|
|
||||||
// sender adds more collateral to their vault
|
|
||||||
IERC20(vault.tokenCollateral).safeTransferFrom(
|
|
||||||
msg.sender,
|
|
||||||
address(this),
|
|
||||||
qCollateral
|
|
||||||
);
|
|
||||||
_vaults[vaultId].amountCollateral += qCollateral;
|
|
||||||
} else {
|
|
||||||
// sender takes back collateral from their vault
|
|
||||||
IERC20(vault.tokenCollateral).safeTransfer(msg.sender, qCollateral);
|
|
||||||
_vaults[vaultId].amountCollateral -= qCollateral;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update debt position
|
|
||||||
if (deltaDebt >= 0) {
|
|
||||||
// sender takes out more debt position
|
|
||||||
IERC20(vault.tokenDebt).safeTransfer(msg.sender, qDebt);
|
|
||||||
_vaults[vaultId].amountDebt += qDebt;
|
|
||||||
} else {
|
|
||||||
// sender sends back debt tokens
|
|
||||||
IERC20(vault.tokenDebt).safeTransferFrom(
|
|
||||||
msg.sender,
|
|
||||||
address(this),
|
|
||||||
qDebt
|
|
||||||
);
|
|
||||||
_vaults[vaultId].amountDebt -= qDebt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice getVault function - getter function to get a vault's parameters
|
|
||||||
*
|
|
||||||
* @param vaultId: Id of the vault
|
|
||||||
*/
|
|
||||||
function getVault(uint256 vaultId) public view returns (Vault memory) {
|
|
||||||
return _vaults[vaultId];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice _updatePriceFeeds function - updates the specified price feeds with given data
|
|
||||||
*
|
|
||||||
* @param updateData: data to update price feeds with
|
|
||||||
*/
|
|
||||||
function _updatePriceFeeds(bytes[] calldata updateData) internal {
|
|
||||||
if (updateData.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
IPyth oracle = IPyth(payable(_oracle));
|
|
||||||
oracle.updatePriceFeeds{value: msg.value}(updateData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice liquidate function - liquidates a vault
|
|
||||||
* This function calculates the health of the vault and based on the vault parameters one of the following actions is taken:
|
|
||||||
* 1. If health >= minHealthRatio, don't liquidate
|
|
||||||
* 2. If minHealthRatio > health >= minPermissionlessHealthRatio, only liquidate if the vault is permissioned via express relay
|
|
||||||
* 3. If minPermissionlessHealthRatio > health, liquidate no matter what
|
|
||||||
*
|
|
||||||
* @param vaultId: Id of the vault to be liquidated
|
|
||||||
*/
|
|
||||||
function liquidate(uint256 vaultId) public {
|
|
||||||
Vault memory vault = _vaults[vaultId];
|
|
||||||
uint256 vaultHealth = _getVaultHealth(vault);
|
|
||||||
|
|
||||||
// if vault health is above the minimum health ratio, don't liquidate
|
|
||||||
if (vaultHealth >= vault.minHealthRatio) {
|
|
||||||
revert InvalidLiquidation();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vaultHealth >= vault.minPermissionlessHealthRatio) {
|
|
||||||
// if vault health is below the minimum health ratio but above the minimum permissionless health ratio,
|
|
||||||
// only liquidate if permissioned
|
|
||||||
if (
|
|
||||||
!IExpressRelay(expressRelay).isPermissioned(
|
|
||||||
address(this), // protocol fee receiver
|
|
||||||
abi.encode(vaultId) // vault id uniquely represents the opportunity and can be used as permission id
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
revert InvalidLiquidation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IERC20(vault.tokenDebt).transferFrom(
|
|
||||||
msg.sender,
|
|
||||||
address(this),
|
|
||||||
vault.amountDebt
|
|
||||||
);
|
|
||||||
IERC20(vault.tokenCollateral).transfer(
|
|
||||||
msg.sender,
|
|
||||||
vault.amountCollateral
|
|
||||||
);
|
|
||||||
|
|
||||||
_vaults[vaultId].amountCollateral = 0;
|
|
||||||
_vaults[vaultId].amountDebt = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice liquidateWithPriceUpdate function - liquidates a vault after updating the specified price feeds with given data
|
|
||||||
*
|
|
||||||
* @param vaultId: Id of the vault to be liquidated
|
|
||||||
* @param updateData: data to update price feeds with
|
|
||||||
*/
|
|
||||||
function liquidateWithPriceUpdate(
|
|
||||||
uint256 vaultId,
|
|
||||||
bytes[] calldata updateData
|
|
||||||
) external payable {
|
|
||||||
_updatePriceFeeds(updateData);
|
|
||||||
liquidate(vaultId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice receiveAuctionProceedings function - receives native token from the express relay
|
|
||||||
* You can use permission key to distribute the received funds to users who got liquidated, LPs, etc...
|
|
||||||
*
|
|
||||||
* @param permissionKey: permission key that was used for the auction
|
|
||||||
*/
|
|
||||||
function receiveAuctionProceedings(
|
|
||||||
bytes calldata permissionKey
|
|
||||||
) external payable {
|
|
||||||
emit VaultReceivedETH(msg.sender, msg.value, permissionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
receive() external payable {}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
// Copyright (C) 2024 Lavra Holdings Limited - All Rights Reserved
|
|
||||||
pragma solidity ^0.8.13;
|
|
||||||
|
|
||||||
// Signature: 0xe922edfd
|
|
||||||
error UncollateralizedVaultCreation();
|
|
||||||
|
|
||||||
// Signature: 0xdcb430ee
|
|
||||||
error InvalidVaultUpdate();
|
|
||||||
|
|
||||||
// Signature: 0x9cd7b1c6
|
|
||||||
error InvalidPriceExponent();
|
|
||||||
|
|
||||||
// Signature: 0x85914873
|
|
||||||
error InvalidLiquidation();
|
|
||||||
|
|
||||||
// Signature: 0x61ca76d2
|
|
||||||
error NegativePrice();
|
|
||||||
|
|
||||||
// Signature: 0x4a7a3163
|
|
||||||
error InvalidHealthRatios();
|
|
|
@ -1,13 +0,0 @@
|
||||||
// Copyright (C) 2024 Lavra Holdings Limited - All Rights Reserved
|
|
||||||
pragma solidity ^0.8.13;
|
|
||||||
|
|
||||||
struct Vault {
|
|
||||||
address tokenCollateral;
|
|
||||||
address tokenDebt;
|
|
||||||
uint256 amountCollateral;
|
|
||||||
uint256 amountDebt;
|
|
||||||
uint256 minHealthRatio; // 10**18 is 100%
|
|
||||||
uint256 minPermissionlessHealthRatio;
|
|
||||||
bytes32 tokenIdCollateral;
|
|
||||||
bytes32 tokenIdDebt;
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
[profile.default]
|
|
||||||
src = "contracts"
|
|
||||||
out = "out"
|
|
||||||
libs = [
|
|
||||||
'lib',
|
|
||||||
'../../../node_modules',
|
|
||||||
'../../../target_chains/ethereum/sdk/solidity'
|
|
||||||
]
|
|
||||||
|
|
||||||
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
|
|
|
@ -1 +0,0 @@
|
||||||
Forge installs the dependencies in this folder. They are .gitignored
|
|
|
@ -1,40 +0,0 @@
|
||||||
{
|
|
||||||
"name": "easylend",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "Example lending protocol with express relay integration",
|
|
||||||
"private": true,
|
|
||||||
"files": [
|
|
||||||
"tslib/**/*"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"lint": "eslint src/",
|
|
||||||
"format": "prettier --write \"src/**/*.ts\"",
|
|
||||||
"monitor": "npm run build && node tslib/monitor.js",
|
|
||||||
"install-forge-deps": "forge install foundry-rs/forge-std@v1.7.6 --no-git --no-commit"
|
|
||||||
},
|
|
||||||
"author": "",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/pyth-network/pyth-crosschain.git"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@openzeppelin/contracts": "^4.5.0",
|
|
||||||
"@pythnetwork/express-relay-evm-js": "*",
|
|
||||||
"@pythnetwork/express-relay-sdk-solidity": "*",
|
|
||||||
"@pythnetwork/pyth-evm-js": "*",
|
|
||||||
"@pythnetwork/pyth-sdk-solidity": "*",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"typescript": "^5.3.3",
|
|
||||||
"viem": "^2.7.6"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/yargs": "^17.0.10",
|
|
||||||
"eslint": "^8.56.0",
|
|
||||||
"prettier": "^2.6.2",
|
|
||||||
"typedoc": "^0.25.7",
|
|
||||||
"typescript": "^5.1",
|
|
||||||
"yargs": "^17.4.1"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
forge-std/=lib/forge-std/src/
|
|
||||||
@openzeppelin/=../../../node_modules/@openzeppelin/
|
|
||||||
@pythnetwork/=../../../node_modules/@pythnetwork/
|
|
|
@ -1,163 +0,0 @@
|
||||||
// This is only a subset of the generated abi necessary for the monitor script
|
|
||||||
export const abi = [
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
name: "getLastVaultId",
|
|
||||||
inputs: [],
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: "",
|
|
||||||
type: "uint256",
|
|
||||||
internalType: "uint256",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
stateMutability: "view",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
name: "getVault",
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: "vaultId",
|
|
||||||
type: "uint256",
|
|
||||||
internalType: "uint256",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: "",
|
|
||||||
type: "tuple",
|
|
||||||
internalType: "struct Vault",
|
|
||||||
components: [
|
|
||||||
{
|
|
||||||
name: "tokenCollateral",
|
|
||||||
type: "address",
|
|
||||||
internalType: "address",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tokenDebt",
|
|
||||||
type: "address",
|
|
||||||
internalType: "address",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "amountCollateral",
|
|
||||||
type: "uint256",
|
|
||||||
internalType: "uint256",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "amountDebt",
|
|
||||||
type: "uint256",
|
|
||||||
internalType: "uint256",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "minHealthRatio",
|
|
||||||
type: "uint256",
|
|
||||||
internalType: "uint256",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "minPermissionLessHealthRatio",
|
|
||||||
type: "uint256",
|
|
||||||
internalType: "uint256",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tokenIdCollateral",
|
|
||||||
type: "bytes32",
|
|
||||||
internalType: "bytes32",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tokenIdDebt",
|
|
||||||
type: "bytes32",
|
|
||||||
internalType: "bytes32",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
stateMutability: "view",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
name: "liquidate",
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: "vaultId",
|
|
||||||
type: "uint256",
|
|
||||||
internalType: "uint256",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
outputs: [],
|
|
||||||
stateMutability: "nonpayable",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
name: "liquidateWithPriceUpdate",
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: "vaultId",
|
|
||||||
type: "uint256",
|
|
||||||
internalType: "uint256",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "updateData",
|
|
||||||
type: "bytes[]",
|
|
||||||
internalType: "bytes[]",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
outputs: [],
|
|
||||||
stateMutability: "payable",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "event",
|
|
||||||
name: "VaultReceivedETH",
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: "sender",
|
|
||||||
type: "address",
|
|
||||||
indexed: false,
|
|
||||||
internalType: "address",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "amount",
|
|
||||||
type: "uint256",
|
|
||||||
indexed: false,
|
|
||||||
internalType: "uint256",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "permissionKey",
|
|
||||||
type: "bytes",
|
|
||||||
indexed: false,
|
|
||||||
internalType: "bytes",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
anonymous: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "error",
|
|
||||||
name: "InvalidHealthRatios",
|
|
||||||
inputs: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "error",
|
|
||||||
name: "InvalidLiquidation",
|
|
||||||
inputs: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "error",
|
|
||||||
name: "InvalidPriceExponent",
|
|
||||||
inputs: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "error",
|
|
||||||
name: "InvalidVaultUpdate",
|
|
||||||
inputs: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "error",
|
|
||||||
name: "NegativePrice",
|
|
||||||
inputs: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "error",
|
|
||||||
name: "UncollateralizedVaultCreation",
|
|
||||||
inputs: [],
|
|
||||||
},
|
|
||||||
] as const;
|
|
|
@ -1,239 +0,0 @@
|
||||||
import yargs from "yargs";
|
|
||||||
import { hideBin } from "yargs/helpers";
|
|
||||||
import {
|
|
||||||
checkAddress,
|
|
||||||
Client,
|
|
||||||
OpportunityParams,
|
|
||||||
} from "@pythnetwork/express-relay-evm-js";
|
|
||||||
import { privateKeyToAccount } from "viem/accounts";
|
|
||||||
import type { ContractFunctionReturnType } from "viem";
|
|
||||||
import {
|
|
||||||
Address,
|
|
||||||
createPublicClient,
|
|
||||||
encodeAbiParameters,
|
|
||||||
encodeFunctionData,
|
|
||||||
getContract,
|
|
||||||
Hex,
|
|
||||||
http,
|
|
||||||
isHex,
|
|
||||||
} from "viem";
|
|
||||||
import { optimismSepolia } from "viem/chains";
|
|
||||||
import { abi } from "./abi";
|
|
||||||
import {
|
|
||||||
PriceFeed,
|
|
||||||
PriceServiceConnection,
|
|
||||||
} from "@pythnetwork/price-service-client";
|
|
||||||
|
|
||||||
type VaultWithId = ContractFunctionReturnType<
|
|
||||||
typeof abi,
|
|
||||||
"view",
|
|
||||||
"getVault"
|
|
||||||
> & { id: bigint };
|
|
||||||
class ProtocolMonitor {
|
|
||||||
private client: Client;
|
|
||||||
private subscribedIds: Set<string> = new Set();
|
|
||||||
private prices: Record<Hex, PriceFeed> = {};
|
|
||||||
private priceConnection: PriceServiceConnection;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
expressRelayEndpoint: string,
|
|
||||||
pythEndpoint: string,
|
|
||||||
private chainId: string,
|
|
||||||
private wethContract: Address,
|
|
||||||
private vaultContract: Address,
|
|
||||||
private onlyRecent: number | undefined
|
|
||||||
) {
|
|
||||||
this.client = new Client({ baseUrl: expressRelayEndpoint });
|
|
||||||
this.priceConnection = new PriceServiceConnection(pythEndpoint, {
|
|
||||||
priceFeedRequestConfig: { binary: true },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePrice(feed: PriceFeed) {
|
|
||||||
this.prices[`0x${feed.id}`] = feed;
|
|
||||||
}
|
|
||||||
|
|
||||||
async subscribeToPriceFeed(tokenId: string) {
|
|
||||||
if (!this.subscribedIds.has(tokenId)) {
|
|
||||||
await this.priceConnection.subscribePriceFeedUpdates(
|
|
||||||
[tokenId],
|
|
||||||
this.updatePrice.bind(this)
|
|
||||||
);
|
|
||||||
this.subscribedIds.add(tokenId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkVaults() {
|
|
||||||
const rpcClient = createPublicClient({
|
|
||||||
chain: optimismSepolia,
|
|
||||||
transport: http(),
|
|
||||||
});
|
|
||||||
const contract = getContract({
|
|
||||||
address: this.vaultContract,
|
|
||||||
abi,
|
|
||||||
client: rpcClient,
|
|
||||||
});
|
|
||||||
const lastVaultId = await contract.read.getLastVaultId();
|
|
||||||
const vaults: VaultWithId[] = [];
|
|
||||||
let startVaultId = 0n;
|
|
||||||
if (this.onlyRecent && lastVaultId > BigInt(this.onlyRecent)) {
|
|
||||||
startVaultId = lastVaultId - BigInt(this.onlyRecent);
|
|
||||||
}
|
|
||||||
for (let vaultId = startVaultId; vaultId < lastVaultId; vaultId++) {
|
|
||||||
const vault = await contract.read.getVault([vaultId]);
|
|
||||||
// Already liquidated vault
|
|
||||||
if (vault.amountCollateral == 0n && vault.amountDebt == 0n) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
vaults.push({ id: vaultId, ...vault });
|
|
||||||
await this.subscribeToPriceFeed(vault.tokenIdCollateral);
|
|
||||||
await this.subscribeToPriceFeed(vault.tokenIdDebt);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const vault of vaults) {
|
|
||||||
if (this.isLiquidatable(vault)) {
|
|
||||||
const opportunity = this.createOpportunity(vault);
|
|
||||||
await this.client.submitOpportunity(opportunity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async start() {
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
while (true) {
|
|
||||||
await this.checkVaults();
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private createOpportunity(vault: VaultWithId) {
|
|
||||||
const priceUpdates = [
|
|
||||||
this.prices[vault.tokenIdCollateral].getVAA()!,
|
|
||||||
this.prices[vault.tokenIdDebt].getVAA()!,
|
|
||||||
];
|
|
||||||
const vaas: Hex[] = priceUpdates.map(
|
|
||||||
(vaa): Hex => `0x${Buffer.from(vaa, "base64").toString("hex")}`
|
|
||||||
);
|
|
||||||
const calldata = encodeFunctionData({
|
|
||||||
abi,
|
|
||||||
functionName: "liquidateWithPriceUpdate",
|
|
||||||
args: [vault.id, vaas],
|
|
||||||
});
|
|
||||||
const permission = this.createPermission(vault.id);
|
|
||||||
const targetCallValue = BigInt(priceUpdates.length);
|
|
||||||
let sellTokens;
|
|
||||||
if (targetCallValue > 0 && vault.tokenDebt == this.wethContract) {
|
|
||||||
sellTokens = [
|
|
||||||
{
|
|
||||||
token: this.wethContract,
|
|
||||||
amount: targetCallValue + vault.amountDebt,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
sellTokens = [
|
|
||||||
{ token: vault.tokenDebt, amount: vault.amountDebt },
|
|
||||||
{ token: this.wethContract, amount: targetCallValue },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
const opportunity: OpportunityParams = {
|
|
||||||
chainId: this.chainId,
|
|
||||||
targetContract: this.vaultContract,
|
|
||||||
targetCalldata: calldata,
|
|
||||||
permissionKey: permission,
|
|
||||||
targetCallValue: targetCallValue,
|
|
||||||
buyTokens: [
|
|
||||||
{ token: vault.tokenCollateral, amount: vault.amountCollateral },
|
|
||||||
],
|
|
||||||
sellTokens: sellTokens,
|
|
||||||
};
|
|
||||||
return opportunity;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isLiquidatable(vault: VaultWithId): boolean {
|
|
||||||
if (
|
|
||||||
!this.prices[vault.tokenIdCollateral] ||
|
|
||||||
!this.prices[vault.tokenIdDebt]
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const priceCollateral = BigInt(
|
|
||||||
this.prices[vault.tokenIdCollateral].getPriceUnchecked().price
|
|
||||||
);
|
|
||||||
const priceDebt = BigInt(
|
|
||||||
this.prices[vault.tokenIdDebt].getPriceUnchecked().price
|
|
||||||
);
|
|
||||||
const valueCollateral = priceCollateral * vault.amountCollateral;
|
|
||||||
const valueDebt = priceDebt * vault.amountDebt;
|
|
||||||
if (valueDebt * vault.minHealthRatio > valueCollateral * 10n ** 18n) {
|
|
||||||
const health = Number(valueCollateral) / Number(valueDebt);
|
|
||||||
console.log(`Vault ${vault.id} is undercollateralized health: ${health}`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private createPermission(vaultId: bigint) {
|
|
||||||
const permissionPayload = encodeAbiParameters(
|
|
||||||
[{ type: "uint256", name: "vaultId" }],
|
|
||||||
[vaultId]
|
|
||||||
);
|
|
||||||
const permission = encodeAbiParameters(
|
|
||||||
[
|
|
||||||
{ type: "address", name: "contract" },
|
|
||||||
{ type: "bytes", name: "vaultId" },
|
|
||||||
],
|
|
||||||
[this.vaultContract, permissionPayload]
|
|
||||||
);
|
|
||||||
return permission;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const argv = yargs(hideBin(process.argv))
|
|
||||||
.option("express-relay-endpoint", {
|
|
||||||
description:
|
|
||||||
"Express relay endpoint. e.g: https://per-staging.dourolabs.app/",
|
|
||||||
type: "string",
|
|
||||||
default: "https://per-staging.dourolabs.app/",
|
|
||||||
})
|
|
||||||
.option("pyth-endpoint", {
|
|
||||||
description: "Pyth endpoint to use for fetching prices",
|
|
||||||
type: "string",
|
|
||||||
default: "https://hermes.pyth.network",
|
|
||||||
})
|
|
||||||
.option("chain-id", {
|
|
||||||
description: "Chain id to send opportunities for. e.g: sepolia",
|
|
||||||
type: "string",
|
|
||||||
demandOption: true,
|
|
||||||
})
|
|
||||||
.option("weth-contract", {
|
|
||||||
description: "wrapped eth contract address",
|
|
||||||
type: "string",
|
|
||||||
demandOption: true,
|
|
||||||
})
|
|
||||||
.option("vault-contract", {
|
|
||||||
description: "Dummy token vault contract address",
|
|
||||||
type: "string",
|
|
||||||
demandOption: true,
|
|
||||||
})
|
|
||||||
.option("only-recent", {
|
|
||||||
description:
|
|
||||||
"Instead of checking all vaults, only check recent ones. Specify the number of recent vaults to check",
|
|
||||||
type: "number",
|
|
||||||
})
|
|
||||||
.help()
|
|
||||||
.alias("help", "h")
|
|
||||||
.parseSync();
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
const monitor = new ProtocolMonitor(
|
|
||||||
argv.expressRelayEndpoint,
|
|
||||||
argv.pythEndpoint,
|
|
||||||
argv.chainId,
|
|
||||||
checkAddress(argv.wethContract),
|
|
||||||
checkAddress(argv.vaultContract),
|
|
||||||
argv.onlyRecent
|
|
||||||
);
|
|
||||||
await monitor.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
run();
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../../../tsconfig.base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "esnext",
|
|
||||||
"module": "commonjs",
|
|
||||||
"declaration": true,
|
|
||||||
"rootDir": "src/",
|
|
||||||
"outDir": "./tslib",
|
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"resolveJsonModule": true
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "**/__tests__/*"]
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@pythnetwork/express-relay-evm-js",
|
"name": "@pythnetwork/express-relay-evm-js",
|
||||||
"version": "0.4.1",
|
"version": "0.1.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@pythnetwork/express-relay-evm-js",
|
"name": "@pythnetwork/express-relay-evm-js",
|
||||||
"version": "0.4.1",
|
"version": "0.2.0",
|
||||||
"description": "Utilities for interacting with the express relay protocol",
|
"description": "Utilities for interacting with the express relay protocol",
|
||||||
"homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/express_relay/sdk/js",
|
"homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/express_relay/sdk/js",
|
||||||
"author": "Douro Labs",
|
"author": "Douro Labs",
|
||||||
|
@ -42,8 +42,6 @@
|
||||||
"ws": "^8.16.0"
|
"ws": "^8.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@pythnetwork/pyth-evm-js": "*",
|
|
||||||
"@types/node": "^20.12.7",
|
|
||||||
"@types/yargs": "^17.0.10",
|
"@types/yargs": "^17.0.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.21.0",
|
"@typescript-eslint/eslint-plugin": "^5.21.0",
|
||||||
"@typescript-eslint/parser": "^5.21.0",
|
"@typescript-eslint/parser": "^5.21.0",
|
||||||
|
|
|
@ -23,17 +23,10 @@ class SimpleSearcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
async bidStatusHandler(bidStatus: BidStatusUpdate) {
|
async bidStatusHandler(bidStatus: BidStatusUpdate) {
|
||||||
let resultDetails = "";
|
|
||||||
if (bidStatus.type == "submitted") {
|
|
||||||
resultDetails = `, transaction ${bidStatus.result}, index ${bidStatus.index} of multicall`;
|
|
||||||
} else if (bidStatus.type == "lost") {
|
|
||||||
resultDetails = `, transaction ${bidStatus.result}`;
|
|
||||||
}
|
|
||||||
console.log(
|
console.log(
|
||||||
`Bid status for bid ${bidStatus.id}: ${bidStatus.type.replaceAll(
|
`Bid status for bid ${bidStatus.id}: ${bidStatus.status} ${
|
||||||
"_",
|
bidStatus.status == "submitted" ? bidStatus.result : ""
|
||||||
" "
|
}`
|
||||||
)}${resultDetails}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,15 @@ import type { components, paths } from "./serverTypes";
|
||||||
import createClient, {
|
import createClient, {
|
||||||
ClientOptions as FetchClientOptions,
|
ClientOptions as FetchClientOptions,
|
||||||
} from "openapi-fetch";
|
} from "openapi-fetch";
|
||||||
import { Address, Hex, isAddress, isHex } from "viem";
|
import {
|
||||||
import { privateKeyToAccount, signTypedData } from "viem/accounts";
|
Address,
|
||||||
|
encodeAbiParameters,
|
||||||
|
Hex,
|
||||||
|
isAddress,
|
||||||
|
isHex,
|
||||||
|
keccak256,
|
||||||
|
} from "viem";
|
||||||
|
import { privateKeyToAccount, sign, signatureToHex } from "viem/accounts";
|
||||||
import WebSocket from "isomorphic-ws";
|
import WebSocket from "isomorphic-ws";
|
||||||
import {
|
import {
|
||||||
Bid,
|
Bid,
|
||||||
|
@ -11,7 +18,6 @@ import {
|
||||||
BidParams,
|
BidParams,
|
||||||
BidStatusUpdate,
|
BidStatusUpdate,
|
||||||
Opportunity,
|
Opportunity,
|
||||||
EIP712Domain,
|
|
||||||
OpportunityBid,
|
OpportunityBid,
|
||||||
OpportunityParams,
|
OpportunityParams,
|
||||||
TokenAmount,
|
TokenAmount,
|
||||||
|
@ -130,17 +136,6 @@ export class Client {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertEIP712Domain(
|
|
||||||
eip712Domain: components["schemas"]["EIP712Domain"]
|
|
||||||
): EIP712Domain {
|
|
||||||
return {
|
|
||||||
name: eip712Domain.name,
|
|
||||||
version: eip712Domain.version,
|
|
||||||
verifyingContract: checkAddress(eip712Domain.verifying_contract),
|
|
||||||
chainId: BigInt(eip712Domain.chain_id),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an opportunity from the server to the client format
|
* Converts an opportunity from the server to the client format
|
||||||
* Returns undefined if the opportunity version is not supported
|
* Returns undefined if the opportunity version is not supported
|
||||||
|
@ -164,7 +159,6 @@ export class Client {
|
||||||
targetCallValue: BigInt(opportunity.target_call_value),
|
targetCallValue: BigInt(opportunity.target_call_value),
|
||||||
sellTokens: opportunity.sell_tokens.map(checkTokenQty),
|
sellTokens: opportunity.sell_tokens.map(checkTokenQty),
|
||||||
buyTokens: opportunity.buy_tokens.map(checkTokenQty),
|
buyTokens: opportunity.buy_tokens.map(checkTokenQty),
|
||||||
eip712Domain: this.convertEIP712Domain(opportunity.eip_712_domain),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,49 +293,62 @@ export class Client {
|
||||||
bidParams: BidParams,
|
bidParams: BidParams,
|
||||||
privateKey: Hex
|
privateKey: Hex
|
||||||
): Promise<OpportunityBid> {
|
): Promise<OpportunityBid> {
|
||||||
const types = {
|
|
||||||
ExecutionParams: [
|
|
||||||
{ name: "sellTokens", type: "TokenAmount[]" },
|
|
||||||
{ name: "buyTokens", type: "TokenAmount[]" },
|
|
||||||
{ name: "executor", type: "address" },
|
|
||||||
{ name: "targetContract", type: "address" },
|
|
||||||
{ name: "targetCalldata", type: "bytes" },
|
|
||||||
{ name: "targetCallValue", type: "uint256" },
|
|
||||||
{ name: "validUntil", type: "uint256" },
|
|
||||||
{ name: "bidAmount", type: "uint256" },
|
|
||||||
],
|
|
||||||
TokenAmount: [
|
|
||||||
{ name: "token", type: "address" },
|
|
||||||
{ name: "amount", type: "uint256" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const account = privateKeyToAccount(privateKey);
|
const account = privateKeyToAccount(privateKey);
|
||||||
const signature = await signTypedData({
|
const convertTokenQty = ({ token, amount }: TokenAmount): [Hex, bigint] => [
|
||||||
privateKey,
|
token,
|
||||||
domain: {
|
amount,
|
||||||
...opportunity.eip712Domain,
|
];
|
||||||
chainId: Number(opportunity.eip712Domain.chainId),
|
const payload = encodeAbiParameters(
|
||||||
},
|
[
|
||||||
types,
|
{
|
||||||
primaryType: "ExecutionParams",
|
name: "repayTokens",
|
||||||
message: {
|
type: "tuple[]",
|
||||||
sellTokens: opportunity.sellTokens,
|
components: [
|
||||||
buyTokens: opportunity.buyTokens,
|
{
|
||||||
executor: account.address,
|
type: "address",
|
||||||
targetContract: opportunity.targetContract,
|
},
|
||||||
targetCalldata: opportunity.targetCalldata,
|
{
|
||||||
targetCallValue: opportunity.targetCallValue,
|
type: "uint256",
|
||||||
validUntil: bidParams.validUntil,
|
},
|
||||||
bidAmount: bidParams.amount,
|
],
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
|
name: "receiptTokens",
|
||||||
|
type: "tuple[]",
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: "contract", type: "address" },
|
||||||
|
{ name: "calldata", type: "bytes" },
|
||||||
|
{ name: "value", type: "uint256" },
|
||||||
|
{ name: "bid", type: "uint256" },
|
||||||
|
{ name: "validUntil", type: "uint256" },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
opportunity.sellTokens.map(convertTokenQty),
|
||||||
|
opportunity.buyTokens.map(convertTokenQty),
|
||||||
|
opportunity.targetContract,
|
||||||
|
opportunity.targetCalldata,
|
||||||
|
opportunity.targetCallValue,
|
||||||
|
bidParams.amount,
|
||||||
|
bidParams.validUntil,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const msgHash = keccak256(payload);
|
||||||
|
|
||||||
|
const hash = signatureToHex(await sign({ hash: msgHash, privateKey }));
|
||||||
return {
|
return {
|
||||||
permissionKey: opportunity.permissionKey,
|
permissionKey: opportunity.permissionKey,
|
||||||
bid: bidParams,
|
bid: bidParams,
|
||||||
executor: account.address,
|
executor: account.address,
|
||||||
signature,
|
signature: hash,
|
||||||
opportunityId: opportunity.opportunityId,
|
opportunityId: opportunity.opportunityId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ export interface paths {
|
||||||
* @description Bid on a specific permission key for a specific chain.
|
* @description Bid on a specific permission key for a specific chain.
|
||||||
*
|
*
|
||||||
* Your bid will be simulated and verified by the server. Depending on the outcome of the auction, a transaction
|
* Your bid will be simulated and verified by the server. Depending on the outcome of the auction, a transaction
|
||||||
* containing the contract call will be sent to the blockchain expecting the bid amount to be paid after the call.
|
* containing the targetContract call will be sent to the blockchain expecting the bid amount to be paid after the call.
|
||||||
*/
|
*/
|
||||||
post: operations["bid"];
|
post: operations["bid"];
|
||||||
};
|
};
|
||||||
|
@ -58,7 +58,7 @@ export interface components {
|
||||||
amount: string;
|
amount: string;
|
||||||
/**
|
/**
|
||||||
* @description The chain id to bid on.
|
* @description The chain id to bid on.
|
||||||
* @example op_sepolia
|
* @example sepolia
|
||||||
*/
|
*/
|
||||||
chain_id: string;
|
chain_id: string;
|
||||||
/**
|
/**
|
||||||
|
@ -67,12 +67,12 @@ export interface components {
|
||||||
*/
|
*/
|
||||||
permission_key: string;
|
permission_key: string;
|
||||||
/**
|
/**
|
||||||
* @description Calldata for the contract call.
|
* @description Calldata for the targetContract call.
|
||||||
* @example 0xdeadbeef
|
* @example 0xdeadbeef
|
||||||
*/
|
*/
|
||||||
target_calldata: string;
|
target_calldata: string;
|
||||||
/**
|
/**
|
||||||
* @description The contract address to call.
|
* @description The targetContract address to call.
|
||||||
* @example 0xcA11bde05977b3631167028862bE2a173976CA11
|
* @example 0xcA11bde05977b3631167028862bE2a173976CA11
|
||||||
*/
|
*/
|
||||||
target_contract: string;
|
target_contract: string;
|
||||||
|
@ -88,28 +88,20 @@ export interface components {
|
||||||
BidStatus:
|
BidStatus:
|
||||||
| {
|
| {
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
type: "pending";
|
status: "pending";
|
||||||
}
|
|
||||||
| {
|
|
||||||
/** @enum {string} */
|
|
||||||
type: "simulation_failed";
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
/**
|
/**
|
||||||
* Format: int32
|
* @description The bid won the auction and was submitted to the chain in a transaction with the given hash
|
||||||
* @example 1
|
* @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3
|
||||||
*/
|
*/
|
||||||
index: number;
|
|
||||||
/** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */
|
|
||||||
result: string;
|
result: string;
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
type: "submitted";
|
status: "submitted";
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
/** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */
|
|
||||||
result: string;
|
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
type: "lost";
|
status: "lost";
|
||||||
};
|
};
|
||||||
BidStatusWithId: {
|
BidStatusWithId: {
|
||||||
bid_status: components["schemas"]["BidStatus"];
|
bid_status: components["schemas"]["BidStatus"];
|
||||||
|
@ -148,28 +140,6 @@ export interface components {
|
||||||
ClientRequest: components["schemas"]["ClientMessage"] & {
|
ClientRequest: components["schemas"]["ClientMessage"] & {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
EIP712Domain: {
|
|
||||||
/**
|
|
||||||
* @description The network chain id parameter for EIP712 domain.
|
|
||||||
* @example 31337
|
|
||||||
*/
|
|
||||||
chain_id: string;
|
|
||||||
/**
|
|
||||||
* @description The name parameter for the EIP712 domain.
|
|
||||||
* @example OpportunityAdapter
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* @description The verifying contract address parameter for the EIP712 domain.
|
|
||||||
* @example 0xcA11bde05977b3631167028862bE2a173976CA11
|
|
||||||
*/
|
|
||||||
verifying_contract: string;
|
|
||||||
/**
|
|
||||||
* @description The version parameter for the EIP712 domain.
|
|
||||||
* @example 1
|
|
||||||
*/
|
|
||||||
version: string;
|
|
||||||
};
|
|
||||||
ErrorBodyResponse: {
|
ErrorBodyResponse: {
|
||||||
error: string;
|
error: string;
|
||||||
};
|
};
|
||||||
|
@ -192,7 +162,7 @@ export interface components {
|
||||||
/** @example 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12 */
|
/** @example 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12 */
|
||||||
signature: string;
|
signature: string;
|
||||||
/**
|
/**
|
||||||
* @description The latest unix timestamp in seconds until which the bid is valid
|
* @description How long the bid will be valid for.
|
||||||
* @example 1000000000000000000
|
* @example 1000000000000000000
|
||||||
*/
|
*/
|
||||||
valid_until: string;
|
valid_until: string;
|
||||||
|
@ -204,14 +174,14 @@ export interface components {
|
||||||
/**
|
/**
|
||||||
* @description Opportunity parameters needed for on-chain execution
|
* @description Opportunity parameters needed for on-chain execution
|
||||||
* If a searcher signs the opportunity and have approved enough tokens to opportunity adapter,
|
* If a searcher signs the opportunity and have approved enough tokens to opportunity adapter,
|
||||||
* by calling this target contract with the given target calldata and structures, they will
|
* by calling this target targetContract with the given target targetCalldata and structures, they will
|
||||||
* send the tokens specified in the sell_tokens field and receive the tokens specified in the buy_tokens field.
|
* send the tokens specified in the sell_tokens field and receive the tokens specified in the buy_tokens field.
|
||||||
*/
|
*/
|
||||||
OpportunityParamsV1: {
|
OpportunityParamsV1: {
|
||||||
buy_tokens: components["schemas"]["TokenAmount"][];
|
buy_tokens: components["schemas"]["TokenAmount"][];
|
||||||
/**
|
/**
|
||||||
* @description The chain id where the opportunity will be executed.
|
* @description The chain id where the opportunity will be executed.
|
||||||
* @example op_sepolia
|
* @example sepolia
|
||||||
*/
|
*/
|
||||||
chain_id: string;
|
chain_id: string;
|
||||||
/**
|
/**
|
||||||
|
@ -221,17 +191,17 @@ export interface components {
|
||||||
permission_key: string;
|
permission_key: string;
|
||||||
sell_tokens: components["schemas"]["TokenAmount"][];
|
sell_tokens: components["schemas"]["TokenAmount"][];
|
||||||
/**
|
/**
|
||||||
* @description The value to send with the contract call.
|
* @description The targetCallValue to send with the targetContract call.
|
||||||
* @example 1
|
* @example 1
|
||||||
*/
|
*/
|
||||||
target_call_value: string;
|
target_call_value: string;
|
||||||
/**
|
/**
|
||||||
* @description Calldata for the target contract call.
|
* @description Calldata for the target targetContract call.
|
||||||
* @example 0xdeadbeef
|
* @example 0xdeadbeef
|
||||||
*/
|
*/
|
||||||
target_calldata: string;
|
target_calldata: string;
|
||||||
/**
|
/**
|
||||||
* @description The contract address to call for execution of the opportunity.
|
* @description The targetContract address to call for execution of the opportunity.
|
||||||
* @example 0xcA11bde05977b3631167028862bE2a173976CA11
|
* @example 0xcA11bde05977b3631167028862bE2a173976CA11
|
||||||
*/
|
*/
|
||||||
target_contract: string;
|
target_contract: string;
|
||||||
|
@ -242,11 +212,11 @@ export interface components {
|
||||||
version: "v1";
|
version: "v1";
|
||||||
}) & {
|
}) & {
|
||||||
/**
|
/**
|
||||||
* @description Creation time of the opportunity (in microseconds since the Unix epoch)
|
* Format: int64
|
||||||
* @example 1700000000000000
|
* @description Creation time of the opportunity
|
||||||
|
* @example 1700000000
|
||||||
*/
|
*/
|
||||||
creation_time: number;
|
creation_time: number;
|
||||||
eip_712_domain: components["schemas"]["EIP712Domain"];
|
|
||||||
/**
|
/**
|
||||||
* @description The opportunity unique id
|
* @description The opportunity unique id
|
||||||
* @example obo3ee3e-58cc-4372-a567-0e02b2c3d479
|
* @example obo3ee3e-58cc-4372-a567-0e02b2c3d479
|
||||||
|
@ -290,7 +260,7 @@ export interface components {
|
||||||
*/
|
*/
|
||||||
amount: string;
|
amount: string;
|
||||||
/**
|
/**
|
||||||
* @description Token contract address
|
* @description Token targetContract address
|
||||||
* @example 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
|
* @example 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
|
||||||
*/
|
*/
|
||||||
token: string;
|
token: string;
|
||||||
|
@ -325,11 +295,11 @@ export interface components {
|
||||||
version: "v1";
|
version: "v1";
|
||||||
}) & {
|
}) & {
|
||||||
/**
|
/**
|
||||||
* @description Creation time of the opportunity (in microseconds since the Unix epoch)
|
* Format: int64
|
||||||
* @example 1700000000000000
|
* @description Creation time of the opportunity
|
||||||
|
* @example 1700000000
|
||||||
*/
|
*/
|
||||||
creation_time: number;
|
creation_time: number;
|
||||||
eip_712_domain: components["schemas"]["EIP712Domain"];
|
|
||||||
/**
|
/**
|
||||||
* @description The opportunity unique id
|
* @description The opportunity unique id
|
||||||
* @example obo3ee3e-58cc-4372-a567-0e02b2c3d479
|
* @example obo3ee3e-58cc-4372-a567-0e02b2c3d479
|
||||||
|
@ -355,7 +325,7 @@ export interface operations {
|
||||||
* @description Bid on a specific permission key for a specific chain.
|
* @description Bid on a specific permission key for a specific chain.
|
||||||
*
|
*
|
||||||
* Your bid will be simulated and verified by the server. Depending on the outcome of the auction, a transaction
|
* Your bid will be simulated and verified by the server. Depending on the outcome of the auction, a transaction
|
||||||
* containing the contract call will be sent to the blockchain expecting the bid amount to be paid after the call.
|
* containing the targetContract call will be sent to the blockchain expecting the bid amount to be paid after the call.
|
||||||
*/
|
*/
|
||||||
bid: {
|
bid: {
|
||||||
requestBody: {
|
requestBody: {
|
||||||
|
@ -413,7 +383,7 @@ export interface operations {
|
||||||
get_opportunities: {
|
get_opportunities: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
/** @example op_sepolia */
|
/** @example sepolia */
|
||||||
chain_id?: string | null;
|
chain_id?: string | null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,27 +23,6 @@ export type BidParams = {
|
||||||
*/
|
*/
|
||||||
validUntil: bigint;
|
validUntil: bigint;
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* Represents the configuration for signing an opportunity
|
|
||||||
*/
|
|
||||||
export type EIP712Domain = {
|
|
||||||
/**
|
|
||||||
* The network chain id for the EIP712 domain.
|
|
||||||
*/
|
|
||||||
chainId: bigint;
|
|
||||||
/**
|
|
||||||
* The verifying contract address for the EIP712 domain.
|
|
||||||
*/
|
|
||||||
verifyingContract: Address;
|
|
||||||
/**
|
|
||||||
* The name parameter for the EIP712 domain.
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* The version parameter for the EIP712 domain.
|
|
||||||
*/
|
|
||||||
version: string;
|
|
||||||
};
|
|
||||||
/**
|
/**
|
||||||
* Represents a valid opportunity ready to be executed
|
* Represents a valid opportunity ready to be executed
|
||||||
*/
|
*/
|
||||||
|
@ -81,18 +60,11 @@ export type Opportunity = {
|
||||||
* Tokens to receive after the opportunity is executed
|
* Tokens to receive after the opportunity is executed
|
||||||
*/
|
*/
|
||||||
buyTokens: TokenAmount[];
|
buyTokens: TokenAmount[];
|
||||||
/**
|
|
||||||
* The data required to sign the opportunity
|
|
||||||
*/
|
|
||||||
eip712Domain: EIP712Domain;
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* All the parameters necessary to represent an opportunity
|
* All the parameters necessary to represent an opportunity
|
||||||
*/
|
*/
|
||||||
export type OpportunityParams = Omit<
|
export type OpportunityParams = Omit<Opportunity, "opportunityId">;
|
||||||
Opportunity,
|
|
||||||
"opportunityId" | "eip712Domain"
|
|
||||||
>;
|
|
||||||
/**
|
/**
|
||||||
* Represents a bid for an opportunity
|
* Represents a bid for an opportunity
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -6,10 +6,13 @@ from typing import Callable, Any
|
||||||
from collections.abc import Coroutine
|
from collections.abc import Coroutine
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
import httpx
|
import httpx
|
||||||
|
import web3
|
||||||
import websockets
|
import websockets
|
||||||
from websockets.client import WebSocketClientProtocol
|
from websockets.client import WebSocketClientProtocol
|
||||||
|
from eth_abi import encode
|
||||||
from eth_account.account import Account
|
from eth_account.account import Account
|
||||||
from express_relay.express_relay_types import (
|
from web3.auto import w3
|
||||||
|
from express_relay.types import (
|
||||||
Opportunity,
|
Opportunity,
|
||||||
BidStatusUpdate,
|
BidStatusUpdate,
|
||||||
ClientMessage,
|
ClientMessage,
|
||||||
|
@ -319,14 +322,10 @@ class ExpressRelayClient:
|
||||||
elif msg_json.get("type") == "bid_status_update":
|
elif msg_json.get("type") == "bid_status_update":
|
||||||
if bid_status_callback is not None:
|
if bid_status_callback is not None:
|
||||||
id = msg_json["status"]["id"]
|
id = msg_json["status"]["id"]
|
||||||
bid_status = msg_json["status"]["bid_status"]["type"]
|
bid_status = msg_json["status"]["bid_status"]["status"]
|
||||||
result = msg_json["status"]["bid_status"].get("result")
|
result = msg_json["status"]["bid_status"].get("result")
|
||||||
index = msg_json["status"]["bid_status"].get("index")
|
|
||||||
bid_status_update = BidStatusUpdate(
|
bid_status_update = BidStatusUpdate(
|
||||||
id=id,
|
id=id, bid_status=BidStatus(bid_status), result=result
|
||||||
bid_status=BidStatus(bid_status),
|
|
||||||
result=result,
|
|
||||||
index=index,
|
|
||||||
)
|
)
|
||||||
asyncio.create_task(bid_status_callback(bid_status_update))
|
asyncio.create_task(bid_status_callback(bid_status_update))
|
||||||
|
|
||||||
|
@ -402,66 +401,42 @@ def sign_bid(
|
||||||
Returns:
|
Returns:
|
||||||
A OpportunityBid object, representing the transaction to submit to the server. This object contains the searcher's signature.
|
A OpportunityBid object, representing the transaction to submit to the server. This object contains the searcher's signature.
|
||||||
"""
|
"""
|
||||||
|
sell_tokens = [
|
||||||
|
(token.token, int(token.amount)) for token in opportunity.sell_tokens
|
||||||
|
]
|
||||||
|
buy_tokens = [(token.token, int(token.amount)) for token in opportunity.buy_tokens]
|
||||||
|
target_calldata = bytes.fromhex(opportunity.target_calldata.replace("0x", ""))
|
||||||
|
|
||||||
executor = Account.from_key(private_key).address
|
digest = encode(
|
||||||
domain_data = {
|
[
|
||||||
"name": opportunity.eip_712_domain.name,
|
"(address,uint256)[]",
|
||||||
"version": opportunity.eip_712_domain.version,
|
"(address,uint256)[]",
|
||||||
"chainId": opportunity.eip_712_domain.chain_id,
|
"address",
|
||||||
"verifyingContract": opportunity.eip_712_domain.verifying_contract,
|
"bytes",
|
||||||
}
|
"uint256",
|
||||||
message_types = {
|
"uint256",
|
||||||
"ExecutionParams": [
|
"uint256",
|
||||||
{"name": "sellTokens", "type": "TokenAmount[]"},
|
|
||||||
{"name": "buyTokens", "type": "TokenAmount[]"},
|
|
||||||
{"name": "executor", "type": "address"},
|
|
||||||
{"name": "targetContract", "type": "address"},
|
|
||||||
{"name": "targetCalldata", "type": "bytes"},
|
|
||||||
{"name": "targetCallValue", "type": "uint256"},
|
|
||||||
{"name": "validUntil", "type": "uint256"},
|
|
||||||
{"name": "bidAmount", "type": "uint256"},
|
|
||||||
],
|
],
|
||||||
"TokenAmount": [
|
[
|
||||||
{"name": "token", "type": "address"},
|
sell_tokens,
|
||||||
{"name": "amount", "type": "uint256"},
|
buy_tokens,
|
||||||
|
opportunity.target_contract,
|
||||||
|
target_calldata,
|
||||||
|
opportunity.target_call_value,
|
||||||
|
bid_amount,
|
||||||
|
valid_until,
|
||||||
],
|
],
|
||||||
}
|
|
||||||
|
|
||||||
# the data to be signed
|
|
||||||
message_data = {
|
|
||||||
"sellTokens": [
|
|
||||||
{
|
|
||||||
"token": token.token,
|
|
||||||
"amount": int(token.amount),
|
|
||||||
}
|
|
||||||
for token in opportunity.sell_tokens
|
|
||||||
],
|
|
||||||
"buyTokens": [
|
|
||||||
{
|
|
||||||
"token": token.token,
|
|
||||||
"amount": int(token.amount),
|
|
||||||
}
|
|
||||||
for token in opportunity.buy_tokens
|
|
||||||
],
|
|
||||||
"executor": executor,
|
|
||||||
"targetContract": opportunity.target_contract,
|
|
||||||
"targetCalldata": bytes.fromhex(opportunity.target_calldata.replace("0x", "")),
|
|
||||||
"targetCallValue": opportunity.target_call_value,
|
|
||||||
"validUntil": valid_until,
|
|
||||||
"bidAmount": bid_amount,
|
|
||||||
}
|
|
||||||
|
|
||||||
signed_typed_data = Account.sign_typed_data(
|
|
||||||
private_key, domain_data, message_types, message_data
|
|
||||||
)
|
)
|
||||||
|
msg_data = web3.Web3.solidity_keccak(["bytes"], [digest])
|
||||||
|
signature = w3.eth.account.signHash(msg_data, private_key=private_key)
|
||||||
|
|
||||||
opportunity_bid = OpportunityBid(
|
opportunity_bid = OpportunityBid(
|
||||||
opportunity_id=opportunity.opportunity_id,
|
opportunity_id=opportunity.opportunity_id,
|
||||||
permission_key=opportunity.permission_key,
|
permission_key=opportunity.permission_key,
|
||||||
amount=bid_amount,
|
amount=bid_amount,
|
||||||
valid_until=valid_until,
|
valid_until=valid_until,
|
||||||
executor=executor,
|
executor=Account.from_key(private_key).address,
|
||||||
signature=signed_typed_data,
|
signature=signature,
|
||||||
)
|
)
|
||||||
|
|
||||||
return opportunity_bid
|
return opportunity_bid
|
||||||
|
|
|
@ -3,7 +3,7 @@ import asyncio
|
||||||
import logging
|
import logging
|
||||||
from eth_account.account import Account
|
from eth_account.account import Account
|
||||||
from express_relay.client import ExpressRelayClient, sign_bid
|
from express_relay.client import ExpressRelayClient, sign_bid
|
||||||
from express_relay.express_relay_types import (
|
from express_relay.types import (
|
||||||
Opportunity,
|
Opportunity,
|
||||||
OpportunityBid,
|
OpportunityBid,
|
||||||
Bytes32,
|
Bytes32,
|
||||||
|
@ -76,16 +76,14 @@ class SimpleSearcher:
|
||||||
bid_status = bid_status_update.bid_status
|
bid_status = bid_status_update.bid_status
|
||||||
result = bid_status_update.result
|
result = bid_status_update.result
|
||||||
|
|
||||||
result_details = ""
|
|
||||||
if bid_status == BidStatus("submitted"):
|
if bid_status == BidStatus("submitted"):
|
||||||
result_details = (
|
logger.info(f"Bid {id} has been submitted in hash {result}")
|
||||||
f", transaction {result}, index {bid_status_update.index} of multicall"
|
|
||||||
)
|
|
||||||
elif bid_status == BidStatus("lost"):
|
elif bid_status == BidStatus("lost"):
|
||||||
result_details = f", transaction {result}"
|
logger.info(f"Bid {id} was unsuccessful")
|
||||||
logger.error(
|
elif bid_status == BidStatus("pending"):
|
||||||
f"Bid status for bid {id}: {bid_status.value.replace('_', ' ')}{result_details}"
|
logger.info(f"Bid {id} is pending")
|
||||||
)
|
else:
|
||||||
|
logger.error(f"Unrecognized status {bid_status} for bid {id}")
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
|
|
@ -105,40 +105,26 @@ class BidStatus(Enum):
|
||||||
SUBMITTED = "submitted"
|
SUBMITTED = "submitted"
|
||||||
LOST = "lost"
|
LOST = "lost"
|
||||||
PENDING = "pending"
|
PENDING = "pending"
|
||||||
SIMULATION_FAILED = "simulation_failed"
|
|
||||||
|
|
||||||
|
|
||||||
class BidStatusUpdate(BaseModel):
|
class BidStatusUpdate(BaseModel):
|
||||||
"""
|
"""
|
||||||
Attributes:
|
Attributes:
|
||||||
id: The ID of the bid.
|
id: The ID of the bid.
|
||||||
bid_status: The current status of the bid.
|
bid_status: The status enum, either SUBMITTED, LOST, or PENDING.
|
||||||
result: The result of the bid: a transaction hash if the status is SUBMITTED or LOST, else None.
|
result: The result of the bid: a transaction hash if the status is SUBMITTED, else None.
|
||||||
index: The index of the bid in the submitted transaction; None if the status is not SUBMITTED.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: UUIDString
|
id: UUIDString
|
||||||
bid_status: BidStatus
|
bid_status: BidStatus
|
||||||
result: Bytes32 | None = Field(default=None)
|
result: Bytes32 | None = Field(default=None)
|
||||||
index: int | None = Field(default=None)
|
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def check_result(self):
|
def check_result(self):
|
||||||
if self.bid_status in [
|
|
||||||
BidStatus("pending"),
|
|
||||||
BidStatus("simulation_failed"),
|
|
||||||
]:
|
|
||||||
assert self.result is None, "result must be None"
|
|
||||||
else:
|
|
||||||
assert self.result is not None, "result must be a valid 32-byte hash"
|
|
||||||
return self
|
|
||||||
|
|
||||||
@model_validator(mode="after")
|
|
||||||
def check_index(self):
|
|
||||||
if self.bid_status == BidStatus("submitted"):
|
if self.bid_status == BidStatus("submitted"):
|
||||||
assert self.index is not None, "index must be a valid integer"
|
assert self.result is not None, "result must be a valid 32-byte hash"
|
||||||
else:
|
else:
|
||||||
assert self.index is None, "index must be None"
|
assert self.result is None, "result must be None"
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
@ -197,13 +183,6 @@ class OpportunityParams(BaseModel):
|
||||||
params: Union[OpportunityParamsV1] = Field(..., discriminator="version")
|
params: Union[OpportunityParamsV1] = Field(..., discriminator="version")
|
||||||
|
|
||||||
|
|
||||||
class EIP712Domain(BaseModel):
|
|
||||||
name: str
|
|
||||||
version: str
|
|
||||||
chain_id: IntString
|
|
||||||
verifying_contract: Address
|
|
||||||
|
|
||||||
|
|
||||||
class Opportunity(BaseModel):
|
class Opportunity(BaseModel):
|
||||||
"""
|
"""
|
||||||
Attributes:
|
Attributes:
|
||||||
|
@ -217,7 +196,6 @@ class Opportunity(BaseModel):
|
||||||
version: The version of the opportunity.
|
version: The version of the opportunity.
|
||||||
creation_time: The creation time of the opportunity.
|
creation_time: The creation time of the opportunity.
|
||||||
opportunity_id: The ID of the opportunity.
|
opportunity_id: The ID of the opportunity.
|
||||||
eip_712_domain: The EIP712 domain data needed for signing.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
target_calldata: HexString
|
target_calldata: HexString
|
||||||
|
@ -230,7 +208,6 @@ class Opportunity(BaseModel):
|
||||||
version: str
|
version: str
|
||||||
creation_time: IntString
|
creation_time: IntString
|
||||||
opportunity_id: UUIDString
|
opportunity_id: UUIDString
|
||||||
eip_712_domain: EIP712Domain
|
|
||||||
|
|
||||||
supported_versions: ClassVar[list[str]] = ["v1"]
|
supported_versions: ClassVar[list[str]] = ["v1"]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiohttp"
|
name = "aiohttp"
|
||||||
|
@ -316,33 +316,33 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "black"
|
name = "black"
|
||||||
version = "24.3.0"
|
version = "24.2.0"
|
||||||
description = "The uncompromising code formatter."
|
description = "The uncompromising code formatter."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"},
|
{file = "black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29"},
|
||||||
{file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"},
|
{file = "black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430"},
|
||||||
{file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"},
|
{file = "black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f"},
|
||||||
{file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"},
|
{file = "black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a"},
|
||||||
{file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"},
|
{file = "black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd"},
|
||||||
{file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"},
|
{file = "black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2"},
|
||||||
{file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"},
|
{file = "black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92"},
|
||||||
{file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"},
|
{file = "black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23"},
|
||||||
{file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"},
|
{file = "black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b"},
|
||||||
{file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"},
|
{file = "black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9"},
|
||||||
{file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"},
|
{file = "black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693"},
|
||||||
{file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"},
|
{file = "black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982"},
|
||||||
{file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"},
|
{file = "black-24.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4"},
|
||||||
{file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"},
|
{file = "black-24.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218"},
|
||||||
{file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"},
|
{file = "black-24.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0"},
|
||||||
{file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"},
|
{file = "black-24.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"},
|
||||||
{file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"},
|
{file = "black-24.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8"},
|
||||||
{file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"},
|
{file = "black-24.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8"},
|
||||||
{file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"},
|
{file = "black-24.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540"},
|
||||||
{file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"},
|
{file = "black-24.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31"},
|
||||||
{file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"},
|
{file = "black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6"},
|
||||||
{file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"},
|
{file = "black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -1506,13 +1506,13 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "referencing"
|
name = "referencing"
|
||||||
version = "0.34.0"
|
version = "0.33.0"
|
||||||
description = "JSON Referencing + Python"
|
description = "JSON Referencing + Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "referencing-0.34.0-py3-none-any.whl", hash = "sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4"},
|
{file = "referencing-0.33.0-py3-none-any.whl", hash = "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5"},
|
||||||
{file = "referencing-0.34.0.tar.gz", hash = "sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844"},
|
{file = "referencing-0.33.0.tar.gz", hash = "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "express-relay"
|
name = "express-relay"
|
||||||
version = "0.4.2"
|
version = "0.2.0"
|
||||||
description = "Utilities for searchers and protocols to interact with the Express Relay protocol."
|
description = "Utilities for searchers and protocols to interact with the Express Relay protocol."
|
||||||
authors = ["dourolabs"]
|
authors = ["dourolabs"]
|
||||||
license = "Proprietary"
|
license = "Proprietary"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/target
|
/target
|
||||||
*config.yaml
|
config.yaml
|
||||||
*secret*
|
*secret*
|
||||||
*private-key*
|
*private-key*
|
|
@ -522,9 +522,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cargo_metadata"
|
name = "cargo_metadata"
|
||||||
version = "0.18.1"
|
version = "0.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
|
checksum = "e7daec1a2a2129eeba1644b220b4647ec537b0b5d4bfd6876fcc5a540056b592"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"camino",
|
"camino",
|
||||||
"cargo-platform",
|
"cargo-platform",
|
||||||
|
@ -1031,9 +1031,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enr"
|
name = "enr"
|
||||||
version = "0.10.0"
|
version = "0.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a3d8dc56e02f954cac8eb489772c552c473346fc34f67412bb6244fd647f7e4"
|
checksum = "fe81b5c06ecfdbc71dd845216f225f53b62a10cb8a16c946836a3467f701d05b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.4",
|
"base64 0.21.4",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -1146,9 +1146,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethers"
|
name = "ethers"
|
||||||
version = "2.0.14"
|
version = "2.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "816841ea989f0c69e459af1cf23a6b0033b19a55424a1ea3a30099becdb8dec0"
|
checksum = "1ad13497f6e0a24292fc7b408e30d22fe9dc262da1f40d7b542c3a44e7fc0476"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ethers-addressbook",
|
"ethers-addressbook",
|
||||||
"ethers-contract",
|
"ethers-contract",
|
||||||
|
@ -1162,9 +1162,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethers-addressbook"
|
name = "ethers-addressbook"
|
||||||
version = "2.0.14"
|
version = "2.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5495afd16b4faa556c3bba1f21b98b4983e53c1755022377051a975c3b021759"
|
checksum = "c6e9e8acd0ed348403cc73a670c24daba3226c40b98dc1a41903766b3ab6240a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ethers-core",
|
"ethers-core",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -1174,9 +1174,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethers-contract"
|
name = "ethers-contract"
|
||||||
version = "2.0.14"
|
version = "2.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6fceafa3578c836eeb874af87abacfb041f92b4da0a78a5edd042564b8ecdaaa"
|
checksum = "d79269278125006bb0552349c03593ffa9702112ca88bc7046cc669f148fb47c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-hex",
|
"const-hex",
|
||||||
"ethers-contract-abigen",
|
"ethers-contract-abigen",
|
||||||
|
@ -1193,9 +1193,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethers-contract-abigen"
|
name = "ethers-contract-abigen"
|
||||||
version = "2.0.14"
|
version = "2.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04ba01fbc2331a38c429eb95d4a570166781f14290ef9fdb144278a90b5a739b"
|
checksum = "ce95a43c939b2e4e2f3191c5ad4a1f279780b8a39139c9905b43a7433531e2ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"const-hex",
|
"const-hex",
|
||||||
|
@ -1211,15 +1211,15 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"syn 2.0.38",
|
"syn 2.0.38",
|
||||||
"toml 0.8.12",
|
"toml 0.7.8",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethers-contract-derive"
|
name = "ethers-contract-derive"
|
||||||
version = "2.0.14"
|
version = "2.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "87689dcabc0051cde10caaade298f9e9093d65f6125c14575db3fd8c669a168f"
|
checksum = "8e9ce44906fc871b3ee8c69a695ca7ec7f70e50cb379c9b9cb5e532269e492f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"const-hex",
|
"const-hex",
|
||||||
|
@ -1233,9 +1233,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethers-core"
|
name = "ethers-core"
|
||||||
version = "2.0.14"
|
version = "2.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f"
|
checksum = "c0a17f0708692024db9956b31d7a20163607d2745953f5ae8125ab368ba280ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -1253,7 +1253,7 @@ dependencies = [
|
||||||
"rlp",
|
"rlp",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum 0.26.2",
|
"strum 0.25.0",
|
||||||
"syn 2.0.38",
|
"syn 2.0.38",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -1263,11 +1263,10 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethers-etherscan"
|
name = "ethers-etherscan"
|
||||||
version = "2.0.14"
|
version = "2.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e79e5973c26d4baf0ce55520bd732314328cabe53193286671b47144145b9649"
|
checksum = "0e53451ea4a8128fbce33966da71132cf9e1040dcfd2a2084fd7733ada7b2045"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
|
||||||
"ethers-core",
|
"ethers-core",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"semver",
|
"semver",
|
||||||
|
@ -1279,9 +1278,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethers-middleware"
|
name = "ethers-middleware"
|
||||||
version = "2.0.14"
|
version = "2.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48f9fdf09aec667c099909d91908d5eaf9be1bd0e2500ba4172c1d28bfaa43de"
|
checksum = "473f1ccd0c793871bbc248729fa8df7e6d2981d6226e4343e3bbaa9281074d5d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"auto_impl",
|
"auto_impl",
|
||||||
|
@ -1306,9 +1305,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethers-providers"
|
name = "ethers-providers"
|
||||||
version = "2.0.14"
|
version = "2.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6434c9a33891f1effc9c75472e12666db2fa5a0fec4b29af6221680a6fe83ab2"
|
checksum = "6838fa110e57d572336178b7c79e94ff88ef976306852d8cb87d9e5b1fc7c0b5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"auto_impl",
|
"auto_impl",
|
||||||
|
@ -1317,7 +1316,6 @@ dependencies = [
|
||||||
"const-hex",
|
"const-hex",
|
||||||
"enr",
|
"enr",
|
||||||
"ethers-core",
|
"ethers-core",
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-timer",
|
"futures-timer",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
@ -1344,9 +1342,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethers-signers"
|
name = "ethers-signers"
|
||||||
version = "2.0.14"
|
version = "2.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "228875491c782ad851773b652dd8ecac62cda8571d3bc32a5853644dd26766c2"
|
checksum = "5ea44bec930f12292866166f9ddbea6aa76304850e4d8dcd66dc492b43d00ff1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"coins-bip32",
|
"coins-bip32",
|
||||||
|
@ -1363,9 +1361,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethers-solc"
|
name = "ethers-solc"
|
||||||
version = "2.0.14"
|
version = "2.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "66244a771d9163282646dbeffe0e6eca4dda4146b6498644e678ac6089b11edd"
|
checksum = "de34e484e7ae3cab99fbfd013d6c5dc7f9013676a4e0e414d8b12e1213e8b3ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"const-hex",
|
"const-hex",
|
||||||
|
@ -1488,7 +1486,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fortuna"
|
name = "fortuna"
|
||||||
version = "5.2.2"
|
version = "3.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
|
@ -1500,7 +1498,6 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"ethabi",
|
"ethabi",
|
||||||
"ethers",
|
"ethers",
|
||||||
"futures",
|
|
||||||
"hex",
|
"hex",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -2761,7 +2758,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
|
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"toml_edit 0.19.15",
|
"toml_edit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2822,7 +2819,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pythnet-sdk"
|
name = "pythnet-sdk"
|
||||||
version = "2.1.0"
|
version = "2.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"borsh",
|
"borsh",
|
||||||
|
@ -3390,9 +3387,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "0.6.5"
|
version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -3584,9 +3581,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "solang-parser"
|
name = "solang-parser"
|
||||||
version = "0.3.3"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c425ce1c59f4b154717592f0bdf4715c3a1d55058883622d3157e1f0908a5b26"
|
checksum = "7cb9fa2fa2fa6837be8a2495486ff92e3ffe68a99b6eeba288e139efdd842457"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"lalrpop",
|
"lalrpop",
|
||||||
|
@ -3648,11 +3645,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
version = "0.26.2"
|
version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
|
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"strum_macros 0.26.2",
|
"strum_macros 0.25.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3670,9 +3667,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum_macros"
|
name = "strum_macros"
|
||||||
version = "0.26.2"
|
version = "0.25.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
|
checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
@ -3958,21 +3955,21 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.12"
|
version = "0.7.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
|
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"toml_edit 0.22.9",
|
"toml_edit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.5"
|
version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -3982,23 +3979,12 @@ name = "toml_edit"
|
||||||
version = "0.19.15"
|
version = "0.19.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||||
dependencies = [
|
|
||||||
"indexmap 2.0.2",
|
|
||||||
"toml_datetime",
|
|
||||||
"winnow 0.5.16",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_edit"
|
|
||||||
version = "0.22.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.0.2",
|
"indexmap 2.0.2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"winnow 0.6.5",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -4526,15 +4512,6 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winnow"
|
|
||||||
version = "0.6.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winreg"
|
name = "winreg"
|
||||||
version = "0.50.0"
|
version = "0.50.0"
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "fortuna"
|
name = "fortuna"
|
||||||
version = "5.2.2"
|
version = "3.3.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -12,11 +12,10 @@ bincode = "1.3.3"
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.5.0"
|
||||||
clap = { version = "4.4.6", features = ["derive", "cargo", "env"] }
|
clap = { version = "4.4.6", features = ["derive", "cargo", "env"] }
|
||||||
ethabi = "18.0.0"
|
ethabi = "18.0.0"
|
||||||
ethers = { version = "2.0.14", features = ["ws"] }
|
ethers = "2.0.10"
|
||||||
futures = { version = "0.3.28" }
|
hex = "0.4.3"
|
||||||
hex = "0.4.3"
|
|
||||||
prometheus-client = { version = "0.21.2" }
|
prometheus-client = { version = "0.21.2" }
|
||||||
pythnet-sdk = { path = "../../pythnet/pythnet_sdk", features = ["strum"] }
|
pythnet-sdk = { path = "../pythnet/pythnet_sdk", features = ["strum"] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
reqwest = { version = "0.11.22", features = ["json", "blocking"] }
|
reqwest = { version = "0.11.22", features = ["json", "blocking"] }
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
|
@ -35,6 +34,5 @@ once_cell = "1.18.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
url = "2.5.0"
|
url = "2.5.0"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
axum-test = "13.1.1"
|
axum-test = "13.1.1"
|
|
@ -7,15 +7,15 @@ RUN rustup default nightly-2023-07-23
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY apps/fortuna apps/fortuna
|
COPY fortuna fortuna
|
||||||
COPY pythnet pythnet
|
COPY pythnet pythnet
|
||||||
COPY target_chains/ethereum/entropy_sdk/solidity/abis target_chains/ethereum/entropy_sdk/solidity/abis
|
COPY target_chains/ethereum/entropy_sdk/solidity/abis target_chains/ethereum/entropy_sdk/solidity/abis
|
||||||
|
|
||||||
WORKDIR /src/apps/fortuna
|
WORKDIR /src/fortuna
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cargo/registry cargo build --release
|
RUN --mount=type=cache,target=/root/.cargo/registry cargo build --release
|
||||||
|
|
||||||
|
|
||||||
FROM rust:${RUST_VERSION}
|
FROM rust:${RUST_VERSION}
|
||||||
# Copy artifacts from other images
|
# Copy artifacts from other images
|
||||||
COPY --from=build /src/apps/fortuna/target/release/fortuna /usr/local/bin/
|
COPY --from=build /src/fortuna/target/release/fortuna /usr/local/bin/
|
|
@ -4,4 +4,3 @@ chains:
|
||||||
contract_addr: 0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a
|
contract_addr: 0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a
|
||||||
reveal_delay_blocks: 0
|
reveal_delay_blocks: 0
|
||||||
legacy_tx: true
|
legacy_tx: true
|
||||||
gas_limit: 500000
|
|
|
@ -73,8 +73,6 @@ impl ApiState {
|
||||||
/// The state of the randomness service for a single blockchain.
|
/// The state of the randomness service for a single blockchain.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BlockchainState {
|
pub struct BlockchainState {
|
||||||
/// The chain id for this blockchain, useful for logging
|
|
||||||
pub id: ChainId,
|
|
||||||
/// The hash chain(s) required to serve random numbers for this blockchain
|
/// The hash chain(s) required to serve random numbers for this blockchain
|
||||||
pub state: Arc<HashChainState>,
|
pub state: Arc<HashChainState>,
|
||||||
/// The contract that the server is fulfilling requests for.
|
/// The contract that the server is fulfilling requests for.
|
||||||
|
@ -247,7 +245,6 @@ mod test {
|
||||||
let eth_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
|
let eth_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
|
||||||
|
|
||||||
let eth_state = BlockchainState {
|
let eth_state = BlockchainState {
|
||||||
id: "ethereum".into(),
|
|
||||||
state: ETH_CHAIN.clone(),
|
state: ETH_CHAIN.clone(),
|
||||||
contract: eth_read.clone(),
|
contract: eth_read.clone(),
|
||||||
provider_address: PROVIDER,
|
provider_address: PROVIDER,
|
||||||
|
@ -258,7 +255,6 @@ mod test {
|
||||||
let avax_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
|
let avax_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
|
||||||
|
|
||||||
let avax_state = BlockchainState {
|
let avax_state = BlockchainState {
|
||||||
id: "avalanche".into(),
|
|
||||||
state: AVAX_CHAIN.clone(),
|
state: AVAX_CHAIN.clone(),
|
||||||
contract: avax_read.clone(),
|
contract: avax_read.clone(),
|
||||||
provider_address: PROVIDER,
|
provider_address: PROVIDER,
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue