From 7928088ab4fbacee254dbab80c35b9f86b5dbc87 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 29 Apr 2024 10:59:02 -0400 Subject: [PATCH 001/454] add deploy workflow --- .github/ISSUE_TEMPLATE/workflows/deploy.yml | 66 +++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/workflows/deploy.yml diff --git a/.github/ISSUE_TEMPLATE/workflows/deploy.yml b/.github/ISSUE_TEMPLATE/workflows/deploy.yml new file mode 100644 index 0000000..6c57370 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/workflows/deploy.yml @@ -0,0 +1,66 @@ +name: Deploy to EC2 on Push + +on: + push: + branches: + - main + - dev + +jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }} + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up SSH key + uses: webfactory/ssh-agent@v0.5.3 + with: + ssh-private-key: ${{ secrets.EC2_SSH_PRIVATE_KEY }} + + - name: Push new code to EC2 + run: | + rsync -avz --exclude '.git*' --exclude 'node_modules' ./ ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }}:/path/to/your/project/ + echo "Code has been pushed to the EC2 instance." + + - name: Check for pending migrations + id: check_migrations + run: | + echo "Checking for pending migrations..." + pending_migrations=$(ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "cd /path/to/your/project/ && source env/bin/activate && python manage.py showmigrations --plan | grep '\[ \]'") + echo "::set-output name=pending::${pending_migrations}" + if [ -z "$pending_migrations" ]; then + echo "No migrations found." + else: + echo "Migrations found, stopping services." + + - name: Stop services if migrations are pending + if: steps.check_migrations.outputs.pending + run: | + echo "Stopping services..." + ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "sudo systemctl stop gunicorn.service indexer.service" + + - name: Run migrations + if: steps.check_migrations.outputs.pending + run: | + echo "Running migrations..." + ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "cd /path/to/your/project/ && source env/bin/activate && python manage.py migrate" + + - name: Run collectstatic + run: | + echo "Running collectstatic..." + ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "cd /path/to/your/project/ && source env/bin/activate && python manage.py collectstatic --noinput" + + - name: Restart services if migrations were run + if: steps.check_migrations.outputs.pending + run: | + echo "Restarting services after migrations..." + ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service indexer.service" + + - name: Restart services if no migrations + if: steps.check_migrations.outputs.pending == '' + run: | + echo "Restarting services without migration..." + ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service" From c7569ec121cd90968ce8c3303237c27a22998615 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 29 Apr 2024 10:59:59 -0400 Subject: [PATCH 002/454] fix workflows location --- .github/{ISSUE_TEMPLATE => }/workflows/deploy.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ISSUE_TEMPLATE => }/workflows/deploy.yml (100%) diff --git a/.github/ISSUE_TEMPLATE/workflows/deploy.yml b/.github/workflows/deploy.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/workflows/deploy.yml rename to .github/workflows/deploy.yml From 1087fd50e4483eb443f205775cdb70ddb12d57db Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 29 Apr 2024 13:16:51 -0400 Subject: [PATCH 003/454] fix ec2 directory path --- .github/workflows/deploy.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6c57370..5c36140 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,14 +22,14 @@ jobs: - name: Push new code to EC2 run: | - rsync -avz --exclude '.git*' --exclude 'node_modules' ./ ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }}:/path/to/your/project/ + rsync -avz --exclude '.git*' --exclude 'node_modules' ./ ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }}:/home/ec2-user/django-indexer echo "Code has been pushed to the EC2 instance." - name: Check for pending migrations id: check_migrations run: | echo "Checking for pending migrations..." - pending_migrations=$(ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "cd /path/to/your/project/ && source env/bin/activate && python manage.py showmigrations --plan | grep '\[ \]'") + pending_migrations=$(ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py showmigrations --plan | grep '\[ \]'") echo "::set-output name=pending::${pending_migrations}" if [ -z "$pending_migrations" ]; then echo "No migrations found." @@ -46,12 +46,12 @@ jobs: if: steps.check_migrations.outputs.pending run: | echo "Running migrations..." - ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "cd /path/to/your/project/ && source env/bin/activate && python manage.py migrate" + ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py migrate" - name: Run collectstatic run: | echo "Running collectstatic..." - ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "cd /path/to/your/project/ && source env/bin/activate && python manage.py collectstatic --noinput" + ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py collectstatic --noinput" - name: Restart services if migrations were run if: steps.check_migrations.outputs.pending From 4bd0940fd5283fda287c309b896035e769a6086a Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 29 Apr 2024 13:24:20 -0400 Subject: [PATCH 004/454] add workflow debug logs --- .github/workflows/deploy.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5c36140..d344b78 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,6 +20,11 @@ jobs: with: ssh-private-key: ${{ secrets.EC2_SSH_PRIVATE_KEY }} + - name: Print environment variables + run: | + echo "EC2_SSH_HOST=${{ env.EC2_SSH_HOST }}" + echo "EC2_USER=${{ env.EC2_USER }}" + - name: Push new code to EC2 run: | rsync -avz --exclude '.git*' --exclude 'node_modules' ./ ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }}:/home/ec2-user/django-indexer From 648e5b834f44abd158634c6a61718045fed480cf Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:02:28 -0400 Subject: [PATCH 005/454] change env. to vars. --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d344b78..6062239 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,8 +22,8 @@ jobs: - name: Print environment variables run: | - echo "EC2_SSH_HOST=${{ env.EC2_SSH_HOST }}" - echo "EC2_USER=${{ env.EC2_USER }}" + echo "EC2_SSH_HOST=${{ vars.EC2_SSH_HOST }}" + echo "EC2_USER=${{ vars.EC2_USER }}" - name: Push new code to EC2 run: | From 5f6c725e47f4ac0f47a8a991dcf55552cc6c8978 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:03:15 -0400 Subject: [PATCH 006/454] change all env. to vars. --- .github/workflows/deploy.yml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6062239..593040c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,21 +20,16 @@ jobs: with: ssh-private-key: ${{ secrets.EC2_SSH_PRIVATE_KEY }} - - name: Print environment variables - run: | - echo "EC2_SSH_HOST=${{ vars.EC2_SSH_HOST }}" - echo "EC2_USER=${{ vars.EC2_USER }}" - - name: Push new code to EC2 run: | - rsync -avz --exclude '.git*' --exclude 'node_modules' ./ ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }}:/home/ec2-user/django-indexer + rsync -avz --exclude '.git*' --exclude 'node_modules' ./ ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }}:/home/ec2-user/django-indexer echo "Code has been pushed to the EC2 instance." - name: Check for pending migrations id: check_migrations run: | echo "Checking for pending migrations..." - pending_migrations=$(ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py showmigrations --plan | grep '\[ \]'") + pending_migrations=$(ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py showmigrations --plan | grep '\[ \]'") echo "::set-output name=pending::${pending_migrations}" if [ -z "$pending_migrations" ]; then echo "No migrations found." @@ -45,27 +40,27 @@ jobs: if: steps.check_migrations.outputs.pending run: | echo "Stopping services..." - ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "sudo systemctl stop gunicorn.service indexer.service" + ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl stop gunicorn.service indexer.service" - name: Run migrations if: steps.check_migrations.outputs.pending run: | echo "Running migrations..." - ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py migrate" + ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py migrate" - name: Run collectstatic run: | echo "Running collectstatic..." - ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py collectstatic --noinput" + ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py collectstatic --noinput" - name: Restart services if migrations were run if: steps.check_migrations.outputs.pending run: | echo "Restarting services after migrations..." - ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service indexer.service" + ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service indexer.service" - name: Restart services if no migrations if: steps.check_migrations.outputs.pending == '' run: | echo "Restarting services without migration..." - ssh -o "StrictHostKeyChecking=no" ${{ env.EC2_USER }}@${{ env.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service" + ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service" From 152cb35ba26d064bdaf165d004202465c4305583 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 29 Apr 2024 17:13:08 -0400 Subject: [PATCH 007/454] add appspec --- appspec.yml | 16 ++++++++++++++++ scripts/after_install.sh | 10 ++++++++++ 2 files changed, 26 insertions(+) create mode 100644 appspec.yml create mode 100644 scripts/after_install.sh diff --git a/appspec.yml b/appspec.yml new file mode 100644 index 0000000..76a51b2 --- /dev/null +++ b/appspec.yml @@ -0,0 +1,16 @@ +version: 0.0 +os: linux +files: + - source: / + destination: /home/ec2-user/django-indexer +# hooks: +# # Install: +# AfterInstall: +# - location: scripts/after_install.sh +# timeout: 300 +# runas: root +# ApplicationStart: +# - location: scripts/application_start.sh +# timeout: 300 +# runas: root +# # ValidateService: diff --git a/scripts/after_install.sh b/scripts/after_install.sh new file mode 100644 index 0000000..2ebca6a --- /dev/null +++ b/scripts/after_install.sh @@ -0,0 +1,10 @@ +#!/bin/bash +echo 'run after_install.sh' >> /home/ec2-user/myrepo/deploy.log + +cd /home/ec2-user/django-indexer + +echo 'poetry install' >> /home/ec2-user/myrepo/deploy.log + +poetry shell + +poetry install >> /home/ec2-user/myrepo/deploy.log \ No newline at end of file From f740c640b1b60269966753a952f08ab276f82e35 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:46:28 -0400 Subject: [PATCH 008/454] update workflow to use codedeploy --- .github/workflows/deploy.yml | 139 +++++++++++++++++++++++------------ 1 file changed, 90 insertions(+), 49 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 593040c..9c958fe 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,66 +1,107 @@ +# This is a basic workflow to help you get started with Actions name: Deploy to EC2 on Push on: push: - branches: - - main - - dev + branches: [dev] +env: + AWS_REGION: "us-east-1" + +# Permission can be added at job level or workflow level +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout jobs: - deploy: + AssumeRoleAndCallIdentity: runs-on: ubuntu-latest - environment: - name: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }} steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up SSH key - uses: webfactory/ssh-agent@v0.5.3 + - name: Git clone the repository + uses: actions/checkout@v3 + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v1.7.0 with: - ssh-private-key: ${{ secrets.EC2_SSH_PRIVATE_KEY }} - - - name: Push new code to EC2 + role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction + role-session-name: GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ env.AWS_REGION }} + # Hello from AWS: WhoAmI + - name: Sts GetCallerIdentity run: | - rsync -avz --exclude '.git*' --exclude 'node_modules' ./ ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }}:/home/ec2-user/django-indexer - echo "Code has been pushed to the EC2 instance." + aws sts get-caller-identity - - name: Check for pending migrations - id: check_migrations + # Step 3 - check the application-name and deployment group name + - name: Create CodeDeploy Deployment + id: deploy run: | - echo "Checking for pending migrations..." - pending_migrations=$(ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py showmigrations --plan | grep '\[ \]'") - echo "::set-output name=pending::${pending_migrations}" - if [ -z "$pending_migrations" ]; then - echo "No migrations found." - else: - echo "Migrations found, stopping services." + aws deploy create-deployment \ + --application-name django-indexer \ + --deployment-group-name django-indexer-dev \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --github-location repository=${{ github.repository }},commitId=${{ github.sha }} - - name: Stop services if migrations are pending - if: steps.check_migrations.outputs.pending - run: | - echo "Stopping services..." - ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl stop gunicorn.service indexer.service" +# name: Deploy to EC2 on Push - - name: Run migrations - if: steps.check_migrations.outputs.pending - run: | - echo "Running migrations..." - ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py migrate" +# on: +# push: +# branches: +# - main +# - dev - - name: Run collectstatic - run: | - echo "Running collectstatic..." - ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py collectstatic --noinput" +# jobs: +# deploy: +# runs-on: ubuntu-latest +# environment: +# name: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }} +# steps: +# - name: Checkout code +# uses: actions/checkout@v2 - - name: Restart services if migrations were run - if: steps.check_migrations.outputs.pending - run: | - echo "Restarting services after migrations..." - ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service indexer.service" +# - name: Set up SSH key +# uses: webfactory/ssh-agent@v0.5.3 +# with: +# ssh-private-key: ${{ secrets.EC2_SSH_PRIVATE_KEY }} - - name: Restart services if no migrations - if: steps.check_migrations.outputs.pending == '' - run: | - echo "Restarting services without migration..." - ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service" +# - name: Push new code to EC2 +# run: | +# rsync -avz --exclude '.git*' --exclude 'node_modules' ./ ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }}:/home/ec2-user/django-indexer +# echo "Code has been pushed to the EC2 instance." + +# - name: Check for pending migrations +# id: check_migrations +# run: | +# echo "Checking for pending migrations..." +# pending_migrations=$(ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py showmigrations --plan | grep '\[ \]'") +# echo "::set-output name=pending::${pending_migrations}" +# if [ -z "$pending_migrations" ]; then +# echo "No migrations found." +# else: +# echo "Migrations found, stopping services." + +# - name: Stop services if migrations are pending +# if: steps.check_migrations.outputs.pending +# run: | +# echo "Stopping services..." +# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl stop gunicorn.service indexer.service" + +# - name: Run migrations +# if: steps.check_migrations.outputs.pending +# run: | +# echo "Running migrations..." +# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py migrate" + +# - name: Run collectstatic +# run: | +# echo "Running collectstatic..." +# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py collectstatic --noinput" + +# - name: Restart services if migrations were run +# if: steps.check_migrations.outputs.pending +# run: | +# echo "Restarting services after migrations..." +# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service indexer.service" + +# - name: Restart services if no migrations +# if: steps.check_migrations.outputs.pending == '' +# run: | +# echo "Restarting services without migration..." +# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service" From 757f774f4418f234f68dd8cc7d6a71a096786760 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:50:59 -0400 Subject: [PATCH 009/454] remove whitespace in deploy workflow --- .github/workflows/deploy.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9c958fe..554fb7f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,4 +1,3 @@ -# This is a basic workflow to help you get started with Actions name: Deploy to EC2 on Push on: @@ -34,7 +33,7 @@ jobs: id: deploy run: | aws deploy create-deployment \ - --application-name django-indexer \ + --application-name django-indexer \ --deployment-group-name django-indexer-dev \ --deployment-config-name CodeDeployDefault.AllAtOnce \ --github-location repository=${{ github.repository }},commitId=${{ github.sha }} From 5eff58c08ed4a02306a3030cdd68386fae84cc78 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:22:22 -0400 Subject: [PATCH 010/454] update after_install script --- appspec.yml | 12 ++++++------ scripts/after_install.sh | 32 +++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/appspec.yml b/appspec.yml index 76a51b2..b6fb084 100644 --- a/appspec.yml +++ b/appspec.yml @@ -3,12 +3,12 @@ os: linux files: - source: / destination: /home/ec2-user/django-indexer -# hooks: -# # Install: -# AfterInstall: -# - location: scripts/after_install.sh -# timeout: 300 -# runas: root +hooks: + # # Install: + AfterInstall: + - location: scripts/after_install.sh + timeout: 300 + runas: ec2-user # ApplicationStart: # - location: scripts/application_start.sh # timeout: 300 diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 2ebca6a..d3fa518 100644 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -1,10 +1,32 @@ #!/bin/bash -echo 'run after_install.sh' >> /home/ec2-user/myrepo/deploy.log +# Log output to a specific file +LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" -cd /home/ec2-user/django-indexer +echo 'Running after_install.sh' >> "$LOG_FILE" -echo 'poetry install' >> /home/ec2-user/myrepo/deploy.log +# Define the project directory +PROJECT_DIR="/home/ec2-user/django-indexer" -poetry shell +# Navigate to the project directory +cd "$PROJECT_DIR" -poetry install >> /home/ec2-user/myrepo/deploy.log \ No newline at end of file +# Source the specific poetry virtual environment +source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" + +# Check if there are pending migrations +if python manage.py showmigrations | grep '\[ \]'; then + echo 'Migrations found, stopping services...' >> "$LOG_FILE" + sudo systemctl stop gunicorn.service celery.service + + echo 'Applying migrations...' >> "$LOG_FILE" + python manage.py migrate >> "$LOG_FILE" + + echo 'Starting services...' >> "$LOG_FILE" + sudo systemctl start gunicorn.service celery.service +else + echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" + python manage.py collectstatic --noinput >> "$LOG_FILE" + sudo systemctl restart gunicorn.service celery.service +fi + +echo 'after_install.sh completed' >> "$LOG_FILE" From 160af1a401018c78728971677641e2c4a064c018 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:36:40 -0400 Subject: [PATCH 011/454] set correct file permissions in after_install.sh --- scripts/after_install.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) mode change 100644 => 100755 scripts/after_install.sh diff --git a/scripts/after_install.sh b/scripts/after_install.sh old mode 100644 new mode 100755 index d3fa518..32cbe2a --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -15,7 +15,7 @@ source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.1 # Check if there are pending migrations if python manage.py showmigrations | grep '\[ \]'; then - echo 'Migrations found, stopping services...' >> "$LOG_FILE" + echo 'Migrations found; stopping services...' >> "$LOG_FILE" sudo systemctl stop gunicorn.service celery.service echo 'Applying migrations...' >> "$LOG_FILE" @@ -29,4 +29,8 @@ else sudo systemctl restart gunicorn.service celery.service fi +# Set correct permissions for all files in the project directory +chown -R ec2-user:ec2-user /home/ec2-user/django-indexer/ +chmod -R 775 /home/ec2-user/django-indexer/ + echo 'after_install.sh completed' >> "$LOG_FILE" From 96611373bef0c3f694bc447e4803ca427a700e8d Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:38:43 -0400 Subject: [PATCH 012/454] add timetstamp to after_install logs --- scripts/after_install.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 32cbe2a..2d47cb9 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -4,6 +4,9 @@ LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" echo 'Running after_install.sh' >> "$LOG_FILE" +# Log the current date and time in a human-readable format +echo "Script execution started at: $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" + # Define the project directory PROJECT_DIR="/home/ec2-user/django-indexer" From a81135438e5689c6bf76c7a1aba23479dedbeb48 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:41:02 -0400 Subject: [PATCH 013/454] add log to after_install --- scripts/after_install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 2d47cb9..3840b2a 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -36,4 +36,6 @@ fi chown -R ec2-user:ec2-user /home/ec2-user/django-indexer/ chmod -R 775 /home/ec2-user/django-indexer/ +echo "Set permissions for all files in the project directory" >> "$LOG_FILE" + echo 'after_install.sh completed' >> "$LOG_FILE" From 88a8cd399ceecfc0a5e9b07a762ac8e78d7b0589 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:52:58 -0400 Subject: [PATCH 014/454] fix permissions settings in after_install --- scripts/after_install.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 3840b2a..7e883df 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -32,10 +32,16 @@ else sudo systemctl restart gunicorn.service celery.service fi -# Set correct permissions for all files in the project directory -chown -R ec2-user:ec2-user /home/ec2-user/django-indexer/ -chmod -R 775 /home/ec2-user/django-indexer/ +# Set correct ownership recursively for all files and directories in the project directory +sudo chown -R ec2-user:ec2-user /home/ec2-user/django-indexer/ +echo "$(date) - Changed ownership to ec2-user for all project files" >> "$LOG_FILE" -echo "Set permissions for all files in the project directory" >> "$LOG_FILE" +# Set read, write, and execute permissions for the owner and group, and read and execute for others +sudo chmod -R 775 /home/ec2-user/django-indexer/ +echo "$(date) - Set permissions to 775 for all project files" >> "$LOG_FILE" + +# Log the results of permissions change +echo "Permissions after update:" >> "$LOG_FILE" +ls -lah /home/ec2-user/django-indexer/ >> "$LOG_FILE" echo 'after_install.sh completed' >> "$LOG_FILE" From 4715bc661766e4e10f550792359ea994f8fbcc51 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:57:25 -0400 Subject: [PATCH 015/454] add elastic IP to allowed hosts --- base/settings.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/base/settings.py b/base/settings.py index 37a0b34..fe661d7 100644 --- a/base/settings.py +++ b/base/settings.py @@ -30,7 +30,7 @@ # TODO: update before prod release SECRET_KEY = "django-insecure-=r_v_es6w6rxv42^#kc2hca6p%=fe_*cog_5!t%19zea!enlju" -ALLOWED_HOSTS = ["ec2-52-23-183-168.compute-1.amazonaws.com", "127.0.0.1"] +ALLOWED_HOSTS = ["ec2-100-27-57-47.compute-1.amazonaws.com", "127.0.0.1"] # Env vars AWS_ACCESS_KEY_ID = os.environ.get("PL_AWS_ACCESS_KEY_ID") @@ -190,7 +190,7 @@ # LOGGING # Setting the log level from an environment variable -LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO').upper() +LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper() log_level = getattr(logging, LOG_LEVEL, logging.INFO) # print("LOG_LEVEL: ", LOG_LEVEL) @@ -199,15 +199,13 @@ "version": 1, "disable_existing_loggers": False, "formatters": { - "standard": { - "format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s" - }, + "standard": {"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"}, }, "handlers": { "console": { "level": log_level, "class": "logging.StreamHandler", - "formatter": "standard" + "formatter": "standard", }, }, "loggers": { @@ -221,11 +219,8 @@ "level": log_level, "propagate": False, }, - "": { # root logger - "handlers": ["console"], - "level": log_level - } - } + "": {"handlers": ["console"], "level": log_level}, # root logger + }, } # Adding Watchtower logging handler for non-local environments From 948a52eea1abfff2b96bac938163982e9d629b89 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:57:55 -0400 Subject: [PATCH 016/454] remove permissions logging from after_install --- scripts/after_install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 7e883df..8139d7e 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -40,8 +40,8 @@ echo "$(date) - Changed ownership to ec2-user for all project files" >> "$LOG_FI sudo chmod -R 775 /home/ec2-user/django-indexer/ echo "$(date) - Set permissions to 775 for all project files" >> "$LOG_FILE" -# Log the results of permissions change -echo "Permissions after update:" >> "$LOG_FILE" -ls -lah /home/ec2-user/django-indexer/ >> "$LOG_FILE" +# # Log the results of permissions change +# echo "Permissions after update:" >> "$LOG_FILE" +# ls -lah /home/ec2-user/django-indexer/ >> "$LOG_FILE" echo 'after_install.sh completed' >> "$LOG_FILE" From 42a976f8f56436beee1f4f423080db069d10376d Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:16:27 -0400 Subject: [PATCH 017/454] update nginx group permissions --- scripts/after_install.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 8139d7e..23d7ae2 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -40,6 +40,12 @@ echo "$(date) - Changed ownership to ec2-user for all project files" >> "$LOG_FI sudo chmod -R 775 /home/ec2-user/django-indexer/ echo "$(date) - Set permissions to 775 for all project files" >> "$LOG_FILE" +# Set the group of the Django project and static files to nginx +sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer + +# Set the group of the socket directory to nginx +sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/run + # # Log the results of permissions change # echo "Permissions after update:" >> "$LOG_FILE" # ls -lah /home/ec2-user/django-indexer/ >> "$LOG_FILE" From fea902f1473921ac56c742e43bc89ea6415c1da9 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:24:46 -0400 Subject: [PATCH 018/454] add poetry install to after_install.sh --- scripts/after_install.sh | 58 +++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 23d7ae2..a3006ad 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -4,8 +4,37 @@ LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" echo 'Running after_install.sh' >> "$LOG_FILE" -# Log the current date and time in a human-readable format -echo "Script execution started at: $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" +# # Log the current date and time in a human-readable format +# echo "Script execution started at: $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" + +# # Set correct ownership recursively for all files and directories in the project directory +# sudo chown -R ec2-user:ec2-user /home/ec2-user/django-indexer/ +# echo "$(date) - Changed ownership to ec2-user for all project files" >> "$LOG_FILE" + +# # Set read, write, and execute permissions for the owner and group, and read and execute for others +# sudo chmod -R 775 /home/ec2-user/django-indexer/ +# echo "$(date) - Set permissions to 775 for all project files" >> "$LOG_FILE" + +# # Set the group of the Django project and static files to nginx +# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer + +# # Set the group of the socket directory to nginx +# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/run + +# # Restart the nginx service to propagate the changes +# sudo systemctl restart nginx + +# Set correct ownership recursively for project directory +sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ +echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" + +# Set the necessary permissions +sudo chmod -R 775 /home/ec2-user/django-indexer/ +echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" + +# Restart nginx to apply any configuration changes +sudo systemctl restart nginx +echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" # Define the project directory PROJECT_DIR="/home/ec2-user/django-indexer" @@ -16,36 +45,27 @@ cd "$PROJECT_DIR" # Source the specific poetry virtual environment source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" +# Install dependencies using Poetry +echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +poetry install >> "$LOG_FILE" +echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" + # Check if there are pending migrations if python manage.py showmigrations | grep '\[ \]'; then echo 'Migrations found; stopping services...' >> "$LOG_FILE" - sudo systemctl stop gunicorn.service celery.service + sudo systemctl stop gunicorn celery echo 'Applying migrations...' >> "$LOG_FILE" python manage.py migrate >> "$LOG_FILE" echo 'Starting services...' >> "$LOG_FILE" - sudo systemctl start gunicorn.service celery.service + sudo systemctl start gunicorn celery else echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" python manage.py collectstatic --noinput >> "$LOG_FILE" - sudo systemctl restart gunicorn.service celery.service + sudo systemctl restart gunicorn celery fi -# Set correct ownership recursively for all files and directories in the project directory -sudo chown -R ec2-user:ec2-user /home/ec2-user/django-indexer/ -echo "$(date) - Changed ownership to ec2-user for all project files" >> "$LOG_FILE" - -# Set read, write, and execute permissions for the owner and group, and read and execute for others -sudo chmod -R 775 /home/ec2-user/django-indexer/ -echo "$(date) - Set permissions to 775 for all project files" >> "$LOG_FILE" - -# Set the group of the Django project and static files to nginx -sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer - -# Set the group of the socket directory to nginx -sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/run - # # Log the results of permissions change # echo "Permissions after update:" >> "$LOG_FILE" # ls -lah /home/ec2-user/django-indexer/ >> "$LOG_FILE" From 3d2fbc0728fafbe584dac065eae168cf2701eb24 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:28:46 -0400 Subject: [PATCH 019/454] remove old code from after_install --- scripts/after_install.sh | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index a3006ad..53d4e4f 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -2,27 +2,10 @@ # Log output to a specific file LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" -echo 'Running after_install.sh' >> "$LOG_FILE" - -# # Log the current date and time in a human-readable format -# echo "Script execution started at: $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" - -# # Set correct ownership recursively for all files and directories in the project directory -# sudo chown -R ec2-user:ec2-user /home/ec2-user/django-indexer/ -# echo "$(date) - Changed ownership to ec2-user for all project files" >> "$LOG_FILE" - -# # Set read, write, and execute permissions for the owner and group, and read and execute for others -# sudo chmod -R 775 /home/ec2-user/django-indexer/ -# echo "$(date) - Set permissions to 775 for all project files" >> "$LOG_FILE" - -# # Set the group of the Django project and static files to nginx -# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer - -# # Set the group of the socket directory to nginx -# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/run - -# # Restart the nginx service to propagate the changes -# sudo systemctl restart nginx +echo -e "\n\n" >> "$LOG_FILE" +echo "=========================================" >> "$LOG_FILE" +echo "Running after_install.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" +echo "=========================================" >> "$LOG_FILE" # Set correct ownership recursively for project directory sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ @@ -66,8 +49,4 @@ else sudo systemctl restart gunicorn celery fi -# # Log the results of permissions change -# echo "Permissions after update:" >> "$LOG_FILE" -# ls -lah /home/ec2-user/django-indexer/ >> "$LOG_FILE" - -echo 'after_install.sh completed' >> "$LOG_FILE" +echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" From 5f06c5d58f3ba52fa4a3a92f4d0721bd370bb64b Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 15:11:14 -0400 Subject: [PATCH 020/454] update donation admin to fix n+1 query issue --- donations/admin.py | 53 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/donations/admin.py b/donations/admin.py index da83b44..ad4e920 100644 --- a/donations/admin.py +++ b/donations/admin.py @@ -1,20 +1,45 @@ from django.contrib import admin from django.utils.dateformat import format from django.utils.timezone import localtime + from .models import Donation + +@admin.register(Donation) class DonationAdmin(admin.ModelAdmin): - list_display = [field.name for field in Donation._meta.get_fields() if field.name != 'id'] - list_display.extend(['donor_address', 'recipient_address', 'ft_address', 'referrer_address', 'chef_address']) # Add custom methods for addresses - search_fields = ('message', 'donor__address') # You can add more fields here - list_filter = ('donated_at', 'donor', 'pot') # Added default filters, you can add custom DateRangeFilter - date_hierarchy = 'donated_at' - ordering = ('-donated_at',) + list_display = [ + field.name for field in Donation._meta.get_fields() if field.name != "id" + ] + list_display.extend( + [ + "donor_address", + "recipient_address", + "ft_address", + "referrer_address", + "chef_address", + ] + ) # Add custom methods for addresses + search_fields = ( + "message", + "donor__id", + ) # Correct field name from 'donor__address' to 'donor__id' if 'id' is used in the model + list_filter = ("donated_at", "donor", "pot") + date_hierarchy = "donated_at" + ordering = ("-donated_at",) + + def get_queryset(self, request): + # Prefetch related donor, recipient, ft, referrer, and chef to prevent N+1 queries + return ( + super() + .get_queryset(request) + .prefetch_related("donor", "recipient", "ft", "referrer", "chef") + ) def donor_address(self, obj): return obj.donor.id - donor_address.admin_order_field = 'donor__address' # Allows column order sorting - donor_address.short_description = 'Donor Address' + + donor_address.admin_order_field = "donor__id" + donor_address.short_description = "Donor Address" def recipient_address(self, obj): return obj.recipient.id if obj.recipient else None @@ -29,10 +54,10 @@ def chef_address(self, obj): return obj.chef.id if obj.chef else None def formfield_for_dbfield(self, db_field, request, **kwargs): - field = super(DonationAdmin, self).formfield_for_dbfield(db_field, request, **kwargs) - if db_field.name in ['donated_at']: # Add more fields if needed - field.widget.format = '%d-%m-%Y %H:%M' # Change to your preferred format - field.widget.attrs.update({'class': 'vDateField', 'size': '20'}) + field = super(DonationAdmin, self).formfield_for_dbfield( + db_field, request, **kwargs + ) + if db_field.name == "donated_at": + field.widget.format = "%d-%m-%Y %H:%M" + field.widget.attrs.update({"class": "vDateField", "size": "20"}) return field - -admin.site.register(Donation, DonationAdmin) From e2ad39c5f72e4ca439e666efba21a81281f26896 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 17:08:05 -0400 Subject: [PATCH 021/454] use tz aware datetime --- indexer_app/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 9c3562b..7e50e4d 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -6,11 +6,11 @@ import requests from django.conf import settings from django.core.cache import cache +from django.utils import timezone from near_lake_framework.near_primitives import ExecutionOutcome, Receipt from accounts.models import Account from activities.models import Activity -from base.logging import logger from base.utils import format_date, format_to_near from donations.models import Donation from indexer_app.models import BlockHeight @@ -25,6 +25,8 @@ ) from tokens.models import Token, TokenHistoricalPrice +from .logging import logger + GECKO_URL = "https://api.coingecko.com/api/v3" @@ -886,7 +888,7 @@ async def cache_block_height(key: str, height: int, block_count: int) -> int: if (block_count % int(settings.BLOCK_SAVE_HEIGHT or 400)) == 0: logger.info(f"saving daylight, {height}") await BlockHeight.objects.aupdate_or_create( - id=1, defaults={"block_height": height, "updated_at": datetime.now()} + id=1, defaults={"block_height": height, "updated_at": timezone.now()} ) # better than ovverriding model's save method to get a singleton? we need only one entry return height From b9dddc5d798ab07cbce279411a2b25c516a2ccec Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 30 Apr 2024 17:10:29 -0400 Subject: [PATCH 022/454] specify start block for debugging --- indexer_app/tasks.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index ecf33cc..010edc4 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -25,13 +25,17 @@ async def indexer(network: str, from_block: int, to_block: int): lake_config.aws_secret_key = settings.AWS_SECRET_ACCESS_KEY _, streamer_messages_queue = streamer(lake_config) block_count = 0 - + while True: try: # streamer_message is the current block streamer_message = await streamer_messages_queue.get() block_count += 1 - await cache_block_height("current_block_height", streamer_message.block.header.height, block_count) # current block height + await cache_block_height( + "current_block_height", + streamer_message.block.header.height, + block_count, + ) # current block height await handle_streamer_message(streamer_message) except Exception as e: logger.error(f"Error in streamer_messages_queue: {e}") @@ -45,16 +49,19 @@ def listen_to_near_events(): try: # Update below with desired network & block height - start_block = get_block_height('current_block_height') - # start_block = 104_963_982 + # start_block = get_block_height('current_block_height') + start_block = 105_923_501 # manually setting for debugging TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") - loop.run_until_complete(indexer("mainnet", start_block-1, None)) + loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: loop.close() from celery.signals import task_revoked + @task_revoked.connect def on_task_revoked(request, terminated, signum, expired, **kwargs): - logger.info(f"Task {request.id} revoked; terminated={terminated}, signum={signum}, expired={expired}") + logger.info( + f"Task {request.id} revoked; terminated={terminated}, signum={signum}, expired={expired}" + ) From 1221100c42923c24abbc6854fb323bfa48f93bd2 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 1 May 2024 08:23:41 -0400 Subject: [PATCH 023/454] trigger redeployment --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 010edc4..89293c0 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -43,7 +43,7 @@ async def indexer(network: str, from_block: int, to_block: int): @shared_task def listen_to_near_events(): - logger.info("Listening to near events...") + logger.info("Listening to NEAR events...") loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) From 8aeb330d4b3140a76a4164eaaa8a8b105d56316c Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 1 May 2024 09:36:38 -0400 Subject: [PATCH 024/454] fix settings.py for local celery --- base/settings.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/base/settings.py b/base/settings.py index fe661d7..41f5753 100644 --- a/base/settings.py +++ b/base/settings.py @@ -124,9 +124,13 @@ # Append SSL parameters as query parameters in the URL SSL_QUERY = "?ssl_cert_reqs=CERT_NONE" # TODO: UPDATE ACCORDING TO ENV (prod should require cert) -CELERY_BROKER_URL = f"{REDIS_BASE_URL}/0{SSL_QUERY}" +CELERY_BROKER_URL = f"{REDIS_BASE_URL}/0" +CELERY_RESULT_BACKEND = f"{REDIS_BASE_URL}/1" -CELERY_RESULT_BACKEND = f"{REDIS_BASE_URL}/1{SSL_QUERY}" + +if ENVIRONMENT != "local": + CELERY_BROKER_URL += SSL_QUERY + CELERY_RESULT_BACKEND += SSL_QUERY CELERY_BROKER_TRANSPORT_OPTIONS = { From 1c69d33e84c290e939c872fa2e3274f04c3d0cfd Mon Sep 17 00:00:00 2001 From: Prometheus Date: Thu, 2 May 2024 20:32:30 +0100 Subject: [PATCH 025/454] refactor batch function, edit model --- donations/models.py | 11 ++- indexer_app/handler.py | 38 ++++++---- indexer_app/models.py | 5 ++ indexer_app/tasks.py | 3 +- indexer_app/utils.py | 165 +---------------------------------------- 5 files changed, 43 insertions(+), 179 deletions(-) diff --git a/donations/models.py b/donations/models.py index 8c932e1..64e1500 100644 --- a/donations/models.py +++ b/donations/models.py @@ -156,6 +156,13 @@ class Donation(models.Model): class Meta: constraints = [ models.UniqueConstraint( - fields=["on_chain_id", "pot"], name="unique_pot_on_chain_id" - ) + fields=["on_chain_id"], + condition=models.Q(pot__isnull=True), + name="unique_on_chain_id_when_pot_is_null", + ), + models.UniqueConstraint( + fields=["on_chain_id", "pot"], + condition=models.Q(pot__isnull=False), + name="unique_on_chain_id_with_pot", + ), ] diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 6fbf24a..45d5b1a 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -5,8 +5,12 @@ from django.core.cache import cache from near_lake_framework import near_primitives +from base.utils import convert_ns_to_utc +from pots.utils import match_pot_factory_version_pattern + from .logging import logger from .utils import ( + handle_batch_donations, handle_default_list_status_change, handle_list_admin_removal, handle_list_registration_update, @@ -22,8 +26,6 @@ handle_set_payouts, handle_transfer_payout, ) -from base.utils import convert_ns_to_utc -from pots.utils import match_pot_factory_version_pattern async def handle_streamer_message(streamer_message: near_primitives.StreamerMessage): @@ -62,7 +64,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess try: parsed_log = json.loads(log[len("EVENT_JSON:") :]) except json.JSONDecodeError: - logging.warning( + logger.warning( f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" ) continue @@ -158,16 +160,24 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess case "donate": # TODO: donate that produces result logger.info(f"switching bazooka to knifee works!! donate his blood: {args_dict}, {receipt}, {action}, {log_data}") - await handle_new_donations( - args_dict, - receiver_id, - signer_id, - "direct", - receipt, - status_obj, - log_data, - created_at, - ) + if len(log_data) > 1: + await handle_batch_donations( + receiver_id, + signer_id, + "direct", + receipt, + log_data + ) + else: + await handle_new_donations( + args_dict, + receiver_id, + signer_id, + "direct", + receipt, + status_obj, + log_data, + ) break case "handle_protocol_fee_callback": @@ -180,7 +190,6 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess receipt, status_obj, log_data, - created_at, ) break @@ -194,7 +203,6 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess receipt, status_obj, log_data, - created_at, ) break diff --git a/indexer_app/models.py b/indexer_app/models.py index f8c0a4b..ace12ec 100644 --- a/indexer_app/models.py +++ b/indexer_app/models.py @@ -12,6 +12,11 @@ class BlockHeight(models.Model): _("blockheight value"), help_text=_("the last blockheight saved to db."), ) + block_timestamp = models.DateTimeField( + _("block timestamp"), + help_text=_("date equivalent of the block height."), + null=True, + ) updated_at = models.DateTimeField( _("updated at"), help_text=_("block height last update at."), diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 89293c0..1df4312 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -35,6 +35,7 @@ async def indexer(network: str, from_block: int, to_block: int): "current_block_height", streamer_message.block.header.height, block_count, + streamer_message.block.header.timestamp ) # current block height await handle_streamer_message(streamer_message) except Exception as e: @@ -52,7 +53,7 @@ def listen_to_near_events(): # start_block = get_block_height('current_block_height') start_block = 105_923_501 # manually setting for debugging TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") - loop.run_until_complete(indexer("mainnet", start_block - 1, None)) + loop.run_until_complete(indexer("mainnet", 111_949_088, None)) finally: loop.close() diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 7e50e4d..cfd995f 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -505,154 +505,7 @@ async def handle_batch_donations( ): logger.info("BAtch Transaction for donation...") for event_data in log_data: - donation_data = event_data["donation"] - net_amount = int(donation_data["total_amount"]) - int( - donation_data["protocol_fee"] - ) - logger.info(f"Donation data: {donation_data}, {net_amount}") - # insert donate contract which is the receiver id(because of activitry relationship mainly) - donate_contract, _ = await Account.objects.aget_or_create(id=receiverId) - donated_at = datetime.fromtimestamp( - (donation_data.get("donated_at") or donation_data.get("donated_at_ms")) - / 1000 - ) - - # Upsert donor account - donor, _ = await Account.objects.aget_or_create(id=donation_data["donor_id"]) - - recipient = None - if donation_data.get("recipient_id"): - recipient, _ = await Account.objects.aget_or_create( - id=donation_data["recipient_id"] - ) - else: - if not donation_data.get("matching_pool"): - recipient, _ = await Account.objects.aget_or_create( - id=donation_data["project_id"] - ) - - if donation_data.get("referrer_id"): - referrer, _ = await Account.objects.aget_or_create( - id=donation_data["referrer_id"] - ) - - # Upsert token account - token_acct, _ = await Account.objects.aget_or_create( - id=(donation_data.get("ft_id") or "near") - ) - - # Upsert token - try: - token = await Token.objects.aget(id=token_acct) - except Token.DoesNotExist: - # TODO: fetch metadata from token contract (ft_metadata) and add decimals to token record. For now adding 12 which is most common - token = await Token.objects.acreate(id=token_acct, decimals=12) - - # Fetch historical token data - # late_p = await token.get_most_recent_price() - try: - logger.info("fetching historical price...") - endpoint = f"{GECKO_URL}/coins/{donation_data.get('ft_id', 'near')}/history?date={format_date(donated_at)}&localization=false" - response = requests.get(endpoint) - price_data = response.json() - unit_price = ( - price_data.get("market_data", {}).get("current_price", {}).get("usd") - ) - logger.info(f"the usd price is what, {unit_price}") - await TokenHistoricalPrice.objects.acreate( - token=token, - price_usd=unit_price, - ) - except Exception as e: - logger.warning(f"api rate limit? {e}") - # TODO: NB: below method has not been tested - # historical_price = await token.get_most_recent_price() # to use model methods, we might have to use asgiref sync_to_async - historical = await TokenHistoricalPrice.objects.aget( - token=token, - price_usd=unit_price, - ) - # print("fetched old price:", historical_price.price_usd) - unit_price = historical.price_usd - - total_amount = donation_data["total_amount"] - net_amount = net_amount - int(donation_data.get("referrer_fee") or 0) - - # Calculate USD amounts - totalnearAmount = format_to_near(total_amount) - netnearAmount = format_to_near(net_amount) - total_amount_usd = unit_price * totalnearAmount - net_amount_usd = unit_price * netnearAmount - - logger.info(f"inserting donations... {total_amount_usd}") - donation = await Donation.objects.acreate( - on_chain_id=donation_data["id"], - donor=donor, - total_amount=total_amount, - total_amount_usd=total_amount_usd, - net_amount_usd=net_amount_usd, - net_amount=net_amount, - ft=token_acct, - message=donation_data.get("message"), - donated_at=donated_at, - matching_pool=donation_data.get("matching_pool", False), - recipient=recipient, - protocol_fee=donation_data["protocol_fee"], - referrer=referrer if donation_data.get("referrer_id") else None, - referrer_fee=donation_data.get("referrer_fee"), - tx_hash=receipt_obj.receipt_id, - ) - - if actionName != "direct": - logger.info("selecting pot to make public donation update") - pot = await Pot.objects.aget(id=receiverId) - await Donation.objects.filter(id=donation.id).aupdate(**{"pot": pot}) - potUpdate = { - "total_public_donations": int(pot.total_public_donations or 0) - + int(total_amount), - } - if donation_data.get("matching_pool"): - potUpdate["total_matching_pool"] = ( - pot.total_matching_pool or 0 - ) + total_amount - potUpdate["matching_pool_donations_count"] = ( - pot.matching_pool_donations_count or 0 - ) + 1 - # accountUpdate = {} - else: - potUpdate["public_donations_count"] = ( - pot.public_donations_count or 0 - ) + 1 - await Pot.objects.filter(id=receiverId).aupdate(**potUpdate) - - # donation_recipient = donation_data.get('project_id', donation_data['recipient_id']) - logger.info( - f"update totl donated for {donor.id}, {donor.total_donations_out_usd + decimal.Decimal(total_amount_usd)}" - ) - await Account.objects.filter(id=donor.id).aupdate( - **{ - "total_donations_out_usd": donor.total_donations_out_usd - + decimal.Decimal(total_amount_usd) - } - ) - if recipient: - acct = await Account.objects.aget(id=recipient.id) - logger.info(f"selected {acct} to perform donor count update") - acctUpdate = { - "donors_count": acct.donors_count + 1, - "total_donations_in_usd": acct.total_donations_in_usd - + decimal.Decimal(net_amount_usd), - } - await Account.objects.filter(id=recipient.id).aupdate(**acctUpdate) - - # Insert activity record - await Activity.objects.acreate( - signer_id=signerId, - receiver_id=receiverId, - timestamp=donation.donated_at, - type="Donate_Direct", - action_result=donation_data, - tx_hash=receipt_obj.receipt_id, - ) + await handle_new_donations(event_data["donation"], receiverId, signerId, actionName, receipt_obj, status_obj=None, log_data=[event_data]) async def handle_new_donations( @@ -663,7 +516,6 @@ async def handle_new_donations( receipt_obj: Receipt, status_obj: ExecutionOutcome, log_data: list, - created_at: datetime, ): logger.info(f"new donation data: {data}, {receiverId}") @@ -676,16 +528,6 @@ async def handle_new_donations( if not log_data: return - if len(log_data) > 1: - # log_data = [ - # x - # for x in log_data - # if x["donation"]["recipient_id"] == data["recipient_id"] - # ] - return await handle_batch_donations( - receiverId, signerId, actionName, receipt_obj, log_data - ) - logger.info(f"event after possible filtering: {log_data}") event_data = log_data[0] @@ -882,13 +724,14 @@ async def handle_new_donations( ) -async def cache_block_height(key: str, height: int, block_count: int) -> int: +async def cache_block_height(key: str, height: int, block_count: int, block_timestamp: int) -> int: await cache.aset(key, height) # the cache os the default go to for the restart block, the db is a backup if the redis server crashes. if (block_count % int(settings.BLOCK_SAVE_HEIGHT or 400)) == 0: logger.info(f"saving daylight, {height}") await BlockHeight.objects.aupdate_or_create( - id=1, defaults={"block_height": height, "updated_at": timezone.now()} + id=1, + defaults={"block_height": height, "block_timestamp": datetime.fromtimestamp(block_timestamp / 1000000000), "updated_at": timezone.now()} ) # better than ovverriding model's save method to get a singleton? we need only one entry return height From a04c04f816afcc1d6fca5c72e502d500726bb785 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Thu, 2 May 2024 20:38:14 +0100 Subject: [PATCH 026/454] correct start block --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 1df4312..4a33bbb 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -53,7 +53,7 @@ def listen_to_near_events(): # start_block = get_block_height('current_block_height') start_block = 105_923_501 # manually setting for debugging TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") - loop.run_until_complete(indexer("mainnet", 111_949_088, None)) + loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: loop.close() From 60801acb699408b12e9fe4e9e2f05f6e6801481c Mon Sep 17 00:00:00 2001 From: Prometheus Date: Fri, 3 May 2024 13:28:30 +0100 Subject: [PATCH 027/454] feat: make donation entries update_or_create --- .../migrations/0002_alter_activity_options.py | 17 ++ ...onation_unique_pot_on_chain_id_and_more.py | 35 ++++ .../0002_blockheight_block_timestamp.py | 22 ++ indexer_app/utils.py | 198 +++++++++--------- 4 files changed, 174 insertions(+), 98 deletions(-) create mode 100644 activities/migrations/0002_alter_activity_options.py create mode 100644 donations/migrations/0005_remove_donation_unique_pot_on_chain_id_and_more.py create mode 100644 indexer_app/migrations/0002_blockheight_block_timestamp.py diff --git a/activities/migrations/0002_alter_activity_options.py b/activities/migrations/0002_alter_activity_options.py new file mode 100644 index 0000000..12fd19c --- /dev/null +++ b/activities/migrations/0002_alter_activity_options.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.4 on 2024-05-03 10:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("activities", "0001_initial"), + ] + + operations = [ + migrations.AlterModelOptions( + name="activity", + options={"verbose_name_plural": "Activities"}, + ), + ] diff --git a/donations/migrations/0005_remove_donation_unique_pot_on_chain_id_and_more.py b/donations/migrations/0005_remove_donation_unique_pot_on_chain_id_and_more.py new file mode 100644 index 0000000..67e0e0b --- /dev/null +++ b/donations/migrations/0005_remove_donation_unique_pot_on_chain_id_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 5.0.4 on 2024-05-03 10:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0001_initial"), + ("donations", "0004_alter_donation_on_chain_id"), + ("pots", "0001_initial"), + ] + + operations = [ + migrations.RemoveConstraint( + model_name="donation", + name="unique_pot_on_chain_id", + ), + migrations.AddConstraint( + model_name="donation", + constraint=models.UniqueConstraint( + condition=models.Q(("pot__isnull", True)), + fields=("on_chain_id",), + name="unique_on_chain_id_when_pot_is_null", + ), + ), + migrations.AddConstraint( + model_name="donation", + constraint=models.UniqueConstraint( + condition=models.Q(("pot__isnull", False)), + fields=("on_chain_id", "pot"), + name="unique_on_chain_id_with_pot", + ), + ), + ] diff --git a/indexer_app/migrations/0002_blockheight_block_timestamp.py b/indexer_app/migrations/0002_blockheight_block_timestamp.py new file mode 100644 index 0000000..deba8d4 --- /dev/null +++ b/indexer_app/migrations/0002_blockheight_block_timestamp.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.4 on 2024-05-03 10:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("indexer_app", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="blockheight", + name="block_timestamp", + field=models.DateTimeField( + help_text="date equivalent of the block height.", + null=True, + verbose_name="block timestamp", + ), + ), + ] diff --git a/indexer_app/utils.py b/indexer_app/utils.py index cfd995f..bd142b4 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -612,116 +612,118 @@ async def handle_new_donations( total_amount_usd = unit_price * totalnearAmount net_amount_usd = unit_price * netnearAmount - logger.info(f"inserting donations... {total_amount_usd}") - if actionName == "direct": # - donation = await Donation.objects.acreate( + logger.info(f"inserting donations... by {actionName}, {total_amount_usd}") + default_data = { + "donor": donor, + "total_amount": total_amount, + "total_amount_usd": total_amount_usd, + "net_amount_usd": net_amount_usd, + "net_amount": net_amount, + "ft": token_acct, + "message": donation_data.get("message"), + "donated_at": donated_at, + "matching_pool": donation_data.get("matching_pool", False), + "recipient": recipient, + "protocol_fee": donation_data["protocol_fee"], + "referrer": referrer if donation_data.get("referrer_id") else None, + "referrer_fee": donation_data.get("referrer_fee"), + "tx_hash": receipt_obj.receipt_id, + } + created = False + if actionName == "direct": + donation, created = await Donation.objects.aupdate_or_create( on_chain_id=donation_data["id"], - donor=donor, - total_amount=total_amount, - total_amount_usd=total_amount_usd, - net_amount_usd=net_amount_usd, - net_amount=net_amount, - ft=token_acct, - message=donation_data.get("message"), - donated_at=donated_at, - matching_pool=donation_data.get("matching_pool", False), - recipient=recipient, - protocol_fee=donation_data["protocol_fee"], - referrer=referrer if donation_data.get("referrer_id") else None, - referrer_fee=donation_data.get("referrer_fee"), - tx_hash=receipt_obj.receipt_id, + defaults={}, + create_defaults=default_data ) + # forgot why i didn't use else, but didn't for a reason. if actionName != "direct": logger.info("selecting pot to make public donation update") pot = await Pot.objects.aget(id=receiverId) - donation = await Donation.objects.acreate( + default_data["pot"] = pot + donation, created = await Donation.objects.aupdate_or_create( on_chain_id=donation_data["id"], - donor=donor, - pot=pot, - total_amount=total_amount, - total_amount_usd=total_amount_usd, - net_amount_usd=net_amount_usd, - net_amount=net_amount, - ft=token_acct, - message=donation_data.get("message"), - donated_at=donated_at, - matching_pool=donation_data.get("matching_pool", False), - recipient=recipient, - protocol_fee=donation_data["protocol_fee"], - referrer=referrer if donation_data.get("referrer_id") else None, - referrer_fee=donation_data.get("referrer_fee"), - tx_hash=receipt_obj.receipt_id, + defaults={}, + create_defaults=default_data ) - potUpdate = { - "total_public_donations": str( - int(pot.total_public_donations or 0) + int(total_amount) - ), - "total_public_donations_usd": int(pot.total_public_donations_usd or 0.0) - + total_amount_usd, - } - if donation_data.get("matching_pool"): - potUpdate["total_matching_pool"] = str( - int(pot.total_matching_pool or 0) + int(total_amount) - ) - potUpdate["total_matching_pool"] = ( - pot.total_matching_pool_usd or 0.0 - ) + total_amount_usd - potUpdate["matching_pool_donations_count"] = ( - pot.matching_pool_donations_count or 0 - ) + 1 - - if recipient: - await Account.objects.filter(id=recipient.id).aupdate( - **{ - "total_matching_pool_allocations_usd": recipient.total_matching_pool_allocations_usd - + total_amount_usd - } - ) - # accountUpdate = {} - else: - potUpdate["public_donations_count"] = (pot.public_donations_count or 0) + 1 + + logger.info(f"Backfilling data? {created}") - await Pot.objects.filter(id=receiverId).aupdate(**potUpdate) + if created: # only do updates if donation object was created - # donation_recipient = donation_data.get('project_id', donation_data['recipient_id']) - logger.info( - f"update totl donated for {donor.id}, {donor.total_donations_out_usd + decimal.Decimal(total_amount_usd)}" - ) - await Account.objects.filter(id=donor.id).aupdate( - **{ - "total_donations_out_usd": donor.total_donations_out_usd - + decimal.Decimal(total_amount_usd) - } - ) - if recipient: - acct = await Account.objects.aget(id=recipient.id) - logger.info(f"selected {acct} to perform donor count update") - acctUpdate = { - "donors_count": acct.donors_count + 1, - "total_donations_in_usd": acct.total_donations_in_usd - + decimal.Decimal(net_amount_usd), - } - await Account.objects.filter(id=recipient.id).aupdate(**acctUpdate) + if actionName != "direct": - # Insert activity record - await Activity.objects.acreate( - signer_id=signerId, - receiver_id=receiverId, - timestamp=donation.donated_at, - type=( - "Donate_Direct" - if actionName == "direct" - else ( - "Donate_Pot_Matching_Pool" - if donation.matching_pool - else "Donate_Pot_Public" - ) - ), - action_result=donation_data, - tx_hash=receipt_obj.receipt_id, - ) + potUpdate = { + "total_public_donations": str( + int(pot.total_public_donations or 0) + int(total_amount) + ), + "total_public_donations_usd": int(pot.total_public_donations_usd or 0.0) + + total_amount_usd, + } + if donation_data.get("matching_pool"): + potUpdate["total_matching_pool"] = str( + int(pot.total_matching_pool or 0) + int(total_amount) + ) + potUpdate["total_matching_pool"] = ( + pot.total_matching_pool_usd or 0.0 + ) + total_amount_usd + potUpdate["matching_pool_donations_count"] = ( + pot.matching_pool_donations_count or 0 + ) + 1 + + if recipient: + await Account.objects.filter(id=recipient.id).aupdate( + **{ + "total_matching_pool_allocations_usd": recipient.total_matching_pool_allocations_usd + + total_amount_usd + } + ) + + # accountUpdate = {} + else: + potUpdate["public_donations_count"] = (pot.public_donations_count or 0) + 1 + + await Pot.objects.filter(id=receiverId).aupdate(**potUpdate) + + # donation_recipient = donation_data.get('project_id', donation_data['recipient_id']) + logger.info( + f"update totl donated for {donor.id}, {donor.total_donations_out_usd + decimal.Decimal(total_amount_usd)}" + ) + await Account.objects.filter(id=donor.id).aupdate( + **{ + "total_donations_out_usd": donor.total_donations_out_usd + + decimal.Decimal(total_amount_usd) + } + ) + if recipient: + acct = await Account.objects.aget(id=recipient.id) + logger.info(f"selected {acct} to perform donor count update") + acctUpdate = { + "donors_count": acct.donors_count + 1, + "total_donations_in_usd": acct.total_donations_in_usd + + decimal.Decimal(net_amount_usd), + } + await Account.objects.filter(id=recipient.id).aupdate(**acctUpdate) + + # Insert activity record + await Activity.objects.acreate( + signer_id=signerId, + receiver_id=receiverId, + timestamp=donation.donated_at, + type=( + "Donate_Direct" + if actionName == "direct" + else ( + "Donate_Pot_Matching_Pool" + if donation.matching_pool + else "Donate_Pot_Public" + ) + ), + action_result=donation_data, + tx_hash=receipt_obj.receipt_id, + ) async def cache_block_height(key: str, height: int, block_count: int, block_timestamp: int) -> int: From d03c11afc5a1812d0688ad6b944f7fd375eede4d Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 12:46:21 -0400 Subject: [PATCH 028/454] update start block for debugging --- indexer_app/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 4a33bbb..9e23a2d 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -35,7 +35,7 @@ async def indexer(network: str, from_block: int, to_block: int): "current_block_height", streamer_message.block.header.height, block_count, - streamer_message.block.header.timestamp + streamer_message.block.header.timestamp, ) # current block height await handle_streamer_message(streamer_message) except Exception as e: @@ -51,7 +51,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height('current_block_height') - start_block = 105_923_501 # manually setting for debugging TODO: remove this + start_block = 104_858_762 # manually setting for debugging TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From 5f66bc44d535e99bb5c1091bd0a0536eb13a00d5 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 13:25:05 -0400 Subject: [PATCH 029/454] debug logs for after_install migration check --- scripts/after_install.sh | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 53d4e4f..d5307c4 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -33,20 +33,38 @@ echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$L poetry install >> "$LOG_FILE" echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" -# Check if there are pending migrations -if python manage.py showmigrations | grep '\[ \]'; then - echo 'Migrations found; stopping services...' >> "$LOG_FILE" - sudo systemctl stop gunicorn celery +# Check if there are pending migrations and log the output +echo "Checking for pending migrations..." >> "$LOG_FILE" +PENDING_MIGRATIONS=$(python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout +echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" - echo 'Applying migrations...' >> "$LOG_FILE" +if [[ ! -z "$PENDING_MIGRATIONS" ]]; then + echo "$(date '+%Y-%m-%d %H:%M:%S') - Migrations found; stopping services..." >> "$LOG_FILE" + sudo systemctl stop gunicorn celery + echo "$(date '+%Y-%m-%d %H:%M:%S') - Applying migrations..." >> "$LOG_FILE" python manage.py migrate >> "$LOG_FILE" - - echo 'Starting services...' >> "$LOG_FILE" + echo "$(date '+%Y-%m-%d %H:%M:%S') - Starting services..." >> "$LOG_FILE" sudo systemctl start gunicorn celery else - echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" + echo "$(date '+%Y-%m-%d %H:%M:%S') - No migrations found. Running collectstatic and restarting services..." >> "$LOG_FILE" python manage.py collectstatic --noinput >> "$LOG_FILE" sudo systemctl restart gunicorn celery fi +# # Check if there are pending migrations +# if python manage.py showmigrations | grep '\[ \]'; then +# echo 'Migrations found; stopping services...' >> "$LOG_FILE" +# sudo systemctl stop gunicorn celery + +# echo 'Applying migrations...' >> "$LOG_FILE" +# python manage.py migrate >> "$LOG_FILE" + +# echo 'Starting services...' >> "$LOG_FILE" +# sudo systemctl start gunicorn celery +# else +# echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" +# python manage.py collectstatic --noinput >> "$LOG_FILE" +# sudo systemctl restart gunicorn celery +# fi + echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" From 7024a416be086193d95ca0a0a5e405c974a71bbd Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 13:30:27 -0400 Subject: [PATCH 030/454] migration check debugging --- scripts/after_install.sh | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index d5307c4..f83b6c9 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -38,18 +38,40 @@ echo "Checking for pending migrations..." >> "$LOG_FILE" PENDING_MIGRATIONS=$(python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" -if [[ ! -z "$PENDING_MIGRATIONS" ]]; then - echo "$(date '+%Y-%m-%d %H:%M:%S') - Migrations found; stopping services..." >> "$LOG_FILE" +# Log the full output of showmigrations +echo "Checking for pending migrations..." >> "$LOG_FILE" +python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose + +# Now, let's use a more direct check for unapplied migrations +PENDING_MIGRATIONS=$(python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations + +if [ "$PENDING_MIGRATIONS" -gt 0 ]; then + echo "Migrations found; stopping services..." >> "$LOG_FILE" sudo systemctl stop gunicorn celery - echo "$(date '+%Y-%m-%d %H:%M:%S') - Applying migrations..." >> "$LOG_FILE" - python manage.py migrate >> "$LOG_FILE" - echo "$(date '+%Y-%m-%d %H:%M:%S') - Starting services..." >> "$LOG_FILE" + + echo 'Applying migrations...' >> "$LOG_FILE" + python manage.py migrate >> "$LOG_FILE" 2>&1 + + echo 'Starting services...' >> "$LOG_FILE" sudo systemctl start gunicorn celery else - echo "$(date '+%Y-%m-%d %H:%M:%S') - No migrations found. Running collectstatic and restarting services..." >> "$LOG_FILE" - python manage.py collectstatic --noinput >> "$LOG_FILE" + echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" + python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 sudo systemctl restart gunicorn celery fi +# # not working version +# if [[ ! -z "$PENDING_MIGRATIONS" ]]; then +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Migrations found; stopping services..." >> "$LOG_FILE" +# sudo systemctl stop gunicorn celery +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Applying migrations..." >> "$LOG_FILE" +# python manage.py migrate >> "$LOG_FILE" +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Starting services..." >> "$LOG_FILE" +# sudo systemctl start gunicorn celery +# else +# echo "$(date '+%Y-%m-%d %H:%M:%S') - No migrations found. Running collectstatic and restarting services..." >> "$LOG_FILE" +# python manage.py collectstatic --noinput >> "$LOG_FILE" +# sudo systemctl restart gunicorn celery +# fi # # Check if there are pending migrations # if python manage.py showmigrations | grep '\[ \]'; then From 002d94b14d14a8430a280b9ad0d5afb375e3a174 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 14:18:20 -0400 Subject: [PATCH 031/454] load env vars in after_install --- scripts/after_install.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index f83b6c9..defd978 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -7,6 +7,9 @@ echo "=========================================" >> "$LOG_FILE" echo "Running after_install.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" echo "=========================================" >> "$LOG_FILE" +# Load env vars +source /home/ec2-user/.bashrc + # Set correct ownership recursively for project directory sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" From 6d4e5762f86b22b4152d1b288551e9ff9b7867ce Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 14:29:40 -0400 Subject: [PATCH 032/454] remove old code --- scripts/after_install.sh | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index defd978..5ad3b7c 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -45,7 +45,7 @@ echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" echo "Checking for pending migrations..." >> "$LOG_FILE" python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose -# Now, let's use a more direct check for unapplied migrations +# Check for unapplied migrations PENDING_MIGRATIONS=$(python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations if [ "$PENDING_MIGRATIONS" -gt 0 ]; then @@ -61,35 +61,6 @@ else echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 sudo systemctl restart gunicorn celery -fi -# # not working version -# if [[ ! -z "$PENDING_MIGRATIONS" ]]; then -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Migrations found; stopping services..." >> "$LOG_FILE" -# sudo systemctl stop gunicorn celery -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Applying migrations..." >> "$LOG_FILE" -# python manage.py migrate >> "$LOG_FILE" -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Starting services..." >> "$LOG_FILE" -# sudo systemctl start gunicorn celery -# else -# echo "$(date '+%Y-%m-%d %H:%M:%S') - No migrations found. Running collectstatic and restarting services..." >> "$LOG_FILE" -# python manage.py collectstatic --noinput >> "$LOG_FILE" -# sudo systemctl restart gunicorn celery -# fi - -# # Check if there are pending migrations -# if python manage.py showmigrations | grep '\[ \]'; then -# echo 'Migrations found; stopping services...' >> "$LOG_FILE" -# sudo systemctl stop gunicorn celery - -# echo 'Applying migrations...' >> "$LOG_FILE" -# python manage.py migrate >> "$LOG_FILE" - -# echo 'Starting services...' >> "$LOG_FILE" -# sudo systemctl start gunicorn celery -# else -# echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" -# python manage.py collectstatic --noinput >> "$LOG_FILE" -# sudo systemctl restart gunicorn celery -# fi +f echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" From b05e457b68d447aa7724fb4607ac41ebca8117f4 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 14:31:10 -0400 Subject: [PATCH 033/454] display donation id on admin --- donations/admin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/donations/admin.py b/donations/admin.py index ad4e920..b79f09b 100644 --- a/donations/admin.py +++ b/donations/admin.py @@ -7,9 +7,10 @@ @admin.register(Donation) class DonationAdmin(admin.ModelAdmin): - list_display = [ - field.name for field in Donation._meta.get_fields() if field.name != "id" - ] + # list_display = [ + # field.name for field in Donation._meta.get_fields() if field.name != "id" + # ] + list_display = ["__all__"] list_display.extend( [ "donor_address", From 544e2c0170d1a2d149e3093f09f29a4114480ca8 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 14:36:56 -0400 Subject: [PATCH 034/454] fix donation id on admin dashboard --- donations/admin.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/donations/admin.py b/donations/admin.py index b79f09b..43f87d3 100644 --- a/donations/admin.py +++ b/donations/admin.py @@ -7,10 +7,7 @@ @admin.register(Donation) class DonationAdmin(admin.ModelAdmin): - # list_display = [ - # field.name for field in Donation._meta.get_fields() if field.name != "id" - # ] - list_display = ["__all__"] + list_display = [field.name for field in Donation._meta.get_fields()] list_display.extend( [ "donor_address", From 6337caa2031bd8c893772f21340c40fcd8b68a72 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 14:41:09 -0400 Subject: [PATCH 035/454] fix typo in after_install --- scripts/after_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 5ad3b7c..0bdcdf0 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -61,6 +61,6 @@ else echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 sudo systemctl restart gunicorn celery -f +fi echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" From 5647bb27ed622aad65ec69a5d9d6d901ae18ac28 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 15:04:57 -0400 Subject: [PATCH 036/454] fix TokenHistoricalPrice price_usd null issue --- indexer_app/utils.py | 65 +++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index bd142b4..3f12910 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -505,7 +505,15 @@ async def handle_batch_donations( ): logger.info("BAtch Transaction for donation...") for event_data in log_data: - await handle_new_donations(event_data["donation"], receiverId, signerId, actionName, receipt_obj, status_obj=None, log_data=[event_data]) + await handle_new_donations( + event_data["donation"], + receiverId, + signerId, + actionName, + receipt_obj, + status_obj=None, + log_data=[event_data], + ) async def handle_new_donations( @@ -588,20 +596,24 @@ async def handle_new_donations( endpoint = f"{GECKO_URL}/coins/{donation_data.get('ft_id', 'near')}/history?date={format_date(donated_at)}&localization=false" response = requests.get(endpoint) price_data = response.json() - unit_price = ( - price_data.get("market_data", {}).get("current_price", {}).get("usd") - ) - await TokenHistoricalPrice.objects.acreate( # need to change token model to use token as id - token=token, - price_usd=unit_price, - ) except Exception as e: - logger.warning(f"api rate limit? {e}") - # TODO: NB: below method has not been tested - # historical_price = await token.get_most_recent_price() # to use model methods, we might have to use asgiref sync_to_async - historical = await TokenHistoricalPrice.objects.aget(token=token) - # print("fetched old price:", historical_price.price_usd) - unit_price = historical.price_usd + logger.warning(f"Failed to fetch price data: {e}") + # logger.debug(f"price data: {price_data}") + unit_price = price_data.get("market_data", {}).get("current_price", {}).get("usd") + logger.debug(f"unit price: {unit_price}") + if unit_price: + try: + await TokenHistoricalPrice.objects.acreate( # need to change token model to use token as id + token=token, + price_usd=unit_price, + ) + except Exception as e: + logger.warning( + f"Error creating TokenHistoricalPrice: {e} token: {token} unit_price: {unit_price}" + ) + # historical_price = await token.get_most_recent_price() # to use model methods, we might have to use asgiref sync_to_async + historical = await TokenHistoricalPrice.objects.aget(token=token) + unit_price = historical.price_usd total_amount = donation_data["total_amount"] net_amount = net_amount - int(donation_data.get("referrer_fee") or 0) @@ -632,9 +644,7 @@ async def handle_new_donations( created = False if actionName == "direct": donation, created = await Donation.objects.aupdate_or_create( - on_chain_id=donation_data["id"], - defaults={}, - create_defaults=default_data + on_chain_id=donation_data["id"], defaults={}, create_defaults=default_data ) # forgot why i didn't use else, but didn't for a reason. @@ -643,15 +653,12 @@ async def handle_new_donations( pot = await Pot.objects.aget(id=receiverId) default_data["pot"] = pot donation, created = await Donation.objects.aupdate_or_create( - on_chain_id=donation_data["id"], - defaults={}, - create_defaults=default_data + on_chain_id=donation_data["id"], defaults={}, create_defaults=default_data ) - logger.info(f"Backfilling data? {created}") - if created: # only do updates if donation object was created + if created: # only do updates if donation object was created if actionName != "direct": @@ -683,7 +690,9 @@ async def handle_new_donations( # accountUpdate = {} else: - potUpdate["public_donations_count"] = (pot.public_donations_count or 0) + 1 + potUpdate["public_donations_count"] = ( + pot.public_donations_count or 0 + ) + 1 await Pot.objects.filter(id=receiverId).aupdate(**potUpdate) @@ -726,14 +735,20 @@ async def handle_new_donations( ) -async def cache_block_height(key: str, height: int, block_count: int, block_timestamp: int) -> int: +async def cache_block_height( + key: str, height: int, block_count: int, block_timestamp: int +) -> int: await cache.aset(key, height) # the cache os the default go to for the restart block, the db is a backup if the redis server crashes. if (block_count % int(settings.BLOCK_SAVE_HEIGHT or 400)) == 0: logger.info(f"saving daylight, {height}") await BlockHeight.objects.aupdate_or_create( id=1, - defaults={"block_height": height, "block_timestamp": datetime.fromtimestamp(block_timestamp / 1000000000), "updated_at": timezone.now()} + defaults={ + "block_height": height, + "block_timestamp": datetime.fromtimestamp(block_timestamp / 1000000000), + "updated_at": timezone.now(), + }, ) # better than ovverriding model's save method to get a singleton? we need only one entry return height From 9550888967639250c8c429b2ea45cfc5a5bc941b Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 15:12:19 -0400 Subject: [PATCH 037/454] add more debug logs --- indexer_app/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 3f12910..8aafe56 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -595,10 +595,11 @@ async def handle_new_donations( logger.info("fetching historical price...") endpoint = f"{GECKO_URL}/coins/{donation_data.get('ft_id', 'near')}/history?date={format_date(donated_at)}&localization=false" response = requests.get(endpoint) + logger.debug(f"response: {response}") price_data = response.json() except Exception as e: logger.warning(f"Failed to fetch price data: {e}") - # logger.debug(f"price data: {price_data}") + logger.debug(f"price data: {price_data}") unit_price = price_data.get("market_data", {}).get("current_price", {}).get("usd") logger.debug(f"unit price: {unit_price}") if unit_price: From 567292bebfed8ce886ac1309062960aaa14faae6 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 15:17:33 -0400 Subject: [PATCH 038/454] change debug to info --- indexer_app/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 8aafe56..aab1ead 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -593,15 +593,17 @@ async def handle_new_donations( # late_p = await token.get_most_recent_price() try: logger.info("fetching historical price...") + logger.info("donated at: {donated_at}") endpoint = f"{GECKO_URL}/coins/{donation_data.get('ft_id', 'near')}/history?date={format_date(donated_at)}&localization=false" + logger.info(f"endpoint: {endpoint}") response = requests.get(endpoint) - logger.debug(f"response: {response}") + logger.info(f"response: {response}") price_data = response.json() except Exception as e: logger.warning(f"Failed to fetch price data: {e}") - logger.debug(f"price data: {price_data}") + logger.info(f"price data: {price_data}") unit_price = price_data.get("market_data", {}).get("current_price", {}).get("usd") - logger.debug(f"unit price: {unit_price}") + logger.info(f"unit price: {unit_price}") if unit_price: try: await TokenHistoricalPrice.objects.acreate( # need to change token model to use token as id From dce6e72ad524426b46551196434b9ae2a5e7a520 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 15:23:28 -0400 Subject: [PATCH 039/454] fix typo --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index aab1ead..e38582f 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -593,7 +593,7 @@ async def handle_new_donations( # late_p = await token.get_most_recent_price() try: logger.info("fetching historical price...") - logger.info("donated at: {donated_at}") + logger.info(f"donated at: {donated_at}") endpoint = f"{GECKO_URL}/coins/{donation_data.get('ft_id', 'near')}/history?date={format_date(donated_at)}&localization=false" logger.info(f"endpoint: {endpoint}") response = requests.get(endpoint) From 680d5a0d0da63447b4ff2c005471df62efa92d4d Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 15:27:35 -0400 Subject: [PATCH 040/454] use logger.debug --- indexer_app/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index e38582f..eabe4a7 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -593,17 +593,17 @@ async def handle_new_donations( # late_p = await token.get_most_recent_price() try: logger.info("fetching historical price...") - logger.info(f"donated at: {donated_at}") + logger.debug(f"donated at: {donated_at}") endpoint = f"{GECKO_URL}/coins/{donation_data.get('ft_id', 'near')}/history?date={format_date(donated_at)}&localization=false" - logger.info(f"endpoint: {endpoint}") + logger.debug(f"endpoint: {endpoint}") response = requests.get(endpoint) - logger.info(f"response: {response}") + logger.debug(f"response: {response}") price_data = response.json() except Exception as e: logger.warning(f"Failed to fetch price data: {e}") - logger.info(f"price data: {price_data}") + logger.debug(f"price data: {price_data}") unit_price = price_data.get("market_data", {}).get("current_price", {}).get("usd") - logger.info(f"unit price: {unit_price}") + logger.debug(f"unit price: {unit_price}") if unit_price: try: await TokenHistoricalPrice.objects.acreate( # need to change token model to use token as id From 6ce7d63c2f36568d420614aa42685702605b944a Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 15:36:02 -0400 Subject: [PATCH 041/454] revert to logger.info --- indexer_app/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index eabe4a7..e38582f 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -593,17 +593,17 @@ async def handle_new_donations( # late_p = await token.get_most_recent_price() try: logger.info("fetching historical price...") - logger.debug(f"donated at: {donated_at}") + logger.info(f"donated at: {donated_at}") endpoint = f"{GECKO_URL}/coins/{donation_data.get('ft_id', 'near')}/history?date={format_date(donated_at)}&localization=false" - logger.debug(f"endpoint: {endpoint}") + logger.info(f"endpoint: {endpoint}") response = requests.get(endpoint) - logger.debug(f"response: {response}") + logger.info(f"response: {response}") price_data = response.json() except Exception as e: logger.warning(f"Failed to fetch price data: {e}") - logger.debug(f"price data: {price_data}") + logger.info(f"price data: {price_data}") unit_price = price_data.get("market_data", {}).get("current_price", {}).get("usd") - logger.debug(f"unit price: {unit_price}") + logger.info(f"unit price: {unit_price}") if unit_price: try: await TokenHistoricalPrice.objects.acreate( # need to change token model to use token as id From e13d46932df47b2a715b75c77e29b0d007501b14 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 16:13:01 -0400 Subject: [PATCH 042/454] fix coingecko api error handling --- indexer_app/utils.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index e38582f..aa57c8d 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -598,6 +598,8 @@ async def handle_new_donations( logger.info(f"endpoint: {endpoint}") response = requests.get(endpoint) logger.info(f"response: {response}") + if response.status_code == 429: + logger.warning("Coingecko rate limit exceeded") price_data = response.json() except Exception as e: logger.warning(f"Failed to fetch price data: {e}") @@ -621,13 +623,13 @@ async def handle_new_donations( total_amount = donation_data["total_amount"] net_amount = net_amount - int(donation_data.get("referrer_fee") or 0) - # Calculate USD amounts - totalnearAmount = format_to_near(total_amount) - netnearAmount = format_to_near(net_amount) - total_amount_usd = unit_price * totalnearAmount - net_amount_usd = unit_price * netnearAmount + # Calculate and format amounts + total_near_amount = format_to_near(total_amount) + net_near_amount = format_to_near(net_amount) + total_amount_usd = None if not unit_price else unit_price * total_near_amount + net_amount_usd = None if not unit_price else unit_price * net_near_amount - logger.info(f"inserting donations... by {actionName}, {total_amount_usd}") + logger.info(f"inserting donations... by {actionName}") default_data = { "donor": donor, "total_amount": total_amount, @@ -644,6 +646,7 @@ async def handle_new_donations( "referrer_fee": donation_data.get("referrer_fee"), "tx_hash": receipt_obj.receipt_id, } + logger.info(f"default donation data: {default_data}") created = False if actionName == "direct": donation, created = await Donation.objects.aupdate_or_create( From 34b86d59cc5ed3503f4b692782951015de86fea4 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 16:14:48 -0400 Subject: [PATCH 043/454] add error test --- indexer_app/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index aa57c8d..fee4965 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -664,6 +664,8 @@ async def handle_new_donations( logger.info(f"Backfilling data? {created}") + logger.error(f"ERROR test") + if created: # only do updates if donation object was created if actionName != "direct": From e54861a1fe15e52efe0da6fa57614b8c4ad1b9e6 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 16:19:21 -0400 Subject: [PATCH 044/454] remove ERROR test --- indexer_app/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index fee4965..aa57c8d 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -664,8 +664,6 @@ async def handle_new_donations( logger.info(f"Backfilling data? {created}") - logger.error(f"ERROR test") - if created: # only do updates if donation object was created if actionName != "direct": From c8583c179848f5672f5299c0bd309a74348cac31 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 16:55:50 -0400 Subject: [PATCH 045/454] save correct timestamp on TokenHistoricalPrice record --- base/utils.py | 11 ++++++----- indexer_app/utils.py | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/base/utils.py b/base/utils.py index 64bfb03..4c443a1 100644 --- a/base/utils.py +++ b/base/utils.py @@ -12,14 +12,15 @@ def format_to_near(yocto_amount: str): near_amount = int(yocto_amount) / (10**24) return near_amount + def convert_ns_to_utc(ns_timestamp): # Convert nanoseconds to seconds (float) seconds = ns_timestamp / 1e9 - + # Create a datetime object from the seconds (UTC) utc_datetime = datetime.utcfromtimestamp(seconds) - + # Format the datetime object as a string - formatted_date = utc_datetime.strftime('%Y-%m-%d %H:%M:%S') - - return formatted_date \ No newline at end of file + formatted_date = utc_datetime.strftime("%Y-%m-%d %H:%M:%S") + + return formatted_date diff --git a/indexer_app/utils.py b/indexer_app/utils.py index aa57c8d..32328fd 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -611,6 +611,7 @@ async def handle_new_donations( await TokenHistoricalPrice.objects.acreate( # need to change token model to use token as id token=token, price_usd=unit_price, + timestamp=donated_at, ) except Exception as e: logger.warning( From 03559c75f16fdcd0d521300a9aa7c4ece11194eb Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 3 May 2024 16:56:15 -0400 Subject: [PATCH 046/454] get start block from cache --- indexer_app/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 9e23a2d..deb440b 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -50,8 +50,8 @@ def listen_to_near_events(): try: # Update below with desired network & block height - # start_block = get_block_height('current_block_height') - start_block = 104_858_762 # manually setting for debugging TODO: remove this + start_block = get_block_height("current_block_height") + # start_block = 104_858_762 # manually setting for debugging TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From a226c70abd2bba9ba5c17b0d347f6a6f69c161fc Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 09:29:57 -0400 Subject: [PATCH 047/454] fix None to decimal conversion bug --- indexer_app/utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 32328fd..90621d6 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -665,6 +665,12 @@ async def handle_new_donations( logger.info(f"Backfilling data? {created}") + # convert total_amount_usd and net_amount_usd from None + if total_amount_usd is None: + total_amount_usd = 0.0 + if net_amount_usd is None: + net_amount_usd = 0.0 + if created: # only do updates if donation object was created if actionName != "direct": @@ -705,7 +711,7 @@ async def handle_new_donations( # donation_recipient = donation_data.get('project_id', donation_data['recipient_id']) logger.info( - f"update totl donated for {donor.id}, {donor.total_donations_out_usd + decimal.Decimal(total_amount_usd)}" + f"update total donated for {donor.id}, {donor.total_donations_out_usd + decimal.Decimal(total_amount_usd)}" ) await Account.objects.filter(id=donor.id).aupdate( **{ From a75da815da993b3d22a60c535349b0060ed511e2 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 09:44:50 -0400 Subject: [PATCH 048/454] add action_result & type unique_together constraint on Activity --- .../0003_alter_activity_unique_together.py | 17 +++++++++ activities/models.py | 2 + indexer_app/tasks.py | 4 +- indexer_app/utils.py | 38 +++++++++++-------- 4 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 activities/migrations/0003_alter_activity_unique_together.py diff --git a/activities/migrations/0003_alter_activity_unique_together.py b/activities/migrations/0003_alter_activity_unique_together.py new file mode 100644 index 0000000..e68f410 --- /dev/null +++ b/activities/migrations/0003_alter_activity_unique_together.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.4 on 2024-05-06 13:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("activities", "0002_alter_activity_options"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="activity", + unique_together={("action_result", "type")}, + ), + ] diff --git a/activities/models.py b/activities/models.py index 62e31e8..b1bab6d 100644 --- a/activities/models.py +++ b/activities/models.py @@ -66,3 +66,5 @@ class Activity(models.Model): class Meta: verbose_name_plural = "Activities" + + unique_together = (("action_result", "type"),) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index deb440b..f5a776d 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -50,8 +50,8 @@ def listen_to_near_events(): try: # Update below with desired network & block height - start_block = get_block_height("current_block_height") - # start_block = 104_858_762 # manually setting for debugging TODO: remove this + # start_block = get_block_height("current_block_height") + start_block = 105_112_120 # manually setting for reindexing TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 90621d6..8b20587 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -729,23 +729,29 @@ async def handle_new_donations( } await Account.objects.filter(id=recipient.id).aupdate(**acctUpdate) - # Insert activity record - await Activity.objects.acreate( - signer_id=signerId, - receiver_id=receiverId, - timestamp=donation.donated_at, - type=( - "Donate_Direct" - if actionName == "direct" - else ( - "Donate_Pot_Matching_Pool" - if donation.matching_pool - else "Donate_Pot_Public" - ) - ), - action_result=donation_data, - tx_hash=receipt_obj.receipt_id, + # Insert or update activity record + activity_type = ( + "Donate_Direct" + if actionName == "direct" + else ( + "Donate_Pot_Matching_Pool" + if donation.matching_pool + else "Donate_Pot_Public" + ) + ) + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": donation.donated_at, + "tx_hash": receipt_obj.receipt_id, + } + activity, created = Activity.objects.aupdate_or_create( + action_result=donation_data, type=activity_type, defaults=defaults ) + if created: + logger.info(f"Activity created: {activity}") + else: + logger.info(f"Activity updated: {activity}") async def cache_block_height( From da4ca3777700411f617123facc2a7279451db75f Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 09:54:59 -0400 Subject: [PATCH 049/454] create or update activity even if donation already exists --- indexer_app/utils.py | 64 ++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 8b20587..3fc5aca 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -648,22 +648,16 @@ async def handle_new_donations( "tx_hash": receipt_obj.receipt_id, } logger.info(f"default donation data: {default_data}") - created = False - if actionName == "direct": - donation, created = await Donation.objects.aupdate_or_create( - on_chain_id=donation_data["id"], defaults={}, create_defaults=default_data - ) - # forgot why i didn't use else, but didn't for a reason. if actionName != "direct": logger.info("selecting pot to make public donation update") pot = await Pot.objects.aget(id=receiverId) default_data["pot"] = pot - donation, created = await Donation.objects.aupdate_or_create( - on_chain_id=donation_data["id"], defaults={}, create_defaults=default_data - ) - logger.info(f"Backfilling data? {created}") + donation, donation_created = await Donation.objects.aupdate_or_create( + on_chain_id=donation_data["id"], defaults={}, create_defaults=default_data + ) + logger.info(f"Created donation? {donation_created}") # convert total_amount_usd and net_amount_usd from None if total_amount_usd is None: @@ -671,7 +665,31 @@ async def handle_new_donations( if net_amount_usd is None: net_amount_usd = 0.0 - if created: # only do updates if donation object was created + # Insert or update activity record + activity_type = ( + "Donate_Direct" + if actionName == "direct" + else ( + "Donate_Pot_Matching_Pool" + if donation.matching_pool + else "Donate_Pot_Public" + ) + ) + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": donation.donated_at, + "tx_hash": receipt_obj.receipt_id, + } + activity, activity_created = Activity.objects.aupdate_or_create( + action_result=donation_data, type=activity_type, defaults=defaults + ) + if activity_created: + logger.info(f"Activity created: {activity}") + else: + logger.info(f"Activity updated: {activity}") + + if donation_created: # only do stats updates if donation object was created if actionName != "direct": @@ -729,30 +747,6 @@ async def handle_new_donations( } await Account.objects.filter(id=recipient.id).aupdate(**acctUpdate) - # Insert or update activity record - activity_type = ( - "Donate_Direct" - if actionName == "direct" - else ( - "Donate_Pot_Matching_Pool" - if donation.matching_pool - else "Donate_Pot_Public" - ) - ) - defaults = { - "signer_id": signerId, - "receiver_id": receiverId, - "timestamp": donation.donated_at, - "tx_hash": receipt_obj.receipt_id, - } - activity, created = Activity.objects.aupdate_or_create( - action_result=donation_data, type=activity_type, defaults=defaults - ) - if created: - logger.info(f"Activity created: {activity}") - else: - logger.info(f"Activity updated: {activity}") - async def cache_block_height( key: str, height: int, block_count: int, block_timestamp: int From 077090dc6286040393d20647f6148a351066ccbb Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 10:04:32 -0400 Subject: [PATCH 050/454] await aupdate_or_create Activity call --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 3fc5aca..cc78781 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -681,7 +681,7 @@ async def handle_new_donations( "timestamp": donation.donated_at, "tx_hash": receipt_obj.receipt_id, } - activity, activity_created = Activity.objects.aupdate_or_create( + activity, activity_created = await Activity.objects.aupdate_or_create( action_result=donation_data, type=activity_type, defaults=defaults ) if activity_created: From c4afcf1579de32c42b7c5c21fbe5df610b27a46a Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 10:07:47 -0400 Subject: [PATCH 051/454] remove manual start block setting --- indexer_app/handler.py | 54 +++++++++++++++++++++++++++--------------- indexer_app/tasks.py | 4 ++-- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 45d5b1a..a3858fe 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -35,7 +35,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess "block_height", block_height ) # TODO: add custom timeout if it should be valid for longer than default (5 minutes) formatted_date = convert_ns_to_utc(block_timestamp) - logger.info(f"Block Height: {block_height}, Block Timestamp: {block_timestamp} ({formatted_date})") + logger.info( + f"Block Height: {block_height}, Block Timestamp: {block_timestamp} ({formatted_date})" + ) # if block_height == 111867204: # with open("indexer_outcome2.json", "w") as file: # file.write(f"{streamer_message}") @@ -108,11 +110,15 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess args_dict = json.loads(decoded_text) except UnicodeDecodeError: # Handle case where the byte sequence cannot be decoded to UTF-8 - logger.warning(f"Cannot decode args to UTF-8 text: {decoded_bytes}") + logger.warning( + f"Cannot decode args to UTF-8 text: {decoded_bytes}" + ) args_dict = {} except json.JSONDecodeError: # Handle case where the text cannot be parsed as JSON - logger.warning(f"Decoded text is not valid JSON: {decoded_text}") + logger.warning( + f"Decoded text is not valid JSON: {decoded_text}" + ) args_dict = {} match method_name: @@ -123,7 +129,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess args_dict, receiver_id, created_at ) else: - logger.info(f"new pot deployment: {args_dict}, {action}") + logger.info( + f"new pot deployment: {args_dict}, {action}" + ) await handle_new_pot( args_dict, receiver_id, @@ -135,7 +143,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess break case "assert_can_apply_callback": - logger.info(f"application case: {args_dict}, {action}, {receipt}") + logger.info( + f"application case: {args_dict}, {action}, {receipt}" + ) await handle_pot_application( args_dict, receiver_id, @@ -147,7 +157,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess break case "apply": - logger.info(f"application case 2: {args_dict}, {action}, {receipt}") + logger.info( + f"application case 2: {args_dict}, {action}, {receipt}" + ) await handle_pot_application( args_dict, receiver_id, @@ -159,14 +171,12 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess break case "donate": # TODO: donate that produces result - logger.info(f"switching bazooka to knifee works!! donate his blood: {args_dict}, {receipt}, {action}, {log_data}") + logger.info( + f"switching bazooka to knifee works!! donate his blood: {args_dict}, {receipt}, {action}, {log_data}" + ) if len(log_data) > 1: await handle_batch_donations( - receiver_id, - signer_id, - "direct", - receipt, - log_data + receiver_id, signer_id, "direct", receipt, log_data ) else: await handle_new_donations( @@ -181,7 +191,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess break case "handle_protocol_fee_callback": - logger.info(f"donations to pool incoming: {args_dict}, {receipt}, {receipt_execution_outcome}") + logger.info( + f"donations to pool incoming: {args_dict}, {receipt}, {receipt_execution_outcome}" + ) await handle_new_donations( args_dict, receiver_id, @@ -194,7 +206,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess break case "transfer_funds_callback": - logger.info(f"new version donations to pool incoming: {args_dict}, {action}") + logger.info( + f"new version donations to pool incoming: {args_dict}, {action}" + ) await handle_new_donations( args_dict, receiver_id, @@ -209,7 +223,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess case ( "register_batch" ): # TODO: listen for create_registration event instead of method call - logger.info(f"registrations incoming: {args_dict}, {action}") + logger.info( + f"registrations incoming: {args_dict}, {action}" + ) if receiver_id != "lists.potlock.near": break await handle_new_list_registration( @@ -218,7 +234,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess break case "chef_set_application_status": - logger.info(f"application status change incoming: {args_dict}") + logger.info( + f"application status change incoming: {args_dict}" + ) await handle_pot_application_status_change( args_dict, receiver_id, signer_id, receipt, status_obj ) @@ -292,8 +310,6 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess # TODO: handle remove upvote except Exception as e: - logger.warning( - f"Error during parsing method call from JSON string to dict\n{e}" - ) + logger.error(f"Error in indexer handler:\n{e}") # with open("indexer_error.txt", "a") as file: # file.write(f"{e}\n") diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index f5a776d..c872e3a 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -50,8 +50,8 @@ def listen_to_near_events(): try: # Update below with desired network & block height - # start_block = get_block_height("current_block_height") - start_block = 105_112_120 # manually setting for reindexing TODO: remove this + start_block = get_block_height("current_block_height") + # start_block = 105_112_120 # manually setting for reindexing TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From 739df7814e45623c790d32b543797ccbb41d5dab Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 10:19:40 -0400 Subject: [PATCH 052/454] restart indexer at 105_159_364 --- indexer_app/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index c872e3a..c01a132 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -50,8 +50,8 @@ def listen_to_near_events(): try: # Update below with desired network & block height - start_block = get_block_height("current_block_height") - # start_block = 105_112_120 # manually setting for reindexing TODO: remove this + # start_block = get_block_height("current_block_height") + start_block = 105_159_364 # manually setting for reindexing TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From 9ecc0029985193b4becac994ff6de67d682b2f87 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 10:49:23 -0400 Subject: [PATCH 053/454] restart indexer at 105,363,093 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index c01a132..cbed4a3 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -51,7 +51,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 105_159_364 # manually setting for reindexing TODO: remove this + start_block = 105_363_093 # manually setting for reindexing TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From 1fb42f7590f384206fa088db508f855f0688e5cb Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 10:55:13 -0400 Subject: [PATCH 054/454] restart indexer at 105534694 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index cbed4a3..42f6389 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -51,7 +51,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 105_363_093 # manually setting for reindexing TODO: remove this + start_block = 105_534_694 # manually setting for reindexing TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From 0a2de4bd378c9a3926b6546eaff7f55c360732c0 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 10:56:05 -0400 Subject: [PATCH 055/454] restart indexer from current block height --- indexer_app/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 42f6389..3a2b859 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -50,8 +50,8 @@ def listen_to_near_events(): try: # Update below with desired network & block height - # start_block = get_block_height("current_block_height") - start_block = 105_534_694 # manually setting for reindexing TODO: remove this + start_block = get_block_height("current_block_height") + # start_block = 105_534_694 # manually setting for reindexing TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From 649e9dcc687c7886608d73120d6c4e666d3d6907 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 15:14:40 -0400 Subject: [PATCH 056/454] add celery recurring task for account totals --- base/celery.py | 20 ++++++++++++++------ donations/models.py | 1 + indexer_app/tasks.py | 45 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/base/celery.py b/base/celery.py index ad77bb6..07d6000 100644 --- a/base/celery.py +++ b/base/celery.py @@ -1,15 +1,16 @@ import os - -from django.conf import settings +import ssl from celery import Celery -import ssl +from celery.schedules import crontab +from django.conf import settings os.environ.setdefault("DJANGO_SETTINGS_MODULE", "base.settings") -app = Celery("indexer", -# broker=settings.CELERY_BROKER_URL, -# backend=settings.CELERY_RESULT_BACKEND +app = Celery( + "indexer", + # broker=settings.CELERY_BROKER_URL, + # backend=settings.CELERY_RESULT_BACKEND ) # SSL configurations for broker and backend @@ -22,3 +23,10 @@ app.config_from_object("django.conf:settings", namespace="CELERY") app.autodiscover_tasks() + +app.conf.beat_schedule = { + "update_account_statistics_every_5_minutes": { + "task": "indexer_app.tasks.update_account_statistics", + "schedule": crontab(minute="*/5"), # Executes every 5 minutes + }, +} diff --git a/donations/models.py b/donations/models.py index 64e1500..355b5f6 100644 --- a/donations/models.py +++ b/donations/models.py @@ -92,6 +92,7 @@ class Donation(models.Model): related_name="received_donations", null=True, help_text=_("Donation recipient."), + db_index=True, ) protocol_fee = models.CharField( _("protocol fee"), diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 3a2b859..a2a8a26 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -2,10 +2,15 @@ from pathlib import Path from celery import shared_task +from celery.signals import task_revoked from django.conf import settings +from django.db.models import Count, Sum from near_lake_framework import LakeConfig, streamer +from accounts.models import Account +from donations.models import Donation from indexer_app.handler import handle_streamer_message +from pots.models import PotPayout from .logging import logger from .utils import cache_block_height, get_block_height @@ -58,7 +63,45 @@ def listen_to_near_events(): loop.close() -from celery.signals import task_revoked +@shared_task +def update_account_statistics(): + # Logic to update account statistics + print("Updating account statistics...") + for account in Account.objects.all(): + # donors count + account.donors_count = Donation.objects.filter(recipient=account).aggregate( + Count("donor", distinct=True) + ) + # donations received usd + account.total_donations_in_usd = ( + Donation.objects.filter(recipient=account).aggregate( + Sum("total_amount_usd") + )["total_amount_usd__sum"] + or 0 + ) + # donations sent usd + account.total_donations_out_usd = ( + Donation.objects.filter(donor=account).aggregate(Sum("total_amount_usd"))[ + "total_amount_usd__sum" + ] + or 0 + ) + # matching pool allocations usd + account.total_matching_pool_allocations_usd = ( + PotPayout.objects.filter( + recipient=account, paid_at__isnull=False + ).aggregate(Sum("amount_usd"))["amount_usd__sum"] + or 0 + ) + # save changes + account.save( + update_fields=[ + "donors_count", + "total_donations_in_usd", + "total_donations_out_usd", + "total_matching_pool_allocations_usd", + ] + ) @task_revoked.connect From 51cb8287ad62e08ed31b28b5c74c08326cebe1b2 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 15:27:31 -0400 Subject: [PATCH 057/454] update after_install to include celery beat service --- scripts/after_install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 0bdcdf0..abb4676 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -50,17 +50,17 @@ PENDING_MIGRATIONS=$(python manage.py showmigrations | grep "\[ \]" | wc -l) # if [ "$PENDING_MIGRATIONS" -gt 0 ]; then echo "Migrations found; stopping services..." >> "$LOG_FILE" - sudo systemctl stop gunicorn celery + sudo systemctl stop gunicorn celery-worker celery-beat echo 'Applying migrations...' >> "$LOG_FILE" python manage.py migrate >> "$LOG_FILE" 2>&1 echo 'Starting services...' >> "$LOG_FILE" - sudo systemctl start gunicorn celery + sudo systemctl start gunicorn celery-worker celery-beat else echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 - sudo systemctl restart gunicorn celery + sudo systemctl restart gunicorn celery-worker celery-beat fi echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" From f6883e451c0b32135ac1f2b6dc1f15cb4f384833 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 15:47:37 -0400 Subject: [PATCH 058/454] use logger in update_account_statistics --- indexer_app/tasks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index a2a8a26..08d6f77 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -66,7 +66,7 @@ def listen_to_near_events(): @shared_task def update_account_statistics(): # Logic to update account statistics - print("Updating account statistics...") + logger.info("Updating account statistics...") for account in Account.objects.all(): # donors count account.donors_count = Donation.objects.filter(recipient=account).aggregate( @@ -102,6 +102,7 @@ def update_account_statistics(): "total_matching_pool_allocations_usd", ] ) + logger.info(f"Account {account.id} statistics updated.") @task_revoked.connect From 39dd68d88b84de76a67c4791781d5c650549842e Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 15:56:29 -0400 Subject: [PATCH 059/454] add jobs logger --- base/settings.py | 6 ++++++ indexer_app/tasks.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/base/settings.py b/base/settings.py index 41f5753..6ba4997 100644 --- a/base/settings.py +++ b/base/settings.py @@ -223,6 +223,11 @@ "level": log_level, "propagate": False, }, + "jobs": { + "handlers": ["console"], + "level": log_level, + "propagate": False, + }, "": {"handlers": ["console"], "level": log_level}, # root logger }, } @@ -241,6 +246,7 @@ LOGGING["loggers"][""]["handlers"].append("watchtower") LOGGING["loggers"]["django"]["handlers"].append("watchtower") LOGGING["loggers"]["indexer"]["handlers"].append("watchtower") + LOGGING["loggers"]["jobs"]["handlers"].append("watchtower") # log_level = getattr(logging, LOG_LEVEL, logging.INFO) # print("LOG_LEVEL: ", LOG_LEVEL) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 08d6f77..c5883b6 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -1,4 +1,5 @@ import asyncio +import logging from pathlib import Path from celery import shared_task @@ -65,9 +66,11 @@ def listen_to_near_events(): @shared_task def update_account_statistics(): + logger = logging.getLogger("jobs") # Logic to update account statistics logger.info("Updating account statistics...") for account in Account.objects.all(): + logger.info(f"Updating statistics for account {account.id}...") # donors count account.donors_count = Donation.objects.filter(recipient=account).aggregate( Count("donor", distinct=True) From 04a46193270cdf78395ec92afdbcda99e9acb59b Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 16:27:45 -0400 Subject: [PATCH 060/454] debugging account stats task --- indexer_app/tasks.py | 86 ++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index c5883b6..17eca58 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -5,6 +5,7 @@ from celery import shared_task from celery.signals import task_revoked from django.conf import settings +from django.db import transaction from django.db.models import Count, Sum from near_lake_framework import LakeConfig, streamer @@ -67,45 +68,54 @@ def listen_to_near_events(): @shared_task def update_account_statistics(): logger = logging.getLogger("jobs") - # Logic to update account statistics logger.info("Updating account statistics...") - for account in Account.objects.all(): - logger.info(f"Updating statistics for account {account.id}...") - # donors count - account.donors_count = Donation.objects.filter(recipient=account).aggregate( - Count("donor", distinct=True) - ) - # donations received usd - account.total_donations_in_usd = ( - Donation.objects.filter(recipient=account).aggregate( - Sum("total_amount_usd") - )["total_amount_usd__sum"] - or 0 - ) - # donations sent usd - account.total_donations_out_usd = ( - Donation.objects.filter(donor=account).aggregate(Sum("total_amount_usd"))[ - "total_amount_usd__sum" - ] - or 0 - ) - # matching pool allocations usd - account.total_matching_pool_allocations_usd = ( - PotPayout.objects.filter( - recipient=account, paid_at__isnull=False - ).aggregate(Sum("amount_usd"))["amount_usd__sum"] - or 0 - ) - # save changes - account.save( - update_fields=[ - "donors_count", - "total_donations_in_usd", - "total_donations_out_usd", - "total_matching_pool_allocations_usd", - ] - ) - logger.info(f"Account {account.id} statistics updated.") + + accounts = Account.objects.all() + for account in accounts: + try: + with transaction.atomic(): + logger.info(f"Updating statistics for account {account.id}...") + # donors count + account.donors_count = Donation.objects.filter( + recipient=account + ).aggregate(Count("donor", distinct=True))["donor__count"] + + # donations received usd + account.total_donations_in_usd = ( + Donation.objects.filter(recipient=account).aggregate( + Sum("total_amount_usd") + )["total_amount_usd__sum"] + or 0 + ) + + # donations sent usd + account.total_donations_out_usd = ( + Donation.objects.filter(donor=account).aggregate( + Sum("total_amount_usd") + )["total_amount_usd__sum"] + or 0 + ) + + # matching pool allocations usd + account.total_matching_pool_allocations_usd = ( + PotPayout.objects.filter( + recipient=account, paid_at__isnull=False + ).aggregate(Sum("amount_usd"))["amount_usd__sum"] + or 0 + ) + + # Save changes + account.save( + update_fields=[ + "donors_count", + "total_donations_in_usd", + "total_donations_out_usd", + "total_matching_pool_allocations_usd", + ] + ) + logger.info(f"Account {account.id} statistics updated.") + except Exception as e: + logger.error(f"Failed to update statistics for account {account.id}: {e}") @task_revoked.connect From 02f615785f071a5cfdf7ab581c8044c18058dcdf Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 16:31:49 -0400 Subject: [PATCH 061/454] fix typo in account stats job --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 17eca58..1d421a3 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -100,7 +100,7 @@ def update_account_statistics(): account.total_matching_pool_allocations_usd = ( PotPayout.objects.filter( recipient=account, paid_at__isnull=False - ).aggregate(Sum("amount_usd"))["amount_usd__sum"] + ).aggregate(Sum("amount_paid_usd"))["amount_paid_usd__sum"] or 0 ) From b1d07c492fc2d291f957f0332ea707d1c0652e2d Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 6 May 2024 16:53:18 -0400 Subject: [PATCH 062/454] remove transaction.atomic in account stats task --- indexer_app/tasks.py | 82 ++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 1d421a3..a775b85 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -73,47 +73,47 @@ def update_account_statistics(): accounts = Account.objects.all() for account in accounts: try: - with transaction.atomic(): - logger.info(f"Updating statistics for account {account.id}...") - # donors count - account.donors_count = Donation.objects.filter( - recipient=account - ).aggregate(Count("donor", distinct=True))["donor__count"] - - # donations received usd - account.total_donations_in_usd = ( - Donation.objects.filter(recipient=account).aggregate( - Sum("total_amount_usd") - )["total_amount_usd__sum"] - or 0 - ) - - # donations sent usd - account.total_donations_out_usd = ( - Donation.objects.filter(donor=account).aggregate( - Sum("total_amount_usd") - )["total_amount_usd__sum"] - or 0 - ) - - # matching pool allocations usd - account.total_matching_pool_allocations_usd = ( - PotPayout.objects.filter( - recipient=account, paid_at__isnull=False - ).aggregate(Sum("amount_paid_usd"))["amount_paid_usd__sum"] - or 0 - ) - - # Save changes - account.save( - update_fields=[ - "donors_count", - "total_donations_in_usd", - "total_donations_out_usd", - "total_matching_pool_allocations_usd", - ] - ) - logger.info(f"Account {account.id} statistics updated.") + # with transaction.atomic(): + logger.info(f"Updating statistics for account {account.id}...") + # donors count + account.donors_count = Donation.objects.filter(recipient=account).aggregate( + Count("donor", distinct=True) + )["donor__count"] + + # donations received usd + account.total_donations_in_usd = ( + Donation.objects.filter(recipient=account).aggregate( + Sum("total_amount_usd") + )["total_amount_usd__sum"] + or 0 + ) + + # donations sent usd + account.total_donations_out_usd = ( + Donation.objects.filter(donor=account).aggregate( + Sum("total_amount_usd") + )["total_amount_usd__sum"] + or 0 + ) + + # matching pool allocations usd + account.total_matching_pool_allocations_usd = ( + PotPayout.objects.filter( + recipient=account, paid_at__isnull=False + ).aggregate(Sum("amount_paid_usd"))["amount_paid_usd__sum"] + or 0 + ) + + # Save changes + account.save( + update_fields=[ + "donors_count", + "total_donations_in_usd", + "total_donations_out_usd", + "total_matching_pool_allocations_usd", + ] + ) + logger.info(f"Account {account.id} statistics updated.") except Exception as e: logger.error(f"Failed to update statistics for account {account.id}: {e}") From d803716c101157f81a5693e04eacfd8c0dca2f96 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 08:51:36 -0400 Subject: [PATCH 063/454] update after_install and celery scripts to handle beat start/stop --- base/celery.py | 7 +++++++ scripts/after_install.sh | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/base/celery.py b/base/celery.py index 07d6000..801159f 100644 --- a/base/celery.py +++ b/base/celery.py @@ -28,5 +28,12 @@ "update_account_statistics_every_5_minutes": { "task": "indexer_app.tasks.update_account_statistics", "schedule": crontab(minute="*/5"), # Executes every 5 minutes + "options": {"queue": "beat_tasks"}, }, } + +app.conf.task_routes = { + "indexer_app.tasks.update_account_statistics": {"queue": "beat_tasks"}, +} + +# TODO: (pick up here): rename & restart services, using correct workers diff --git a/scripts/after_install.sh b/scripts/after_install.sh index abb4676..c847c6c 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -50,17 +50,17 @@ PENDING_MIGRATIONS=$(python manage.py showmigrations | grep "\[ \]" | wc -l) # if [ "$PENDING_MIGRATIONS" -gt 0 ]; then echo "Migrations found; stopping services..." >> "$LOG_FILE" - sudo systemctl stop gunicorn celery-worker celery-beat + sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker echo 'Applying migrations...' >> "$LOG_FILE" python manage.py migrate >> "$LOG_FILE" 2>&1 echo 'Starting services...' >> "$LOG_FILE" - sudo systemctl start gunicorn celery-worker celery-beat + sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker else echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 - sudo systemctl restart gunicorn celery-worker celery-beat + sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker fi echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" From 058363518f4269ac3b35344889e508730ad5dcdc Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 08:56:39 -0400 Subject: [PATCH 064/454] remove excess logs for update_account_statistics job --- indexer_app/tasks.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index a775b85..9b019b5 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -68,13 +68,13 @@ def listen_to_near_events(): @shared_task def update_account_statistics(): logger = logging.getLogger("jobs") - logger.info("Updating account statistics...") accounts = Account.objects.all() + accounts_count = accounts.count() + logger.info(f"Updating statistics for {accounts_count} accounts...") for account in accounts: try: - # with transaction.atomic(): - logger.info(f"Updating statistics for account {account.id}...") + # logger.info(f"Updating statistics for account {account.id}...") # donors count account.donors_count = Donation.objects.filter(recipient=account).aggregate( Count("donor", distinct=True) @@ -113,9 +113,10 @@ def update_account_statistics(): "total_matching_pool_allocations_usd", ] ) - logger.info(f"Account {account.id} statistics updated.") + # logger.info(f"Account {account.id} statistics updated.") except Exception as e: logger.error(f"Failed to update statistics for account {account.id}: {e}") + logger.info(f"Account stats for {accounts.count()} accounts updated.") @task_revoked.connect From c5df8892e96784ec93c6c807e892d93b70adec8f Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 10:00:16 -0400 Subject: [PATCH 065/454] add celery-beat to restart in after_install --- scripts/after_install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index c847c6c..b5acf94 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -56,11 +56,11 @@ if [ "$PENDING_MIGRATIONS" -gt 0 ]; then python manage.py migrate >> "$LOG_FILE" 2>&1 echo 'Starting services...' >> "$LOG_FILE" - sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker + sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat else echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 - sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker + sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat fi echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" From 2c243f66b26017a2ec2caff3058338c9b43b53ad Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 10:10:55 -0400 Subject: [PATCH 066/454] remove celery-beat from after_install --- scripts/after_install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index b5acf94..c847c6c 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -56,11 +56,11 @@ if [ "$PENDING_MIGRATIONS" -gt 0 ]; then python manage.py migrate >> "$LOG_FILE" 2>&1 echo 'Starting services...' >> "$LOG_FILE" - sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat + sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker else echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 - sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat + sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker fi echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" From 048ea99977d6548b83ff8d6c9edc4ab9c7d8437a Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 14:09:14 -0400 Subject: [PATCH 067/454] move fetch_usd_prices to Donation model method --- base/logging.py | 3 +- base/settings.py | 4 + donations/models.py | 99 +++++++++++++++++++- indexer_app/tasks.py | 37 ++++++-- indexer_app/utils.py | 211 ++++++++++++++++++++++--------------------- tokens/models.py | 4 + 6 files changed, 246 insertions(+), 112 deletions(-) diff --git a/base/logging.py b/base/logging.py index cda9ae6..12bef01 100644 --- a/base/logging.py +++ b/base/logging.py @@ -1,3 +1,4 @@ import logging -logger = logging.getLogger("django") \ No newline at end of file +logger = logging.getLogger("django") +jobs_logger = logging.getLogger("jobs") diff --git a/base/settings.py b/base/settings.py index 6ba4997..02a6664 100644 --- a/base/settings.py +++ b/base/settings.py @@ -53,6 +53,10 @@ BLOCK_SAVE_HEIGHT = os.environ.get("BLOCK_SAVE_HEIGHT") +COINGECKO_URL = "https://api.coingecko.com/api/v3" +# Number of hours around a given timestamp for querying historical prices +HISTORICAL_PRICE_QUERY_HOURS = 24 + # Application definition INSTALLED_APPS = [ diff --git a/donations/models.py b/donations/models.py index 355b5f6..7f39a4f 100644 --- a/donations/models.py +++ b/donations/models.py @@ -1,9 +1,15 @@ -from django import db +from datetime import timedelta + +import requests +from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ from accounts.models import Account +from base.logging import logger +from base.utils import format_date from pots.models import Pot +from tokens.models import Token, TokenHistoricalPrice class Donation(models.Model): @@ -54,7 +60,7 @@ class Donation(models.Model): help_text=_("Net amount in USD."), ) ft = models.ForeignKey( - Account, + Account, # should probably be Token on_delete=models.CASCADE, related_name="ft_donations", null=False, @@ -167,3 +173,92 @@ class Meta: name="unique_on_chain_id_with_pot", ), ] + + async def get_ft_token(self): + try: + token = await Token.objects.aget(id=self.ft) + except Token.DoesNotExist: + # TODO: fetch metadata from token contract (ft_metadata) and add decimals to token record. For now adding 12 which is most common + token = await Token.objects.acreate(id=self.ft, decimals=12) + return token + + ### Fetches USD prices for the Donation record and saves USD totals + async def fetch_usd_prices(self): + # get existing values for stats adjustments later + existing_total_amount_usd = self.total_amount_usd + existing_net_amount_usd = self.net_amount_usd + existing_protocol_fee_usd = self.protocol_fee_usd + existing_referrer_fee_usd = self.referrer_fee_usd + existing_chef_fee_usd = self.chef_fee_usd + # first, see if there is a TokenHistoricalPrice within 1 day (or HISTORICAL_PRICE_QUERY_HOURS) of self.donated_at + token = await self.get_ft_token() + time_window = timedelta(hours=settings.HISTORICAL_PRICE_QUERY_HOURS or 24) + token_prices = TokenHistoricalPrice.objects.filter( + token=token, + timestamp__gte=self.donated_at - time_window, + timestamp__lte=self.donated_at + time_window, + ) + existing_token_price = token_prices.first() + logger.info(f"existing token price: {existing_token_price}") + total_amount = token.format_price(self.total_amount) + net_amount = token.format_price(self.net_amount) + protocol_amount = token.format_price(self.protocol_fee) + referrer_amount = token.format_price(self.referrer_fee or "0") + chef_amount = token.format_price(self.chef_fee) + if existing_token_price: + try: + price_usd = existing_token_price.price_usd + self.total_amount_usd = total_amount * price_usd + self.net_amount_usd = net_amount * price_usd + self.protocol_fee_usd = protocol_amount * price_usd + self.referrer_fee_usd = referrer_amount * price_usd + self.chef_fee_usd = chef_amount * price_usd + self.save() + except Exception as e: + logger.error( + f"Failed to calculate and save USD prices using existing TokenHistoricalPrice: {e}" + ) + # TODO: update totals for relevant accounts + else: + # no existing price within acceptable time period; fetch from coingecko + try: + logger.info( + "No existing price within acceptable time period; fetching historical price..." + ) + endpoint = f"{settings.COINGECKO_URL}/coins/{self.ft.id}/history?date={format_date(self.donated_at)}&localization=false" + logger.info(f"coingecko endpoint: {endpoint}") + response = requests.get(endpoint) + logger.info(f"coingecko response: {response}") + if response.status_code == 429: + logger.warning("Coingecko rate limit exceeded") + price_data = response.json() + except Exception as e: + logger.warning(f"Failed to fetch coingecko price data: {e}") + logger.info(f"coingecko price data: {price_data}") + price_usd = ( + price_data.get("market_data", {}).get("current_price", {}).get("usd") + ) + logger.info(f"unit price: {price_usd}") + if price_usd: + try: + self.total_amount_usd = total_amount * price_usd + self.net_amount_usd = net_amount * price_usd + self.protocol_fee_usd = protocol_amount * price_usd + self.referrer_fee_usd = referrer_amount * price_usd + self.chef_fee_usd = chef_amount * price_usd + self.save() + except Exception as e: + logger.error( + f"Failed to calculate and save USD prices using fetched price: {e}" + ) + # TODO: update totals for relevant accounts + try: + await TokenHistoricalPrice.objects.acreate( + token=token, + price_usd=price_usd, + timestamp=self.donated_at, + ) + except Exception as e: + logger.warning( + f"Error creating TokenHistoricalPrice: {e} token: {token} price_usd: {price_usd}" + ) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 9b019b5..93eafb0 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -6,7 +6,7 @@ from celery.signals import task_revoked from django.conf import settings from django.db import transaction -from django.db.models import Count, Sum +from django.db.models import Count, Q, Sum from near_lake_framework import LakeConfig, streamer from accounts.models import Account @@ -65,16 +65,37 @@ def listen_to_near_events(): loop.close() +jobs_logger = logging.getLogger("jobs") + + +@shared_task +async def fetch_usd_prices(): + # fetch all Donation records that have a null total_amount_usd or net_amount_usd + # get closest TokenHistoricalPrice record for each donation, and check if it's within 1 day + # if not, fetch the price from coingecko API, create new TokenHistoricalPrice record and update the Donation record + + donations = Donation.objects.filter( + Q(total_amount_usd__isnull=True) | Q(net_amount_usd__isnull=True) + ) + jobs_logger.info(f"Fetching USD prices for {donations.count()} donations...") + for donation in donations: + try: + await donation.fetch_usd_prices() + except Exception as e: + jobs_logger.error( + f"Failed to fetch USD prices for donation {donation.id}: {e}" + ) + + @shared_task def update_account_statistics(): - logger = logging.getLogger("jobs") accounts = Account.objects.all() accounts_count = accounts.count() - logger.info(f"Updating statistics for {accounts_count} accounts...") + jobs_logger.info(f"Updating statistics for {accounts_count} accounts...") for account in accounts: try: - # logger.info(f"Updating statistics for account {account.id}...") + # jobs_logger.info(f"Updating statistics for account {account.id}...") # donors count account.donors_count = Donation.objects.filter(recipient=account).aggregate( Count("donor", distinct=True) @@ -113,10 +134,12 @@ def update_account_statistics(): "total_matching_pool_allocations_usd", ] ) - # logger.info(f"Account {account.id} statistics updated.") + # jobs_logger.info(f"Account {account.id} statistics updated.") except Exception as e: - logger.error(f"Failed to update statistics for account {account.id}: {e}") - logger.info(f"Account stats for {accounts.count()} accounts updated.") + jobs_logger.error( + f"Failed to update statistics for account {account.id}: {e}" + ) + jobs_logger.info(f"Account stats for {accounts.count()} accounts updated.") @task_revoked.connect diff --git a/indexer_app/utils.py b/indexer_app/utils.py index cc78781..e6560d4 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -27,7 +27,7 @@ from .logging import logger -GECKO_URL = "https://api.coingecko.com/api/v3" +# GECKO_URL = "https://api.coingecko.com/api/v3" # TODO: move to settings async def handle_new_pot( @@ -589,53 +589,55 @@ async def handle_new_donations( # TODO: fetch metadata from token contract (ft_metadata) and add decimals to token record. For now adding 12 which is most common token = await Token.objects.acreate(id=token_acct, decimals=12) - # Fetch historical token data - # late_p = await token.get_most_recent_price() - try: - logger.info("fetching historical price...") - logger.info(f"donated at: {donated_at}") + # # Fetch historical token data + # # late_p = await token.get_most_recent_price() + # try: + # logger.info("fetching historical price...") + # logger.info(f"donated at: {donated_at}") endpoint = f"{GECKO_URL}/coins/{donation_data.get('ft_id', 'near')}/history?date={format_date(donated_at)}&localization=false" - logger.info(f"endpoint: {endpoint}") - response = requests.get(endpoint) - logger.info(f"response: {response}") - if response.status_code == 429: - logger.warning("Coingecko rate limit exceeded") - price_data = response.json() - except Exception as e: - logger.warning(f"Failed to fetch price data: {e}") - logger.info(f"price data: {price_data}") - unit_price = price_data.get("market_data", {}).get("current_price", {}).get("usd") - logger.info(f"unit price: {unit_price}") - if unit_price: - try: - await TokenHistoricalPrice.objects.acreate( # need to change token model to use token as id - token=token, - price_usd=unit_price, - timestamp=donated_at, - ) - except Exception as e: - logger.warning( - f"Error creating TokenHistoricalPrice: {e} token: {token} unit_price: {unit_price}" - ) - # historical_price = await token.get_most_recent_price() # to use model methods, we might have to use asgiref sync_to_async - historical = await TokenHistoricalPrice.objects.aget(token=token) - unit_price = historical.price_usd + # logger.info(f"endpoint: {endpoint}") + # response = requests.get(endpoint) + # logger.info(f"response: {response}") + # if response.status_code == 429: + # logger.warning("Coingecko rate limit exceeded") + # price_data = response.json() + # except Exception as e: + # logger.warning(f"Failed to fetch price data: {e}") + # logger.info(f"price data: {price_data}") + # unit_price = price_data.get("market_data", {}).get("current_price", {}).get("usd") + # logger.info(f"unit price: {unit_price}") + # if unit_price: + # try: + # await TokenHistoricalPrice.objects.acreate( # need to change token model to use token as id + # token=token, + # price_usd=unit_price, + # timestamp=donated_at, + # ) + # except Exception as e: + # logger.warning( + # f"Error creating TokenHistoricalPrice: {e} token: {token} unit_price: {unit_price}" + # ) + # # historical_price = await token.get_most_recent_price() # to use model methods, we might have to use asgiref sync_to_async + # historical = await TokenHistoricalPrice.objects.aget(token=token) + # unit_price = historical.price_usd + + # total_amount = donation_data["total_amount"] + # net_amount = net_amount - int(donation_data.get("referrer_fee") or 0) + + # # Calculate and format amounts + # total_near_amount = format_to_near(total_amount) + # net_near_amount = format_to_near(net_amount) + # total_amount_usd = None if not unit_price else unit_price * total_near_amount + # net_amount_usd = None if not unit_price else unit_price * net_near_amount total_amount = donation_data["total_amount"] - net_amount = net_amount - int(donation_data.get("referrer_fee") or 0) - - # Calculate and format amounts - total_near_amount = format_to_near(total_amount) - net_near_amount = format_to_near(net_amount) - total_amount_usd = None if not unit_price else unit_price * total_near_amount - net_amount_usd = None if not unit_price else unit_price * net_near_amount logger.info(f"inserting donations... by {actionName}") default_data = { "donor": donor, "total_amount": total_amount, - "total_amount_usd": total_amount_usd, - "net_amount_usd": net_amount_usd, + "total_amount_usd": None, # USD amounts will be added later (could be in pre-save hook) + "net_amount_usd": None, "net_amount": net_amount, "ft": token_acct, "message": donation_data.get("message"), @@ -659,11 +661,14 @@ async def handle_new_donations( ) logger.info(f"Created donation? {donation_created}") - # convert total_amount_usd and net_amount_usd from None - if total_amount_usd is None: - total_amount_usd = 0.0 - if net_amount_usd is None: - net_amount_usd = 0.0 + # fetch USD prices + await donation.fetch_usd_prices() # might not need to await this? + + # # convert total_amount_usd and net_amount_usd from None + # if total_amount_usd is None: + # total_amount_usd = 0.0 + # if net_amount_usd is None: + # net_amount_usd = 0.0 # Insert or update activity record activity_type = ( @@ -689,63 +694,65 @@ async def handle_new_donations( else: logger.info(f"Activity updated: {activity}") - if donation_created: # only do stats updates if donation object was created - - if actionName != "direct": - - potUpdate = { - "total_public_donations": str( - int(pot.total_public_donations or 0) + int(total_amount) - ), - "total_public_donations_usd": int(pot.total_public_donations_usd or 0.0) - + total_amount_usd, - } - if donation_data.get("matching_pool"): - potUpdate["total_matching_pool"] = str( - int(pot.total_matching_pool or 0) + int(total_amount) - ) - potUpdate["total_matching_pool"] = ( - pot.total_matching_pool_usd or 0.0 - ) + total_amount_usd - potUpdate["matching_pool_donations_count"] = ( - pot.matching_pool_donations_count or 0 - ) + 1 - - if recipient: - await Account.objects.filter(id=recipient.id).aupdate( - **{ - "total_matching_pool_allocations_usd": recipient.total_matching_pool_allocations_usd - + total_amount_usd - } - ) - - # accountUpdate = {} - else: - potUpdate["public_donations_count"] = ( - pot.public_donations_count or 0 - ) + 1 - - await Pot.objects.filter(id=receiverId).aupdate(**potUpdate) - - # donation_recipient = donation_data.get('project_id', donation_data['recipient_id']) - logger.info( - f"update total donated for {donor.id}, {donor.total_donations_out_usd + decimal.Decimal(total_amount_usd)}" - ) - await Account.objects.filter(id=donor.id).aupdate( - **{ - "total_donations_out_usd": donor.total_donations_out_usd - + decimal.Decimal(total_amount_usd) - } - ) - if recipient: - acct = await Account.objects.aget(id=recipient.id) - logger.info(f"selected {acct} to perform donor count update") - acctUpdate = { - "donors_count": acct.donors_count + 1, - "total_donations_in_usd": acct.total_donations_in_usd - + decimal.Decimal(net_amount_usd), - } - await Account.objects.filter(id=recipient.id).aupdate(**acctUpdate) + ### COMMENTING OUT FOR NOW SINCE WE HAVE PERIODIC JOB RUNNING TO UPDATE ACCOUNT STATS (NB: DOESN'T CURRENTLY COVER POT STATS) + ### CAN ALWAYS ADD BACK IF DESIRED + # if donation_created: # only do stats updates if donation object was created + + # if actionName != "direct": + + # potUpdate = { + # "total_public_donations": str( + # int(pot.total_public_donations or 0) + int(total_amount) + # ), + # "total_public_donations_usd": int(pot.total_public_donations_usd or 0.0) + # + total_amount_usd, + # } + # if donation_data.get("matching_pool"): + # potUpdate["total_matching_pool"] = str( + # int(pot.total_matching_pool or 0) + int(total_amount) + # ) + # potUpdate["total_matching_pool"] = ( + # pot.total_matching_pool_usd or 0.0 + # ) + total_amount_usd + # potUpdate["matching_pool_donations_count"] = ( + # pot.matching_pool_donations_count or 0 + # ) + 1 + + # if recipient: + # await Account.objects.filter(id=recipient.id).aupdate( + # **{ + # "total_matching_pool_allocations_usd": recipient.total_matching_pool_allocations_usd + # + total_amount_usd + # } + # ) + + # # accountUpdate = {} + # else: + # potUpdate["public_donations_count"] = ( + # pot.public_donations_count or 0 + # ) + 1 + + # await Pot.objects.filter(id=receiverId).aupdate(**potUpdate) + + # # donation_recipient = donation_data.get('project_id', donation_data['recipient_id']) + # logger.info( + # f"update total donated for {donor.id}, {donor.total_donations_out_usd + decimal.Decimal(total_amount_usd)}" + # ) + # await Account.objects.filter(id=donor.id).aupdate( + # **{ + # "total_donations_out_usd": donor.total_donations_out_usd + # + decimal.Decimal(total_amount_usd) + # } + # ) + # if recipient: + # acct = await Account.objects.aget(id=recipient.id) + # logger.info(f"selected {acct} to perform donor count update") + # acctUpdate = { + # "donors_count": acct.donors_count + 1, + # "total_donations_in_usd": acct.total_donations_in_usd + # + decimal.Decimal(net_amount_usd), + # } + # await Account.objects.filter(id=recipient.id).aupdate(**acctUpdate) async def cache_block_height( diff --git a/tokens/models.py b/tokens/models.py index aa4a309..5db0b46 100644 --- a/tokens/models.py +++ b/tokens/models.py @@ -21,6 +21,10 @@ class Token(models.Model): def get_most_recent_price(self): return self.historical_prices.order_by("-timestamp").first() + def format_price(self, amount_str: str): + formatted_amount = int(amount_str) / (10**self.decimals) + return formatted_amount + class TokenHistoricalPrice(models.Model): token = models.ForeignKey( From de076444efa671bf6458c83354aa9f652583895e Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 14:13:29 -0400 Subject: [PATCH 068/454] add fetch_usd_prices to celery beat schedule --- base/celery.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/base/celery.py b/base/celery.py index 801159f..1aa4d8f 100644 --- a/base/celery.py +++ b/base/celery.py @@ -30,10 +30,14 @@ "schedule": crontab(minute="*/5"), # Executes every 5 minutes "options": {"queue": "beat_tasks"}, }, + "fetch_usd_prices_every_5_minutes": { + "task": "indexer_app.tasks.fetch_usd_prices", + "schedule": crontab(minute="*/5"), # Executes every 5 minutes + "options": {"queue": "beat_tasks"}, + }, } app.conf.task_routes = { "indexer_app.tasks.update_account_statistics": {"queue": "beat_tasks"}, + "indexer_app.tasks.fetch_usd_prices": {"queue": "beat_tasks"}, } - -# TODO: (pick up here): rename & restart services, using correct workers From 96c4afe5481eff1172752c4ea1a1cad76436295f Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 14:20:46 -0400 Subject: [PATCH 069/454] add celery-beat stop/restart to after_install --- scripts/after_install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index c847c6c..4ffdb25 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -50,17 +50,17 @@ PENDING_MIGRATIONS=$(python manage.py showmigrations | grep "\[ \]" | wc -l) # if [ "$PENDING_MIGRATIONS" -gt 0 ]; then echo "Migrations found; stopping services..." >> "$LOG_FILE" - sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker + sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat echo 'Applying migrations...' >> "$LOG_FILE" python manage.py migrate >> "$LOG_FILE" 2>&1 echo 'Starting services...' >> "$LOG_FILE" - sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker + sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat else echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 - sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker + sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat fi echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" From d1180aa20ff84b3ac8652a4a0908f04550404ead Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 14:37:02 -0400 Subject: [PATCH 070/454] make fetch_usd_prices task synchronous --- donations/models.py | 17 +++++++++++------ indexer_app/tasks.py | 23 ++++++++++++++++------- indexer_app/utils.py | 2 +- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/donations/models.py b/donations/models.py index 7f39a4f..d6af2ce 100644 --- a/donations/models.py +++ b/donations/models.py @@ -1,6 +1,7 @@ from datetime import timedelta import requests +from asgiref.sync import sync_to_async from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ @@ -174,16 +175,20 @@ class Meta: ), ] - async def get_ft_token(self): + def get_ft_token(self): try: - token = await Token.objects.aget(id=self.ft) + token = Token.objects.aget(id=self.ft) except Token.DoesNotExist: # TODO: fetch metadata from token contract (ft_metadata) and add decimals to token record. For now adding 12 which is most common - token = await Token.objects.acreate(id=self.ft, decimals=12) + token = Token.objects.acreate(id=self.ft, decimals=12) return token + async def fetch_usd_prices_async(self): + fetch_prices = sync_to_async(self.fetch_usd_prices) + await fetch_prices() + ### Fetches USD prices for the Donation record and saves USD totals - async def fetch_usd_prices(self): + def fetch_usd_prices(self): # get existing values for stats adjustments later existing_total_amount_usd = self.total_amount_usd existing_net_amount_usd = self.net_amount_usd @@ -191,7 +196,7 @@ async def fetch_usd_prices(self): existing_referrer_fee_usd = self.referrer_fee_usd existing_chef_fee_usd = self.chef_fee_usd # first, see if there is a TokenHistoricalPrice within 1 day (or HISTORICAL_PRICE_QUERY_HOURS) of self.donated_at - token = await self.get_ft_token() + token = self.get_ft_token() time_window = timedelta(hours=settings.HISTORICAL_PRICE_QUERY_HOURS or 24) token_prices = TokenHistoricalPrice.objects.filter( token=token, @@ -253,7 +258,7 @@ async def fetch_usd_prices(self): ) # TODO: update totals for relevant accounts try: - await TokenHistoricalPrice.objects.acreate( + TokenHistoricalPrice.objects.create( token=token, price_usd=price_usd, timestamp=self.donated_at, diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 93eafb0..c0f6891 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -68,23 +68,32 @@ def listen_to_near_events(): jobs_logger = logging.getLogger("jobs") -@shared_task -async def fetch_usd_prices(): - # fetch all Donation records that have a null total_amount_usd or net_amount_usd - # get closest TokenHistoricalPrice record for each donation, and check if it's within 1 day - # if not, fetch the price from coingecko API, create new TokenHistoricalPrice record and update the Donation record +# @shared_task +# def fetch_usd_prices(): +# donations = Donation.objects.filter( +# Q(total_amount_usd__isnull=True) | Q(net_amount_usd__isnull=True) +# ) +# jobs_logger.info(f"Fetching USD prices for {donations.count()} donations...") +# loop = asyncio.get_event_loop() +# tasks = [loop.create_task(donation.fetch_usd_prices()) for donation in donations] +# loop.run_until_complete(asyncio.gather(*tasks)) + +@shared_task +def fetch_usd_prices(): donations = Donation.objects.filter( Q(total_amount_usd__isnull=True) | Q(net_amount_usd__isnull=True) ) - jobs_logger.info(f"Fetching USD prices for {donations.count()} donations...") + donations_count = donations.count() + jobs_logger.info(f"Fetching USD prices for {donations_count} donations...") for donation in donations: try: - await donation.fetch_usd_prices() + donation.fetch_usd_prices() except Exception as e: jobs_logger.error( f"Failed to fetch USD prices for donation {donation.id}: {e}" ) + jobs_logger.info(f"USD prices fetched for {donations_count} donations.") @shared_task diff --git a/indexer_app/utils.py b/indexer_app/utils.py index e6560d4..e7daeca 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -662,7 +662,7 @@ async def handle_new_donations( logger.info(f"Created donation? {donation_created}") # fetch USD prices - await donation.fetch_usd_prices() # might not need to await this? + await donation.fetch_usd_prices_async() # might not need to await this? # # convert total_amount_usd and net_amount_usd from None # if total_amount_usd is None: From 92c1915c34886abaa0cd43156ada012793e9e692 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 14:43:44 -0400 Subject: [PATCH 071/454] use sync db methods in Donation model methods --- donations/models.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/donations/models.py b/donations/models.py index d6af2ce..c5d1df6 100644 --- a/donations/models.py +++ b/donations/models.py @@ -176,11 +176,13 @@ class Meta: ] def get_ft_token(self): - try: - token = Token.objects.aget(id=self.ft) - except Token.DoesNotExist: - # TODO: fetch metadata from token contract (ft_metadata) and add decimals to token record. For now adding 12 which is most common - token = Token.objects.acreate(id=self.ft, decimals=12) + token, created = Token.objects.get_or_create( + id=self.ft, + defaults={"decimals": 12}, # Default values for new Token creation + ) + if created: + # TODO: fetch token metadata and add correct decimals, possibly other metadata + pass return token async def fetch_usd_prices_async(self): From d0f384d567a7cca3ed86881a7a587ce578444ade Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 14:52:00 -0400 Subject: [PATCH 072/454] allow None chef_fee on Donation --- .../0006_alter_donation_chef_fee.py | 20 ++++++++++++++ donations/models.py | 2 +- indexer_app/utils.py | 26 +++++++++---------- 3 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 donations/migrations/0006_alter_donation_chef_fee.py diff --git a/donations/migrations/0006_alter_donation_chef_fee.py b/donations/migrations/0006_alter_donation_chef_fee.py new file mode 100644 index 0000000..c22db14 --- /dev/null +++ b/donations/migrations/0006_alter_donation_chef_fee.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.4 on 2024-05-07 18:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("donations", "0005_remove_donation_unique_pot_on_chain_id_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="donation", + name="chef_fee", + field=models.CharField( + help_text="Chef fee.", max_length=64, null=True, verbose_name="chef fee" + ), + ), + ] diff --git a/donations/models.py b/donations/models.py index c5d1df6..85d5f7b 100644 --- a/donations/models.py +++ b/donations/models.py @@ -144,7 +144,7 @@ class Donation(models.Model): chef_fee = models.CharField( _("chef fee"), max_length=64, - null=False, + null=True, help_text=_("Chef fee."), ) chef_fee_usd = models.DecimalField( diff --git a/indexer_app/utils.py b/indexer_app/utils.py index e7daeca..dc4b986 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -582,19 +582,19 @@ async def handle_new_donations( id=(donation_data.get("ft_id") or "near") ) - # Upsert token - try: - token = await Token.objects.aget(id=token_acct) - except Token.DoesNotExist: - # TODO: fetch metadata from token contract (ft_metadata) and add decimals to token record. For now adding 12 which is most common - token = await Token.objects.acreate(id=token_acct, decimals=12) - - # # Fetch historical token data - # # late_p = await token.get_most_recent_price() - # try: - # logger.info("fetching historical price...") - # logger.info(f"donated at: {donated_at}") - endpoint = f"{GECKO_URL}/coins/{donation_data.get('ft_id', 'near')}/history?date={format_date(donated_at)}&localization=false" + # # Upsert token + # try: + # token = await Token.objects.aget(id=token_acct) + # except Token.DoesNotExist: + # # TODO: fetch metadata from token contract (ft_metadata) and add decimals to token record. For now adding 12 which is most common + # token = await Token.objects.acreate(id=token_acct, decimals=12) + + # # Fetch historical token data + # # late_p = await token.get_most_recent_price() + # try: + # logger.info("fetching historical price...") + # logger.info(f"donated at: {donated_at}") + # endpoint = f"{GECKO_URL}/coins/{donation_data.get('ft_id', 'near')}/history?date={format_date(donated_at)}&localization=false" # logger.info(f"endpoint: {endpoint}") # response = requests.get(endpoint) # logger.info(f"response: {response}") From 1e5209683f4e55d4fc49335ec2d5d61002811ec5 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 14:59:45 -0400 Subject: [PATCH 073/454] add migration to set chef_fee to None for existing donations --- .../migrations/0007_auto_20240507_1857.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 donations/migrations/0007_auto_20240507_1857.py diff --git a/donations/migrations/0007_auto_20240507_1857.py b/donations/migrations/0007_auto_20240507_1857.py new file mode 100644 index 0000000..1052a55 --- /dev/null +++ b/donations/migrations/0007_auto_20240507_1857.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0.4 on 2024-05-07 18:57 + +from django.db import migrations + +from ..models import Donation + + +class Migration(migrations.Migration): + + def set_chef_fee_none(apps, schema_editor): + Donation.objects.update(chef_fee=None) + + dependencies = [ + ("donations", "0006_alter_donation_chef_fee"), + ] + + operations = [ + migrations.RunPython(set_chef_fee_none), + ] From 58d560ce02a082bcccb4f70ef67397a965849cad Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 15:06:10 -0400 Subject: [PATCH 074/454] fix int None issue in fetch_usd_prices --- donations/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/donations/models.py b/donations/models.py index 85d5f7b..d2380e9 100644 --- a/donations/models.py +++ b/donations/models.py @@ -211,7 +211,7 @@ def fetch_usd_prices(self): net_amount = token.format_price(self.net_amount) protocol_amount = token.format_price(self.protocol_fee) referrer_amount = token.format_price(self.referrer_fee or "0") - chef_amount = token.format_price(self.chef_fee) + chef_amount = token.format_price(self.chef_fee or "0") if existing_token_price: try: price_usd = existing_token_price.price_usd From 12dc03b7e162f065739cba2c8770bf255f5f1c9f Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 15:13:44 -0400 Subject: [PATCH 075/454] return decimal from Token.format_price --- tokens/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tokens/models.py b/tokens/models.py index 5db0b46..1b4d071 100644 --- a/tokens/models.py +++ b/tokens/models.py @@ -1,3 +1,5 @@ +from decimal import Decimal + from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -22,7 +24,8 @@ def get_most_recent_price(self): return self.historical_prices.order_by("-timestamp").first() def format_price(self, amount_str: str): - formatted_amount = int(amount_str) / (10**self.decimals) + # Convert the string amount to a Decimal, then adjust by the token's decimal places + formatted_amount = Decimal(amount_str) / (Decimal("10") ** self.decimals) return formatted_amount From 3ca9a75f43eebcca10c618f936d6b2b0f6e8d02e Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 7 May 2024 15:19:52 -0400 Subject: [PATCH 076/454] remove redundant log from fetch_usd_prices --- donations/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/donations/models.py b/donations/models.py index d2380e9..5d7e0c4 100644 --- a/donations/models.py +++ b/donations/models.py @@ -206,7 +206,6 @@ def fetch_usd_prices(self): timestamp__lte=self.donated_at + time_window, ) existing_token_price = token_prices.first() - logger.info(f"existing token price: {existing_token_price}") total_amount = token.format_price(self.total_amount) net_amount = token.format_price(self.net_amount) protocol_amount = token.format_price(self.protocol_fee) @@ -221,6 +220,9 @@ def fetch_usd_prices(self): self.referrer_fee_usd = referrer_amount * price_usd self.chef_fee_usd = chef_amount * price_usd self.save() + logger.info( + "USD prices calculated and saved using existing TokenHistoricalPrice" + ) except Exception as e: logger.error( f"Failed to calculate and save USD prices using existing TokenHistoricalPrice: {e}" From 2d0ddaf8cc7b37e35d0a57a982106d252ebd9f8f Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 8 May 2024 08:38:11 -0400 Subject: [PATCH 077/454] fix decimal * flot issue & restart indexer from block 105_854_538 --- donations/models.py | 3 +++ indexer_app/tasks.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/donations/models.py b/donations/models.py index 5d7e0c4..c4f1727 100644 --- a/donations/models.py +++ b/donations/models.py @@ -1,4 +1,5 @@ from datetime import timedelta +from decimal import Decimal import requests from asgiref.sync import sync_to_async @@ -250,6 +251,8 @@ def fetch_usd_prices(self): logger.info(f"unit price: {price_usd}") if price_usd: try: + # convert price_usd to decimal + price_usd = Decimal(price_usd) self.total_amount_usd = total_amount * price_usd self.net_amount_usd = net_amount * price_usd self.protocol_fee_usd = protocol_amount * price_usd diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index c0f6891..fb5b4b5 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -57,8 +57,8 @@ def listen_to_near_events(): try: # Update below with desired network & block height - start_block = get_block_height("current_block_height") - # start_block = 105_534_694 # manually setting for reindexing TODO: remove this + # start_block = get_block_height("current_block_height") + start_block = 105_854_538 # manually setting for reindexing TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From 27ac54caf67894c9da048df480be6ce03f5e2aab Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 8 May 2024 08:42:35 -0400 Subject: [PATCH 078/454] update usd prices job to check for no protocol_fee_usd value --- donations/models.py | 21 +++++++++++++++------ indexer_app/tasks.py | 6 +++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/donations/models.py b/donations/models.py index c4f1727..bc671cc 100644 --- a/donations/models.py +++ b/donations/models.py @@ -210,16 +210,21 @@ def fetch_usd_prices(self): total_amount = token.format_price(self.total_amount) net_amount = token.format_price(self.net_amount) protocol_amount = token.format_price(self.protocol_fee) - referrer_amount = token.format_price(self.referrer_fee or "0") - chef_amount = token.format_price(self.chef_fee or "0") + referrer_amount = ( + None if not self.referrer_fee else token.format_price(self.referrer_fee) + ) + chef_amount = None if not self.chef_fee else token.format_price(self.chef_fee) + # chef_amount = token.format_price(self.chef_fee or "0") if existing_token_price: try: price_usd = existing_token_price.price_usd self.total_amount_usd = total_amount * price_usd self.net_amount_usd = net_amount * price_usd self.protocol_fee_usd = protocol_amount * price_usd - self.referrer_fee_usd = referrer_amount * price_usd - self.chef_fee_usd = chef_amount * price_usd + self.referrer_fee_usd = ( + None if not referrer_amount else referrer_amount * price_usd + ) + self.chef_fee_usd = None if not chef_amount else chef_amount * price_usd self.save() logger.info( "USD prices calculated and saved using existing TokenHistoricalPrice" @@ -256,8 +261,12 @@ def fetch_usd_prices(self): self.total_amount_usd = total_amount * price_usd self.net_amount_usd = net_amount * price_usd self.protocol_fee_usd = protocol_amount * price_usd - self.referrer_fee_usd = referrer_amount * price_usd - self.chef_fee_usd = chef_amount * price_usd + self.referrer_fee_usd = ( + None if not referrer_amount else referrer_amount * price_usd + ) + self.chef_fee_usd = ( + None if not chef_amount else chef_amount * price_usd + ) self.save() except Exception as e: logger.error( diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index fb5b4b5..0b88cf0 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -82,7 +82,11 @@ def listen_to_near_events(): @shared_task def fetch_usd_prices(): donations = Donation.objects.filter( - Q(total_amount_usd__isnull=True) | Q(net_amount_usd__isnull=True) + Q(total_amount_usd__isnull=True) + | Q(net_amount_usd__isnull=True) + | Q(protocol_fee_usd__isnull=True) + | Q(referrer_fee_usd__isnull=True) + | Q(chef_fee_usd__isnull=True) ) donations_count = donations.count() jobs_logger.info(f"Fetching USD prices for {donations_count} donations...") From d29667430063ce1c47b04fd99871fb45479ed882 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 8 May 2024 08:43:25 -0400 Subject: [PATCH 079/454] remove manual start block setting --- indexer_app/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 0b88cf0..21eab1a 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -57,8 +57,8 @@ def listen_to_near_events(): try: # Update below with desired network & block height - # start_block = get_block_height("current_block_height") - start_block = 105_854_538 # manually setting for reindexing TODO: remove this + start_block = get_block_height("current_block_height") + # start_block = 105_854_538 # manually setting for reindexing TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From 4f1ce84e6c1d86e8ed91d687e32c18aa91b2c4cd Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 8 May 2024 08:46:01 -0400 Subject: [PATCH 080/454] fix donations query in fetch_usd_prices --- indexer_app/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 21eab1a..8272711 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -85,8 +85,8 @@ def fetch_usd_prices(): Q(total_amount_usd__isnull=True) | Q(net_amount_usd__isnull=True) | Q(protocol_fee_usd__isnull=True) - | Q(referrer_fee_usd__isnull=True) - | Q(chef_fee_usd__isnull=True) + | Q(referrer_fee__isnull=False, referrer_fee_usd__isnull=True) + | Q(chef_fee__isnull=False, chef_fee_usd__isnull=True) ) donations_count = donations.count() jobs_logger.info(f"Fetching USD prices for {donations_count} donations...") From e9eb35a53be9c0019bcc5d310855cd7ec3317edb Mon Sep 17 00:00:00 2001 From: Prometheus Date: Thu, 9 May 2024 13:56:30 +0100 Subject: [PATCH 081/454] Backfill compatible (#23) * feat: allow backfill by update_create,index admin challenge response * fix verbose name typo * remove unused imports --------- Co-authored-by: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> --- indexer_app/handler.py | 10 +- indexer_app/utils.py | 856 ++++++++++-------- .../0002_alter_listupvote_options_and_more.py | 22 + lists/models.py | 5 + pots/admin.py | 13 +- ...2_alter_potapplication_options_and_more.py | 73 ++ pots/models.py | 39 +- 7 files changed, 639 insertions(+), 379 deletions(-) create mode 100644 lists/migrations/0002_alter_listupvote_options_and_more.py create mode 100644 pots/migrations/0002_alter_potapplication_options_and_more.py diff --git a/indexer_app/handler.py b/indexer_app/handler.py index a3858fe..5c251cf 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -21,6 +21,7 @@ handle_new_pot, handle_new_pot_factory, handle_payout_challenge, + handle_payout_challenge_response, handle_pot_application, handle_pot_application_status_change, handle_set_payouts, @@ -270,7 +271,14 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess case "challenge_payouts": logger.info(f"challenge payout: {args_dict}") await handle_payout_challenge( - args_dict, receiver_id, signer_id, receipt.receipt_id + args_dict, receiver_id, signer_id, receipt.receipt_id, created_at + ) + break + + case "admin_update_payouts_challenge": + logger.info(f"challenge payout: {args_dict}") + await handle_payout_challenge_response( + args_dict, receiver_id, signer_id, receipt.receipt_id, created_at ) break diff --git a/indexer_app/utils.py b/indexer_app/utils.py index dc4b986..e3fd803 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1,7 +1,7 @@ import base64 import decimal import json -from datetime import date, datetime +from datetime import datetime import requests from django.conf import settings @@ -11,7 +11,6 @@ from accounts.models import Account from activities.models import Activity -from base.utils import format_date, format_to_near from donations.models import Donation from indexer_app.models import BlockHeight from lists.models import List, ListRegistration, ListUpvote @@ -22,8 +21,8 @@ PotFactory, PotPayout, PotPayoutChallenge, + PotPayoutChallengeAdminResponse, ) -from tokens.models import Token, TokenHistoricalPrice from .logging import logger @@ -38,152 +37,177 @@ async def handle_new_pot( receiptId: str, created_at: datetime, ): - logger.info("new pot deployment process... upsert accounts,") - - # Upsert accounts - owner, _ = await Account.objects.aget_or_create(id=data["owner"]) - signer, _ = await Account.objects.aget_or_create(id=signerId) - receiver, _ = await Account.objects.aget_or_create(id=receiverId) - - logger.info("upsert chef") - if data.get("chef"): - chef, _ = await Account.objects.aget_or_create(id=data["chef"]) - - # Create Pot object - logger.info("create pot....") - potObject = await Pot.objects.acreate( - id=receiver, - pot_factory_id=predecessorId, - deployer=signer, - deployed_at=created_at, - source_metadata=data["source_metadata"], - owner_id=data["owner"], - chef_id=data.get("chef"), - name=data["pot_name"], - description=data["pot_description"], - max_approved_applicants=data["max_projects"], - base_currency="near", - application_start=datetime.fromtimestamp(data["application_start_ms"] / 1000), - application_end=datetime.fromtimestamp(data["application_end_ms"] / 1000), - matching_round_start=datetime.fromtimestamp( - data["public_round_start_ms"] / 1000 - ), - matching_round_end=datetime.fromtimestamp(data["public_round_end_ms"] / 1000), - registry_provider=data["registry_provider"], - min_matching_pool_donation_amount=data["min_matching_pool_donation_amount"], - sybil_wrapper_provider=data["sybil_wrapper_provider"], - custom_sybil_checks=data.get("custom_sybil_checks"), - custom_min_threshold_score=data.get("custom_min_threshold_score"), - referral_fee_matching_pool_basis_points=data[ - "referral_fee_matching_pool_basis_points" - ], - referral_fee_public_round_basis_points=data[ - "referral_fee_public_round_basis_points" - ], - chef_fee_basis_points=data["chef_fee_basis_points"], - total_matching_pool="0", - matching_pool_balance="0", - matching_pool_donations_count=0, - total_public_donations="0", - public_donations_count=0, - cooldown_period_ms=None, - all_paid_out=False, - protocol_config_provider=data["protocol_config_provider"], - ) + try: - # Add admins to the Pot - if data.get("admins"): - for admin_id in data["admins"]: - admin, _ = await Account.objects.aget_or_create(id=admin_id) - potObject.admins.aadd(admin) - - # Create activity object - await Activity.objects.acreate( - signer_id=signerId, - receiver_id=receiverId, - timestamp=created_at, - type="Deploy_Pot", - action_result=data, - tx_hash=receiptId, - ) + logger.info("new pot deployment process... upsert accounts,") + + # Upsert accounts + owner, _ = await Account.objects.aget_or_create(id=data["owner"]) + signer, _ = await Account.objects.aget_or_create(id=signerId) + receiver, _ = await Account.objects.aget_or_create(id=receiverId) + + logger.info("upsert chef") + if data.get("chef"): + chef, _ = await Account.objects.aget_or_create(id=data["chef"]) + + # Create Pot object + logger.info("create pot....") + pot_defaults = { + "pot_factory_id": predecessorId, + "deployer": signer, + "deployed_at": created_at, + "source_metadata": data["source_metadata"], + "owner_id": data["owner"], + "chef_id": data.get("chef"), + "name": data["pot_name"], + "description": data["pot_description"], + "max_approved_applicants": data["max_projects"], + "base_currency": "near", + "application_start": datetime.fromtimestamp( + data["application_start_ms"] / 1000 + ), + "application_end": datetime.fromtimestamp( + data["application_end_ms"] / 1000 + ), + "matching_round_start": datetime.fromtimestamp( + data["public_round_start_ms"] / 1000 + ), + "matching_round_end": datetime.fromtimestamp( + data["public_round_end_ms"] / 1000 + ), + "registry_provider": data["registry_provider"], + "min_matching_pool_donation_amount": data[ + "min_matching_pool_donation_amount" + ], + "sybil_wrapper_provider": data["sybil_wrapper_provider"], + "custom_sybil_checks": data.get("custom_sybil_checks"), + "custom_min_threshold_score": data.get("custom_min_threshold_score"), + "referral_fee_matching_pool_basis_points": data[ + "referral_fee_matching_pool_basis_points" + ], + "referral_fee_public_round_basis_points": data[ + "referral_fee_public_round_basis_points" + ], + "chef_fee_basis_points": data["chef_fee_basis_points"], + "total_matching_pool": "0", + "matching_pool_balance": "0", + "matching_pool_donations_count": 0, + "total_public_donations": "0", + "public_donations_count": 0, + "cooldown_period_ms": None, + "all_paid_out": False, + "protocol_config_provider": data["protocol_config_provider"], + } + potObject = await Pot.objects.aupdate_or_create( + id=receiver, defaults=pot_defaults + ) + + # Add admins to the Pot + if data.get("admins"): + for admin_id in data["admins"]: + admin, _ = await Account.objects.aget_or_create(id=admin_id) + potObject.admins.aadd(admin) + + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": created_at, + "tx_hash": receiptId, + } + + activity, activity_created = await Activity.objects.aupdate_or_create( + action_result=data, type="Deploy_Pot", defaults=defaults + ) + except Exception as e: + logger.error(f"Failed to handle new pot, Error: {e}") async def handle_new_pot_factory(data: dict, receiverId: str, created_at: datetime): - logger.info("upserting accounts...") + try: - # Upsert accounts - owner, _ = await Account.objects.aget_or_create( - id=data["owner"], - ) - protocol_fee_recipient_account, _ = await Account.objects.aget_or_create( - id=data["protocol_fee_recipient_account"], - ) + logger.info("upserting accounts...") - receiver, _ = await Account.objects.aget_or_create( - id=receiverId, - ) + # Upsert accounts + owner, _ = await Account.objects.aget_or_create( + id=data["owner"], + ) + protocol_fee_recipient_account, _ = await Account.objects.aget_or_create( + id=data["protocol_fee_recipient_account"], + ) - logger.info("creating factory....") - # Create Factory object - factory = await PotFactory.objects.acreate( - id=receiver, - owner=owner, - deployed_at=created_at, - source_metadata=data["source_metadata"], - protocol_fee_basis_points=data["protocol_fee_basis_points"], - protocol_fee_recipient=protocol_fee_recipient_account, - require_whitelist=data["require_whitelist"], - ) + receiver, _ = await Account.objects.aget_or_create( + id=receiverId, + ) - # Add admins to the PotFactory - if data.get("admins"): - for admin_id in data["admins"]: - admin, _ = await Account.objects.aget_or_create( - id=admin_id, - ) - await factory.admins.aadd(admin) + logger.info("creating factory....") + defaults = { + "owner": owner, + "deployed_at": created_at, + "source_metadata": data["source_metadata"], + "protocol_fee_basis_points": data["protocol_fee_basis_points"], + "protocol_fee_recipient": protocol_fee_recipient_account, + "require_whitelist": data["require_whitelist"], + } + # Create Factory object + factory, factory_created = await PotFactory.objects.aupdate_or_create( + id=receiver, defaults=defaults + ) - # Add whitelisted deployers to the PotFactory - if data.get("whitelisted_deployers"): - for deployer_id in data["whitelisted_deployers"]: - deployer, _ = await Account.objects.aget_or_create(id=deployer_id) - await factory.whitelisted_deployers.aadd(deployer) + # Add admins to the PotFactory + if data.get("admins"): + for admin_id in data["admins"]: + admin, _ = await Account.objects.aget_or_create( + id=admin_id, + ) + await factory.admins.aadd(admin) + + # Add whitelisted deployers to the PotFactory + if data.get("whitelisted_deployers"): + for deployer_id in data["whitelisted_deployers"]: + deployer, _ = await Account.objects.aget_or_create(id=deployer_id) + await factory.whitelisted_deployers.aadd(deployer) + except Exception as e: + logger.error(f"Failed to handle new pot Factory, Error: {e}") async def handle_new_list(signerId: str, receiverId: str, status_obj: ExecutionOutcome): # receipt = block.receipts().filter(receiptId=receiptId)[0] - data = json.loads( - base64.b64decode(status_obj.status.get("SuccessValue")).decode("utf-8") - ) + try: - logger.info(f"creating list..... {data}") - - listObject = await List.objects.acreate( - id=data["id"], - owner_id=data["owner"], - default_registration_status=data["default_registration_status"], - name=data["name"], - description=data["description"], - cover_image_url=data["cover_image_url"], - admin_only_registrations=data["admin_only_registrations"], - created_at=datetime.fromtimestamp(data["created_at"] / 1000), - updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), - ) + data = json.loads( + base64.b64decode(status_obj.status.get("SuccessValue")).decode("utf-8") + ) - logger.info("upserting involveed accts...") + logger.info(f"creating list..... {data}") + + listObject = await List.objects.acreate( + id=data["id"], + owner_id=data["owner"], + default_registration_status=data["default_registration_status"], + name=data["name"], + description=data["description"], + cover_image_url=data["cover_image_url"], + admin_only_registrations=data["admin_only_registrations"], + created_at=datetime.fromtimestamp(data["created_at"] / 1000), + updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), + ) - await Account.objects.aget_or_create(id=data["owner"]) + logger.info("upserting involveed accts...") - await Account.objects.aget_or_create(id=signerId) + await Account.objects.aget_or_create(id=data["owner"]) - await Account.objects.aget_or_create(id=receiverId) + await Account.objects.aget_or_create(id=signerId) - if data.get("admins"): - for admin_id in data["admins"]: - admin_object, _ = await Account.objects.aget_or_create( - id=admin_id, - ) - await listObject.admins.aadd(admin_object) + await Account.objects.aget_or_create(id=receiverId) + + if data.get("admins"): + for admin_id in data["admins"]: + admin_object, _ = await Account.objects.aget_or_create( + id=admin_id, + ) + await listObject.admins.aadd(admin_object) + except Exception as e: + logger.error(f"Failed to handle new list, Error: {e}") async def handle_new_list_registration( @@ -240,13 +264,15 @@ async def handle_new_list_registration( # Insert activity try: - await Activity.objects.acreate( - signer_id=signerId, - receiver_id=receiverId, - timestamp=insert_data[0]["submitted_at"], - type="Register_Batch", - action_result=reg_data, - tx_hash=receipt.receipt_id, + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": datetime.fromtimestamp(insert_data[0]["submitted_at"] / 1000), + "tx_hash": receipt.receipt_id, + } + + activity, activity_created = await Activity.objects.aupdate_or_create( + action_result=reg_data, type="Register_Batch", defaults=defaults ) except Exception as e: logger.error(f"Encountered error trying to insert activity: {e}") @@ -283,47 +309,61 @@ async def handle_pot_application( status_obj: ExecutionOutcome, created_at: datetime, ): - # receipt = block.receipts().filter(lambda receipt: receipt.receiptId == receiptId)[0] - result = status_obj.status.get("SuccessValue") - if not result: - return + try: - appl_data = json.loads(base64.b64decode(result).decode("utf-8")) - logger.info(f"new pot application data: {data}, {appl_data}") + # receipt = block.receipts().filter(lambda receipt: receipt.receiptId == receiptId)[0] + result = status_obj.status.get("SuccessValue") + if not result: + return - # Update or create the account - project, _ = await Account.objects.aget_or_create( - id=data["project_id"], - ) + appl_data = json.loads(base64.b64decode(result).decode("utf-8")) + logger.info(f"new pot application data: {data}, {appl_data}") - signer, _ = await Account.objects.aget_or_create( - id=signerId, - ) + # Update or create the account + project, _ = await Account.objects.aget_or_create( + id=data["project_id"], + ) - # Create the PotApplication object - logger.info("creating application.......") - application = await PotApplication.objects.acreate( - pot_id=receiverId, - applicant=project, - message=appl_data["message"], - submitted_at=datetime.fromtimestamp(appl_data["submitted_at"] / 1000), - updated_at=created_at, - status=appl_data["status"], - tx_hash=receipt.receipt_id, - ) + signer, _ = await Account.objects.aget_or_create( + id=signerId, + ) - # Create the activity object - logger.info("creating activity for action....") - await Activity.objects.acreate( - signer=signer, - receiver_id=receiverId, - timestamp=application.submitted_at, - type="Submit_Application", - action_result=appl_data, - tx_hash=receipt.receipt_id, - ) + # Create the PotApplication object + logger.info("creating application.......") + appl_defaults = { + "message": appl_data["message"], + "submitted_at": datetime.fromtimestamp(appl_data["submitted_at"] / 1000), + "updated_at": created_at, + "status": appl_data["status"], + "tx_hash": receipt.receipt_id, + } + application, application_created = ( + await PotApplication.objects.aupdate_or_create( + applicant=project, + pot_id=receiverId, + defaults=appl_defaults, + ) + ) - logger.info("PotApplication and Activity created successfully.") + # Create the activity object + logger.info("creating activity for action....") + + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": created_at, + "tx_hash": receipt.receipt_id, + } + + activity, activity_created = await Activity.objects.aupdate_or_create( + action_result=appl_data, type="Submit_Application", defaults=defaults + ) + + logger.info( + f"PotApplication and Activity created successfully, {activity_created}" + ) + except Exception as e: + logger.error(f"Failed to handle pot application, Error: {e}") async def handle_pot_application_status_change( @@ -333,166 +373,227 @@ async def handle_pot_application_status_change( receipt: Receipt, status_obj: ExecutionOutcome, ): - logger.info(f"pot application update data: {data}, {receiverId}") + try: - # receipt = next(receipt for receipt in block.receipts() if receipt.receiptId == receiptId) - update_data = json.loads( - base64.b64decode(status_obj.status["SuccessValue"]).decode("utf-8") - ) + logger.info(f"pot application update data: {data}, {receiverId}") - # Retrieve the PotApplication object - appl = await PotApplication.objects.filter( - applicant_id=data["project_id"] - ).afirst() # TODO: handle this being None - - # Create the PotApplicationReview object - logger.info(f"create review...... {appl}") - updated_at = datetime.fromtimestamp(update_data.get("updated_at") / 1000) - await PotApplicationReview.objects.acreate( - application_id=appl.id, - reviewer_id=signerId, - notes=update_data.get("review_notes"), - status=update_data["status"], - reviewed_at=updated_at, - tx_hash=receipt.receipt_id, - ) + # receipt = next(receipt for receipt in block.receipts() if receipt.receiptId == receiptId) + update_data = json.loads( + base64.b64decode(status_obj.status["SuccessValue"]).decode("utf-8") + ) - # Update the PotApplication object - await PotApplication.objects.filter(applicant_id=data["project_id"]).aupdate( - **{"status": update_data["status"], "updated_at": updated_at} - ) + # Retrieve the PotApplication object + appl = await PotApplication.objects.filter( + applicant_id=data["project_id"] + ).afirst() # TODO: handle this being None + + # Create the PotApplicationReview object + logger.info(f"create review...... {appl}") + updated_at = datetime.fromtimestamp(update_data.get("updated_at") / 1000) + + defaults = { + "notes": update_data.get("review_notes"), + "status": update_data["status"], + "tx_hash": receipt.receipt_id, + } + + await PotApplicationReview.objects.aupdate_or_create( + application_id=appl.id, + reviewer_id=signerId, + reviewed_at=updated_at, + defaults=defaults, + ) - logger.info("PotApplicationReview and PotApplication updated successfully.") + # Update the PotApplication object + await PotApplication.objects.filter(applicant_id=data["project_id"]).aupdate( + **{"status": update_data["status"], "updated_at": updated_at} + ) + + logger.info("PotApplicationReview and PotApplication updated successfully.") + except Exception as e: + logger.warning(f"Failed to change pot application status, Error: {e}") async def handle_default_list_status_change( data: dict, receiverId: str, status_obj: ExecutionOutcome ): - logger.info(f"update project data: {data}, {receiverId}") + try: - result_data = json.loads( - base64.b64decode(status_obj.status.get("SuccessValue")).decode("utf-8") - ) + logger.info(f"update project data: {data}, {receiverId}") - list_id = data.get("registration_id") - list_update = { - "name": result_data["name"], - "owner_id": result_data["owner"], - "default_registration_status": result_data["default_registration_status"], - "admin_only_registrations": result_data["admin_only_registrations"], - "updated_at": result_data["updated_at"], - } - if result_data.get("description"): - list_update["description"] = result_data["description"] - if result_data.get("cover_image_url"): - list_update["cover_image_url"] = result_data["cover_image_url"] + result_data = json.loads( + base64.b64decode(status_obj.status.get("SuccessValue")).decode("utf-8") + ) - await List.objects.filter(id=list_id).aupdate(**list_update) + list_id = data.get("registration_id") + list_update = { + "name": result_data["name"], + "owner_id": result_data["owner"], + "default_registration_status": result_data["default_registration_status"], + "admin_only_registrations": result_data["admin_only_registrations"], + "updated_at": result_data["updated_at"], + } + if result_data.get("description"): + list_update["description"] = result_data["description"] + if result_data.get("cover_image_url"): + list_update["cover_image_url"] = result_data["cover_image_url"] - logger.info("List updated successfully.") + await List.objects.filter(id=list_id).aupdate(**list_update) + + logger.info("List updated successfully.") + except Exception as e: + logger.warning(f"Failed to change list status, Error: {e}") async def handle_list_upvote( data: dict, receiverId: str, signerId: str, receiptId: str ): - logger.info(f"upvote list: {data}, {receiverId}") + try: - acct, _ = await Account.objects.aget_or_create( - id=signerId, - ) + logger.info(f"upvote list: {data}, {receiverId}") - created_at = datetime.now() + acct, _ = await Account.objects.aget_or_create( + id=signerId, + ) - await ListUpvote.objects.acreate( - list_id=data.get("list_id") or receiverId, - account_id=signerId, - created_at=created_at, - ) + created_at = datetime.now() - await Activity.objects.acreate( - signer_id=signerId, - receiver_id=receiverId, - timestamp=created_at, - type="Upvote", - action_result=data, - tx_hash=receiptId, - ) + await ListUpvote.objects.aupdate_or_create( + list_id=data.get("list_id") or receiverId, + account_id=signerId, + ) + + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": created_at, + "tx_hash": receiptId, + } + + activity, activity_created = await Activity.objects.aupdate_or_create( + action_result=data, type="Upvote", defaults=defaults + ) - logger.info("Upvote and activity records created successfully.") + logger.info( + f"Upvote and activity records created successfully. {activity_created}" + ) + except Exception as e: + logger.warning(f"Failed to upvote list, Error: {e}") async def handle_set_payouts(data: dict, receiverId: str, receipt: Receipt): - logger.info(f"set payout data: {data}, {receiverId}") - payouts = data.get("payouts", []) - - insertion_data = [] - for payout in payouts: - # General question: should we register projects as accounts? - potPayout = { - "recipient_id": payout.get("project_id"), - "amount": payout.get("amount"), - "ft_id": payout.get("ft_id", "near"), - "tx_hash": receipt.receipt_id, - } - insertion_data.append(potPayout) + try: + + logger.info(f"set payout data: {data}, {receiverId}") + payouts = data.get("payouts", []) + + insertion_data = [] + for payout in payouts: + # General question: should we register projects as accounts? + potPayout = { + "recipient_id": payout.get("project_id"), + "amount": payout.get("amount"), + "ft_id": payout.get("ft_id", "near"), + "tx_hash": receipt.receipt_id, + } + insertion_data.append(potPayout) - await PotPayout.objects.abulk_create(insertion_data) + await PotPayout.objects.abulk_create(insertion_data, ignore_conflicts=True) + except Exception as e: + logger.warning(f"Failed to set payouts, Error: {e}") async def handle_transfer_payout( data: dict, receiverId: str, receiptId: str, created_at: datetime ): - data = data["payout"] - logger.info(f"fulfill payout data: {data}, {receiverId}") - payout = { - "recipient_id": data["project_id"], - "amount": data["amount"], - "paid_at": data.get("paid_at", created_at), - "tx_hash": receiptId, - } - await PotPayout.objects.filter(recipient_id=data["project_id"]).aupdate(**payout) + try: + + data = data["payout"] + logger.info(f"fulfill payout data: {data}, {receiverId}") + payout = { + "recipient_id": data["project_id"], + "amount": data["amount"], + "paid_at": data.get("paid_at", created_at), + "tx_hash": receiptId, + } + await PotPayout.objects.filter(recipient_id=data["project_id"]).aupdate( + **payout + ) + except Exception as e: + logger.warning(f"Failed to create payout data, Error: {e}") async def handle_payout_challenge( - data: dict, receiverId: str, signerId: str, receiptId: str + data: dict, receiverId: str, signerId: str, receiptId: str, created_at: datetime ): - logger.info(f"challenging payout..: {data}, {receiverId}") - created_at = datetime.now() - payoutChallenge = { - "challenger_id": signerId, - "pot_id": receiverId, - "created_at": created_at, - "message": data["reason"], - "tx_hash": receiptId, - } - await PotPayoutChallenge.objects.acreate(**payoutChallenge) - - await Activity.objects.acreate( - signer_id=signerId, - receiver_id=receiverId, - timestamp=created_at, - type="Challenge_Payout", - action_result=payoutChallenge, - tx_hash=receiptId, - ) + try: + + logger.info(f"challenging payout..: {data}, {receiverId}") + payoutChallenge = { + "created_at": created_at, + "message": data["reason"], + "tx_hash": receiptId, + } + await PotPayoutChallenge.objects.aupdate_or_create( + challenger_id=signerId, pot_id=receiverId, defaults=payoutChallenge + ) + + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": created_at, + "tx_hash": receiptId, + } + + activity, activity_created = await Activity.objects.aupdate_or_create( + action_result=payoutChallenge, type="Challenge_Payout", defaults=defaults + ) + except Exception as e: + logger.warning(f"Failed to create payoutchallenge, Error: {e}") + + +async def handle_payout_challenge_response( + data: dict, receiverId: str, signerId: str, receiptId: str, created_at: datetime +): + try: + logger.info(f"responding to payout challenge..: {data}, {receiverId}") + response_defaults = { + "admin": signerId, + "message": data.get("notes"), + "resolved": data.get("resolve_challenge"), + "tx_hash": receiptId, + } + await PotPayoutChallengeAdminResponse.objects.aupdate_or_create( + challenger_id=data["challenger_id"], + pot_id=receiverId, + created_at=created_at, + defaults=response_defaults, + ) + except Exception as e: + logger.error(f"Failed to handle admin challeneg response, Error: {e}") async def handle_list_admin_removal(data, receiverId, signerId, receiptId): - logger.info(f"removing admin...: {data}, {receiverId}") - list_obj = await List.objects.aget(id=data["list_id"]) - - for acct in data["admins"]: - list_obj.admins.remove({"admins_id": acct}) # ?? - - activity = { - "signer_id": signerId, - "receiver_id": receiverId, - "timestamp": datetime.now(), - "type": "Remove_List_Admin", - "tx_hash": receiptId, - } + try: + + logger.info(f"removing admin...: {data}, {receiverId}") + list_obj = await List.objects.aget(id=data["list_id"]) + + for acct in data["admins"]: + list_obj.admins.remove({"admins_id": acct}) # maybe check - await Activity.objects.acreate(**activity) + activity = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": datetime.now(), + "tx_hash": receiptId, + } + + activity, activity_created = await Activity.objects.aupdate_or_create( + type="Remove_List_Admin", defaults=activity + ) + except Exception as e: + logger.warning(f"Failed to remove list admin, Error: {e}") # TODO: Need to abstract some actions. @@ -559,28 +660,32 @@ async def handle_new_donations( (donation_data.get("donated_at") or donation_data.get("donated_at_ms")) / 1000 ) - # Upsert donor account - donor, _ = await Account.objects.aget_or_create(id=donation_data["donor_id"]) - recipient = None + try: - if donation_data.get("recipient_id"): - recipient, _ = await Account.objects.aget_or_create( - id=donation_data["recipient_id"] - ) - if donation_data.get("project_id"): - recipient, _ = await Account.objects.aget_or_create( - id=donation_data["project_id"] - ) + # Upsert donor account + donor, _ = await Account.objects.aget_or_create(id=donation_data["donor_id"]) + recipient = None - if donation_data.get("referrer_id"): - referrer, _ = await Account.objects.aget_or_create( - id=donation_data["referrer_id"] - ) + if donation_data.get("recipient_id"): + recipient, _ = await Account.objects.aget_or_create( + id=donation_data["recipient_id"] + ) + if donation_data.get("project_id"): + recipient, _ = await Account.objects.aget_or_create( + id=donation_data["project_id"] + ) - # Upsert token account - token_acct, _ = await Account.objects.aget_or_create( - id=(donation_data.get("ft_id") or "near") - ) + if donation_data.get("referrer_id"): + referrer, _ = await Account.objects.aget_or_create( + id=donation_data["referrer_id"] + ) + + # Upsert token account + token_acct, _ = await Account.objects.aget_or_create( + id=(donation_data.get("ft_id") or "near") + ) + except Exception as e: + logger.warning(f"Failed to create/get an account involved in donation: {e}") # # Upsert token # try: @@ -630,69 +735,78 @@ async def handle_new_donations( # total_amount_usd = None if not unit_price else unit_price * total_near_amount # net_amount_usd = None if not unit_price else unit_price * net_near_amount - total_amount = donation_data["total_amount"] - - logger.info(f"inserting donations... by {actionName}") - default_data = { - "donor": donor, - "total_amount": total_amount, - "total_amount_usd": None, # USD amounts will be added later (could be in pre-save hook) - "net_amount_usd": None, - "net_amount": net_amount, - "ft": token_acct, - "message": donation_data.get("message"), - "donated_at": donated_at, - "matching_pool": donation_data.get("matching_pool", False), - "recipient": recipient, - "protocol_fee": donation_data["protocol_fee"], - "referrer": referrer if donation_data.get("referrer_id") else None, - "referrer_fee": donation_data.get("referrer_fee"), - "tx_hash": receipt_obj.receipt_id, - } - logger.info(f"default donation data: {default_data}") + try: - if actionName != "direct": - logger.info("selecting pot to make public donation update") - pot = await Pot.objects.aget(id=receiverId) - default_data["pot"] = pot + total_amount = donation_data["total_amount"] + + logger.info(f"inserting donations... by {actionName}") + default_data = { + "donor": donor, + "total_amount": total_amount, + "total_amount_usd": None, # USD amounts will be added later (could be in pre-save hook) + "net_amount_usd": None, + "net_amount": net_amount, + "ft": token_acct, + "message": donation_data.get("message"), + "donated_at": donated_at, + "matching_pool": donation_data.get("matching_pool", False), + "recipient": recipient, + "protocol_fee": donation_data["protocol_fee"], + "referrer": referrer if donation_data.get("referrer_id") else None, + "referrer_fee": donation_data.get("referrer_fee"), + "tx_hash": receipt_obj.receipt_id, + } + logger.info(f"default donation data: {default_data}") - donation, donation_created = await Donation.objects.aupdate_or_create( - on_chain_id=donation_data["id"], defaults={}, create_defaults=default_data - ) - logger.info(f"Created donation? {donation_created}") - - # fetch USD prices - await donation.fetch_usd_prices_async() # might not need to await this? - - # # convert total_amount_usd and net_amount_usd from None - # if total_amount_usd is None: - # total_amount_usd = 0.0 - # if net_amount_usd is None: - # net_amount_usd = 0.0 - - # Insert or update activity record - activity_type = ( - "Donate_Direct" - if actionName == "direct" - else ( - "Donate_Pot_Matching_Pool" - if donation.matching_pool - else "Donate_Pot_Public" + if actionName != "direct": + logger.info("selecting pot to make public donation update") + pot = await Pot.objects.aget(id=receiverId) + default_data["pot"] = pot + + donation, donation_created = await Donation.objects.aupdate_or_create( + on_chain_id=donation_data["id"], defaults={}, create_defaults=default_data ) - ) - defaults = { - "signer_id": signerId, - "receiver_id": receiverId, - "timestamp": donation.donated_at, - "tx_hash": receipt_obj.receipt_id, - } - activity, activity_created = await Activity.objects.aupdate_or_create( - action_result=donation_data, type=activity_type, defaults=defaults - ) - if activity_created: - logger.info(f"Activity created: {activity}") - else: - logger.info(f"Activity updated: {activity}") + logger.info(f"Created donation? {donation_created}") + + # fetch USD prices + await donation.fetch_usd_prices_async() # might not need to await this? + + # # convert total_amount_usd and net_amount_usd from None + # if total_amount_usd is None: + # total_amount_usd = 0.0 + # if net_amount_usd is None: + # net_amount_usd = 0.0 + + logger.info(f"Created donation? {donation_created}") + # Insert or update activity record + activity_type = ( + "Donate_Direct" + if actionName == "direct" + else ( + "Donate_Pot_Matching_Pool" + if donation.matching_pool + else "Donate_Pot_Public" + ) + ) + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": donation.donated_at, + "tx_hash": receipt_obj.receipt_id, + } + try: + + activity, activity_created = await Activity.objects.aupdate_or_create( + action_result=donation_data, type=activity_type, defaults=defaults + ) + if activity_created: + logger.info(f"Activity created: {activity}") + else: + logger.info(f"Activity updated: {activity}") + except Exception as e: + logger.info(f"Failed to create Activity: {e}") + except Exception as e: + logger.warning(f"Failed to create/update donation: {e}") ### COMMENTING OUT FOR NOW SINCE WE HAVE PERIODIC JOB RUNNING TO UPDATE ACCOUNT STATS (NB: DOESN'T CURRENTLY COVER POT STATS) ### CAN ALWAYS ADD BACK IF DESIRED diff --git a/lists/migrations/0002_alter_listupvote_options_and_more.py b/lists/migrations/0002_alter_listupvote_options_and_more.py new file mode 100644 index 0000000..af8f5c2 --- /dev/null +++ b/lists/migrations/0002_alter_listupvote_options_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.4 on 2024-05-08 15:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0001_initial"), + ("lists", "0001_initial"), + ] + + operations = [ + migrations.AlterModelOptions( + name="listupvote", + options={"verbose_name_plural": "ListUpvotes"}, + ), + migrations.AlterUniqueTogether( + name="listupvote", + unique_together={("list", "account")}, + ), + ] diff --git a/lists/models.py b/lists/models.py index a3e93df..7732f1e 100644 --- a/lists/models.py +++ b/lists/models.py @@ -101,6 +101,11 @@ class ListUpvote(models.Model): help_text=_("Upvote creation date."), ) + class Meta: + verbose_name_plural = "ListUpvotes" + + unique_together = (("list", "account"),) + class ListRegistration(models.Model): id = models.AutoField( diff --git a/pots/admin.py b/pots/admin.py index 24862fa..d49ee44 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -1,5 +1,14 @@ from django.contrib import admin -from .models import PotFactory, Pot, PotApplication, PotPayout, PotPayoutChallenge, PotPayoutChallengeAdminResponse + +from .models import ( + Pot, + PotApplication, + PotFactory, + PotPayout, + PotPayoutChallenge, + PotPayoutChallengeAdminResponse, +) + @admin.register(PotFactory) class PotFactoryAdmin(admin.ModelAdmin): @@ -32,6 +41,6 @@ class PotPayoutChallengeAdmin(admin.ModelAdmin): @admin.register(PotPayoutChallengeAdminResponse) class PotPayoutChallengeAdminResponseAdmin(admin.ModelAdmin): - list_display = ('id', 'challenge', 'admin', 'created_at', 'resolved') + list_display = ('id', 'pot', 'admin', 'created_at', 'resolved') search_fields = ('admin__id', 'challenge__id') list_filter = ('created_at', 'resolved') diff --git a/pots/migrations/0002_alter_potapplication_options_and_more.py b/pots/migrations/0002_alter_potapplication_options_and_more.py new file mode 100644 index 0000000..60f1eed --- /dev/null +++ b/pots/migrations/0002_alter_potapplication_options_and_more.py @@ -0,0 +1,73 @@ +# Generated by Django 5.0.4 on 2024-05-09 12:44 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0001_initial"), + ("pots", "0001_initial"), + ] + + operations = [ + migrations.AlterModelOptions( + name="potapplication", + options={"verbose_name_plural": "PotApplications"}, + ), + migrations.AlterModelOptions( + name="potapplicationreview", + options={"verbose_name_plural": "PotApplicationReviews"}, + ), + migrations.AlterModelOptions( + name="potpayoutchallenge", + options={"verbose_name_plural": "PayoutChallenges"}, + ), + migrations.AlterModelOptions( + name="potpayoutchallengeadminresponse", + options={"verbose_name_plural": "PotPayoutChallengeResponses"}, + ), + migrations.AddField( + model_name="potpayoutchallengeadminresponse", + name="challenger", + field=models.ForeignKey( + help_text="challenger being responded to.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="payout_admin_responses", + to="accounts.account", + ), + ), + migrations.AddField( + model_name="potpayoutchallengeadminresponse", + name="pot", + field=models.ForeignKey( + help_text="Pot being challenged.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="payout_responses", + to="pots.pot", + ), + ), + migrations.AlterUniqueTogether( + name="potpayoutchallengeadminresponse", + unique_together={("challenger", "pot", "created_at")}, + ), + migrations.AlterUniqueTogether( + name="potapplication", + unique_together={("pot", "applicant")}, + ), + migrations.AlterUniqueTogether( + name="potapplicationreview", + unique_together={("application", "reviewer", "reviewed_at")}, + ), + migrations.AlterUniqueTogether( + name="potpayoutchallenge", + unique_together={("challenger", "pot")}, + ), + migrations.RemoveField( + model_name="potpayoutchallengeadminresponse", + name="challenge", + ), + ] diff --git a/pots/models.py b/pots/models.py index 8941bec..b216713 100644 --- a/pots/models.py +++ b/pots/models.py @@ -332,6 +332,11 @@ class PotApplication(models.Model): help_text=_("Transaction hash."), ) + class Meta: + verbose_name_plural = "PotApplications" + + unique_together = (("pot", "applicant"),) + class PotApplicationReview(models.Model): id = models.AutoField( @@ -378,6 +383,11 @@ class PotApplicationReview(models.Model): help_text=_("Transaction hash."), ) + class Meta: + verbose_name_plural = "PotApplicationReviews" + + unique_together = (("application", "reviewer", "reviewed_at"),) + class PotPayout(models.Model): id = models.AutoField( @@ -468,6 +478,11 @@ class PotPayoutChallenge(models.Model): help_text=_("Challenge message."), ) + class Meta: + verbose_name_plural = "PayoutChallenges" + + unique_together = (("challenger", "pot"),) + class PotPayoutChallengeAdminResponse(models.Model): id = models.AutoField( @@ -475,13 +490,22 @@ class PotPayoutChallengeAdminResponse(models.Model): primary_key=True, help_text=_("Admin response id."), ) - challenge = models.ForeignKey( - PotPayoutChallenge, + challenger = models.ForeignKey( + Account, on_delete=models.CASCADE, - related_name="admin_responses", - null=False, - help_text=_("Challenge responded to."), + related_name="payout_admin_responses", + null=True, + help_text=_("challenger being responded to."), ) + + pot = models.ForeignKey( + Pot, + on_delete=models.CASCADE, + related_name="payout_responses", + null=True, + help_text=_("Pot being challenged."), + ) + admin = models.ForeignKey( Account, on_delete=models.CASCADE, @@ -511,3 +535,8 @@ class PotPayoutChallengeAdminResponse(models.Model): null=False, help_text=_("Transaction hash."), ) + + class Meta: + verbose_name_plural = "PotPayoutChallengeResponses" + + unique_together = (("challenger", "pot", "created_at"),) From a7de2d4c2ca5c9110d4784059cc15382a448eb38 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 10 May 2024 10:57:17 -0400 Subject: [PATCH 082/454] restart at block 106388588 --- indexer_app/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 8272711..f68814d 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -57,8 +57,8 @@ def listen_to_near_events(): try: # Update below with desired network & block height - start_block = get_block_height("current_block_height") - # start_block = 105_854_538 # manually setting for reindexing TODO: remove this + # start_block = get_block_height("current_block_height") + start_block = 106388590 # manually setting for reindexing TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From 1959ca0a5cc2f4989260761ef431540091e994b1 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 10 May 2024 10:57:45 -0400 Subject: [PATCH 083/454] restart at block 106388588 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index f68814d..b05a3be 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -58,7 +58,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 106388590 # manually setting for reindexing TODO: remove this + start_block = 106388588 # manually setting for reindexing TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From d6534988ce5ff1d95cfd6c81f2f7c7f4f33a09fa Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 10 May 2024 11:03:28 -0400 Subject: [PATCH 084/454] restart indexer at 106431915 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index b05a3be..0219603 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -58,7 +58,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 106388588 # manually setting for reindexing TODO: remove this + start_block = 106431915 # manually setting for reindexing TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From aa6a4670d9ea422250cba393afbc349ce81d3876 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 10 May 2024 11:06:00 -0400 Subject: [PATCH 085/454] restart at 106435734 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 0219603..7c7a402 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -58,7 +58,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 106431915 # manually setting for reindexing TODO: remove this + start_block = 106435734 # manually setting for reindexing TODO: remove this logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From 875bdb7615700a46466328591c31b5ca9ffcc57c Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 13 May 2024 10:31:38 -0400 Subject: [PATCH 086/454] add pattern matching for pot subaccount --- indexer_app/handler.py | 23 ++++++++++++++++++----- pots/utils.py | 11 ++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 5c251cf..c0c67af 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -6,7 +6,10 @@ from near_lake_framework import near_primitives from base.utils import convert_ns_to_utc -from pots.utils import match_pot_factory_version_pattern +from pots.utils import ( + match_pot_factory_version_pattern, + match_pot_subaccount_version_pattern, +) from .logging import logger from .utils import ( @@ -129,7 +132,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess await handle_new_pot_factory( args_dict, receiver_id, created_at ) - else: + elif match_pot_subaccount_version_pattern( + receipt.receiver_id + ): logger.info( f"new pot deployment: {args_dict}, {action}" ) @@ -271,14 +276,22 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess case "challenge_payouts": logger.info(f"challenge payout: {args_dict}") await handle_payout_challenge( - args_dict, receiver_id, signer_id, receipt.receipt_id, created_at + args_dict, + receiver_id, + signer_id, + receipt.receipt_id, + created_at, ) break - + case "admin_update_payouts_challenge": logger.info(f"challenge payout: {args_dict}") await handle_payout_challenge_response( - args_dict, receiver_id, signer_id, receipt.receipt_id, created_at + args_dict, + receiver_id, + signer_id, + receipt.receipt_id, + created_at, ) break diff --git a/pots/utils.py b/pots/utils.py index 30d4c14..01bfab6 100644 --- a/pots/utils.py +++ b/pots/utils.py @@ -1,6 +1,15 @@ import re +BASE_PATTERN = r"v\d+\.potfactory\.potlock\.near$" + def match_pot_factory_version_pattern(receiver): - pattern = r"^v\d+\.potfactory\.potlock\.near$" + """Matches the base pot factory version pattern without a subaccount.""" + pattern = f"^{BASE_PATTERN}" + return bool(re.match(pattern, receiver)) + + +def match_pot_subaccount_version_pattern(receiver): + """Matches the pot factory version pattern with a subaccount.""" + pattern = f"^[a-zA-Z0-9_]+\.{BASE_PATTERN}" return bool(re.match(pattern, receiver)) From fd0e2d2942e3e8cbdffefc0ad1ac666a930d33b4 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 14 May 2024 20:59:09 -0400 Subject: [PATCH 087/454] add cors headers & localhost support --- base/settings.py | 6 ++++++ indexer_app/tasks.py | 2 +- pyproject.toml | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index 02a6664..698936e 100644 --- a/base/settings.py +++ b/base/settings.py @@ -67,6 +67,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework", + "corsheaders", # "cachalot", "celery", "api", @@ -88,6 +89,7 @@ MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", @@ -117,6 +119,10 @@ WSGI_APPLICATION = "base.wsgi.application" +CORS_ALLOWED_ORIGINS = [ + "http://localhost:3000", +] + # REDIS / CACHE CONFIGS REDIS_SCHEMA = ( diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 7c7a402..b5fa879 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -58,7 +58,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 106435734 # manually setting for reindexing TODO: remove this + start_block = 111854642 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: diff --git a/pyproject.toml b/pyproject.toml index 2dd543a..1bb5530 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ django-redis = "^5.4.0" gunicorn = "^22.0.0" sentry-sdk = {extras = ["django"], version = "^1.45.0"} watchtower = "^3.1.0" +django-cors-headers = "^4.3.1" [tool.poetry.group.dev.dependencies] black = "^24.3.0" From 137e348c09673001b3e0cf4291f8275e6dc04cb1 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 14 May 2024 21:08:52 -0400 Subject: [PATCH 088/454] fix virtual env usage in after_install.sh --- scripts/after_install.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 4ffdb25..a52c69c 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -43,23 +43,23 @@ echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" # Log the full output of showmigrations echo "Checking for pending migrations..." >> "$LOG_FILE" -python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose +poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose # Check for unapplied migrations -PENDING_MIGRATIONS=$(python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations +PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations if [ "$PENDING_MIGRATIONS" -gt 0 ]; then echo "Migrations found; stopping services..." >> "$LOG_FILE" sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat echo 'Applying migrations...' >> "$LOG_FILE" - python manage.py migrate >> "$LOG_FILE" 2>&1 + poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 echo 'Starting services...' >> "$LOG_FILE" sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat else echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" - python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 + poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat fi From 466c0936ac521a7c7be04be5384ad5cce7b36058 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 14 May 2024 21:13:07 -0400 Subject: [PATCH 089/454] update after_install.sh --- scripts/after_install.sh | 80 +++++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 10 deletions(-) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index a52c69c..4b921ba 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -28,22 +28,14 @@ PROJECT_DIR="/home/ec2-user/django-indexer" # Navigate to the project directory cd "$PROJECT_DIR" -# Source the specific poetry virtual environment -source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" - # Install dependencies using Poetry echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" poetry install >> "$LOG_FILE" echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" -# Check if there are pending migrations and log the output -echo "Checking for pending migrations..." >> "$LOG_FILE" -PENDING_MIGRATIONS=$(python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout -echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" - -# Log the full output of showmigrations +# Log the full output of showmigrations to diagnose echo "Checking for pending migrations..." >> "$LOG_FILE" -poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose +poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Check for unapplied migrations PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations @@ -64,3 +56,71 @@ else fi echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" + + +# #!/bin/bash +# # Log output to a specific file +# LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" + +# echo -e "\n\n" >> "$LOG_FILE" +# echo "=========================================" >> "$LOG_FILE" +# echo "Running after_install.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" +# echo "=========================================" >> "$LOG_FILE" + +# # Load env vars +# source /home/ec2-user/.bashrc + +# # Set correct ownership recursively for project directory +# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" + +# # Set the necessary permissions +# sudo chmod -R 775 /home/ec2-user/django-indexer/ +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" + +# # Restart nginx to apply any configuration changes +# sudo systemctl restart nginx +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" + +# # Define the project directory +# PROJECT_DIR="/home/ec2-user/django-indexer" + +# # Navigate to the project directory +# cd "$PROJECT_DIR" + +# # Source the specific poetry virtual environment +# # source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" + +# # Install dependencies using Poetry +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +# poetry install >> "$LOG_FILE" +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" + +# # Check if there are pending migrations and log the output +# echo "Checking for pending migrations..." >> "$LOG_FILE" +# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout +# echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" + +# # Log the full output of showmigrations +# echo "Checking for pending migrations..." >> "$LOG_FILE" +# poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose + +# # Check for unapplied migrations +# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations + +# if [ "$PENDING_MIGRATIONS" -gt 0 ]; then +# echo "Migrations found; stopping services..." >> "$LOG_FILE" +# sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat + +# echo 'Applying migrations...' >> "$LOG_FILE" +# poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 + +# echo 'Starting services...' >> "$LOG_FILE" +# sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat +# else +# echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" +# poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 +# sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat +# fi + +# echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" From 7d83b98c0cd55bcbc731a12723cd036243aef088 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 20 May 2024 09:37:15 -0400 Subject: [PATCH 090/454] revert after_install --- indexer_app/tasks.py | 2 +- scripts/after_install.sh | 141 ++++++++++++++++++++------------------- 2 files changed, 72 insertions(+), 71 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index b5fa879..e0591f1 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -58,7 +58,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 111854642 + start_block = 109991746 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 4b921ba..6c56c5b 100755 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -1,63 +1,3 @@ -#!/bin/bash -# Log output to a specific file -LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" - -echo -e "\n\n" >> "$LOG_FILE" -echo "=========================================" >> "$LOG_FILE" -echo "Running after_install.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" -echo "=========================================" >> "$LOG_FILE" - -# Load env vars -source /home/ec2-user/.bashrc - -# Set correct ownership recursively for project directory -sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ -echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" - -# Set the necessary permissions -sudo chmod -R 775 /home/ec2-user/django-indexer/ -echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" - -# Restart nginx to apply any configuration changes -sudo systemctl restart nginx -echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" - -# Define the project directory -PROJECT_DIR="/home/ec2-user/django-indexer" - -# Navigate to the project directory -cd "$PROJECT_DIR" - -# Install dependencies using Poetry -echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" -poetry install >> "$LOG_FILE" -echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" - -# Log the full output of showmigrations to diagnose -echo "Checking for pending migrations..." >> "$LOG_FILE" -poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 - -# Check for unapplied migrations -PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations - -if [ "$PENDING_MIGRATIONS" -gt 0 ]; then - echo "Migrations found; stopping services..." >> "$LOG_FILE" - sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat - - echo 'Applying migrations...' >> "$LOG_FILE" - poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 - - echo 'Starting services...' >> "$LOG_FILE" - sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat -else - echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" - poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 - sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat -fi - -echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" - - # #!/bin/bash # # Log output to a specific file # LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" @@ -88,22 +28,14 @@ echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" # # Navigate to the project directory # cd "$PROJECT_DIR" -# # Source the specific poetry virtual environment -# # source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" - # # Install dependencies using Poetry # echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" # poetry install >> "$LOG_FILE" # echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" -# # Check if there are pending migrations and log the output +# # Log the full output of showmigrations to diagnose # echo "Checking for pending migrations..." >> "$LOG_FILE" -# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout -# echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" - -# # Log the full output of showmigrations -# echo "Checking for pending migrations..." >> "$LOG_FILE" -# poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose +# poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # # Check for unapplied migrations # PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations @@ -124,3 +56,72 @@ echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" # fi # echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" + + + +#!/bin/bash +# Log output to a specific file +LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" + +echo -e "\n\n" >> "$LOG_FILE" +echo "=========================================" >> "$LOG_FILE" +echo "Running after_install.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" +echo "=========================================" >> "$LOG_FILE" + +# Load env vars +source /home/ec2-user/.bashrc + +# Set correct ownership recursively for project directory +sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ +echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" + +# Set the necessary permissions +sudo chmod -R 775 /home/ec2-user/django-indexer/ +echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" + +# Restart nginx to apply any configuration changes +sudo systemctl restart nginx +echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" + +# Define the project directory +PROJECT_DIR="/home/ec2-user/django-indexer" + +# Navigate to the project directory +cd "$PROJECT_DIR" + +# Source the specific poetry virtual environment +source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" + +# Install dependencies using Poetry +echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +poetry install >> "$LOG_FILE" +echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" + +# Check if there are pending migrations and log the output +echo "Checking for pending migrations..." >> "$LOG_FILE" +PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout +echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" + +# Log the full output of showmigrations +echo "Checking for pending migrations..." >> "$LOG_FILE" +poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose + +# Check for unapplied migrations +PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations + +if [ "$PENDING_MIGRATIONS" -gt 0 ]; then + echo "Migrations found; stopping services..." >> "$LOG_FILE" + sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat + + echo 'Applying migrations...' >> "$LOG_FILE" + poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 + + echo 'Starting services...' >> "$LOG_FILE" + sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat +else + echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" + poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 + sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat +fi + +echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" From 7b8f79affca335c5c7de2ae38dc532242d8f0f82 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 20 May 2024 09:42:51 -0400 Subject: [PATCH 091/454] update start block to 110046700 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index e0591f1..b35c384 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -58,7 +58,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 109991746 + start_block = 110046700 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) finally: From b8f87aaa3d62f1b86aa3a45376601d5fdc19be9e Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 20 May 2024 09:55:26 -0400 Subject: [PATCH 092/454] avoid logging WorkerLostErrors to Sentry --- indexer_app/tasks.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index b35c384..84bb319 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -2,8 +2,9 @@ import logging from pathlib import Path +from billiard.exceptions import WorkerLostError from celery import shared_task -from celery.signals import task_revoked +from celery.signals import task_revoked, worker_shutdown from django.conf import settings from django.db import transaction from django.db.models import Count, Q, Sum @@ -58,13 +59,25 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 110046700 + start_block = 111932487 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) + except WorkerLostError: + pass # don't log to Sentry finally: loop.close() +@worker_shutdown.connect +def worker_shutdown_handler(sig, how, exitcode, **kwargs): + if sig == 15: + logger.info( + "Celery worker shutdown initiated by signal 15 (SIGTERM)." + ) # avoid logging to Sentry + else: + logger.error("Celery worker shutdown due to signal %d.", sig) + + jobs_logger = logging.getLogger("jobs") From fa5c4bc011acbe595483b025a8d798a3a93d0ba8 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 20 May 2024 10:00:20 -0400 Subject: [PATCH 093/454] update start block to 111941200 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 84bb319..c6bd07e 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 111932487 + start_block = 111941200 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From e83dda82de214fb705a8fb1e77433344777d78fe Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 20 May 2024 10:10:42 -0400 Subject: [PATCH 094/454] update start block to 112034320 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index c6bd07e..595adb9 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 111941200 + start_block = 112034320 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From f61cc0debcb35d3df4dd3e0376898441ad7c42c8 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 20 May 2024 10:14:58 -0400 Subject: [PATCH 095/454] update start block to 112158850 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 595adb9..5ed211e 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 112034320 + start_block = 112158850 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From eaf0cf32a03824944ff62bb265de953162cc8f6b Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 20 May 2024 10:29:46 -0400 Subject: [PATCH 096/454] update start block to 112461673 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 5ed211e..75e30da 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 112158850 + start_block = 112461673 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From c2cc3674d306ce7d5034510040b30a0d73ad45ce Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 20 May 2024 13:15:57 -0400 Subject: [PATCH 097/454] fix display of admins & wl deployers in PotFactory detail (admin) --- pots/admin.py | 69 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/pots/admin.py b/pots/admin.py index d49ee44..03cdba9 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -1,5 +1,8 @@ +from django import forms from django.contrib import admin +from accounts.models import Account + from .models import ( Pot, PotApplication, @@ -10,37 +13,69 @@ ) +class PotFactoryForm(forms.ModelForm): + class Meta: + model = PotFactory + fields = "__all__" + + def __init__(self, *args, **kwargs): + super(PotFactoryForm, self).__init__(*args, **kwargs) + # Ensure self.instance is available before accessing it + if self.instance.pk: + # Set the queryset for the admins field to only include relevant accounts + self.fields["admins"].queryset = self.instance.admins.all() + # Set the queryset for the whitelisted_deployers field to only include relevant accounts + self.fields["whitelisted_deployers"].queryset = ( + self.instance.whitelisted_deployers.all() + ) + + @admin.register(PotFactory) class PotFactoryAdmin(admin.ModelAdmin): - list_display = ('id', 'owner', 'deployed_at') - search_fields = ('id', 'owner__id') + form = PotFactoryForm + list_display = ("id", "owner", "deployed_at") + search_fields = ("id", "owner__id") + + def get_form(self, request, obj=None, **kwargs): + form = super(PotFactoryAdmin, self).get_form(request, obj, **kwargs) + if obj: + form.base_fields["admins"].queryset = obj.admins.all() + form.base_fields["whitelisted_deployers"].queryset = ( + obj.whitelisted_deployers.all() + ) + return form + @admin.register(Pot) class PotAdmin(admin.ModelAdmin): - list_display = ('id', 'pot_factory', 'deployer', 'deployed_at', 'name') - search_fields = ('id', 'name', 'deployer__id') - list_filter = ('deployed_at',) + list_display = ("id", "pot_factory", "deployer", "deployed_at", "name") + search_fields = ("id", "name", "deployer__id") + list_filter = ("deployed_at",) + @admin.register(PotApplication) class PotApplicationAdmin(admin.ModelAdmin): - list_display = ('id', 'pot', 'applicant', 'status', 'submitted_at') - search_fields = ('pot__id', 'applicant__id') - list_filter = ('status', 'submitted_at') + list_display = ("id", "pot", "applicant", "status", "submitted_at") + search_fields = ("pot__id", "applicant__id") + list_filter = ("status", "submitted_at") + @admin.register(PotPayout) class PotPayoutAdmin(admin.ModelAdmin): - list_display = ('id', 'pot', 'recipient', 'amount', 'paid_at') - search_fields = ('pot__id', 'recipient__id') - list_filter = ('paid_at',) + list_display = ("id", "pot", "recipient", "amount", "paid_at") + search_fields = ("pot__id", "recipient__id") + list_filter = ("paid_at",) + @admin.register(PotPayoutChallenge) class PotPayoutChallengeAdmin(admin.ModelAdmin): - list_display = ('id', 'challenger', 'pot', 'created_at') - search_fields = ('challenger__id', 'pot__id') - list_filter = ('created_at',) + list_display = ("id", "challenger", "pot", "created_at") + search_fields = ("challenger__id", "pot__id") + list_filter = ("created_at",) + @admin.register(PotPayoutChallengeAdminResponse) class PotPayoutChallengeAdminResponseAdmin(admin.ModelAdmin): - list_display = ('id', 'pot', 'admin', 'created_at', 'resolved') - search_fields = ('admin__id', 'challenge__id') - list_filter = ('created_at', 'resolved') + list_display = ("id", "pot", "admin", "created_at", "resolved") + search_fields = ("admin__id", "challenge__id") + list_filter = ("created_at", "resolved") From 6456274fb49efcc92e689d99cb40a458e8446dcf Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 20 May 2024 13:20:28 -0400 Subject: [PATCH 098/454] update verbose name plurals --- lists/models.py | 2 +- pots/models.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lists/models.py b/lists/models.py index 7732f1e..ad412d5 100644 --- a/lists/models.py +++ b/lists/models.py @@ -102,7 +102,7 @@ class ListUpvote(models.Model): ) class Meta: - verbose_name_plural = "ListUpvotes" + verbose_name_plural = "List Upvotes" unique_together = (("list", "account"),) diff --git a/pots/models.py b/pots/models.py index b216713..a495aea 100644 --- a/pots/models.py +++ b/pots/models.py @@ -57,6 +57,9 @@ class PotFactory(models.Model): help_text=_("Require whitelist."), ) + class Meta: + verbose_name_plural = "Pot Factories" + class Pot(models.Model): id = models.OneToOneField( @@ -333,7 +336,7 @@ class PotApplication(models.Model): ) class Meta: - verbose_name_plural = "PotApplications" + verbose_name_plural = "Pot Applications" unique_together = (("pot", "applicant"),) @@ -384,7 +387,7 @@ class PotApplicationReview(models.Model): ) class Meta: - verbose_name_plural = "PotApplicationReviews" + verbose_name_plural = "Pot Application Reviews" unique_together = (("application", "reviewer", "reviewed_at"),) @@ -479,7 +482,7 @@ class PotPayoutChallenge(models.Model): ) class Meta: - verbose_name_plural = "PayoutChallenges" + verbose_name_plural = "Payout Challenges" unique_together = (("challenger", "pot"),) @@ -537,6 +540,6 @@ class PotPayoutChallengeAdminResponse(models.Model): ) class Meta: - verbose_name_plural = "PotPayoutChallengeResponses" + verbose_name_plural = "Payout Challenge Responses" unique_together = (("challenger", "pot", "created_at"),) From eeeb00e92002d7ccc35d6c5690be286140788e2f Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 20 May 2024 13:21:43 -0400 Subject: [PATCH 099/454] add Pot custom form --- pots/admin.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pots/admin.py b/pots/admin.py index 03cdba9..c934392 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -46,12 +46,32 @@ def get_form(self, request, obj=None, **kwargs): return form +class PotForm(forms.ModelForm): + class Meta: + model = Pot + fields = "__all__" + + def __init__(self, *args, **kwargs): + super(PotForm, self).__init__(*args, **kwargs) + # Ensure self.instance is available before accessing it + if self.instance.pk: + # Set the queryset for the admins field to only include relevant accounts + self.fields["admins"].queryset = self.instance.admins.all() + + @admin.register(Pot) class PotAdmin(admin.ModelAdmin): + form = PotForm list_display = ("id", "pot_factory", "deployer", "deployed_at", "name") search_fields = ("id", "name", "deployer__id") list_filter = ("deployed_at",) + def get_form(self, request, obj=None, **kwargs): + form = super(PotAdmin, self).get_form(request, obj, **kwargs) + if obj: + form.base_fields["admins"].queryset = obj.admins.all() + return form + @admin.register(PotApplication) class PotApplicationAdmin(admin.ModelAdmin): From 898a512dd85ea572621fcec7357140da126670a9 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 20 May 2024 14:59:24 -0400 Subject: [PATCH 100/454] add admin for PotApplicationReview --- pots/admin.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pots/admin.py b/pots/admin.py index c934392..09e40b3 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -6,6 +6,7 @@ from .models import ( Pot, PotApplication, + PotApplicationReview, PotFactory, PotPayout, PotPayoutChallenge, @@ -80,6 +81,21 @@ class PotApplicationAdmin(admin.ModelAdmin): list_filter = ("status", "submitted_at") +@admin.register(PotApplicationReview) +class PotApplicationReviewAdmin(admin.ModelAdmin): + list_display = ( + "id", + "application", + "reviewer", + "notes", + "status", + "reviewed_at", + "tx_hash", + ) + search_fields = ("application__id", "reviewer__id") + list_filter = ("status", "reviewed_at") + + @admin.register(PotPayout) class PotPayoutAdmin(admin.ModelAdmin): list_display = ("id", "pot", "recipient", "amount", "paid_at") From 16e046842de5b3e98774a882c8c74dc5c4fed5e5 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 20 May 2024 15:06:11 -0400 Subject: [PATCH 101/454] comment out worker_shutdown_handler --- indexer_app/tasks.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 75e30da..90056d0 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -68,14 +68,14 @@ def listen_to_near_events(): loop.close() -@worker_shutdown.connect -def worker_shutdown_handler(sig, how, exitcode, **kwargs): - if sig == 15: - logger.info( - "Celery worker shutdown initiated by signal 15 (SIGTERM)." - ) # avoid logging to Sentry - else: - logger.error("Celery worker shutdown due to signal %d.", sig) +# @worker_shutdown.connect +# def worker_shutdown_handler(sig, how, exitcode, **kwargs): +# if sig == 15: +# logger.info( +# "Celery worker shutdown initiated by signal 15 (SIGTERM)." +# ) # avoid logging to Sentry +# else: +# logger.error("Celery worker shutdown due to signal %d.", sig) jobs_logger = logging.getLogger("jobs") From fb4614fb7dc61d12b541940265c493f1389a2981 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 21 May 2024 15:07:23 -0400 Subject: [PATCH 102/454] fix tuple error when creating pot, update start block to 112961757 to reindex --- indexer_app/tasks.py | 2 +- indexer_app/utils.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 90056d0..706ba85 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 112461673 + start_block = 112961757 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index e3fd803..9e05d25 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -98,7 +98,7 @@ async def handle_new_pot( "all_paid_out": False, "protocol_config_provider": data["protocol_config_provider"], } - potObject = await Pot.objects.aupdate_or_create( + pot, created = await Pot.objects.aupdate_or_create( id=receiver, defaults=pot_defaults ) @@ -106,7 +106,7 @@ async def handle_new_pot( if data.get("admins"): for admin_id in data["admins"]: admin, _ = await Account.objects.aget_or_create(id=admin_id) - potObject.admins.aadd(admin) + pot.admins.aadd(admin) defaults = { "signer_id": signerId, From e160c50b26c519afb6646e766254fa741a60b120 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 10:55:55 -0400 Subject: [PATCH 103/454] update start block to 112920268 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 706ba85..9100cb6 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 112961757 + start_block = 112920268 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From cb8ed9be23bf76eead20f80cf30511846a033b14 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 11:23:09 -0400 Subject: [PATCH 104/454] update start block to 113030298 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 9100cb6..ac41d84 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 112920268 + start_block = 113030298 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From 3e4678bd1fa03889b8e3e778f1ff56c97f1e8adc Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 12:02:17 -0400 Subject: [PATCH 105/454] update start block to 113042335 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index ac41d84..654cd9f 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 113030298 + start_block = 113042335 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From fcd90b8554c90f38c7b4f6a7a2f995b17c7f0b43 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 12:57:48 -0400 Subject: [PATCH 106/454] update start block to 113058915 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 654cd9f..1c2f7d2 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 113042335 + start_block = 113058915 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From 12e0db412b125d715edca5a7f62346ec164d6228 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 13:02:06 -0400 Subject: [PATCH 107/454] update start block to 113066329 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 1c2f7d2..b99358a 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 113058915 + start_block = 113066329 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From 6307a8905c8188c774d520752cfe92e80afafc71 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 13:11:24 -0400 Subject: [PATCH 108/454] update start block to 113155075 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index b99358a..0af92e7 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 113066329 + start_block = 113155075 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From 19e1916b187388cb7b73f919d8e6daf5b7f5188a Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 13:21:48 -0400 Subject: [PATCH 109/454] update start block to 113284717 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 0af92e7..e78ac36 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 113155075 + start_block = 113284717 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From 93db4c074b3e3583a72ec53cb21bee8bf58d3b6c Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 13:25:49 -0400 Subject: [PATCH 110/454] update start block to 113455493 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index e78ac36..f36e89e 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 113284717 + start_block = 113455493 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From ee1827dfc61478c75c6917bb76f715b2017678f7 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 13:33:21 -0400 Subject: [PATCH 111/454] update start block to 113613466 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index f36e89e..4a39ad1 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 113455493 + start_block = 113613466 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From ff92358c35392b1512e993a7d5475382f75ad1ce Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 13:37:45 -0400 Subject: [PATCH 112/454] update start block to 113614770 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 4a39ad1..abdd121 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 113613466 + start_block = 113614770 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From fc024b94ac1c372007a9458b637fb5d46104d34e Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 13:43:42 -0400 Subject: [PATCH 113/454] update start block to 114027103 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index abdd121..626d640 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 113614770 + start_block = 114027103 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From a7f097a888a167791c5b0f3214c935bbfd9d3cce Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 13:58:45 -0400 Subject: [PATCH 114/454] update start block to 114037338 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 626d640..2059b41 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 114027103 + start_block = 114037338 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From c8275976b905a1621074774a09e38287298cd572 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 22 May 2024 14:08:56 -0400 Subject: [PATCH 115/454] update start block to 114056625 --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 2059b41..db6cdbb 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 114037338 + start_block = 114056625 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From ba8c3e9117a4046745dd6196f262936f10fe4231 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 23 May 2024 14:20:30 -0400 Subject: [PATCH 116/454] handle None pot application & update start block --- indexer_app/handler.py | 2 +- indexer_app/tasks.py | 2 +- indexer_app/utils.py | 32 +++++++++++++++++++------------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index c0c67af..9d86fbf 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -147,7 +147,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess created_at, ) break - + # TODO: update to use handle_apply method?? case "assert_can_apply_callback": logger.info( f"application case: {args_dict}, {action}, {receipt}" diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index db6cdbb..99ea51a 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 114056625 + start_block = 112952680 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 9e05d25..7e44c2f 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -385,7 +385,13 @@ async def handle_pot_application_status_change( # Retrieve the PotApplication object appl = await PotApplication.objects.filter( applicant_id=data["project_id"] - ).afirst() # TODO: handle this being None + ).afirst() + + if not appl: + logger.error( + f"PotApplication object not found for project_id: {data['project_id']}" + ) + return # Create the PotApplicationReview object logger.info(f"create review...... {appl}") @@ -411,7 +417,7 @@ async def handle_pot_application_status_change( logger.info("PotApplicationReview and PotApplication updated successfully.") except Exception as e: - logger.warning(f"Failed to change pot application status, Error: {e}") + logger.error(f"Failed to change pot application status, Error: {e}") async def handle_default_list_status_change( @@ -442,7 +448,7 @@ async def handle_default_list_status_change( logger.info("List updated successfully.") except Exception as e: - logger.warning(f"Failed to change list status, Error: {e}") + logger.error(f"Failed to change list status, Error: {e}") async def handle_list_upvote( @@ -478,7 +484,7 @@ async def handle_list_upvote( f"Upvote and activity records created successfully. {activity_created}" ) except Exception as e: - logger.warning(f"Failed to upvote list, Error: {e}") + logger.error(f"Failed to upvote list, Error: {e}") async def handle_set_payouts(data: dict, receiverId: str, receipt: Receipt): @@ -500,7 +506,7 @@ async def handle_set_payouts(data: dict, receiverId: str, receipt: Receipt): await PotPayout.objects.abulk_create(insertion_data, ignore_conflicts=True) except Exception as e: - logger.warning(f"Failed to set payouts, Error: {e}") + logger.error(f"Failed to set payouts, Error: {e}") async def handle_transfer_payout( @@ -520,7 +526,7 @@ async def handle_transfer_payout( **payout ) except Exception as e: - logger.warning(f"Failed to create payout data, Error: {e}") + logger.error(f"Failed to create payout data, Error: {e}") async def handle_payout_challenge( @@ -549,7 +555,7 @@ async def handle_payout_challenge( action_result=payoutChallenge, type="Challenge_Payout", defaults=defaults ) except Exception as e: - logger.warning(f"Failed to create payoutchallenge, Error: {e}") + logger.error(f"Failed to create payoutchallenge, Error: {e}") async def handle_payout_challenge_response( @@ -593,7 +599,7 @@ async def handle_list_admin_removal(data, receiverId, signerId, receiptId): type="Remove_List_Admin", defaults=activity ) except Exception as e: - logger.warning(f"Failed to remove list admin, Error: {e}") + logger.error(f"Failed to remove list admin, Error: {e}") # TODO: Need to abstract some actions. @@ -685,7 +691,7 @@ async def handle_new_donations( id=(donation_data.get("ft_id") or "near") ) except Exception as e: - logger.warning(f"Failed to create/get an account involved in donation: {e}") + logger.error(f"Failed to create/get an account involved in donation: {e}") # # Upsert token # try: @@ -704,10 +710,10 @@ async def handle_new_donations( # response = requests.get(endpoint) # logger.info(f"response: {response}") # if response.status_code == 429: - # logger.warning("Coingecko rate limit exceeded") + # logger.error("Coingecko rate limit exceeded") # price_data = response.json() # except Exception as e: - # logger.warning(f"Failed to fetch price data: {e}") + # logger.error(f"Failed to fetch price data: {e}") # logger.info(f"price data: {price_data}") # unit_price = price_data.get("market_data", {}).get("current_price", {}).get("usd") # logger.info(f"unit price: {unit_price}") @@ -719,7 +725,7 @@ async def handle_new_donations( # timestamp=donated_at, # ) # except Exception as e: - # logger.warning( + # logger.error( # f"Error creating TokenHistoricalPrice: {e} token: {token} unit_price: {unit_price}" # ) # # historical_price = await token.get_most_recent_price() # to use model methods, we might have to use asgiref sync_to_async @@ -806,7 +812,7 @@ async def handle_new_donations( except Exception as e: logger.info(f"Failed to create Activity: {e}") except Exception as e: - logger.warning(f"Failed to create/update donation: {e}") + logger.error(f"Failed to create/update donation: {e}") ### COMMENTING OUT FOR NOW SINCE WE HAVE PERIODIC JOB RUNNING TO UPDATE ACCOUNT STATS (NB: DOESN'T CURRENTLY COVER POT STATS) ### CAN ALWAYS ADD BACK IF DESIRED From 48b20ec87910ec5b1e0499cc05edd2a79b7f91c5 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 23 May 2024 16:41:38 -0400 Subject: [PATCH 117/454] use coingecko pro api --- base/settings.py | 7 ++++++- donations/models.py | 2 ++ indexer_app/tasks.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/base/settings.py b/base/settings.py index 698936e..ce99c04 100644 --- a/base/settings.py +++ b/base/settings.py @@ -37,6 +37,7 @@ AWS_SECRET_ACCESS_KEY = os.environ.get("PL_AWS_SECRET_ACCESS_KEY") # CACHALOT_ENABLED = strtobool(os.environ.get("PL_CACHALOT_ENABLED", "False")) # CACHALOT_TIMEOUT = os.environ.get("PL_CACHALOT_TIMEOUT") +COINGECKO_API_KEY = os.environ.get("PL_COINGECKO_API_KEY") DEBUG = strtobool(os.environ.get("PL_DEBUG", "False")) ENVIRONMENT = os.environ.get("PL_ENVIRONMENT", "local") LOG_LEVEL = os.getenv("PL_LOG_LEVEL", "INFO").upper() @@ -53,7 +54,11 @@ BLOCK_SAVE_HEIGHT = os.environ.get("BLOCK_SAVE_HEIGHT") -COINGECKO_URL = "https://api.coingecko.com/api/v3" +COINGECKO_URL = ( + "https://pro-api.coingecko.com/api/v3" + if COINGECKO_API_KEY + else "https://api.coingecko.com/api/v3" +) # Number of hours around a given timestamp for querying historical prices HISTORICAL_PRICE_QUERY_HOURS = 24 diff --git a/donations/models.py b/donations/models.py index bc671cc..b101250 100644 --- a/donations/models.py +++ b/donations/models.py @@ -241,6 +241,8 @@ def fetch_usd_prices(self): "No existing price within acceptable time period; fetching historical price..." ) endpoint = f"{settings.COINGECKO_URL}/coins/{self.ft.id}/history?date={format_date(self.donated_at)}&localization=false" + if settings.COINGECKO_API_KEY: + endpoint += f"&x_cg_pro_api_key={settings.COINGECKO_API_KEY}" logger.info(f"coingecko endpoint: {endpoint}") response = requests.get(endpoint) logger.info(f"coingecko response: {response}") diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 99ea51a..1ae32c2 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -59,7 +59,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height # start_block = get_block_height("current_block_height") - start_block = 112952680 + start_block = 112959664 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer("mainnet", start_block - 1, None)) except WorkerLostError: From c7274aa2063285afaf2ea68822fd0927ca7f504a Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 29 May 2024 16:20:50 -0400 Subject: [PATCH 118/454] choose LakeConfig network based on PL_ENVIRONMENT --- indexer_app/tasks.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 1ae32c2..4d3f179 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -19,15 +19,23 @@ from .utils import cache_block_height, get_block_height -async def indexer(network: str, from_block: int, to_block: int): +async def indexer(from_block: int, to_block: int): """ Runs the lake indexer framework """ # Initialize lake indexer logger.info(f"from block: {from_block}") - lake_config = LakeConfig.mainnet() if network == "mainnet" else LakeConfig.testnet() + lake_config = ( + LakeConfig.testnet() + if settings.ENVIRONMENT == "testnet" + else LakeConfig.mainnet() + ) lake_config.start_block_height = ( - from_block if from_block else logger.info("Starting to index from latest block") + from_block + if from_block + else logger.info( + "Starting to index from latest block" + ) # TODO: wtf is this shitty code ) lake_config.aws_access_key_id = settings.AWS_ACCESS_KEY_ID lake_config.aws_secret_key = settings.AWS_SECRET_ACCESS_KEY @@ -58,10 +66,10 @@ def listen_to_near_events(): try: # Update below with desired network & block height - # start_block = get_block_height("current_block_height") - start_block = 112959664 + start_block = get_block_height("current_block_height") + # start_block = 112959664 logger.info(f"what's the start block, pray tell? {start_block-1}") - loop.run_until_complete(indexer("mainnet", start_block - 1, None)) + loop.run_until_complete(indexer(start_block - 1, None)) except WorkerLostError: pass # don't log to Sentry finally: From 8ed907000efe7a29d8722e42d20e00cfd8f7eb2a Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 29 May 2024 16:51:03 -0400 Subject: [PATCH 119/454] add deploy-testnet.yml --- .github/workflows/deploy-dev.yml | 39 ++++++++++ .github/workflows/deploy-testnet.yml | 39 ++++++++++ .github/workflows/deploy.yml | 106 --------------------------- 3 files changed, 78 insertions(+), 106 deletions(-) create mode 100644 .github/workflows/deploy-dev.yml create mode 100644 .github/workflows/deploy-testnet.yml delete mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 0000000..713d950 --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,39 @@ +name: Dev deploy to EC2 on Push + +on: + push: + branches: [dev] + +env: + AWS_REGION: "us-east-1" + +# Permission can be added at job level or workflow level +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout +jobs: + AssumeRoleAndCallIdentity: + runs-on: ubuntu-latest + steps: + - name: Git clone the repository + uses: actions/checkout@v3 + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v1.7.0 + with: + role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction + role-session-name: GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ env.AWS_REGION }} + # Hello from AWS: WhoAmI + - name: Sts GetCallerIdentity + run: | + aws sts get-caller-identity + + # Step 3 - check the application-name and deployment group name + - name: Create CodeDeploy Deployment + id: deploy + run: | + aws deploy create-deployment \ + --application-name django-indexer \ + --deployment-group-name django-indexer-dev \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --github-location repository=${{ github.repository }},commitId=${{ github.sha }} diff --git a/.github/workflows/deploy-testnet.yml b/.github/workflows/deploy-testnet.yml new file mode 100644 index 0000000..6f33e05 --- /dev/null +++ b/.github/workflows/deploy-testnet.yml @@ -0,0 +1,39 @@ +name: Testnet deploy to EC2 on Push + +on: + push: + branches: [testnet] + +env: + AWS_REGION: "us-east-1" + +# Permission can be added at job level or workflow level +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout +jobs: + AssumeRoleAndCallIdentity: + runs-on: ubuntu-latest + steps: + - name: Git clone the repository + uses: actions/checkout@v3 + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v1.7.0 + with: + role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction + role-session-name: GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ env.AWS_REGION }} + # Hello from AWS: WhoAmI + - name: Sts GetCallerIdentity + run: | + aws sts get-caller-identity + + # Step 3 - check the application-name and deployment group name + - name: Create CodeDeploy Deployment + id: deploy + run: | + aws deploy create-deployment \ + --application-name django-indexer-testnet \ + --deployment-group-name django-indexer-testnet \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --github-location repository=${{ github.repository }},commitId=${{ github.sha }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 554fb7f..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,106 +0,0 @@ -name: Deploy to EC2 on Push - -on: - push: - branches: [dev] - -env: - AWS_REGION: "us-east-1" - -# Permission can be added at job level or workflow level -permissions: - id-token: write # This is required for requesting the JWT - contents: read # This is required for actions/checkout -jobs: - AssumeRoleAndCallIdentity: - runs-on: ubuntu-latest - steps: - - name: Git clone the repository - uses: actions/checkout@v3 - - name: configure aws credentials - uses: aws-actions/configure-aws-credentials@v1.7.0 - with: - role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction - role-session-name: GitHub_to_AWS_via_FederatedOIDC - aws-region: ${{ env.AWS_REGION }} - # Hello from AWS: WhoAmI - - name: Sts GetCallerIdentity - run: | - aws sts get-caller-identity - - # Step 3 - check the application-name and deployment group name - - name: Create CodeDeploy Deployment - id: deploy - run: | - aws deploy create-deployment \ - --application-name django-indexer \ - --deployment-group-name django-indexer-dev \ - --deployment-config-name CodeDeployDefault.AllAtOnce \ - --github-location repository=${{ github.repository }},commitId=${{ github.sha }} - -# name: Deploy to EC2 on Push - -# on: -# push: -# branches: -# - main -# - dev - -# jobs: -# deploy: -# runs-on: ubuntu-latest -# environment: -# name: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }} -# steps: -# - name: Checkout code -# uses: actions/checkout@v2 - -# - name: Set up SSH key -# uses: webfactory/ssh-agent@v0.5.3 -# with: -# ssh-private-key: ${{ secrets.EC2_SSH_PRIVATE_KEY }} - -# - name: Push new code to EC2 -# run: | -# rsync -avz --exclude '.git*' --exclude 'node_modules' ./ ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }}:/home/ec2-user/django-indexer -# echo "Code has been pushed to the EC2 instance." - -# - name: Check for pending migrations -# id: check_migrations -# run: | -# echo "Checking for pending migrations..." -# pending_migrations=$(ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py showmigrations --plan | grep '\[ \]'") -# echo "::set-output name=pending::${pending_migrations}" -# if [ -z "$pending_migrations" ]; then -# echo "No migrations found." -# else: -# echo "Migrations found, stopping services." - -# - name: Stop services if migrations are pending -# if: steps.check_migrations.outputs.pending -# run: | -# echo "Stopping services..." -# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl stop gunicorn.service indexer.service" - -# - name: Run migrations -# if: steps.check_migrations.outputs.pending -# run: | -# echo "Running migrations..." -# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py migrate" - -# - name: Run collectstatic -# run: | -# echo "Running collectstatic..." -# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py collectstatic --noinput" - -# - name: Restart services if migrations were run -# if: steps.check_migrations.outputs.pending -# run: | -# echo "Restarting services after migrations..." -# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service indexer.service" - -# - name: Restart services if no migrations -# if: steps.check_migrations.outputs.pending == '' -# run: | -# echo "Restarting services without migration..." -# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service" From b1349bcf3362986912748cea60ddb62187bb396e Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 30 May 2024 11:29:55 -0400 Subject: [PATCH 120/454] add testnet deployment workflow & appspec --- .github/workflows/deploy-testnet.yml | 7 +- appspec-testnet.yml | 16 ++++ scripts/after_install.sh | 64 +------------ scripts/after_install_dev.sh | 130 +++++++++++++++++++++++++++ scripts/after_install_testnet.sh | 69 ++++++++++++++ 5 files changed, 220 insertions(+), 66 deletions(-) create mode 100644 appspec-testnet.yml mode change 100755 => 100644 scripts/after_install.sh create mode 100755 scripts/after_install_dev.sh create mode 100755 scripts/after_install_testnet.sh diff --git a/.github/workflows/deploy-testnet.yml b/.github/workflows/deploy-testnet.yml index 6f33e05..8e9b471 100644 --- a/.github/workflows/deploy-testnet.yml +++ b/.github/workflows/deploy-testnet.yml @@ -23,17 +23,16 @@ jobs: role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction role-session-name: GitHub_to_AWS_via_FederatedOIDC aws-region: ${{ env.AWS_REGION }} - # Hello from AWS: WhoAmI - name: Sts GetCallerIdentity run: | aws sts get-caller-identity - - # Step 3 - check the application-name and deployment group name + - name: Generate appspec.yml for testnet + run: cp appspec-testnet.yml appspec.yml - name: Create CodeDeploy Deployment id: deploy run: | aws deploy create-deployment \ --application-name django-indexer-testnet \ - --deployment-group-name django-indexer-testnet \ + --deployment-group-name django-indexer-testnet-group \ --deployment-config-name CodeDeployDefault.AllAtOnce \ --github-location repository=${{ github.repository }},commitId=${{ github.sha }} diff --git a/appspec-testnet.yml b/appspec-testnet.yml new file mode 100644 index 0000000..b006f0e --- /dev/null +++ b/appspec-testnet.yml @@ -0,0 +1,16 @@ +version: 0.0 +os: linux +files: + - source: / + destination: /home/ec2-user/django-indexer-testnet +hooks: + # # Install: + AfterInstall: + - location: scripts/after_install_testnet.sh + timeout: 300 + runas: ec2-user +# ApplicationStart: +# - location: scripts/application_start.sh +# timeout: 300 +# runas: root +# # ValidateService: diff --git a/scripts/after_install.sh b/scripts/after_install.sh old mode 100755 new mode 100644 index 6c56c5b..152ad3a --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -1,65 +1,5 @@ -# #!/bin/bash -# # Log output to a specific file -# LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" - -# echo -e "\n\n" >> "$LOG_FILE" -# echo "=========================================" >> "$LOG_FILE" -# echo "Running after_install.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" -# echo "=========================================" >> "$LOG_FILE" - -# # Load env vars -# source /home/ec2-user/.bashrc - -# # Set correct ownership recursively for project directory -# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" - -# # Set the necessary permissions -# sudo chmod -R 775 /home/ec2-user/django-indexer/ -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" - -# # Restart nginx to apply any configuration changes -# sudo systemctl restart nginx -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" - -# # Define the project directory -# PROJECT_DIR="/home/ec2-user/django-indexer" - -# # Navigate to the project directory -# cd "$PROJECT_DIR" - -# # Install dependencies using Poetry -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" -# poetry install >> "$LOG_FILE" -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" - -# # Log the full output of showmigrations to diagnose -# echo "Checking for pending migrations..." >> "$LOG_FILE" -# poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 - -# # Check for unapplied migrations -# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations - -# if [ "$PENDING_MIGRATIONS" -gt 0 ]; then -# echo "Migrations found; stopping services..." >> "$LOG_FILE" -# sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat - -# echo 'Applying migrations...' >> "$LOG_FILE" -# poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 - -# echo 'Starting services...' >> "$LOG_FILE" -# sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat -# else -# echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" -# poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 -# sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat -# fi - -# echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" - - - #!/bin/bash +# TODO: deprecate this (move to _dev & _testnet files) # Log output to a specific file LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" @@ -124,4 +64,4 @@ else sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat fi -echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" +echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" \ No newline at end of file diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh new file mode 100755 index 0000000..6cfa006 --- /dev/null +++ b/scripts/after_install_dev.sh @@ -0,0 +1,130 @@ +# #!/bin/bash +# # Log output to a specific file +# LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" + +# echo -e "\n\n" >> "$LOG_FILE" +# echo "=========================================" >> "$LOG_FILE" +# echo "Running after_install.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" +# echo "=========================================" >> "$LOG_FILE" + +# # Load env vars +# source /home/ec2-user/.bashrc + +# # Set correct ownership recursively for project directory +# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" + +# # Set the necessary permissions +# sudo chmod -R 775 /home/ec2-user/django-indexer/ +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" + +# # Restart nginx to apply any configuration changes +# sudo systemctl restart nginx +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" + +# # Define the project directory +# PROJECT_DIR="/home/ec2-user/django-indexer" + +# # Navigate to the project directory +# cd "$PROJECT_DIR" + +# # Install dependencies using Poetry +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +# poetry install >> "$LOG_FILE" +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" + +# # Log the full output of showmigrations to diagnose +# echo "Checking for pending migrations..." >> "$LOG_FILE" +# poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 + +# # Check for unapplied migrations +# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations + +# if [ "$PENDING_MIGRATIONS" -gt 0 ]; then +# echo "Migrations found; stopping services..." >> "$LOG_FILE" +# sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat + +# echo 'Applying migrations...' >> "$LOG_FILE" +# poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 + +# echo 'Starting services...' >> "$LOG_FILE" +# sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat +# else +# echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" +# poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 +# sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat +# fi + +# echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" + + + +#!/bin/bash +# Log output to a specific file +LOG_FILE="/home/ec2-user/django-indexer-dev/logs/deploy.log" + +# print placeholder +echo -e "\n THIS IS A PLACEHOLDER \n" >> "$LOG_FILE" + +# echo -e "\n\n" >> "$LOG_FILE" +# echo "=========================================" >> "$LOG_FILE" +# echo "Running after_install_dev.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" +# echo "=========================================" >> "$LOG_FILE" + +# # Load env vars +# source /home/ec2-user/.bashrc + +# # Set correct ownership recursively for project directory +# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" + +# # Set the necessary permissions +# sudo chmod -R 775 /home/ec2-user/django-indexer/ +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" + +# # Restart nginx to apply any configuration changes +# sudo systemctl restart nginx +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" + +# # Define the project directory +# PROJECT_DIR="/home/ec2-user/django-indexer" + +# # Navigate to the project directory +# cd "$PROJECT_DIR" + +# # Source the specific poetry virtual environment +# source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" # TODO: UPDATE THIS + +# # Install dependencies using Poetry +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +# poetry install >> "$LOG_FILE" +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" + +# # Check if there are pending migrations and log the output +# echo "Checking for pending migrations..." >> "$LOG_FILE" +# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout +# echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" + +# # Log the full output of showmigrations +# echo "Checking for pending migrations..." >> "$LOG_FILE" +# poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose + +# # Check for unapplied migrations +# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations + +# if [ "$PENDING_MIGRATIONS" -gt 0 ]; then +# echo "Migrations found; stopping services..." >> "$LOG_FILE" +# sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat + +# echo 'Applying migrations...' >> "$LOG_FILE" +# poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 + +# echo 'Starting services...' >> "$LOG_FILE" +# sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat +# else +# echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" +# poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 +# sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat +# fi + +echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_dev.sh completed" >> "$LOG_FILE" diff --git a/scripts/after_install_testnet.sh b/scripts/after_install_testnet.sh new file mode 100755 index 0000000..46f8640 --- /dev/null +++ b/scripts/after_install_testnet.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# Log output to a specific file +LOG_FILE="/home/ec2-user/django-indexer-testnet/logs/deploy.log" + +# print placeholder +echo -e "\n THIS IS A PLACEHOLDER \n" >> "$LOG_FILE" + +# echo -e "\n\n" >> "$LOG_FILE" +# echo "=========================================" >> "$LOG_FILE" +# echo "Running after_install_testnet.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" +# echo "=========================================" >> "$LOG_FILE" + +# # Load env vars +# source /home/ec2-user/.bashrc + +# # Set correct ownership recursively for project directory +# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer-testnet/ +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" + +# # Set the necessary permissions +# sudo chmod -R 775 /home/ec2-user/django-indexer-testnet/ +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" + +# # Restart nginx to apply any configuration changes +# sudo systemctl restart nginx +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" + +# # Define the project directory +# PROJECT_DIR="/home/ec2-user/django-indexer-testnet" + +# # Navigate to the project directory +# cd "$PROJECT_DIR" + +# # Source the specific poetry virtual environment +# source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" # TODO: UPDATE THIS + +# # Install dependencies using Poetry +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +# poetry install >> "$LOG_FILE" +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" + +# # Check if there are pending migrations and log the output +# echo "Checking for pending migrations..." >> "$LOG_FILE" +# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout +# echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" + +# # Log the full output of showmigrations +# echo "Checking for pending migrations..." >> "$LOG_FILE" +# poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose + +# # Check for unapplied migrations +# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations + +# if [ "$PENDING_MIGRATIONS" -gt 0 ]; then +# echo "Migrations found; stopping services..." >> "$LOG_FILE" +# sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat + +# echo 'Applying migrations...' >> "$LOG_FILE" +# poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 + +# echo 'Starting services...' >> "$LOG_FILE" +# sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat +# else +# echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" +# poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 +# sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat +# fi + +echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_testnet.sh completed" >> "$LOG_FILE" From c38519466d99b6ecad4b97ff507cbce7c4eebfcb Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 30 May 2024 11:46:00 -0400 Subject: [PATCH 121/454] testnet workflow debugging --- .github/workflows/deploy-testnet.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/deploy-testnet.yml b/.github/workflows/deploy-testnet.yml index 8e9b471..69e77e9 100644 --- a/.github/workflows/deploy-testnet.yml +++ b/.github/workflows/deploy-testnet.yml @@ -28,6 +28,8 @@ jobs: aws sts get-caller-identity - name: Generate appspec.yml for testnet run: cp appspec-testnet.yml appspec.yml + - name: Verify appspec.yml content + run: cat appspec.yml - name: Create CodeDeploy Deployment id: deploy run: | From 6b163b0880868cd95b1ba85049231475cb89fb81 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 30 May 2024 11:58:28 -0400 Subject: [PATCH 122/454] upload appspec as artifact --- .github/workflows/deploy-testnet.yml | 65 +++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-testnet.yml b/.github/workflows/deploy-testnet.yml index 69e77e9..7872151 100644 --- a/.github/workflows/deploy-testnet.yml +++ b/.github/workflows/deploy-testnet.yml @@ -12,24 +12,47 @@ permissions: id-token: write # This is required for requesting the JWT contents: read # This is required for actions/checkout jobs: - AssumeRoleAndCallIdentity: + GenerateAndDeploy: runs-on: ubuntu-latest steps: - name: Git clone the repository uses: actions/checkout@v3 - - name: configure aws credentials + + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1.7.0 with: role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction role-session-name: GitHub_to_AWS_via_FederatedOIDC aws-region: ${{ env.AWS_REGION }} - - name: Sts GetCallerIdentity - run: | - aws sts get-caller-identity + - name: Generate appspec.yml for testnet run: cp appspec-testnet.yml appspec.yml - - name: Verify appspec.yml content - run: cat appspec.yml + + - name: Upload appspec.yml as artifact + uses: actions/upload-artifact@v3 + with: + name: appspec + path: appspec.yml + + DeployToCodeDeploy: + needs: GenerateAndDeploy + runs-on: ubuntu-latest + steps: + - name: Git clone the repository + uses: actions/checkout@v3 + + - name: Download appspec.yml artifact + uses: actions/download-artifact@v3 + with: + name: appspec + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1.7.0 + with: + role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction + role-session-name: GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ env.AWS_REGION }} + - name: Create CodeDeploy Deployment id: deploy run: | @@ -38,3 +61,31 @@ jobs: --deployment-group-name django-indexer-testnet-group \ --deployment-config-name CodeDeployDefault.AllAtOnce \ --github-location repository=${{ github.repository }},commitId=${{ github.sha }} + +# jobs: +# AssumeRoleAndCallIdentity: +# runs-on: ubuntu-latest +# steps: +# - name: Git clone the repository +# uses: actions/checkout@v3 +# - name: configure aws credentials +# uses: aws-actions/configure-aws-credentials@v1.7.0 +# with: +# role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction +# role-session-name: GitHub_to_AWS_via_FederatedOIDC +# aws-region: ${{ env.AWS_REGION }} +# - name: Sts GetCallerIdentity +# run: | +# aws sts get-caller-identity +# - name: Generate appspec.yml for testnet +# run: cp appspec-testnet.yml appspec.yml +# - name: Verify appspec.yml content +# run: cat appspec.yml +# - name: Create CodeDeploy Deployment +# id: deploy +# run: | +# aws deploy create-deployment \ +# --application-name django-indexer-testnet \ +# --deployment-group-name django-indexer-testnet-group \ +# --deployment-config-name CodeDeployDefault.AllAtOnce \ +# --github-location repository=${{ github.repository }},commitId=${{ github.sha }} From 1577af55eedd38b605e995ecb88657fe67d39485 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 30 May 2024 12:13:52 -0400 Subject: [PATCH 123/454] upload zipped codebase to s3 in workflow --- .github/workflows/deploy-testnet.yml | 31 ++++++---------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/.github/workflows/deploy-testnet.yml b/.github/workflows/deploy-testnet.yml index 7872151..b88088a 100644 --- a/.github/workflows/deploy-testnet.yml +++ b/.github/workflows/deploy-testnet.yml @@ -12,7 +12,7 @@ permissions: id-token: write # This is required for requesting the JWT contents: read # This is required for actions/checkout jobs: - GenerateAndDeploy: + DeployToCodeDeploy: runs-on: ubuntu-latest steps: - name: Git clone the repository @@ -28,30 +28,11 @@ jobs: - name: Generate appspec.yml for testnet run: cp appspec-testnet.yml appspec.yml - - name: Upload appspec.yml as artifact - uses: actions/upload-artifact@v3 - with: - name: appspec - path: appspec.yml - - DeployToCodeDeploy: - needs: GenerateAndDeploy - runs-on: ubuntu-latest - steps: - - name: Git clone the repository - uses: actions/checkout@v3 - - - name: Download appspec.yml artifact - uses: actions/download-artifact@v3 - with: - name: appspec + - name: Create zip of repository + run: zip -r django-indexer-testnet.zip . - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1.7.0 - with: - role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction - role-session-name: GitHub_to_AWS_via_FederatedOIDC - aws-region: ${{ env.AWS_REGION }} + - name: Upload repository to S3 + run: aws s3 cp django-indexer-testnet.zip s3://django-indexer-testnet/ - name: Create CodeDeploy Deployment id: deploy @@ -60,7 +41,7 @@ jobs: --application-name django-indexer-testnet \ --deployment-group-name django-indexer-testnet-group \ --deployment-config-name CodeDeployDefault.AllAtOnce \ - --github-location repository=${{ github.repository }},commitId=${{ github.sha }} + --s3-location bucket=django-indexer-testnet,bundleType=zip,key=django-indexer-testnet.zip # jobs: # AssumeRoleAndCallIdentity: From 643402d7a0ed9a0dfe4243ecbc538a1ea422fa05 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 09:49:43 -0400 Subject: [PATCH 124/454] continue working on after_install_testnet --- scripts/after_install_testnet.sh | 93 ++++++++++++++++---------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/scripts/after_install_testnet.sh b/scripts/after_install_testnet.sh index 46f8640..439cd2e 100755 --- a/scripts/after_install_testnet.sh +++ b/scripts/after_install_testnet.sh @@ -2,68 +2,71 @@ # Log output to a specific file LOG_FILE="/home/ec2-user/django-indexer-testnet/logs/deploy.log" -# print placeholder -echo -e "\n THIS IS A PLACEHOLDER \n" >> "$LOG_FILE" +# # print placeholder +# echo -e "\n THIS IS A PLACEHOLDER \n" >> "$LOG_FILE" -# echo -e "\n\n" >> "$LOG_FILE" -# echo "=========================================" >> "$LOG_FILE" -# echo "Running after_install_testnet.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" -# echo "=========================================" >> "$LOG_FILE" +echo -e "\n\n" >> "$LOG_FILE" +echo "=========================================" >> "$LOG_FILE" +echo "Running after_install_testnet.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" +echo "=========================================" >> "$LOG_FILE" -# # Load env vars -# source /home/ec2-user/.bashrc +# Load env vars +export PL_ENVIRONMENT=testnet +source /home/ec2-user/.bashrc -# # Set correct ownership recursively for project directory -# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer-testnet/ -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" +# Set correct ownership recursively for project directory +sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer-testnet/ +echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" -# # Set the necessary permissions -# sudo chmod -R 775 /home/ec2-user/django-indexer-testnet/ -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" +# Set the necessary permissions +sudo chmod -R 775 /home/ec2-user/django-indexer-testnet/ +echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" + +# TODO: ADD BELOW BACK IN # # Restart nginx to apply any configuration changes # sudo systemctl restart nginx # echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" -# # Define the project directory -# PROJECT_DIR="/home/ec2-user/django-indexer-testnet" +# Define the project directory +PROJECT_DIR="/home/ec2-user/django-indexer-testnet" -# # Navigate to the project directory -# cd "$PROJECT_DIR" +# Navigate to the project directory +cd "$PROJECT_DIR" -# # Source the specific poetry virtual environment -# source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" # TODO: UPDATE THIS +# Source the specific poetry virtual environment +source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-AhfQkQzj-py3.11/bin/activate" -# # Install dependencies using Poetry -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" -# poetry install >> "$LOG_FILE" -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" +# Install dependencies using Poetry +echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +poetry install >> "$LOG_FILE" +echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" -# # Check if there are pending migrations and log the output -# echo "Checking for pending migrations..." >> "$LOG_FILE" -# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout -# echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" +# Check if there are pending migrations and log the output +echo "Checking for pending migrations..." >> "$LOG_FILE" +PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout +echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" -# # Log the full output of showmigrations -# echo "Checking for pending migrations..." >> "$LOG_FILE" -# poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose +# Log the full output of showmigrations +echo "Checking for pending migrations..." >> "$LOG_FILE" +poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose -# # Check for unapplied migrations -# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations +# Check for unapplied migrations +PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations -# if [ "$PENDING_MIGRATIONS" -gt 0 ]; then -# echo "Migrations found; stopping services..." >> "$LOG_FILE" -# sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat +if [ "$PENDING_MIGRATIONS" -gt 0 ]; then + echo "Migrations found; stopping services..." >> "$LOG_FILE" + sudo systemctl stop gunicorn-testnet celery-indexer-worker-testnet celery-beat-worker-testnet celery-beat-testnet -# echo 'Applying migrations...' >> "$LOG_FILE" -# poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 + echo 'Applying migrations...' >> "$LOG_FILE" + poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 -# echo 'Starting services...' >> "$LOG_FILE" -# sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat -# else -# echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" -# poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 -# sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat -# fi + echo 'Starting services...' >> "$LOG_FILE" + sudo systemctl start gunicorn-testnet celery-indexer-worker-testnet celery-beat-worker-testnet celery-beat-testnet +else + echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" + poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 + sudo systemctl restart gunicorn-testnet celery-indexer-worker-testnet celery-beat-worker-testnet celery-beat-testnet +fi echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_testnet.sh completed" >> "$LOG_FILE" From aec742dd462ef18245d3ee9bd659f34300694963 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 10:24:32 -0400 Subject: [PATCH 125/454] separate watchtower log groups by django env --- base/settings.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index ce99c04..c69239b 100644 --- a/base/settings.py +++ b/base/settings.py @@ -213,6 +213,9 @@ log_level = getattr(logging, LOG_LEVEL, logging.INFO) # print("LOG_LEVEL: ", LOG_LEVEL) +# Set log group name based on environment +log_group_name = f"django-indexer-{ENVIRONMENT}" + # Setting up the logging configuration LOGGING = { "version": 1, @@ -254,7 +257,7 @@ LOGGING["handlers"]["watchtower"] = { "class": "watchtower.CloudWatchLogHandler", "boto3_client": boto3_logs_client, - "log_group_name": "django-indexer", + "log_group_name": log_group_name, "formatter": "standard", "level": log_level, } From 98d90367d9c3981da51ec569796e47aca8be888a Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 14:35:51 -0400 Subject: [PATCH 126/454] add testnet potlock TLA to indexer handler --- indexer_app/handler.py | 17 ++++++++++++----- pots/utils.py | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 9d86fbf..7159db4 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -2,6 +2,7 @@ import json from datetime import datetime +from django.conf import settings from django.core.cache import cache from near_lake_framework import near_primitives @@ -49,8 +50,14 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess for shard in streamer_message.shards: for receipt_execution_outcome in shard.receipt_execution_outcomes: # we only want to proceed if it's a potlock tx and it succeeded.... (unreadable if statement?) + potlock_tla = ( + "potlock.testnet" + if settings.ENVIRONMENT == "testnet" + else "potlock.near" + ) + lists_contract = "lists." + potlock_tla if not receipt_execution_outcome.receipt.receiver_id.endswith( - "potlock.near" + potlock_tla ) or ( "SuccessReceiptId" not in receipt_execution_outcome.execution_outcome.outcome.status @@ -232,7 +239,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess logger.info( f"registrations incoming: {args_dict}, {action}" ) - if receiver_id != "lists.potlock.near": + if receiver_id != lists_contract: break await handle_new_list_registration( args_dict, receiver_id, signer_id, receipt, status_obj @@ -306,7 +313,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess "owner_remove_admins" ): # TODO: use update_admins event instead of method call to handle all cases logger.info(f"attempting to remove admins....: {args_dict}") - if receiver_id != "lists.potlock.near": + if receiver_id != lists_contract: break await handle_list_admin_removal( args_dict, receiver_id, signer_id, receipt.receipt_id @@ -315,14 +322,14 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess case "create_list": logger.info(f"creating list... {args_dict}, {action}") - if receiver_id != "lists.potlock.near": + if receiver_id != lists_contract: break await handle_new_list(signer_id, receiver_id, status_obj) break case "upvote": logger.info(f"up voting... {args_dict}") - if receiver_id != "lists.potlock.near": + if receiver_id != lists_contract: break await handle_list_upvote( args_dict, receiver_id, signer_id, receipt.receipt_id diff --git a/pots/utils.py b/pots/utils.py index 01bfab6..1a5f991 100644 --- a/pots/utils.py +++ b/pots/utils.py @@ -4,12 +4,12 @@ def match_pot_factory_version_pattern(receiver): - """Matches the base pot factory version pattern without a subaccount.""" + """Matches the base pot factory version pattern without a subaccount. NB: does not currently handle testnet factory.""" pattern = f"^{BASE_PATTERN}" return bool(re.match(pattern, receiver)) def match_pot_subaccount_version_pattern(receiver): - """Matches the pot factory version pattern with a subaccount.""" + """Matches the pot factory version pattern with a subaccount. NB: does not currently handle testnet factory.""" pattern = f"^[a-zA-Z0-9_]+\.{BASE_PATTERN}" return bool(re.match(pattern, receiver)) From d505368d0ba79eea4adc2358568968813a525d6f Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 15:16:55 -0400 Subject: [PATCH 127/454] add POTLOCK_TLA to settings --- base/settings.py | 2 ++ indexer_app/handler.py | 9 ++------- indexer_app/utils.py | 3 ++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/base/settings.py b/base/settings.py index c69239b..6552d13 100644 --- a/base/settings.py +++ b/base/settings.py @@ -52,6 +52,8 @@ REDIS_PORT = os.environ.get("PL_REDIS_PORT", 6379) SENTRY_DSN = os.environ.get("PL_SENTRY_DSN") +POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else "potlock.near" + BLOCK_SAVE_HEIGHT = os.environ.get("BLOCK_SAVE_HEIGHT") COINGECKO_URL = ( diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 7159db4..0cdfa99 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -50,14 +50,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess for shard in streamer_message.shards: for receipt_execution_outcome in shard.receipt_execution_outcomes: # we only want to proceed if it's a potlock tx and it succeeded.... (unreadable if statement?) - potlock_tla = ( - "potlock.testnet" - if settings.ENVIRONMENT == "testnet" - else "potlock.near" - ) - lists_contract = "lists." + potlock_tla + lists_contract = "lists." + settings.POTLOCK_TLA if not receipt_execution_outcome.receipt.receiver_id.endswith( - potlock_tla + settings.POTLOCK_TLA ) or ( "SuccessReceiptId" not in receipt_execution_outcome.execution_outcome.outcome.status diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 7e44c2f..5f394a3 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -633,10 +633,11 @@ async def handle_new_donations( log_data: list, ): logger.info(f"new donation data: {data}, {receiverId}") + donate_contract_addr = "donate." + settings.POTLOCK_TLA if ( actionName == "direct" - ) and receiverId == "donate.potlock.near": # early pot donations followed similarly to direct donations i.e they returned result instead of events. + ) and receiverId == donate_contract_addr: # early pot donations followed similarly to direct donations i.e they returned result instead of events. logger.info("calling donate contract...") # Handle direct donation From 9b505cec3a6d104123a7d26327f7cdebeffeff25 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 16:20:47 -0400 Subject: [PATCH 128/454] add dev.potlock.io and test-dev.potlock.io to allowed hosts --- base/settings.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index 6552d13..7525f40 100644 --- a/base/settings.py +++ b/base/settings.py @@ -30,7 +30,12 @@ # TODO: update before prod release SECRET_KEY = "django-insecure-=r_v_es6w6rxv42^#kc2hca6p%=fe_*cog_5!t%19zea!enlju" -ALLOWED_HOSTS = ["ec2-100-27-57-47.compute-1.amazonaws.com", "127.0.0.1"] +ALLOWED_HOSTS = [ + "ec2-100-27-57-47.compute-1.amazonaws.com", + "127.0.0.1", + "dev.potlock.io", + "test-dev.potlock.io", +] # Env vars AWS_ACCESS_KEY_ID = os.environ.get("PL_AWS_ACCESS_KEY_ID") From a2f6a8cc4a1de2d79bd561922b577d84d9f5422f Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 16:40:34 -0400 Subject: [PATCH 129/454] remove admin dashboard edit access --- donations/admin.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/donations/admin.py b/donations/admin.py index 43f87d3..f27994d 100644 --- a/donations/admin.py +++ b/donations/admin.py @@ -59,3 +59,12 @@ def formfield_for_dbfield(self, db_field, request, **kwargs): field.widget.format = "%d-%m-%Y %H:%M" field.widget.attrs.update({"class": "vDateField", "size": "20"}) return field + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False From 4a9c08215516f64cf4c0f316e62ec36fd28ce30b Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 16:45:53 -0400 Subject: [PATCH 130/454] remove admin edit permission for all models --- accounts/admin.py | 47 ++++++++++++++++++++++------- activities/admin.py | 44 +++++++++++++++++++++------- indexer_app/admin.py | 15 ++++++++-- lists/admin.py | 70 ++++++++++++++++++++++++++++++++++++-------- pots/admin.py | 63 +++++++++++++++++++++++++++++++++++++++ tokens/admin.py | 34 +++++++++++++++++---- 6 files changed, 232 insertions(+), 41 deletions(-) diff --git a/accounts/admin.py b/accounts/admin.py index fb44fbe..3b8aca7 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -1,25 +1,52 @@ from django.contrib import admin + from .models import Account + @admin.register(Account) class AccountAdmin(admin.ModelAdmin): - list_display = ('id', 'total_donations_in_usd', 'total_donations_out_usd', 'total_matching_pool_allocations_usd', 'donors_count') - search_fields = ('id',) # Allow searching by account address - list_filter = ('total_donations_in_usd', 'total_donations_out_usd') # Filter by donation amounts - ordering = ('-total_donations_in_usd',) # Default ordering + list_display = ( + "id", + "total_donations_in_usd", + "total_donations_out_usd", + "total_matching_pool_allocations_usd", + "donors_count", + ) + search_fields = ("id",) # Allow searching by account address + list_filter = ( + "total_donations_in_usd", + "total_donations_out_usd", + ) # Filter by donation amounts + ordering = ("-total_donations_in_usd",) # Default ordering # Optionally, format decimal fields for better readability in the admin def total_donations_in_usd_display(self, obj): return "${:,.2f}".format(obj.total_donations_in_usd) - total_donations_in_usd_display.admin_order_field = 'total_donations_in_usd' - total_donations_in_usd_display.short_description = 'Total Donations Received (USD)' + + total_donations_in_usd_display.admin_order_field = "total_donations_in_usd" + total_donations_in_usd_display.short_description = "Total Donations Received (USD)" def total_donations_out_usd_display(self, obj): return "${:,.2f}".format(obj.total_donations_out_usd) - total_donations_out_usd_display.admin_order_field = 'total_donations_out_usd' - total_donations_out_usd_display.short_description = 'Total Donations Sent (USD)' + + total_donations_out_usd_display.admin_order_field = "total_donations_out_usd" + total_donations_out_usd_display.short_description = "Total Donations Sent (USD)" def total_matching_pool_allocations_usd_display(self, obj): return "${:,.2f}".format(obj.total_matching_pool_allocations_usd) - total_matching_pool_allocations_usd_display.admin_order_field = 'total_matching_pool_allocations_usd' - total_matching_pool_allocations_usd_display.short_description = 'Total Matching Pool Allocations (USD)' + + total_matching_pool_allocations_usd_display.admin_order_field = ( + "total_matching_pool_allocations_usd" + ) + total_matching_pool_allocations_usd_display.short_description = ( + "Total Matching Pool Allocations (USD)" + ) + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/activities/admin.py b/activities/admin.py index cab9f72..219e369 100644 --- a/activities/admin.py +++ b/activities/admin.py @@ -1,30 +1,52 @@ from django.contrib import admin from django.utils.html import format_html -from .models import Activity, Account + +from .models import Account, Activity + @admin.register(Activity) class ActivityAdmin(admin.ModelAdmin): - list_display = ('id', 'signer_address', 'receiver_address', 'timestamp', 'type', 'transaction_link', 'action_result') - list_filter = ('timestamp', 'type', 'signer', 'receiver') - search_fields = ('signer__id', 'receiver__id', 'tx_hash') - date_hierarchy = 'timestamp' - ordering = ('-timestamp',) + list_display = ( + "id", + "signer_address", + "receiver_address", + "timestamp", + "type", + "transaction_link", + "action_result", + ) + list_filter = ("timestamp", "type", "signer", "receiver") + search_fields = ("signer__id", "receiver__id", "tx_hash") + date_hierarchy = "timestamp" + ordering = ("-timestamp",) def signer_address(self, obj): return obj.signer.id - signer_address.admin_order_field = 'signer' - signer_address.short_description = 'Signer Address' + + signer_address.admin_order_field = "signer" + signer_address.short_description = "Signer Address" def receiver_address(self, obj): return obj.receiver.id - receiver_address.admin_order_field = 'receiver' - receiver_address.short_description = 'Receiver Address' + + receiver_address.admin_order_field = "receiver" + receiver_address.short_description = "Receiver Address" def transaction_link(self, obj): url = f"https://nearblocks.io?query={obj.tx_hash}" return format_html('{}', url, obj.tx_hash) - transaction_link.short_description = 'Transaction Hash' # Sets the column header + + transaction_link.short_description = "Transaction Hash" # Sets the column header # def action_result_summary(self, obj): # return "Has Result" if obj.action_result else "No Result" # action_result_summary.short_description = 'Action Result' + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/indexer_app/admin.py b/indexer_app/admin.py index 3b56656..e94e67a 100644 --- a/indexer_app/admin.py +++ b/indexer_app/admin.py @@ -1,7 +1,18 @@ from django.contrib import admin + from .models import BlockHeight + @admin.register(BlockHeight) class BlockHeightAdmin(admin.ModelAdmin): - list_display = ('id', 'block_height', 'updated_at') - ordering = ('-updated_at',) + list_display = ("id", "block_height", "updated_at") + ordering = ("-updated_at",) + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/lists/admin.py b/lists/admin.py index ae08c51..db244ed 100644 --- a/lists/admin.py +++ b/lists/admin.py @@ -1,22 +1,68 @@ from django.contrib import admin -from .models import List, ListUpvote, ListRegistration, Account + +from .models import Account, List, ListRegistration, ListUpvote + @admin.register(List) class ListAdmin(admin.ModelAdmin): - list_display = ('id', 'name', 'owner', 'default_registration_status', 'created_at', 'updated_at') - list_filter = ('created_at', 'updated_at', 'default_registration_status') - search_fields = ('name', 'owner__id') - ordering = ('-created_at',) + list_display = ( + "id", + "name", + "owner", + "default_registration_status", + "created_at", + "updated_at", + ) + list_filter = ("created_at", "updated_at", "default_registration_status") + search_fields = ("name", "owner__id") + ordering = ("-created_at",) + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(ListUpvote) class ListUpvoteAdmin(admin.ModelAdmin): - list_display = ('id', 'list', 'account', 'created_at') - list_filter = ('created_at',) - search_fields = ('list__name', 'account__id') + list_display = ("id", "list", "account", "created_at") + list_filter = ("created_at",) + search_fields = ("list__name", "account__id") + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(ListRegistration) class ListRegistrationAdmin(admin.ModelAdmin): - list_display = ('id', 'list', 'registrant', 'registered_by', 'status', 'submitted_at', 'updated_at') - list_filter = ('status', 'submitted_at', 'updated_at') - search_fields = ('list__name', 'registrant__id', 'registered_by__id') - ordering = ('-submitted_at',) + list_display = ( + "id", + "list", + "registrant", + "registered_by", + "status", + "submitted_at", + "updated_at", + ) + list_filter = ("status", "submitted_at", "updated_at") + search_fields = ("list__name", "registrant__id", "registered_by__id") + ordering = ("-submitted_at",) + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/pots/admin.py b/pots/admin.py index 09e40b3..aca414b 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -46,6 +46,15 @@ def get_form(self, request, obj=None, **kwargs): ) return form + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + class PotForm(forms.ModelForm): class Meta: @@ -73,6 +82,15 @@ def get_form(self, request, obj=None, **kwargs): form.base_fields["admins"].queryset = obj.admins.all() return form + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(PotApplication) class PotApplicationAdmin(admin.ModelAdmin): @@ -80,6 +98,15 @@ class PotApplicationAdmin(admin.ModelAdmin): search_fields = ("pot__id", "applicant__id") list_filter = ("status", "submitted_at") + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(PotApplicationReview) class PotApplicationReviewAdmin(admin.ModelAdmin): @@ -95,6 +122,15 @@ class PotApplicationReviewAdmin(admin.ModelAdmin): search_fields = ("application__id", "reviewer__id") list_filter = ("status", "reviewed_at") + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(PotPayout) class PotPayoutAdmin(admin.ModelAdmin): @@ -102,6 +138,15 @@ class PotPayoutAdmin(admin.ModelAdmin): search_fields = ("pot__id", "recipient__id") list_filter = ("paid_at",) + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(PotPayoutChallenge) class PotPayoutChallengeAdmin(admin.ModelAdmin): @@ -109,9 +154,27 @@ class PotPayoutChallengeAdmin(admin.ModelAdmin): search_fields = ("challenger__id", "pot__id") list_filter = ("created_at",) + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(PotPayoutChallengeAdminResponse) class PotPayoutChallengeAdminResponseAdmin(admin.ModelAdmin): list_display = ("id", "pot", "admin", "created_at", "resolved") search_fields = ("admin__id", "challenge__id") list_filter = ("created_at", "resolved") + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/tokens/admin.py b/tokens/admin.py index 0ca45d6..f783399 100644 --- a/tokens/admin.py +++ b/tokens/admin.py @@ -1,18 +1,40 @@ from django.contrib import admin + from .models import Token, TokenHistoricalPrice + @admin.register(Token) class TokenAdmin(admin.ModelAdmin): - list_display = ('id', 'decimals', 'get_most_recent_price') - search_fields = ('id',) + list_display = ("id", "decimals", "get_most_recent_price") + search_fields = ("id",) def get_most_recent_price(self, obj): price = obj.get_most_recent_price() return price.price_usd if price else None - get_most_recent_price.short_description = 'Most Recent Price (USD)' + + get_most_recent_price.short_description = "Most Recent Price (USD)" + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(TokenHistoricalPrice) class TokenHistoricalPriceAdmin(admin.ModelAdmin): - list_display = ('token', 'timestamp', 'price_usd') - search_fields = ('token__id',) - list_filter = ('timestamp',) + list_display = ("token", "timestamp", "price_usd") + search_fields = ("token__id",) + list_filter = ("timestamp",) + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False From 9f32cfddd57261530118ba94272aabf6601d6c5f Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 16:54:30 -0400 Subject: [PATCH 131/454] add timestamp to deployment zip filename --- .github/workflows/deploy-testnet.yml | 59 ++++++++++++---------------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/.github/workflows/deploy-testnet.yml b/.github/workflows/deploy-testnet.yml index b88088a..8b06f83 100644 --- a/.github/workflows/deploy-testnet.yml +++ b/.github/workflows/deploy-testnet.yml @@ -28,45 +28,36 @@ jobs: - name: Generate appspec.yml for testnet run: cp appspec-testnet.yml appspec.yml + - name: Set filename variable + id: vars + run: echo "FILENAME=django-indexer-testnet-$(date +'%Y-%m-%d_%H-%M-%S').zip" >> $GITHUB_ENV + - name: Create zip of repository - run: zip -r django-indexer-testnet.zip . + run: zip -r "${{ env.FILENAME }}" . - name: Upload repository to S3 - run: aws s3 cp django-indexer-testnet.zip s3://django-indexer-testnet/ + run: aws s3 cp "${{ env.FILENAME }}" s3://django-indexer-testnet/ - name: Create CodeDeploy Deployment id: deploy run: | aws deploy create-deployment \ - --application-name django-indexer-testnet \ - --deployment-group-name django-indexer-testnet-group \ - --deployment-config-name CodeDeployDefault.AllAtOnce \ - --s3-location bucket=django-indexer-testnet,bundleType=zip,key=django-indexer-testnet.zip - -# jobs: -# AssumeRoleAndCallIdentity: -# runs-on: ubuntu-latest -# steps: -# - name: Git clone the repository -# uses: actions/checkout@v3 -# - name: configure aws credentials -# uses: aws-actions/configure-aws-credentials@v1.7.0 -# with: -# role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction -# role-session-name: GitHub_to_AWS_via_FederatedOIDC -# aws-region: ${{ env.AWS_REGION }} -# - name: Sts GetCallerIdentity -# run: | -# aws sts get-caller-identity -# - name: Generate appspec.yml for testnet -# run: cp appspec-testnet.yml appspec.yml -# - name: Verify appspec.yml content -# run: cat appspec.yml -# - name: Create CodeDeploy Deployment -# id: deploy -# run: | -# aws deploy create-deployment \ -# --application-name django-indexer-testnet \ -# --deployment-group-name django-indexer-testnet-group \ -# --deployment-config-name CodeDeployDefault.AllAtOnce \ -# --github-location repository=${{ github.repository }},commitId=${{ github.sha }} + --application-name django-indexer-testnet \ + --deployment-group-name django-indexer-testnet-group \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --s3-location bucket=django-indexer-testnet,bundleType=zip,key=${{ env.FILENAME }} + + # - name: Create zip of repository + # run: zip -r django-indexer-testnet.zip . + + # - name: Upload repository to S3 + # run: aws s3 cp django-indexer-testnet.zip s3://django-indexer-testnet/ + + # - name: Create CodeDeploy Deployment + # id: deploy + # run: | + # aws deploy create-deployment \ + # --application-name django-indexer-testnet \ + # --deployment-group-name django-indexer-testnet-group \ + # --deployment-config-name CodeDeployDefault.AllAtOnce \ + # --s3-location bucket=django-indexer-testnet,bundleType=zip,key=django-indexer-testnet.zip From b14512bb403b85c21d57e3a7e9953a58baf8e764 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 17:02:49 -0400 Subject: [PATCH 132/454] add env vars to deploy-testnet --- .github/workflows/deploy-dev.yml | 31 ++++++++++++++++++---------- .github/workflows/deploy-testnet.yml | 11 ++++++---- appspec-dev.yml | 16 ++++++++++++++ 3 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 appspec-dev.yml diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 713d950..0640711 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -12,28 +12,37 @@ permissions: id-token: write # This is required for requesting the JWT contents: read # This is required for actions/checkout jobs: - AssumeRoleAndCallIdentity: + DeployToCodeDeploy: runs-on: ubuntu-latest steps: - name: Git clone the repository uses: actions/checkout@v3 - - name: configure aws credentials + + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1.7.0 with: role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction role-session-name: GitHub_to_AWS_via_FederatedOIDC aws-region: ${{ env.AWS_REGION }} - # Hello from AWS: WhoAmI - - name: Sts GetCallerIdentity - run: | - aws sts get-caller-identity - # Step 3 - check the application-name and deployment group name + - name: Generate appspec.yml for dev + run: cp appspec-dev.yml appspec.yml + + - name: Set filename variable + id: vars + run: echo "FILENAME=django-indexer-dev-$(date +'%Y-%m-%d_%H-%M-%S').zip" >> $GITHUB_ENV + + - name: Create zip of repository + run: zip -r "${{ env.FILENAME }}" . + + - name: Upload repository to S3 + run: aws s3 cp "${{ env.FILENAME }}" s3://django-indexer-dev/ + - name: Create CodeDeploy Deployment id: deploy run: | aws deploy create-deployment \ - --application-name django-indexer \ - --deployment-group-name django-indexer-dev \ - --deployment-config-name CodeDeployDefault.AllAtOnce \ - --github-location repository=${{ github.repository }},commitId=${{ github.sha }} + --application-name django-indexer \ + --deployment-group-name django-indexer-dev-group \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --s3-location bucket=django-indexer-dev,bundleType=zip,key=${{ env.FILENAME }} diff --git a/.github/workflows/deploy-testnet.yml b/.github/workflows/deploy-testnet.yml index 8b06f83..d22df3c 100644 --- a/.github/workflows/deploy-testnet.yml +++ b/.github/workflows/deploy-testnet.yml @@ -28,15 +28,18 @@ jobs: - name: Generate appspec.yml for testnet run: cp appspec-testnet.yml appspec.yml - - name: Set filename variable + - name: Set environment variables id: vars - run: echo "FILENAME=django-indexer-testnet-$(date +'%Y-%m-%d_%H-%M-%S').zip" >> $GITHUB_ENV + run: | + echo "DATETIME=$(date +'%Y-%m-%d_%H-%M-%S')" >> $GITHUB_ENV + echo "FILENAME=django-indexer-testnet-${DATETIME}.zip" >> $GITHUB_ENV + echo "S3_BUCKET=django-indexer-testnet" >> $GITHUB_ENV - name: Create zip of repository run: zip -r "${{ env.FILENAME }}" . - name: Upload repository to S3 - run: aws s3 cp "${{ env.FILENAME }}" s3://django-indexer-testnet/ + run: aws s3 cp "${{ env.FILENAME }}" "s3://${{ env.S3_BUCKET }}/" - name: Create CodeDeploy Deployment id: deploy @@ -45,7 +48,7 @@ jobs: --application-name django-indexer-testnet \ --deployment-group-name django-indexer-testnet-group \ --deployment-config-name CodeDeployDefault.AllAtOnce \ - --s3-location bucket=django-indexer-testnet,bundleType=zip,key=${{ env.FILENAME }} + --s3-location bucket=${{ env.S3_BUCKET }},bundleType=zip,key=${{ env.FILENAME }} # - name: Create zip of repository # run: zip -r django-indexer-testnet.zip . diff --git a/appspec-dev.yml b/appspec-dev.yml new file mode 100644 index 0000000..192e4cf --- /dev/null +++ b/appspec-dev.yml @@ -0,0 +1,16 @@ +version: 0.0 +os: linux +files: + - source: / + destination: /home/ec2-user/django-indexer +hooks: + # # Install: + AfterInstall: + - location: scripts/after_install_dev.sh + timeout: 300 + runas: ec2-user +# ApplicationStart: +# - location: scripts/application_start.sh +# timeout: 300 +# runas: root +# # ValidateService: From c4f545cbbdb625cce3690b7947678aa5b1b42ac3 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 17:29:38 -0400 Subject: [PATCH 133/454] add after_install_dev script --- .github/workflows/deploy-dev.yml | 11 ++-- scripts/after_install_dev.sh | 110 +++++++++++++++++-------------- scripts/after_install_testnet.sh | 11 +--- 3 files changed, 70 insertions(+), 62 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 0640711..334d04e 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -28,15 +28,18 @@ jobs: - name: Generate appspec.yml for dev run: cp appspec-dev.yml appspec.yml - - name: Set filename variable + - name: Set environment variables id: vars - run: echo "FILENAME=django-indexer-dev-$(date +'%Y-%m-%d_%H-%M-%S').zip" >> $GITHUB_ENV + run: | + echo "DATETIME=$(date +'%Y-%m-%d_%H-%M-%S')" >> $GITHUB_ENV + echo "FILENAME=django-indexer-dev-${DATETIME}.zip" >> $GITHUB_ENV + echo "S3_BUCKET=django-indexer-dev" >> $GITHUB_ENV - name: Create zip of repository run: zip -r "${{ env.FILENAME }}" . - name: Upload repository to S3 - run: aws s3 cp "${{ env.FILENAME }}" s3://django-indexer-dev/ + run: aws s3 cp "${{ env.FILENAME }}" "s3://${{ env.S3_BUCKET }}/" - name: Create CodeDeploy Deployment id: deploy @@ -45,4 +48,4 @@ jobs: --application-name django-indexer \ --deployment-group-name django-indexer-dev-group \ --deployment-config-name CodeDeployDefault.AllAtOnce \ - --s3-location bucket=django-indexer-dev,bundleType=zip,key=${{ env.FILENAME }} + --s3-location bucket=${{ env.S3_BUCKET }},bundleType=zip,key=${{ env.FILENAME }} diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh index 6cfa006..0cc5a0a 100755 --- a/scripts/after_install_dev.sh +++ b/scripts/after_install_dev.sh @@ -1,70 +1,80 @@ -# #!/bin/bash -# # Log output to a specific file -# LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" +#!/bin/bash +# Log output to a specific file +LOG_FILE="/home/ec2-user/django-indexer-dev/logs/deploy.log" -# echo -e "\n\n" >> "$LOG_FILE" -# echo "=========================================" >> "$LOG_FILE" -# echo "Running after_install.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" -# echo "=========================================" >> "$LOG_FILE" +echo -e "\n\n" >> "$LOG_FILE" +echo "=========================================" >> "$LOG_FILE" +echo "Running after_install_dev.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" +echo "=========================================" >> "$LOG_FILE" -# # Load env vars -# source /home/ec2-user/.bashrc +# Load env vars +export PL_ENVIRONMENT=dev +source /home/ec2-user/.bashrc -# # Set correct ownership recursively for project directory -# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" +# Set correct ownership recursively for project directory +sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer-dev/ +echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" -# # Set the necessary permissions -# sudo chmod -R 775 /home/ec2-user/django-indexer/ -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" +# Set the necessary permissions +sudo chmod -R 775 /home/ec2-user/django-indexer-dev/ +echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" -# # Restart nginx to apply any configuration changes -# sudo systemctl restart nginx -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" +# Restart nginx to apply any configuration changes +sudo systemctl restart nginx +echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" -# # Define the project directory -# PROJECT_DIR="/home/ec2-user/django-indexer" +# Define the project directory +PROJECT_DIR="/home/ec2-user/django-indexer-dev" -# # Navigate to the project directory -# cd "$PROJECT_DIR" +# Navigate to the project directory +cd "$PROJECT_DIR" -# # Install dependencies using Poetry -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" -# poetry install >> "$LOG_FILE" -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" +# Source the specific poetry virtual environment +source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" -# # Log the full output of showmigrations to diagnose -# echo "Checking for pending migrations..." >> "$LOG_FILE" -# poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 +# Install dependencies using Poetry +echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +poetry install >> "$LOG_FILE" +echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" -# # Check for unapplied migrations -# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations +# Check if there are pending migrations and log the output +echo "Checking for pending migrations..." >> "$LOG_FILE" +PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout +echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" -# if [ "$PENDING_MIGRATIONS" -gt 0 ]; then -# echo "Migrations found; stopping services..." >> "$LOG_FILE" -# sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat +# Log the full output of showmigrations +echo "Checking for pending migrations..." >> "$LOG_FILE" +poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose -# echo 'Applying migrations...' >> "$LOG_FILE" -# poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 +# Check for unapplied migrations +PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations -# echo 'Starting services...' >> "$LOG_FILE" -# sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat -# else -# echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" -# poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 -# sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat -# fi +if [ "$PENDING_MIGRATIONS" -gt 0 ]; then + echo "Migrations found; stopping services..." >> "$LOG_FILE" + sudo systemctl stop gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev -# echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" + echo 'Applying migrations...' >> "$LOG_FILE" + poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 + echo 'Starting services...' >> "$LOG_FILE" + sudo systemctl start gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev +else + echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" + poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 + sudo systemctl restart gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev +fi +echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_dev.sh completed" >> "$LOG_FILE" -#!/bin/bash -# Log output to a specific file -LOG_FILE="/home/ec2-user/django-indexer-dev/logs/deploy.log" -# print placeholder -echo -e "\n THIS IS A PLACEHOLDER \n" >> "$LOG_FILE" + + +# #!/bin/bash +# # Log output to a specific file +# LOG_FILE="/home/ec2-user/django-indexer-dev/logs/deploy.log" + +# # print placeholder +# echo -e "\n THIS IS A PLACEHOLDER \n" >> "$LOG_FILE" # echo -e "\n\n" >> "$LOG_FILE" # echo "=========================================" >> "$LOG_FILE" @@ -127,4 +137,4 @@ echo -e "\n THIS IS A PLACEHOLDER \n" >> "$LOG_FILE" # sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat # fi -echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_dev.sh completed" >> "$LOG_FILE" +# echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_dev.sh completed" >> "$LOG_FILE" diff --git a/scripts/after_install_testnet.sh b/scripts/after_install_testnet.sh index 439cd2e..94c517b 100755 --- a/scripts/after_install_testnet.sh +++ b/scripts/after_install_testnet.sh @@ -2,9 +2,6 @@ # Log output to a specific file LOG_FILE="/home/ec2-user/django-indexer-testnet/logs/deploy.log" -# # print placeholder -# echo -e "\n THIS IS A PLACEHOLDER \n" >> "$LOG_FILE" - echo -e "\n\n" >> "$LOG_FILE" echo "=========================================" >> "$LOG_FILE" echo "Running after_install_testnet.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" @@ -22,11 +19,9 @@ echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> " sudo chmod -R 775 /home/ec2-user/django-indexer-testnet/ echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" -# TODO: ADD BELOW BACK IN - -# # Restart nginx to apply any configuration changes -# sudo systemctl restart nginx -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" +# Restart nginx to apply any configuration changes +sudo systemctl restart nginx +echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" # Define the project directory PROJECT_DIR="/home/ec2-user/django-indexer-testnet" From a8fd173bf89a36fd7bcb2a957c863ca6faf82b5e Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 17:31:35 -0400 Subject: [PATCH 134/454] Testnet -> Dev (#26) * add deploy-testnet.yml * add testnet deployment workflow & appspec * testnet workflow debugging * upload appspec as artifact * upload zipped codebase to s3 in workflow * continue working on after_install_testnet * separate watchtower log groups by django env * add testnet potlock TLA to indexer handler * add POTLOCK_TLA to settings * add dev.potlock.io and test-dev.potlock.io to allowed hosts * remove admin dashboard edit access * remove admin edit permission for all models * add timestamp to deployment zip filename * add env vars to deploy-testnet * add after_install_dev script --- .github/workflows/deploy-dev.yml | 51 ++++++++++ .github/workflows/deploy-testnet.yml | 66 +++++++++++++ .github/workflows/deploy.yml | 106 -------------------- accounts/admin.py | 47 +++++++-- activities/admin.py | 44 ++++++--- appspec-dev.yml | 16 +++ appspec-testnet.yml | 16 +++ base/settings.py | 14 ++- donations/admin.py | 9 ++ indexer_app/admin.py | 15 ++- indexer_app/handler.py | 12 ++- indexer_app/utils.py | 3 +- lists/admin.py | 70 +++++++++++--- pots/admin.py | 63 ++++++++++++ pots/utils.py | 4 +- scripts/after_install.sh | 64 +----------- scripts/after_install_dev.sh | 140 +++++++++++++++++++++++++++ scripts/after_install_testnet.sh | 67 +++++++++++++ tokens/admin.py | 34 +++++-- 19 files changed, 622 insertions(+), 219 deletions(-) create mode 100644 .github/workflows/deploy-dev.yml create mode 100644 .github/workflows/deploy-testnet.yml delete mode 100644 .github/workflows/deploy.yml create mode 100644 appspec-dev.yml create mode 100644 appspec-testnet.yml mode change 100755 => 100644 scripts/after_install.sh create mode 100755 scripts/after_install_dev.sh create mode 100755 scripts/after_install_testnet.sh diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 0000000..334d04e --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,51 @@ +name: Dev deploy to EC2 on Push + +on: + push: + branches: [dev] + +env: + AWS_REGION: "us-east-1" + +# Permission can be added at job level or workflow level +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout +jobs: + DeployToCodeDeploy: + runs-on: ubuntu-latest + steps: + - name: Git clone the repository + uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1.7.0 + with: + role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction + role-session-name: GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ env.AWS_REGION }} + + - name: Generate appspec.yml for dev + run: cp appspec-dev.yml appspec.yml + + - name: Set environment variables + id: vars + run: | + echo "DATETIME=$(date +'%Y-%m-%d_%H-%M-%S')" >> $GITHUB_ENV + echo "FILENAME=django-indexer-dev-${DATETIME}.zip" >> $GITHUB_ENV + echo "S3_BUCKET=django-indexer-dev" >> $GITHUB_ENV + + - name: Create zip of repository + run: zip -r "${{ env.FILENAME }}" . + + - name: Upload repository to S3 + run: aws s3 cp "${{ env.FILENAME }}" "s3://${{ env.S3_BUCKET }}/" + + - name: Create CodeDeploy Deployment + id: deploy + run: | + aws deploy create-deployment \ + --application-name django-indexer \ + --deployment-group-name django-indexer-dev-group \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --s3-location bucket=${{ env.S3_BUCKET }},bundleType=zip,key=${{ env.FILENAME }} diff --git a/.github/workflows/deploy-testnet.yml b/.github/workflows/deploy-testnet.yml new file mode 100644 index 0000000..d22df3c --- /dev/null +++ b/.github/workflows/deploy-testnet.yml @@ -0,0 +1,66 @@ +name: Testnet deploy to EC2 on Push + +on: + push: + branches: [testnet] + +env: + AWS_REGION: "us-east-1" + +# Permission can be added at job level or workflow level +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout +jobs: + DeployToCodeDeploy: + runs-on: ubuntu-latest + steps: + - name: Git clone the repository + uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1.7.0 + with: + role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction + role-session-name: GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ env.AWS_REGION }} + + - name: Generate appspec.yml for testnet + run: cp appspec-testnet.yml appspec.yml + + - name: Set environment variables + id: vars + run: | + echo "DATETIME=$(date +'%Y-%m-%d_%H-%M-%S')" >> $GITHUB_ENV + echo "FILENAME=django-indexer-testnet-${DATETIME}.zip" >> $GITHUB_ENV + echo "S3_BUCKET=django-indexer-testnet" >> $GITHUB_ENV + + - name: Create zip of repository + run: zip -r "${{ env.FILENAME }}" . + + - name: Upload repository to S3 + run: aws s3 cp "${{ env.FILENAME }}" "s3://${{ env.S3_BUCKET }}/" + + - name: Create CodeDeploy Deployment + id: deploy + run: | + aws deploy create-deployment \ + --application-name django-indexer-testnet \ + --deployment-group-name django-indexer-testnet-group \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --s3-location bucket=${{ env.S3_BUCKET }},bundleType=zip,key=${{ env.FILENAME }} + + # - name: Create zip of repository + # run: zip -r django-indexer-testnet.zip . + + # - name: Upload repository to S3 + # run: aws s3 cp django-indexer-testnet.zip s3://django-indexer-testnet/ + + # - name: Create CodeDeploy Deployment + # id: deploy + # run: | + # aws deploy create-deployment \ + # --application-name django-indexer-testnet \ + # --deployment-group-name django-indexer-testnet-group \ + # --deployment-config-name CodeDeployDefault.AllAtOnce \ + # --s3-location bucket=django-indexer-testnet,bundleType=zip,key=django-indexer-testnet.zip diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 554fb7f..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,106 +0,0 @@ -name: Deploy to EC2 on Push - -on: - push: - branches: [dev] - -env: - AWS_REGION: "us-east-1" - -# Permission can be added at job level or workflow level -permissions: - id-token: write # This is required for requesting the JWT - contents: read # This is required for actions/checkout -jobs: - AssumeRoleAndCallIdentity: - runs-on: ubuntu-latest - steps: - - name: Git clone the repository - uses: actions/checkout@v3 - - name: configure aws credentials - uses: aws-actions/configure-aws-credentials@v1.7.0 - with: - role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction - role-session-name: GitHub_to_AWS_via_FederatedOIDC - aws-region: ${{ env.AWS_REGION }} - # Hello from AWS: WhoAmI - - name: Sts GetCallerIdentity - run: | - aws sts get-caller-identity - - # Step 3 - check the application-name and deployment group name - - name: Create CodeDeploy Deployment - id: deploy - run: | - aws deploy create-deployment \ - --application-name django-indexer \ - --deployment-group-name django-indexer-dev \ - --deployment-config-name CodeDeployDefault.AllAtOnce \ - --github-location repository=${{ github.repository }},commitId=${{ github.sha }} - -# name: Deploy to EC2 on Push - -# on: -# push: -# branches: -# - main -# - dev - -# jobs: -# deploy: -# runs-on: ubuntu-latest -# environment: -# name: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }} -# steps: -# - name: Checkout code -# uses: actions/checkout@v2 - -# - name: Set up SSH key -# uses: webfactory/ssh-agent@v0.5.3 -# with: -# ssh-private-key: ${{ secrets.EC2_SSH_PRIVATE_KEY }} - -# - name: Push new code to EC2 -# run: | -# rsync -avz --exclude '.git*' --exclude 'node_modules' ./ ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }}:/home/ec2-user/django-indexer -# echo "Code has been pushed to the EC2 instance." - -# - name: Check for pending migrations -# id: check_migrations -# run: | -# echo "Checking for pending migrations..." -# pending_migrations=$(ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py showmigrations --plan | grep '\[ \]'") -# echo "::set-output name=pending::${pending_migrations}" -# if [ -z "$pending_migrations" ]; then -# echo "No migrations found." -# else: -# echo "Migrations found, stopping services." - -# - name: Stop services if migrations are pending -# if: steps.check_migrations.outputs.pending -# run: | -# echo "Stopping services..." -# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl stop gunicorn.service indexer.service" - -# - name: Run migrations -# if: steps.check_migrations.outputs.pending -# run: | -# echo "Running migrations..." -# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py migrate" - -# - name: Run collectstatic -# run: | -# echo "Running collectstatic..." -# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "cd /home/ec2-user/django-indexer && source env/bin/activate && python manage.py collectstatic --noinput" - -# - name: Restart services if migrations were run -# if: steps.check_migrations.outputs.pending -# run: | -# echo "Restarting services after migrations..." -# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service indexer.service" - -# - name: Restart services if no migrations -# if: steps.check_migrations.outputs.pending == '' -# run: | -# echo "Restarting services without migration..." -# ssh -o "StrictHostKeyChecking=no" ${{ vars.EC2_USER }}@${{ vars.EC2_SSH_HOST }} "sudo systemctl restart gunicorn.service" diff --git a/accounts/admin.py b/accounts/admin.py index fb44fbe..3b8aca7 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -1,25 +1,52 @@ from django.contrib import admin + from .models import Account + @admin.register(Account) class AccountAdmin(admin.ModelAdmin): - list_display = ('id', 'total_donations_in_usd', 'total_donations_out_usd', 'total_matching_pool_allocations_usd', 'donors_count') - search_fields = ('id',) # Allow searching by account address - list_filter = ('total_donations_in_usd', 'total_donations_out_usd') # Filter by donation amounts - ordering = ('-total_donations_in_usd',) # Default ordering + list_display = ( + "id", + "total_donations_in_usd", + "total_donations_out_usd", + "total_matching_pool_allocations_usd", + "donors_count", + ) + search_fields = ("id",) # Allow searching by account address + list_filter = ( + "total_donations_in_usd", + "total_donations_out_usd", + ) # Filter by donation amounts + ordering = ("-total_donations_in_usd",) # Default ordering # Optionally, format decimal fields for better readability in the admin def total_donations_in_usd_display(self, obj): return "${:,.2f}".format(obj.total_donations_in_usd) - total_donations_in_usd_display.admin_order_field = 'total_donations_in_usd' - total_donations_in_usd_display.short_description = 'Total Donations Received (USD)' + + total_donations_in_usd_display.admin_order_field = "total_donations_in_usd" + total_donations_in_usd_display.short_description = "Total Donations Received (USD)" def total_donations_out_usd_display(self, obj): return "${:,.2f}".format(obj.total_donations_out_usd) - total_donations_out_usd_display.admin_order_field = 'total_donations_out_usd' - total_donations_out_usd_display.short_description = 'Total Donations Sent (USD)' + + total_donations_out_usd_display.admin_order_field = "total_donations_out_usd" + total_donations_out_usd_display.short_description = "Total Donations Sent (USD)" def total_matching_pool_allocations_usd_display(self, obj): return "${:,.2f}".format(obj.total_matching_pool_allocations_usd) - total_matching_pool_allocations_usd_display.admin_order_field = 'total_matching_pool_allocations_usd' - total_matching_pool_allocations_usd_display.short_description = 'Total Matching Pool Allocations (USD)' + + total_matching_pool_allocations_usd_display.admin_order_field = ( + "total_matching_pool_allocations_usd" + ) + total_matching_pool_allocations_usd_display.short_description = ( + "Total Matching Pool Allocations (USD)" + ) + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/activities/admin.py b/activities/admin.py index cab9f72..219e369 100644 --- a/activities/admin.py +++ b/activities/admin.py @@ -1,30 +1,52 @@ from django.contrib import admin from django.utils.html import format_html -from .models import Activity, Account + +from .models import Account, Activity + @admin.register(Activity) class ActivityAdmin(admin.ModelAdmin): - list_display = ('id', 'signer_address', 'receiver_address', 'timestamp', 'type', 'transaction_link', 'action_result') - list_filter = ('timestamp', 'type', 'signer', 'receiver') - search_fields = ('signer__id', 'receiver__id', 'tx_hash') - date_hierarchy = 'timestamp' - ordering = ('-timestamp',) + list_display = ( + "id", + "signer_address", + "receiver_address", + "timestamp", + "type", + "transaction_link", + "action_result", + ) + list_filter = ("timestamp", "type", "signer", "receiver") + search_fields = ("signer__id", "receiver__id", "tx_hash") + date_hierarchy = "timestamp" + ordering = ("-timestamp",) def signer_address(self, obj): return obj.signer.id - signer_address.admin_order_field = 'signer' - signer_address.short_description = 'Signer Address' + + signer_address.admin_order_field = "signer" + signer_address.short_description = "Signer Address" def receiver_address(self, obj): return obj.receiver.id - receiver_address.admin_order_field = 'receiver' - receiver_address.short_description = 'Receiver Address' + + receiver_address.admin_order_field = "receiver" + receiver_address.short_description = "Receiver Address" def transaction_link(self, obj): url = f"https://nearblocks.io?query={obj.tx_hash}" return format_html('{}', url, obj.tx_hash) - transaction_link.short_description = 'Transaction Hash' # Sets the column header + + transaction_link.short_description = "Transaction Hash" # Sets the column header # def action_result_summary(self, obj): # return "Has Result" if obj.action_result else "No Result" # action_result_summary.short_description = 'Action Result' + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/appspec-dev.yml b/appspec-dev.yml new file mode 100644 index 0000000..192e4cf --- /dev/null +++ b/appspec-dev.yml @@ -0,0 +1,16 @@ +version: 0.0 +os: linux +files: + - source: / + destination: /home/ec2-user/django-indexer +hooks: + # # Install: + AfterInstall: + - location: scripts/after_install_dev.sh + timeout: 300 + runas: ec2-user +# ApplicationStart: +# - location: scripts/application_start.sh +# timeout: 300 +# runas: root +# # ValidateService: diff --git a/appspec-testnet.yml b/appspec-testnet.yml new file mode 100644 index 0000000..b006f0e --- /dev/null +++ b/appspec-testnet.yml @@ -0,0 +1,16 @@ +version: 0.0 +os: linux +files: + - source: / + destination: /home/ec2-user/django-indexer-testnet +hooks: + # # Install: + AfterInstall: + - location: scripts/after_install_testnet.sh + timeout: 300 + runas: ec2-user +# ApplicationStart: +# - location: scripts/application_start.sh +# timeout: 300 +# runas: root +# # ValidateService: diff --git a/base/settings.py b/base/settings.py index ce99c04..7525f40 100644 --- a/base/settings.py +++ b/base/settings.py @@ -30,7 +30,12 @@ # TODO: update before prod release SECRET_KEY = "django-insecure-=r_v_es6w6rxv42^#kc2hca6p%=fe_*cog_5!t%19zea!enlju" -ALLOWED_HOSTS = ["ec2-100-27-57-47.compute-1.amazonaws.com", "127.0.0.1"] +ALLOWED_HOSTS = [ + "ec2-100-27-57-47.compute-1.amazonaws.com", + "127.0.0.1", + "dev.potlock.io", + "test-dev.potlock.io", +] # Env vars AWS_ACCESS_KEY_ID = os.environ.get("PL_AWS_ACCESS_KEY_ID") @@ -52,6 +57,8 @@ REDIS_PORT = os.environ.get("PL_REDIS_PORT", 6379) SENTRY_DSN = os.environ.get("PL_SENTRY_DSN") +POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else "potlock.near" + BLOCK_SAVE_HEIGHT = os.environ.get("BLOCK_SAVE_HEIGHT") COINGECKO_URL = ( @@ -213,6 +220,9 @@ log_level = getattr(logging, LOG_LEVEL, logging.INFO) # print("LOG_LEVEL: ", LOG_LEVEL) +# Set log group name based on environment +log_group_name = f"django-indexer-{ENVIRONMENT}" + # Setting up the logging configuration LOGGING = { "version": 1, @@ -254,7 +264,7 @@ LOGGING["handlers"]["watchtower"] = { "class": "watchtower.CloudWatchLogHandler", "boto3_client": boto3_logs_client, - "log_group_name": "django-indexer", + "log_group_name": log_group_name, "formatter": "standard", "level": log_level, } diff --git a/donations/admin.py b/donations/admin.py index 43f87d3..f27994d 100644 --- a/donations/admin.py +++ b/donations/admin.py @@ -59,3 +59,12 @@ def formfield_for_dbfield(self, db_field, request, **kwargs): field.widget.format = "%d-%m-%Y %H:%M" field.widget.attrs.update({"class": "vDateField", "size": "20"}) return field + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/indexer_app/admin.py b/indexer_app/admin.py index 3b56656..e94e67a 100644 --- a/indexer_app/admin.py +++ b/indexer_app/admin.py @@ -1,7 +1,18 @@ from django.contrib import admin + from .models import BlockHeight + @admin.register(BlockHeight) class BlockHeightAdmin(admin.ModelAdmin): - list_display = ('id', 'block_height', 'updated_at') - ordering = ('-updated_at',) + list_display = ("id", "block_height", "updated_at") + ordering = ("-updated_at",) + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 9d86fbf..0cdfa99 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -2,6 +2,7 @@ import json from datetime import datetime +from django.conf import settings from django.core.cache import cache from near_lake_framework import near_primitives @@ -49,8 +50,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess for shard in streamer_message.shards: for receipt_execution_outcome in shard.receipt_execution_outcomes: # we only want to proceed if it's a potlock tx and it succeeded.... (unreadable if statement?) + lists_contract = "lists." + settings.POTLOCK_TLA if not receipt_execution_outcome.receipt.receiver_id.endswith( - "potlock.near" + settings.POTLOCK_TLA ) or ( "SuccessReceiptId" not in receipt_execution_outcome.execution_outcome.outcome.status @@ -232,7 +234,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess logger.info( f"registrations incoming: {args_dict}, {action}" ) - if receiver_id != "lists.potlock.near": + if receiver_id != lists_contract: break await handle_new_list_registration( args_dict, receiver_id, signer_id, receipt, status_obj @@ -306,7 +308,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess "owner_remove_admins" ): # TODO: use update_admins event instead of method call to handle all cases logger.info(f"attempting to remove admins....: {args_dict}") - if receiver_id != "lists.potlock.near": + if receiver_id != lists_contract: break await handle_list_admin_removal( args_dict, receiver_id, signer_id, receipt.receipt_id @@ -315,14 +317,14 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess case "create_list": logger.info(f"creating list... {args_dict}, {action}") - if receiver_id != "lists.potlock.near": + if receiver_id != lists_contract: break await handle_new_list(signer_id, receiver_id, status_obj) break case "upvote": logger.info(f"up voting... {args_dict}") - if receiver_id != "lists.potlock.near": + if receiver_id != lists_contract: break await handle_list_upvote( args_dict, receiver_id, signer_id, receipt.receipt_id diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 7e44c2f..5f394a3 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -633,10 +633,11 @@ async def handle_new_donations( log_data: list, ): logger.info(f"new donation data: {data}, {receiverId}") + donate_contract_addr = "donate." + settings.POTLOCK_TLA if ( actionName == "direct" - ) and receiverId == "donate.potlock.near": # early pot donations followed similarly to direct donations i.e they returned result instead of events. + ) and receiverId == donate_contract_addr: # early pot donations followed similarly to direct donations i.e they returned result instead of events. logger.info("calling donate contract...") # Handle direct donation diff --git a/lists/admin.py b/lists/admin.py index ae08c51..db244ed 100644 --- a/lists/admin.py +++ b/lists/admin.py @@ -1,22 +1,68 @@ from django.contrib import admin -from .models import List, ListUpvote, ListRegistration, Account + +from .models import Account, List, ListRegistration, ListUpvote + @admin.register(List) class ListAdmin(admin.ModelAdmin): - list_display = ('id', 'name', 'owner', 'default_registration_status', 'created_at', 'updated_at') - list_filter = ('created_at', 'updated_at', 'default_registration_status') - search_fields = ('name', 'owner__id') - ordering = ('-created_at',) + list_display = ( + "id", + "name", + "owner", + "default_registration_status", + "created_at", + "updated_at", + ) + list_filter = ("created_at", "updated_at", "default_registration_status") + search_fields = ("name", "owner__id") + ordering = ("-created_at",) + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(ListUpvote) class ListUpvoteAdmin(admin.ModelAdmin): - list_display = ('id', 'list', 'account', 'created_at') - list_filter = ('created_at',) - search_fields = ('list__name', 'account__id') + list_display = ("id", "list", "account", "created_at") + list_filter = ("created_at",) + search_fields = ("list__name", "account__id") + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(ListRegistration) class ListRegistrationAdmin(admin.ModelAdmin): - list_display = ('id', 'list', 'registrant', 'registered_by', 'status', 'submitted_at', 'updated_at') - list_filter = ('status', 'submitted_at', 'updated_at') - search_fields = ('list__name', 'registrant__id', 'registered_by__id') - ordering = ('-submitted_at',) + list_display = ( + "id", + "list", + "registrant", + "registered_by", + "status", + "submitted_at", + "updated_at", + ) + list_filter = ("status", "submitted_at", "updated_at") + search_fields = ("list__name", "registrant__id", "registered_by__id") + ordering = ("-submitted_at",) + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/pots/admin.py b/pots/admin.py index 09e40b3..aca414b 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -46,6 +46,15 @@ def get_form(self, request, obj=None, **kwargs): ) return form + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + class PotForm(forms.ModelForm): class Meta: @@ -73,6 +82,15 @@ def get_form(self, request, obj=None, **kwargs): form.base_fields["admins"].queryset = obj.admins.all() return form + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(PotApplication) class PotApplicationAdmin(admin.ModelAdmin): @@ -80,6 +98,15 @@ class PotApplicationAdmin(admin.ModelAdmin): search_fields = ("pot__id", "applicant__id") list_filter = ("status", "submitted_at") + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(PotApplicationReview) class PotApplicationReviewAdmin(admin.ModelAdmin): @@ -95,6 +122,15 @@ class PotApplicationReviewAdmin(admin.ModelAdmin): search_fields = ("application__id", "reviewer__id") list_filter = ("status", "reviewed_at") + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(PotPayout) class PotPayoutAdmin(admin.ModelAdmin): @@ -102,6 +138,15 @@ class PotPayoutAdmin(admin.ModelAdmin): search_fields = ("pot__id", "recipient__id") list_filter = ("paid_at",) + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(PotPayoutChallenge) class PotPayoutChallengeAdmin(admin.ModelAdmin): @@ -109,9 +154,27 @@ class PotPayoutChallengeAdmin(admin.ModelAdmin): search_fields = ("challenger__id", "pot__id") list_filter = ("created_at",) + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(PotPayoutChallengeAdminResponse) class PotPayoutChallengeAdminResponseAdmin(admin.ModelAdmin): list_display = ("id", "pot", "admin", "created_at", "resolved") search_fields = ("admin__id", "challenge__id") list_filter = ("created_at", "resolved") + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/pots/utils.py b/pots/utils.py index 01bfab6..1a5f991 100644 --- a/pots/utils.py +++ b/pots/utils.py @@ -4,12 +4,12 @@ def match_pot_factory_version_pattern(receiver): - """Matches the base pot factory version pattern without a subaccount.""" + """Matches the base pot factory version pattern without a subaccount. NB: does not currently handle testnet factory.""" pattern = f"^{BASE_PATTERN}" return bool(re.match(pattern, receiver)) def match_pot_subaccount_version_pattern(receiver): - """Matches the pot factory version pattern with a subaccount.""" + """Matches the pot factory version pattern with a subaccount. NB: does not currently handle testnet factory.""" pattern = f"^[a-zA-Z0-9_]+\.{BASE_PATTERN}" return bool(re.match(pattern, receiver)) diff --git a/scripts/after_install.sh b/scripts/after_install.sh old mode 100755 new mode 100644 index 6c56c5b..152ad3a --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -1,65 +1,5 @@ -# #!/bin/bash -# # Log output to a specific file -# LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" - -# echo -e "\n\n" >> "$LOG_FILE" -# echo "=========================================" >> "$LOG_FILE" -# echo "Running after_install.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" -# echo "=========================================" >> "$LOG_FILE" - -# # Load env vars -# source /home/ec2-user/.bashrc - -# # Set correct ownership recursively for project directory -# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" - -# # Set the necessary permissions -# sudo chmod -R 775 /home/ec2-user/django-indexer/ -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" - -# # Restart nginx to apply any configuration changes -# sudo systemctl restart nginx -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" - -# # Define the project directory -# PROJECT_DIR="/home/ec2-user/django-indexer" - -# # Navigate to the project directory -# cd "$PROJECT_DIR" - -# # Install dependencies using Poetry -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" -# poetry install >> "$LOG_FILE" -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" - -# # Log the full output of showmigrations to diagnose -# echo "Checking for pending migrations..." >> "$LOG_FILE" -# poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 - -# # Check for unapplied migrations -# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations - -# if [ "$PENDING_MIGRATIONS" -gt 0 ]; then -# echo "Migrations found; stopping services..." >> "$LOG_FILE" -# sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat - -# echo 'Applying migrations...' >> "$LOG_FILE" -# poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 - -# echo 'Starting services...' >> "$LOG_FILE" -# sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat -# else -# echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" -# poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 -# sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat -# fi - -# echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" - - - #!/bin/bash +# TODO: deprecate this (move to _dev & _testnet files) # Log output to a specific file LOG_FILE="/home/ec2-user/django-indexer/logs/deploy.log" @@ -124,4 +64,4 @@ else sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat fi -echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" +echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" \ No newline at end of file diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh new file mode 100755 index 0000000..0cc5a0a --- /dev/null +++ b/scripts/after_install_dev.sh @@ -0,0 +1,140 @@ +#!/bin/bash +# Log output to a specific file +LOG_FILE="/home/ec2-user/django-indexer-dev/logs/deploy.log" + +echo -e "\n\n" >> "$LOG_FILE" +echo "=========================================" >> "$LOG_FILE" +echo "Running after_install_dev.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" +echo "=========================================" >> "$LOG_FILE" + +# Load env vars +export PL_ENVIRONMENT=dev +source /home/ec2-user/.bashrc + +# Set correct ownership recursively for project directory +sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer-dev/ +echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" + +# Set the necessary permissions +sudo chmod -R 775 /home/ec2-user/django-indexer-dev/ +echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" + +# Restart nginx to apply any configuration changes +sudo systemctl restart nginx +echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" + +# Define the project directory +PROJECT_DIR="/home/ec2-user/django-indexer-dev" + +# Navigate to the project directory +cd "$PROJECT_DIR" + +# Source the specific poetry virtual environment +source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" + +# Install dependencies using Poetry +echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +poetry install >> "$LOG_FILE" +echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" + +# Check if there are pending migrations and log the output +echo "Checking for pending migrations..." >> "$LOG_FILE" +PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout +echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" + +# Log the full output of showmigrations +echo "Checking for pending migrations..." >> "$LOG_FILE" +poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose + +# Check for unapplied migrations +PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations + +if [ "$PENDING_MIGRATIONS" -gt 0 ]; then + echo "Migrations found; stopping services..." >> "$LOG_FILE" + sudo systemctl stop gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev + + echo 'Applying migrations...' >> "$LOG_FILE" + poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 + + echo 'Starting services...' >> "$LOG_FILE" + sudo systemctl start gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev +else + echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" + poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 + sudo systemctl restart gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev +fi + +echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_dev.sh completed" >> "$LOG_FILE" + + + + +# #!/bin/bash +# # Log output to a specific file +# LOG_FILE="/home/ec2-user/django-indexer-dev/logs/deploy.log" + +# # print placeholder +# echo -e "\n THIS IS A PLACEHOLDER \n" >> "$LOG_FILE" + +# echo -e "\n\n" >> "$LOG_FILE" +# echo "=========================================" >> "$LOG_FILE" +# echo "Running after_install_dev.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" +# echo "=========================================" >> "$LOG_FILE" + +# # Load env vars +# source /home/ec2-user/.bashrc + +# # Set correct ownership recursively for project directory +# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" + +# # Set the necessary permissions +# sudo chmod -R 775 /home/ec2-user/django-indexer/ +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" + +# # Restart nginx to apply any configuration changes +# sudo systemctl restart nginx +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" + +# # Define the project directory +# PROJECT_DIR="/home/ec2-user/django-indexer" + +# # Navigate to the project directory +# cd "$PROJECT_DIR" + +# # Source the specific poetry virtual environment +# source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" # TODO: UPDATE THIS + +# # Install dependencies using Poetry +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +# poetry install >> "$LOG_FILE" +# echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" + +# # Check if there are pending migrations and log the output +# echo "Checking for pending migrations..." >> "$LOG_FILE" +# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout +# echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" + +# # Log the full output of showmigrations +# echo "Checking for pending migrations..." >> "$LOG_FILE" +# poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose + +# # Check for unapplied migrations +# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations + +# if [ "$PENDING_MIGRATIONS" -gt 0 ]; then +# echo "Migrations found; stopping services..." >> "$LOG_FILE" +# sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat + +# echo 'Applying migrations...' >> "$LOG_FILE" +# poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 + +# echo 'Starting services...' >> "$LOG_FILE" +# sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat +# else +# echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" +# poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 +# sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat +# fi + +# echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_dev.sh completed" >> "$LOG_FILE" diff --git a/scripts/after_install_testnet.sh b/scripts/after_install_testnet.sh new file mode 100755 index 0000000..94c517b --- /dev/null +++ b/scripts/after_install_testnet.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# Log output to a specific file +LOG_FILE="/home/ec2-user/django-indexer-testnet/logs/deploy.log" + +echo -e "\n\n" >> "$LOG_FILE" +echo "=========================================" >> "$LOG_FILE" +echo "Running after_install_testnet.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" +echo "=========================================" >> "$LOG_FILE" + +# Load env vars +export PL_ENVIRONMENT=testnet +source /home/ec2-user/.bashrc + +# Set correct ownership recursively for project directory +sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer-testnet/ +echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" + +# Set the necessary permissions +sudo chmod -R 775 /home/ec2-user/django-indexer-testnet/ +echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" + +# Restart nginx to apply any configuration changes +sudo systemctl restart nginx +echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" + +# Define the project directory +PROJECT_DIR="/home/ec2-user/django-indexer-testnet" + +# Navigate to the project directory +cd "$PROJECT_DIR" + +# Source the specific poetry virtual environment +source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-AhfQkQzj-py3.11/bin/activate" + +# Install dependencies using Poetry +echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +poetry install >> "$LOG_FILE" +echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" + +# Check if there are pending migrations and log the output +echo "Checking for pending migrations..." >> "$LOG_FILE" +PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout +echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" + +# Log the full output of showmigrations +echo "Checking for pending migrations..." >> "$LOG_FILE" +poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose + +# Check for unapplied migrations +PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations + +if [ "$PENDING_MIGRATIONS" -gt 0 ]; then + echo "Migrations found; stopping services..." >> "$LOG_FILE" + sudo systemctl stop gunicorn-testnet celery-indexer-worker-testnet celery-beat-worker-testnet celery-beat-testnet + + echo 'Applying migrations...' >> "$LOG_FILE" + poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 + + echo 'Starting services...' >> "$LOG_FILE" + sudo systemctl start gunicorn-testnet celery-indexer-worker-testnet celery-beat-worker-testnet celery-beat-testnet +else + echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" + poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 + sudo systemctl restart gunicorn-testnet celery-indexer-worker-testnet celery-beat-worker-testnet celery-beat-testnet +fi + +echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_testnet.sh completed" >> "$LOG_FILE" diff --git a/tokens/admin.py b/tokens/admin.py index 0ca45d6..f783399 100644 --- a/tokens/admin.py +++ b/tokens/admin.py @@ -1,18 +1,40 @@ from django.contrib import admin + from .models import Token, TokenHistoricalPrice + @admin.register(Token) class TokenAdmin(admin.ModelAdmin): - list_display = ('id', 'decimals', 'get_most_recent_price') - search_fields = ('id',) + list_display = ("id", "decimals", "get_most_recent_price") + search_fields = ("id",) def get_most_recent_price(self, obj): price = obj.get_most_recent_price() return price.price_usd if price else None - get_most_recent_price.short_description = 'Most Recent Price (USD)' + + get_most_recent_price.short_description = "Most Recent Price (USD)" + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + @admin.register(TokenHistoricalPrice) class TokenHistoricalPriceAdmin(admin.ModelAdmin): - list_display = ('token', 'timestamp', 'price_usd') - search_fields = ('token__id',) - list_filter = ('timestamp',) + list_display = ("token", "timestamp", "price_usd") + search_fields = ("token__id",) + list_filter = ("timestamp",) + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False From 9b8e6e7932afa448d0c4d9d94baff9eb3d35528b Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 18:28:09 -0400 Subject: [PATCH 135/454] add after_install debug logs --- scripts/after_install_dev.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh index 0cc5a0a..9ba2224 100755 --- a/scripts/after_install_dev.sh +++ b/scripts/after_install_dev.sh @@ -11,6 +11,9 @@ echo "=========================================" >> "$LOG_FILE" export PL_ENVIRONMENT=dev source /home/ec2-user/.bashrc +echo "PL_ENVIRONMENT: $PL_ENVIRONMENT" >> "$LOG_FILE" +echo "PL_POSTGRES_HOST: $PL_POSTGRES_HOST" >> "$LOG_FILE" + # Set correct ownership recursively for project directory sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer-dev/ echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" From a95d83e67019d69d1584559b7f859a9e0a8feb5c Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 18:30:10 -0400 Subject: [PATCH 136/454] add debug logs --- scripts/after_install_dev.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh index 0cc5a0a..9ba2224 100755 --- a/scripts/after_install_dev.sh +++ b/scripts/after_install_dev.sh @@ -11,6 +11,9 @@ echo "=========================================" >> "$LOG_FILE" export PL_ENVIRONMENT=dev source /home/ec2-user/.bashrc +echo "PL_ENVIRONMENT: $PL_ENVIRONMENT" >> "$LOG_FILE" +echo "PL_POSTGRES_HOST: $PL_POSTGRES_HOST" >> "$LOG_FILE" + # Set correct ownership recursively for project directory sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer-dev/ echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" From cd2f0b246646e9c2d0af64d6692144ee2c599612 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 18:31:23 -0400 Subject: [PATCH 137/454] add logs --- scripts/after_install_dev.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh index 9ba2224..e735389 100755 --- a/scripts/after_install_dev.sh +++ b/scripts/after_install_dev.sh @@ -14,6 +14,9 @@ source /home/ec2-user/.bashrc echo "PL_ENVIRONMENT: $PL_ENVIRONMENT" >> "$LOG_FILE" echo "PL_POSTGRES_HOST: $PL_POSTGRES_HOST" >> "$LOG_FILE" +echo "PL_ENVIRONMENT: $PL_ENVIRONMENT" >> "$LOG_FILE" +echo "PL_POSTGRES_HOST: $PL_POSTGRES_HOST" >> "$LOG_FILE" + # Set correct ownership recursively for project directory sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer-dev/ echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" From 7d770abb819111502acc4ed9884d4eaf22aa70a7 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 18:40:55 -0400 Subject: [PATCH 138/454] fix appspec destination for dev --- appspec-dev.yml | 2 +- scripts/after_install_dev.sh | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/appspec-dev.yml b/appspec-dev.yml index 192e4cf..6cb73af 100644 --- a/appspec-dev.yml +++ b/appspec-dev.yml @@ -2,7 +2,7 @@ version: 0.0 os: linux files: - source: / - destination: /home/ec2-user/django-indexer + destination: /home/ec2-user/django-indexer-dev hooks: # # Install: AfterInstall: diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh index e735389..0cc5a0a 100755 --- a/scripts/after_install_dev.sh +++ b/scripts/after_install_dev.sh @@ -11,12 +11,6 @@ echo "=========================================" >> "$LOG_FILE" export PL_ENVIRONMENT=dev source /home/ec2-user/.bashrc -echo "PL_ENVIRONMENT: $PL_ENVIRONMENT" >> "$LOG_FILE" -echo "PL_POSTGRES_HOST: $PL_POSTGRES_HOST" >> "$LOG_FILE" - -echo "PL_ENVIRONMENT: $PL_ENVIRONMENT" >> "$LOG_FILE" -echo "PL_POSTGRES_HOST: $PL_POSTGRES_HOST" >> "$LOG_FILE" - # Set correct ownership recursively for project directory sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer-dev/ echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" From 1d81df12587bbddb9a5cc3273c60597b12922452 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 18:47:10 -0400 Subject: [PATCH 139/454] clean up destination directory before installing in deployment (only dev) --- appspec-dev.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/appspec-dev.yml b/appspec-dev.yml index 6cb73af..02a6c66 100644 --- a/appspec-dev.yml +++ b/appspec-dev.yml @@ -4,7 +4,10 @@ files: - source: / destination: /home/ec2-user/django-indexer-dev hooks: - # # Install: + BeforeInstall: + - location: "echo 'Cleaning up destination directory' && rm -rf /home/ec2-user/django-indexer-dev/*" + timeout: 300 + runas: ec2-user AfterInstall: - location: scripts/after_install_dev.sh timeout: 300 From 41217edbfcfd2d8cf6a8f224a363293c72bd6387 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 31 May 2024 18:50:52 -0400 Subject: [PATCH 140/454] add clean_destination scripts --- appspec-dev.yml | 2 +- appspec-testnet.yml | 4 ++++ scripts/clean_destination_dev.sh | 8 ++++++++ scripts/clean_destination_testnet.sh | 8 ++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 scripts/clean_destination_dev.sh create mode 100644 scripts/clean_destination_testnet.sh diff --git a/appspec-dev.yml b/appspec-dev.yml index 02a6c66..f83a7fc 100644 --- a/appspec-dev.yml +++ b/appspec-dev.yml @@ -5,7 +5,7 @@ files: destination: /home/ec2-user/django-indexer-dev hooks: BeforeInstall: - - location: "echo 'Cleaning up destination directory' && rm -rf /home/ec2-user/django-indexer-dev/*" + - location: scripts/clean_destination_dev.sh timeout: 300 runas: ec2-user AfterInstall: diff --git a/appspec-testnet.yml b/appspec-testnet.yml index b006f0e..3ed0e62 100644 --- a/appspec-testnet.yml +++ b/appspec-testnet.yml @@ -5,6 +5,10 @@ files: destination: /home/ec2-user/django-indexer-testnet hooks: # # Install: + BeforeInstall: + - location: scripts/clean_destination_testnet.sh + timeout: 300 + runas: ec2-user AfterInstall: - location: scripts/after_install_testnet.sh timeout: 300 diff --git a/scripts/clean_destination_dev.sh b/scripts/clean_destination_dev.sh new file mode 100644 index 0000000..638c035 --- /dev/null +++ b/scripts/clean_destination_dev.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# Directory to clean +DEST_DIR="/home/ec2-user/django-indexer-dev" + +# Delete all contents of the destination directory +if [ -d "$DEST_DIR" ]; then + rm -rf "${DEST_DIR:?}/*" +fi \ No newline at end of file diff --git a/scripts/clean_destination_testnet.sh b/scripts/clean_destination_testnet.sh new file mode 100644 index 0000000..62b020a --- /dev/null +++ b/scripts/clean_destination_testnet.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# Directory to clean +DEST_DIR="/home/ec2-user/django-indexer-testnet" + +# Delete all contents of the destination directory +if [ -d "$DEST_DIR" ]; then + rm -rf "${DEST_DIR:?}/*" +fi \ No newline at end of file From 0bf1a26a9313c8eafb015c1480938d635e41359d Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Sat, 1 Jun 2024 11:15:38 -0400 Subject: [PATCH 141/454] remove BeforeInstall clean destination step --- appspec-dev.yml | 8 ++++---- appspec-testnet.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/appspec-dev.yml b/appspec-dev.yml index f83a7fc..348d01b 100644 --- a/appspec-dev.yml +++ b/appspec-dev.yml @@ -4,10 +4,10 @@ files: - source: / destination: /home/ec2-user/django-indexer-dev hooks: - BeforeInstall: - - location: scripts/clean_destination_dev.sh - timeout: 300 - runas: ec2-user + # BeforeInstall: + # - location: scripts/clean_destination_dev.sh + # timeout: 300 + # runas: ec2-user AfterInstall: - location: scripts/after_install_dev.sh timeout: 300 diff --git a/appspec-testnet.yml b/appspec-testnet.yml index 3ed0e62..dafa210 100644 --- a/appspec-testnet.yml +++ b/appspec-testnet.yml @@ -5,10 +5,10 @@ files: destination: /home/ec2-user/django-indexer-testnet hooks: # # Install: - BeforeInstall: - - location: scripts/clean_destination_testnet.sh - timeout: 300 - runas: ec2-user + # BeforeInstall: + # - location: scripts/clean_destination_testnet.sh + # timeout: 300 + # runas: ec2-user AfterInstall: - location: scripts/after_install_testnet.sh timeout: 300 From 9b12842c9a6a6feca8bd7d50997d484da5a879d6 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 3 Jun 2024 10:32:16 -0400 Subject: [PATCH 142/454] handle testnet potfactory & pot contract patterns --- indexer_app/handler.py | 11 +++-------- pots/utils.py | 12 +++++++++--- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 0cdfa99..1981fb3 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -7,10 +7,7 @@ from near_lake_framework import near_primitives from base.utils import convert_ns_to_utc -from pots.utils import ( - match_pot_factory_version_pattern, - match_pot_subaccount_version_pattern, -) +from pots.utils import match_pot_factory_pattern, match_pot_subaccount_pattern from .logging import logger from .utils import ( @@ -129,14 +126,12 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess match method_name: case "new": - if match_pot_factory_version_pattern(receipt.receiver_id): + if match_pot_factory_pattern(receipt.receiver_id): logger.info(f"matched for factory pattern: {args_dict}") await handle_new_pot_factory( args_dict, receiver_id, created_at ) - elif match_pot_subaccount_version_pattern( - receipt.receiver_id - ): + elif match_pot_subaccount_pattern(receipt.receiver_id): logger.info( f"new pot deployment: {args_dict}, {action}" ) diff --git a/pots/utils.py b/pots/utils.py index 1a5f991..0d98146 100644 --- a/pots/utils.py +++ b/pots/utils.py @@ -1,15 +1,21 @@ import re -BASE_PATTERN = r"v\d+\.potfactory\.potlock\.near$" +from django.conf import settings +BASE_PATTERN = ( + r"^potlock\.testnet$" + if settings.ENVIRONMENT == "testnet" + else r"v\d+\.potfactory\.potlock\.near$" +) -def match_pot_factory_version_pattern(receiver): + +def match_pot_factory_pattern(receiver): """Matches the base pot factory version pattern without a subaccount. NB: does not currently handle testnet factory.""" pattern = f"^{BASE_PATTERN}" return bool(re.match(pattern, receiver)) -def match_pot_subaccount_version_pattern(receiver): +def match_pot_subaccount_pattern(receiver): """Matches the pot factory version pattern with a subaccount. NB: does not currently handle testnet factory.""" pattern = f"^[a-zA-Z0-9_]+\.{BASE_PATTERN}" return bool(re.match(pattern, receiver)) From 202e37f42521344d71005f18ade713354d1e1676 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:14:31 -0400 Subject: [PATCH 143/454] test throttle rate --- base/settings.py | 68 +++++++----------------------------------------- 1 file changed, 10 insertions(+), 58 deletions(-) diff --git a/base/settings.py b/base/settings.py index 7525f40..485e29f 100644 --- a/base/settings.py +++ b/base/settings.py @@ -97,6 +97,14 @@ REST_FRAMEWORK = { "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "PAGE_SIZE": DEFAULT_PAGE_SIZE, + "DEFAULT_THROTTLE_CLASSES": [ + # "rest_framework.throttling.UserRateThrottle", + "rest_framework.throttling.AnonRateThrottle", + ], + "DEFAULT_THROTTLE_RATES": { + # "user": "100/day", + "anon": "5/minute", + }, } @@ -273,64 +281,8 @@ LOGGING["loggers"]["indexer"]["handlers"].append("watchtower") LOGGING["loggers"]["jobs"]["handlers"].append("watchtower") -# log_level = getattr(logging, LOG_LEVEL, logging.INFO) -# print("LOG_LEVEL: ", LOG_LEVEL) -# # print("log_level: ", log_level) - -# if ENVIRONMENT != "local": -# AWS_REGION_NAME = "us-east-1" -# boto3_logs_client = boto3.client("logs", region_name=AWS_REGION_NAME) - - -# LOGGING = { -# "version": 1, -# "disable_existing_loggers": False, -# "root": { -# "level": log_level, -# # Adding the watchtower handler here causes all loggers in the project that -# # have propagate=True (the default) to send messages to watchtower. If you -# # wish to send only from specific loggers instead, remove "watchtower" here -# # and configure individual loggers below. -# # "handlers": ["watchtower", "console"], -# "handlers": ["console"], -# }, -# "handlers": { -# "console": { -# "class": "logging.StreamHandler", -# }, -# # "watchtower": { -# # "class": "watchtower.CloudWatchLogHandler", -# # "boto3_client": boto3_logs_client, -# # "log_group_name": "django-indexer", -# # # Decrease the verbosity level here to send only those logs to watchtower, -# # # but still see more verbose logs in the console. See the watchtower -# # # documentation for other parameters that can be set here. -# # "level": log_level, -# # }, -# }, -# "loggers": { -# # In the debug server (`manage.py runserver`), several Django system loggers cause -# # deadlocks when using threading in the logging handler, and are not supported by -# # watchtower. This limitation does not apply when running on production WSGI servers -# # (gunicorn, uwsgi, etc.), so we recommend that you set `propagate=True` below in your -# # production-specific Django settings file to receive Django system logs in CloudWatch. -# "django": {"level": log_level, "handlers": ["console"], "propagate": False} -# # Add any other logger-specific configuration here. -# }, -# } - -# if ENVIRONMENT != "local": -# LOGGING["handlers"]["watchtower"] = { -# "class": "watchtower.CloudWatchLogHandler", -# "boto3_client": boto3_logs_client, -# "log_group_name": "django-indexer", -# # Decrease the verbosity level here to send only those logs to watchtower, -# # but still see more verbose logs in the console. See the watchtower -# # documentation for other parameters that can be set here. -# "level": log_level, -# } - -# LOGGING["root"]["handlers"].append("watchtower") + +## SENTRY CONFIG sentry_sdk.init( environment=ENVIRONMENT, From 65bb172a03d3ef9436c1c763452d31f97e6606e0 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:19:18 -0400 Subject: [PATCH 144/454] update throttling to 100/minute --- base/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index 485e29f..5125bbf 100644 --- a/base/settings.py +++ b/base/settings.py @@ -103,7 +103,7 @@ ], "DEFAULT_THROTTLE_RATES": { # "user": "100/day", - "anon": "5/minute", + "anon": "100/minute", }, } From 224125b4d33c78f887a1c03eb186aa8b0d1f8d7c Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:41:54 -0400 Subject: [PATCH 145/454] Testnet (#27) * add deploy-testnet.yml * add testnet deployment workflow & appspec * testnet workflow debugging * upload appspec as artifact * upload zipped codebase to s3 in workflow * continue working on after_install_testnet * separate watchtower log groups by django env * add testnet potlock TLA to indexer handler * add POTLOCK_TLA to settings * add dev.potlock.io and test-dev.potlock.io to allowed hosts * remove admin dashboard edit access * remove admin edit permission for all models * add timestamp to deployment zip filename * add env vars to deploy-testnet * add after_install_dev script * add after_install debug logs * handle testnet potfactory & pot contract patterns * test throttle rate * update throttling to 100/minute --- base/settings.py | 68 +++++---------------------------- indexer_app/handler.py | 11 ++---- pots/utils.py | 12 ++++-- scripts/after_install_dev.sh | 73 ------------------------------------ 4 files changed, 22 insertions(+), 142 deletions(-) diff --git a/base/settings.py b/base/settings.py index 7525f40..5125bbf 100644 --- a/base/settings.py +++ b/base/settings.py @@ -97,6 +97,14 @@ REST_FRAMEWORK = { "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "PAGE_SIZE": DEFAULT_PAGE_SIZE, + "DEFAULT_THROTTLE_CLASSES": [ + # "rest_framework.throttling.UserRateThrottle", + "rest_framework.throttling.AnonRateThrottle", + ], + "DEFAULT_THROTTLE_RATES": { + # "user": "100/day", + "anon": "100/minute", + }, } @@ -273,64 +281,8 @@ LOGGING["loggers"]["indexer"]["handlers"].append("watchtower") LOGGING["loggers"]["jobs"]["handlers"].append("watchtower") -# log_level = getattr(logging, LOG_LEVEL, logging.INFO) -# print("LOG_LEVEL: ", LOG_LEVEL) -# # print("log_level: ", log_level) - -# if ENVIRONMENT != "local": -# AWS_REGION_NAME = "us-east-1" -# boto3_logs_client = boto3.client("logs", region_name=AWS_REGION_NAME) - - -# LOGGING = { -# "version": 1, -# "disable_existing_loggers": False, -# "root": { -# "level": log_level, -# # Adding the watchtower handler here causes all loggers in the project that -# # have propagate=True (the default) to send messages to watchtower. If you -# # wish to send only from specific loggers instead, remove "watchtower" here -# # and configure individual loggers below. -# # "handlers": ["watchtower", "console"], -# "handlers": ["console"], -# }, -# "handlers": { -# "console": { -# "class": "logging.StreamHandler", -# }, -# # "watchtower": { -# # "class": "watchtower.CloudWatchLogHandler", -# # "boto3_client": boto3_logs_client, -# # "log_group_name": "django-indexer", -# # # Decrease the verbosity level here to send only those logs to watchtower, -# # # but still see more verbose logs in the console. See the watchtower -# # # documentation for other parameters that can be set here. -# # "level": log_level, -# # }, -# }, -# "loggers": { -# # In the debug server (`manage.py runserver`), several Django system loggers cause -# # deadlocks when using threading in the logging handler, and are not supported by -# # watchtower. This limitation does not apply when running on production WSGI servers -# # (gunicorn, uwsgi, etc.), so we recommend that you set `propagate=True` below in your -# # production-specific Django settings file to receive Django system logs in CloudWatch. -# "django": {"level": log_level, "handlers": ["console"], "propagate": False} -# # Add any other logger-specific configuration here. -# }, -# } - -# if ENVIRONMENT != "local": -# LOGGING["handlers"]["watchtower"] = { -# "class": "watchtower.CloudWatchLogHandler", -# "boto3_client": boto3_logs_client, -# "log_group_name": "django-indexer", -# # Decrease the verbosity level here to send only those logs to watchtower, -# # but still see more verbose logs in the console. See the watchtower -# # documentation for other parameters that can be set here. -# "level": log_level, -# } - -# LOGGING["root"]["handlers"].append("watchtower") + +## SENTRY CONFIG sentry_sdk.init( environment=ENVIRONMENT, diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 0cdfa99..1981fb3 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -7,10 +7,7 @@ from near_lake_framework import near_primitives from base.utils import convert_ns_to_utc -from pots.utils import ( - match_pot_factory_version_pattern, - match_pot_subaccount_version_pattern, -) +from pots.utils import match_pot_factory_pattern, match_pot_subaccount_pattern from .logging import logger from .utils import ( @@ -129,14 +126,12 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess match method_name: case "new": - if match_pot_factory_version_pattern(receipt.receiver_id): + if match_pot_factory_pattern(receipt.receiver_id): logger.info(f"matched for factory pattern: {args_dict}") await handle_new_pot_factory( args_dict, receiver_id, created_at ) - elif match_pot_subaccount_version_pattern( - receipt.receiver_id - ): + elif match_pot_subaccount_pattern(receipt.receiver_id): logger.info( f"new pot deployment: {args_dict}, {action}" ) diff --git a/pots/utils.py b/pots/utils.py index 1a5f991..0d98146 100644 --- a/pots/utils.py +++ b/pots/utils.py @@ -1,15 +1,21 @@ import re -BASE_PATTERN = r"v\d+\.potfactory\.potlock\.near$" +from django.conf import settings +BASE_PATTERN = ( + r"^potlock\.testnet$" + if settings.ENVIRONMENT == "testnet" + else r"v\d+\.potfactory\.potlock\.near$" +) -def match_pot_factory_version_pattern(receiver): + +def match_pot_factory_pattern(receiver): """Matches the base pot factory version pattern without a subaccount. NB: does not currently handle testnet factory.""" pattern = f"^{BASE_PATTERN}" return bool(re.match(pattern, receiver)) -def match_pot_subaccount_version_pattern(receiver): +def match_pot_subaccount_pattern(receiver): """Matches the pot factory version pattern with a subaccount. NB: does not currently handle testnet factory.""" pattern = f"^[a-zA-Z0-9_]+\.{BASE_PATTERN}" return bool(re.match(pattern, receiver)) diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh index 0cc5a0a..d9b54f1 100755 --- a/scripts/after_install_dev.sh +++ b/scripts/after_install_dev.sh @@ -65,76 +65,3 @@ else fi echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_dev.sh completed" >> "$LOG_FILE" - - - - -# #!/bin/bash -# # Log output to a specific file -# LOG_FILE="/home/ec2-user/django-indexer-dev/logs/deploy.log" - -# # print placeholder -# echo -e "\n THIS IS A PLACEHOLDER \n" >> "$LOG_FILE" - -# echo -e "\n\n" >> "$LOG_FILE" -# echo "=========================================" >> "$LOG_FILE" -# echo "Running after_install_dev.sh at $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" -# echo "=========================================" >> "$LOG_FILE" - -# # Load env vars -# source /home/ec2-user/.bashrc - -# # Set correct ownership recursively for project directory -# sudo chown -R ec2-user:nginx /home/ec2-user/django-indexer/ -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Corrected ownership to ec2-user:nginx" >> "$LOG_FILE" - -# # Set the necessary permissions -# sudo chmod -R 775 /home/ec2-user/django-indexer/ -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Set permissions to 775" >> "$LOG_FILE" - -# # Restart nginx to apply any configuration changes -# sudo systemctl restart nginx -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Restarted nginx" >> "$LOG_FILE" - -# # Define the project directory -# PROJECT_DIR="/home/ec2-user/django-indexer" - -# # Navigate to the project directory -# cd "$PROJECT_DIR" - -# # Source the specific poetry virtual environment -# source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.11/bin/activate" # TODO: UPDATE THIS - -# # Install dependencies using Poetry -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" -# poetry install >> "$LOG_FILE" -# echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" - -# # Check if there are pending migrations and log the output -# echo "Checking for pending migrations..." >> "$LOG_FILE" -# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep '\[ \]' 2>&1) # Redirect stderr to stdout -# echo "Migration check output: $PENDING_MIGRATIONS" >> "$LOG_FILE" - -# # Log the full output of showmigrations -# echo "Checking for pending migrations..." >> "$LOG_FILE" -# poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full output to diagnose - -# # Check for unapplied migrations -# PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations - -# if [ "$PENDING_MIGRATIONS" -gt 0 ]; then -# echo "Migrations found; stopping services..." >> "$LOG_FILE" -# sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat - -# echo 'Applying migrations...' >> "$LOG_FILE" -# poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 - -# echo 'Starting services...' >> "$LOG_FILE" -# sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat -# else -# echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" -# poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 -# sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat -# fi - -# echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_dev.sh completed" >> "$LOG_FILE" From 1a3e8d74de05c213fb27ff06ae4cca59caf7c759 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:46:43 -0400 Subject: [PATCH 146/454] update readme with API urls --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e04c4f7..fc89206 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@ - [Steps to run:](#steps-to-run) - [Env vars example](#env-vars-example) - [API Basics](#api-basics) - - [Base URL](#base-url) - - [Authorization](#authorization) - - [Error Responses](#error-responses) - - [Pagination](#pagination) + - [Base URL](#base-url) + - [Authorization](#authorization) + - [Error Responses](#error-responses) + - [Pagination](#pagination) - [API Endpoints](#api-endpoints) - [`Account` endpoints](#account-endpoints) - [✅ Get all accounts: `GET /accounts` (paginated)](#-get-all-accounts-get-accounts-paginated) @@ -52,6 +52,7 @@ - If for some reason this doesn't kill any active celery tasks, run `ps auxww | grep 'celery' | grep -v grep` and kill resulting PIDs Extra commands that might come in useful: + - Purge celery queue (`celery -A base purge`) ### Env vars example @@ -77,13 +78,14 @@ export PL_SENTRY_DSN= #### Base URL -`/api/v1/` +**dev (mainnet):** `https://dev.potlock.io/api/v1/` +**testnet:** `https://test-dev.potlock.io/api/v1/` #### Authorization This is a public, read-only API and as such does not currently implement authentication or authorization. -Rate limits of (FILL THIS IN) are enforced to ensure service for all users. +Rate limits of 100 requests/min are enforced to ensure service for all users. #### Error Responses From 2e159f95d120532d712cddf45ce5835af0101272 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:47:38 -0400 Subject: [PATCH 147/454] Testnet (#28) * add deploy-testnet.yml * add testnet deployment workflow & appspec * testnet workflow debugging * upload appspec as artifact * upload zipped codebase to s3 in workflow * continue working on after_install_testnet * separate watchtower log groups by django env * add testnet potlock TLA to indexer handler * add POTLOCK_TLA to settings * add dev.potlock.io and test-dev.potlock.io to allowed hosts * remove admin dashboard edit access * remove admin edit permission for all models * add timestamp to deployment zip filename * add env vars to deploy-testnet * add after_install_dev script * add after_install debug logs * handle testnet potfactory & pot contract patterns * test throttle rate * update throttling to 100/minute * update readme with API urls --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e04c4f7..fc89206 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@ - [Steps to run:](#steps-to-run) - [Env vars example](#env-vars-example) - [API Basics](#api-basics) - - [Base URL](#base-url) - - [Authorization](#authorization) - - [Error Responses](#error-responses) - - [Pagination](#pagination) + - [Base URL](#base-url) + - [Authorization](#authorization) + - [Error Responses](#error-responses) + - [Pagination](#pagination) - [API Endpoints](#api-endpoints) - [`Account` endpoints](#account-endpoints) - [✅ Get all accounts: `GET /accounts` (paginated)](#-get-all-accounts-get-accounts-paginated) @@ -52,6 +52,7 @@ - If for some reason this doesn't kill any active celery tasks, run `ps auxww | grep 'celery' | grep -v grep` and kill resulting PIDs Extra commands that might come in useful: + - Purge celery queue (`celery -A base purge`) ### Env vars example @@ -77,13 +78,14 @@ export PL_SENTRY_DSN= #### Base URL -`/api/v1/` +**dev (mainnet):** `https://dev.potlock.io/api/v1/` +**testnet:** `https://test-dev.potlock.io/api/v1/` #### Authorization This is a public, read-only API and as such does not currently implement authentication or authorization. -Rate limits of (FILL THIS IN) are enforced to ensure service for all users. +Rate limits of 100 requests/min are enforced to ensure service for all users. #### Error Responses From 4d2fdb7c383f95375f16339cd4ee4e3a2f9a0a33 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:53:00 -0400 Subject: [PATCH 148/454] handle None new pot owner --- indexer_app/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 5f394a3..0b8d161 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -42,7 +42,8 @@ async def handle_new_pot( logger.info("new pot deployment process... upsert accounts,") # Upsert accounts - owner, _ = await Account.objects.aget_or_create(id=data["owner"]) + if data.get("owner"): + owner, _ = await Account.objects.aget_or_create(id=data["owner"]) signer, _ = await Account.objects.aget_or_create(id=signerId) receiver, _ = await Account.objects.aget_or_create(id=receiverId) @@ -57,7 +58,9 @@ async def handle_new_pot( "deployer": signer, "deployed_at": created_at, "source_metadata": data["source_metadata"], - "owner_id": data["owner"], + "owner_id": data.get( + "owner", signerId + ), # owner is optional; if not provided, owner will be transaction signer (this logic is implemented by Pot contract's "new" method) "chef_id": data.get("chef"), "name": data["pot_name"], "description": data["pot_description"], From 7adb8ce20e9f73561cb31fb704f77264c0a2c644 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:06:14 -0400 Subject: [PATCH 149/454] fix owner_id in new pot --- indexer_app/utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 0b8d161..3e8ee85 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -42,8 +42,10 @@ async def handle_new_pot( logger.info("new pot deployment process... upsert accounts,") # Upsert accounts - if data.get("owner"): - owner, _ = await Account.objects.aget_or_create(id=data["owner"]) + owner_id = ( + data.get("owner") or signerId + ) # owner is optional; if not provided, owner will be transaction signer (this logic is implemented by Pot contract's "new" method) + owner, _ = await Account.objects.aget_or_create(id=owner_id) signer, _ = await Account.objects.aget_or_create(id=signerId) receiver, _ = await Account.objects.aget_or_create(id=receiverId) @@ -52,15 +54,13 @@ async def handle_new_pot( chef, _ = await Account.objects.aget_or_create(id=data["chef"]) # Create Pot object - logger.info("create pot....") + logger.info(f"creating pot with owner {owner_id}....") pot_defaults = { "pot_factory_id": predecessorId, "deployer": signer, "deployed_at": created_at, "source_metadata": data["source_metadata"], - "owner_id": data.get( - "owner", signerId - ), # owner is optional; if not provided, owner will be transaction signer (this logic is implemented by Pot contract's "new" method) + "owner_id": owner_id, "chef_id": data.get("chef"), "name": data["pot_name"], "description": data["pot_description"], From 79eb67a844701f22cf656ac1cd7656f65b1ce69a Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:18:58 -0400 Subject: [PATCH 150/454] fix keyerrors in pots admin --- pots/admin.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pots/admin.py b/pots/admin.py index aca414b..41243af 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -22,7 +22,11 @@ class Meta: def __init__(self, *args, **kwargs): super(PotFactoryForm, self).__init__(*args, **kwargs) # Ensure self.instance is available before accessing it - if self.instance.pk: + if ( + self.instance.pk + and "admins" in self.fields + and "whitelisted_deployers" in self.fields + ): # Set the queryset for the admins field to only include relevant accounts self.fields["admins"].queryset = self.instance.admins.all() # Set the queryset for the whitelisted_deployers field to only include relevant accounts @@ -40,10 +44,13 @@ class PotFactoryAdmin(admin.ModelAdmin): def get_form(self, request, obj=None, **kwargs): form = super(PotFactoryAdmin, self).get_form(request, obj, **kwargs) if obj: - form.base_fields["admins"].queryset = obj.admins.all() - form.base_fields["whitelisted_deployers"].queryset = ( - obj.whitelisted_deployers.all() - ) + try: + form.base_fields["admins"].queryset = obj.admins.all() + form.base_fields["whitelisted_deployers"].queryset = ( + obj.whitelisted_deployers.all() + ) + except KeyError: + pass return form def has_add_permission(self, request): @@ -64,7 +71,7 @@ class Meta: def __init__(self, *args, **kwargs): super(PotForm, self).__init__(*args, **kwargs) # Ensure self.instance is available before accessing it - if self.instance.pk: + if self.instance.pk and "admins" in self.fields: # Set the queryset for the admins field to only include relevant accounts self.fields["admins"].queryset = self.instance.admins.all() @@ -79,7 +86,10 @@ class PotAdmin(admin.ModelAdmin): def get_form(self, request, obj=None, **kwargs): form = super(PotAdmin, self).get_form(request, obj, **kwargs) if obj: - form.base_fields["admins"].queryset = obj.admins.all() + try: + form.base_fields["admins"].queryset = obj.admins.all() + except KeyError: + pass return form def has_add_permission(self, request): From 2358477c658f5c16c1d43fdb88d944a93f330ef1 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:00:19 -0400 Subject: [PATCH 151/454] add swagger (drf-spectacular) --- api/urls.py | 15 +++++++++++++++ base/settings.py | 9 +++++++++ pyproject.toml | 1 + 3 files changed, 25 insertions(+) diff --git a/api/urls.py b/api/urls.py index dda1608..e8a5f57 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,4 +1,9 @@ from django.urls import path +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularRedocView, + SpectacularSwaggerView, +) from accounts.api import AccountsAPI, DonorsAPI from base.api import StatsAPI @@ -6,6 +11,16 @@ from pots.api import PotsAPI urlpatterns = [ + # schema + path("schema/", SpectacularAPIView.as_view(), name="schema"), + path( + "schema/swagger-ui/", + SpectacularSwaggerView.as_view(url_name="schema"), + name="swagger-ui", + ), + path( + "schema/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc" + ), # accounts path("v1/accounts", AccountsAPI.as_view(), name="accounts_api"), path( diff --git a/base/settings.py b/base/settings.py index 5125bbf..dcfdc83 100644 --- a/base/settings.py +++ b/base/settings.py @@ -79,6 +79,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework", + "drf_spectacular", "corsheaders", # "cachalot", "celery", @@ -105,6 +106,14 @@ # "user": "100/day", "anon": "100/minute", }, + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", +} + +SPECTACULAR_SETTINGS = { + "TITLE": "PotLock Indexer API " + ENVIRONMENT, + "DESCRIPTION": "Indexed data for the PotLock protocol", + "VERSION": "1.0.0", + "SERVE_INCLUDE_SCHEMA": False, } diff --git a/pyproject.toml b/pyproject.toml index 1bb5530..dca95a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ gunicorn = "^22.0.0" sentry-sdk = {extras = ["django"], version = "^1.45.0"} watchtower = "^3.1.0" django-cors-headers = "^4.3.1" +drf-spectacular = "^0.27.2" [tool.poetry.group.dev.dependencies] black = "^24.3.0" From 7155a7b274eefd5effb29af1fd880734b051d16d Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:20:31 -0400 Subject: [PATCH 152/454] add poetry lock --no-update to after_install_testnet --- scripts/after_install_testnet.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/after_install_testnet.sh b/scripts/after_install_testnet.sh index 94c517b..32e4356 100755 --- a/scripts/after_install_testnet.sh +++ b/scripts/after_install_testnet.sh @@ -34,6 +34,7 @@ source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-AhfQkQzj-py3.1 # Install dependencies using Poetry echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +poetry lock --no-update >> "$LOG_FILE" poetry install >> "$LOG_FILE" echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" From 3f53f6ecda0f90a9dd91faddd62f45c058618fa0 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:24:44 -0400 Subject: [PATCH 153/454] add poetry lock --no-update to after_install_dev --- scripts/after_install_dev.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh index d9b54f1..01bed73 100755 --- a/scripts/after_install_dev.sh +++ b/scripts/after_install_dev.sh @@ -34,6 +34,7 @@ source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.1 # Install dependencies using Poetry echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +poetry lock --no-update >> "$LOG_FILE" poetry install >> "$LOG_FILE" echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" From 2c4737f333872a8dda689adb592f8029b69b86f1 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:19:53 -0400 Subject: [PATCH 154/454] add api response examples --- accounts/api.py | 197 ++++++++++++++++++++------- accounts/serializers.py | 12 +- api/urls.py | 63 ++++++--- base/api.py | 39 ++++++ base/serializers.py | 8 ++ donations/serializers.py | 28 ++++ lists/api.py | 168 ++++++++++++++++++----- pots/api.py | 284 ++++++++++++++++++++++++++++++--------- pots/serializers.py | 110 ++++++++++++++- 9 files changed, 743 insertions(+), 166 deletions(-) create mode 100644 base/serializers.py diff --git a/accounts/api.py b/accounts/api.py index 99d1141..dbd51e9 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -2,6 +2,12 @@ from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page +from drf_spectacular.utils import ( + OpenApiExample, + OpenApiParameter, + OpenApiResponse, + extend_schema, +) from rest_framework.pagination import LimitOffsetPagination from rest_framework.request import Request from rest_framework.response import Response @@ -10,17 +16,41 @@ from base.logging import logger from donations.models import Donation from pots.models import Pot, PotApplication, PotApplicationStatus -from pots.serializers import PotSerializer +from pots.serializers import SIMPLE_POT_EXAMPLE, PotSerializer from .models import Account -from .serializers import AccountSerializer +from .serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer class DonorsAPI(APIView, LimitOffsetPagination): - def dispatch(self, request, *args, **kwargs): - return super(DonorsAPI, self).dispatch(request, *args, **kwargs) @method_decorator(cache_page(60 * 15)) # Cache for 15 mins + @extend_schema( + parameters=[ + OpenApiParameter( + name="sort", + type=str, + location=OpenApiParameter.QUERY, + description="Sort by field, e.g., most_donated_usd", + ) + ], + responses={ + 200: OpenApiResponse( + response=AccountSerializer(many=True), + description="Returns a list of donor accounts", + examples=[ + OpenApiExample( + "example-1", + summary="user.near", + description="Example response for donor accounts", + value=SIMPLE_ACCOUNT_EXAMPLE, + response_only=True, + ), + ], + ), + 500: OpenApiResponse(description="Internal server error"), + }, + ) def get(self, request: Request, *args, **kwargs): # Return all donors donations_subquery = Donation.objects.filter(donor_id=OuterRef("pk")) @@ -36,47 +66,122 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountsAPI(APIView, LimitOffsetPagination): - def dispatch(self, request, *args, **kwargs): - return super(AccountsAPI, self).dispatch(request, *args, **kwargs) +@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins +class AccountsListAPI(APIView, LimitOffsetPagination): - @method_decorator(cache_page(60 * 15)) # Cache for 15 mins + @extend_schema( + responses={ + 200: OpenApiResponse( + response=AccountSerializer(many=True), + description="Returns a list of accounts", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for accounts data", + value=SIMPLE_ACCOUNT_EXAMPLE, + response_only=True, + ), + ], + ), + 500: OpenApiResponse(description="Internal server error"), + } + ) + def get(self, request: Request, *args, **kwargs): + accounts = Account.objects.all() + results = self.paginate_queryset(accounts, request, view=self) + serializer = AccountSerializer(results, many=True) + return self.get_paginated_response(serializer.data) + + +@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins +class AccountDetailAPI(APIView): + + @extend_schema( + parameters=[ + OpenApiParameter("account_id", str, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=AccountSerializer, + description="Returns account details", + examples=[ + OpenApiExample( + "example-1", + summary="user.near", + description="Example response for account detail", + value=SIMPLE_ACCOUNT_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Account not found"), + 500: OpenApiResponse(description="Internal server error"), + }, + ) def get(self, request: Request, *args, **kwargs): - account_id = kwargs.get("account_id", None) - action = kwargs.get("action", None) - if account_id: - # Request pertaining to a specific account_id - try: - account = Account.objects.get(id=account_id) - except Account.DoesNotExist: - return Response( - {"message": f"Account with ID {account_id} not found."}, status=404 - ) - if action: - # Handle action if present; only valid option currently is "active_pots" - if action == "active_pots": - # Return active pots for account_id - now = timezone.now() - applications = PotApplication.objects.filter( - applicant=account, status=PotApplicationStatus.APPROVED - ) - pot_ids = applications.values_list("pot_id", flat=True) - pots = Pot.objects.filter(id__in=pot_ids) - if request.query_params.get("status") == "live": - pots = pots.filter( - matching_round_start__lte=now, matching_round_end__gte=now - ) - results = self.paginate_queryset(pots, request, view=self) - serializer = PotSerializer(results, many=True) - return self.get_paginated_response(serializer.data) - return Response({"message": f"Invalid action {action}."}, status=400) - # If no action, return account by account_id - else: - serializer = AccountSerializer(account) - return Response(serializer.data) - else: - # No account_id specified; return all accounts - accounts = Account.objects.all() - results = self.paginate_queryset(accounts, request, view=self) - serializer = AccountSerializer(results, many=True) - return self.get_paginated_response(serializer.data) + account_id = kwargs.get("account_id") + try: + account = Account.objects.get(id=account_id) + except Account.DoesNotExist: + return Response( + {"message": f"Account with ID {account_id} not found."}, status=404 + ) + serializer = AccountSerializer(account) + return Response(serializer.data) + + +@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins +class AccountActivePotsAPI(APIView, LimitOffsetPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("account_id", str, OpenApiParameter.PATH), + OpenApiParameter( + "status", + str, + OpenApiParameter.QUERY, + required=False, + description="Filter by pot status", + ), + ], + responses={ + 200: OpenApiResponse( + response=PotSerializer(many=True), + description="Returns paginated active pots for the account", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for active pots", + value=SIMPLE_POT_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Account not found"), + 500: OpenApiResponse(description="Internal server error"), + }, + ) + def get(self, request: Request, *args, **kwargs): + account_id = kwargs.get("account_id") + try: + account = Account.objects.get(id=account_id) + except Account.DoesNotExist: + return Response( + {"message": f"Account with ID {account_id} not found."}, status=404 + ) + + now = timezone.now() + applications = PotApplication.objects.filter( + applicant=account, status=PotApplicationStatus.APPROVED + ) + pot_ids = applications.values_list("pot_id", flat=True) + pots = Pot.objects.filter(id__in=pot_ids) + if request.query_params.get("status") == "live": + pots = pots.filter( + matching_round_start__lte=now, matching_round_end__gte=now + ) + results = self.paginate_queryset(pots, request, view=self) + serializer = PotSerializer(results, many=True) + return self.get_paginated_response(serializer.data) diff --git a/accounts/serializers.py b/accounts/serializers.py index e051984..4f330e9 100644 --- a/accounts/serializers.py +++ b/accounts/serializers.py @@ -1,9 +1,10 @@ +from rest_framework import serializers from rest_framework.serializers import ModelSerializer, SerializerMethodField from .models import Account -class AccountSerializer(ModelSerializer): +class AccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = [ @@ -13,3 +14,12 @@ class Meta: "total_matching_pool_allocations_usd", "donors_count", ] + + +SIMPLE_ACCOUNT_EXAMPLE = { + "id": "user.near", + "total_donations_in_usd": "740.00", + "total_donations_out_usd": "1234.56", + "total_matching_pool_allocations_usd": "800.01", + "donors_count": 321, +} diff --git a/api/urls.py b/api/urls.py index e8a5f57..6509a4f 100644 --- a/api/urls.py +++ b/api/urls.py @@ -5,10 +5,22 @@ SpectacularSwaggerView, ) -from accounts.api import AccountsAPI, DonorsAPI +from accounts.api import ( + AccountActivePotsAPI, + AccountDetailAPI, + AccountsListAPI, + DonorsAPI, +) from base.api import StatsAPI -from lists.api import ListsAPI -from pots.api import PotsAPI +from lists.api import ListDetailAPI, ListRegistrationsAPI, ListsListAPI +from pots.api import ( + PotApplicationsAPI, + PotDetailAPI, + PotDonationsAPI, + PotPayoutsAPI, + PotsListAPI, + PotSponsorsAPI, +) urlpatterns = [ # schema @@ -22,32 +34,47 @@ "schema/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc" ), # accounts - path("v1/accounts", AccountsAPI.as_view(), name="accounts_api"), + path("v1/accounts", AccountsListAPI.as_view(), name="accounts_api"), path( - "v1/accounts/", AccountsAPI.as_view(), name="accounts_api_by_id" + "v1/accounts/", + AccountDetailAPI.as_view(), + name="accounts_api_by_id", ), path( - "v1/accounts//", # e.g. /accounts/lachlan.near/active_pots - consider putting this under /pots instead of /accounts since Pots are the resource being fetched, even though the action is being performed on an Account - AccountsAPI.as_view(), - name="accounts_api_by_id_with_action", + "v1/accounts//active_pots", + AccountActivePotsAPI.as_view(), + name="accounts_api_by_id_active_pots", ), # donors path("v1/donors", DonorsAPI.as_view(), name="donors_api"), # lists - path("v1/lists", ListsAPI.as_view(), name="lists_api"), - path("v1/lists/", ListsAPI.as_view(), name="lists_api_by_id"), + path("v1/lists", ListsListAPI.as_view(), name="lists_api"), + path("v1/lists/", ListDetailAPI.as_view(), name="lists_api_by_id"), path( - "v1/lists//", # e.g. /lists/1/registrations - ListsAPI.as_view(), - name="lists_api_by_id_with_action", + "v1/lists//registrations", + ListRegistrationsAPI.as_view(), + name="lists_api_by_id_registrations", ), # pots - path("v1/pots", PotsAPI.as_view(), name="pots_api"), - path("v1/pots//", PotsAPI.as_view(), name="pots_api_by_id"), + path("v1/pots", PotsListAPI.as_view(), name="pots_api"), + path("v1/pots//", PotDetailAPI.as_view(), name="pots_api_by_id"), + path( + "v1/pots//applications", + PotApplicationsAPI.as_view(), + name="pots_applications_api", + ), + path( + "v1/pots//donations", + PotDonationsAPI.as_view(), + name="pots_donations_api", + ), + path( + "v1/pots//sponsors", + PotSponsorsAPI.as_view(), + name="pots_sponsors_api", + ), path( - "v1/pots//", - PotsAPI.as_view(), - name="pots_api_by_id_with_action", + "v1/pots//payouts", PotPayoutsAPI.as_view(), name="pots_payouts_api" ), # stats path("v1/stats", StatsAPI.as_view(), name="stats_api"), diff --git a/base/api.py b/base/api.py index ef52d70..37bd407 100644 --- a/base/api.py +++ b/base/api.py @@ -2,6 +2,13 @@ from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page +from drf_spectacular.utils import ( + OpenApiExample, + OpenApiParameter, + OpenApiResponse, + extend_schema, +) +from rest_framework import serializers from rest_framework.pagination import LimitOffsetPagination from rest_framework.request import Request from rest_framework.response import Response @@ -12,6 +19,14 @@ from pots.models import PotPayout +class StatsResponseSerializer(serializers.Serializer): + total_donations_usd = serializers.FloatField() + total_payouts_usd = serializers.FloatField() + total_donations_count = serializers.IntegerField() + total_donors_count = serializers.IntegerField() + total_recipients_count = serializers.IntegerField() + + class StatsAPI(APIView): def dispatch(self, request, *args, **kwargs): return super(StatsAPI, self).dispatch(request, *args, **kwargs) @@ -19,6 +34,30 @@ def dispatch(self, request, *args, **kwargs): @method_decorator( cache_page(60 * 5) ) # Cache for 5 mins (using page-level caching for now for simplicity, but can move to data-level caching if desired) + @extend_schema( + responses={ + 200: OpenApiResponse( + response=StatsResponseSerializer, + description="Returns statistics data", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for statistics data", + value={ + "total_donations_usd": 12345.67, + "total_payouts_usd": 8901.23, + "total_donations_count": 456, + "total_donors_count": 789, + "total_recipients_count": 321, + }, + response_only=True, + ), + ], + ), + 500: OpenApiResponse(description="Internal server error"), + } + ) def get(self, request: Request, *args, **kwargs): total_donations_usd = ( Donation.objects.all().aggregate(Sum("total_amount_usd"))[ diff --git a/base/serializers.py b/base/serializers.py new file mode 100644 index 0000000..a47e77c --- /dev/null +++ b/base/serializers.py @@ -0,0 +1,8 @@ +from rest_framework import serializers + + +class TwoDecimalStringField(serializers.DecimalField): + def to_representation(self, value): + if value is None: + return value + return format(value, ".2f") # 2 decimal places diff --git a/donations/serializers.py b/donations/serializers.py index b772dbd..9fd8783 100644 --- a/donations/serializers.py +++ b/donations/serializers.py @@ -1,5 +1,7 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField +from pots.serializers import EXAMPLE_POT_ID + from .models import Donation @@ -7,3 +9,29 @@ class DonationSerializer(ModelSerializer): class Meta: model = Donation fields = "__all__" # TODO: potentially adjust this e.g. for formatting of datetimes, adding convenience fields etc + + +SIMPLE_DONATION_EXAMPLE = { + "id": 100, + "on_chain_id": 156, + "total_amount": "1000000000000000000000000", + "total_amount_usd": "1.17", + "net_amount": "1000000000000000000000000", + "net_amount_usd": "1.02", + "matching_pool": True, + "message": None, + "donated_at": "2024-06-05T18:08:40.751Z", + "protocol_fee": "10000000000000000000000", + "protocol_fee_usd": "0.27", + "referrer_fee": "10000000000000000000000", + "referrer_fee_usd": "0.27", + "chef_fee": "10000000000000000000000", + "chef_fee_usd": "0.27", + "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", + "donor": "donor.near", + "ft": "near", + "pot": EXAMPLE_POT_ID, + "recipient": "someproject.near", + "referrer": "somereferrer.near", + "chef": "chef.near", +} diff --git a/lists/api.py b/lists/api.py index 44ef940..eb4885b 100644 --- a/lists/api.py +++ b/lists/api.py @@ -2,6 +2,12 @@ from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page +from drf_spectacular.utils import ( + OpenApiExample, + OpenApiParameter, + OpenApiResponse, + extend_schema, +) from rest_framework.pagination import LimitOffsetPagination from rest_framework.request import Request from rest_framework.response import Response @@ -10,43 +16,131 @@ from .models import List from .serializers import ListRegistrationSerializer, ListSerializer +SIMPLE_LIST_EXAMPLE = { + "id": 1, + "name": "PotLock Public Goods Registry", + "description": "The official registry of public goods on NEAR Protocol", + "cover_image_url": "https://images.unsplash.com/photo-1593642532973-d31b6557fa68", + "admin_only_registrations": True, + "default_registration_status": "Approved", + "created_at": "2024-06-05T17:55:14.030Z", + "updated_at": "2024-06-05T17:55:14.030Z", + "owner": "potlock.near", + "admins": ["plugrel.near"], +} -class ListsAPI(APIView, LimitOffsetPagination): - def dispatch(self, request, *args, **kwargs): - return super(ListsAPI, self).dispatch(request, *args, **kwargs) +SIMPLE_LIST_REGISTRATION_EXAMPLE = { + "id": 10, + "status": "Approved", + "submitted_at": "2024-06-05T18:01:02.319Z", + "updated_at": "2024-06-05T18:01:02.319Z", + "registrant_notes": "I'm excited to apply for this list", + "admin_notes": "This is a great project that I want on my list.", + "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", + "list": 1, + "registrant": "project.near", + "registered_by": "project-admin.near", +} - @method_decorator(cache_page(60 * 15)) # Cache for 15 mins + +@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins +class ListsListAPI(APIView, LimitOffsetPagination): + + @extend_schema( + responses={ + 200: OpenApiResponse( + response=ListSerializer(many=True), + description="Returns a list of lists", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for lists", + value=SIMPLE_LIST_EXAMPLE, + response_only=True, + ), + ], + ), + 500: OpenApiResponse(description="Internal server error"), + } + ) + def get(self, request: Request, *args, **kwargs): + lists = List.objects.all() + results = self.paginate_queryset(lists, request, view=self) + serializer = ListSerializer(results, many=True) + return self.get_paginated_response(serializer.data) + + +class ListDetailAPI(APIView): + + @extend_schema( + parameters=[ + OpenApiParameter("list_id", int, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=ListSerializer, + description="Returns list details", + examples=[ + OpenApiExample( + "example-1", + summary="Simple list example", + description="Example response for list detail", + value=SIMPLE_LIST_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="List not found"), + 500: OpenApiResponse(description="Internal server error"), + }, + ) def get(self, request: Request, *args, **kwargs): - list_id = kwargs.get("list_id", None) - action = kwargs.get("action", None) - if list_id: - # Request pertaining to a specific list_id - try: - list = List.objects.get(id=list_id) - except List.DoesNotExist: - return Response( - {"message": f"List with ID {list_id} not found."}, status=404 - ) - if action: - # Handle action if present; only valid option currently is "registrations" - if action == "registrations": - # Return registrations for list_id - registrations = list.registrations.all() - results = self.paginate_queryset(registrations, request, view=self) - serializer = ListRegistrationSerializer(results, many=True) - return self.get_paginated_response(serializer.data) - else: - return Response( - {"error": f"Invalid action: {action}"}, - status=400, - ) - else: - # Return list - serializer = ListSerializer(list) - return Response(serializer.data) - else: - # Return all lists - lists = List.objects.all() - results = self.paginate_queryset(lists, request, view=self) - serializer = ListSerializer(results, many=True) - return self.get_paginated_response(serializer.data) + list_id = kwargs.get("list_id") + try: + list_obj = List.objects.get(id=list_id) + except List.DoesNotExist: + return Response( + {"message": f"List with ID {list_id} not found."}, status=404 + ) + serializer = ListSerializer(list_obj) + return Response(serializer.data) + + +class ListRegistrationsAPI(APIView, LimitOffsetPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("list_id", int, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=ListRegistrationSerializer(many=True), + description="Returns registrations for the list", + examples=[ + OpenApiExample( + "example-1", + summary="Simple registration example", + description="Example response for list registrations", + value=SIMPLE_LIST_REGISTRATION_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="List not found"), + 500: OpenApiResponse(description="Internal server error"), + }, + ) + def get(self, request: Request, *args, **kwargs): + list_id = kwargs.get("list_id") + try: + list_obj = List.objects.get(id=list_id) + except List.DoesNotExist: + return Response( + {"message": f"List with ID {list_id} not found."}, status=404 + ) + + registrations = list_obj.registrations.all() + results = self.paginate_queryset(registrations, request, view=self) + serializer = ListRegistrationSerializer(results, many=True) + return self.get_paginated_response(serializer.data) diff --git a/pots/api.py b/pots/api.py index a568d4d..19742f7 100644 --- a/pots/api.py +++ b/pots/api.py @@ -2,79 +2,237 @@ from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page +from drf_spectacular.utils import ( + OpenApiExample, + OpenApiParameter, + OpenApiResponse, + extend_schema, +) from rest_framework.pagination import LimitOffsetPagination from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView from accounts.models import Account -from accounts.serializers import AccountSerializer +from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer from donations.models import Donation -from donations.serializers import DonationSerializer +from donations.serializers import SIMPLE_DONATION_EXAMPLE, DonationSerializer from .models import Pot, PotApplication, PotApplicationStatus -from .serializers import PotApplicationSerializer, PotPayoutSerializer, PotSerializer +from .serializers import ( + SIMPLE_PAYOUT_EXAMPLE, + SIMPLE_POT_APPLICATION_EXAMPLE, + SIMPLE_POT_EXAMPLE, + PotApplicationSerializer, + PotPayoutSerializer, + PotSerializer, +) -class PotsAPI(APIView, LimitOffsetPagination): - def dispatch(self, request, *args, **kwargs): - return super(PotsAPI, self).dispatch(request, *args, **kwargs) +@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins +class PotsListAPI(APIView, LimitOffsetPagination): - @method_decorator(cache_page(60 * 15)) # Cache for 15 mins + @extend_schema( + responses={ + 200: OpenApiResponse( + response=PotSerializer(many=True), + description="Returns a list of pots", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for pots", + value=SIMPLE_POT_EXAMPLE, + response_only=True, + ), + ], + ), + } + ) def get(self, request: Request, *args, **kwargs): - pot_id = kwargs.get("pot_id", None) - action = kwargs.get("action", None) - if pot_id: - # Request pertaining to a specific pot_id - try: - pot = Pot.objects.get(id=pot_id) - except Pot.DoesNotExist: - return Response( - {"message": f"Pot with ID {pot_id} not found."}, status=404 - ) - if action: - # Handle action if present; only valid option currently is "applications" - if action == "applications": - # Return applications for pot_id - applications = pot.applications.all() - results = self.paginate_queryset(applications, request, view=self) - serializer = PotApplicationSerializer(results, many=True) - return self.get_paginated_response(serializer.data) - elif action == "donations": - # Return donations for pot_id - donations = pot.donations.all() - results = self.paginate_queryset(donations, request, view=self) - serializer = DonationSerializer(results, many=True) - return self.get_paginated_response(serializer.data) - elif action == "sponsors": - # Return sponsors for pot_id - sponsor_ids = ( - Donation.objects.filter(pot=pot, matching_pool=True) - .values_list("donor", flat=True) - .distinct() - ) - sponsors = Account.objects.filter(id__in=sponsor_ids) - results = self.paginate_queryset(sponsors, request, view=self) - serializer = AccountSerializer(results, many=True) - return self.get_paginated_response(serializer.data) - elif action == "payouts": - # Return payouts for pot_id - payouts = pot.payouts.all() - results = self.paginate_queryset(payouts, request, view=self) - serializer = PotPayoutSerializer(results, many=True) - return self.get_paginated_response(serializer.data) - else: - return Response( - {"error": f"Invalid action: {action}"}, - status=400, - ) - else: - # Return pot - serializer = PotSerializer(pot) - return Response(serializer.data) - else: - # Return all pots - pots = Pot.objects.all() - results = self.paginate_queryset(pots, request, view=self) - serializer = PotSerializer(results, many=True) - return self.get_paginated_response(serializer.data) + pots = Pot.objects.all() + results = self.paginate_queryset(pots, request, view=self) + serializer = PotSerializer(results, many=True) + return self.get_paginated_response(serializer.data) + + +class PotDetailAPI(APIView): + + @extend_schema( + parameters=[ + OpenApiParameter("pot_id", str, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=PotSerializer, + description="Returns pot details", + examples=[ + OpenApiExample( + "example-1", + summary="Simple pot example", + description="Example response for pot detail", + value=SIMPLE_POT_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Pot not found"), + }, + ) + def get(self, request: Request, *args, **kwargs): + pot_id = kwargs.get("pot_id") + try: + pot = Pot.objects.get(id=pot_id) + except Pot.DoesNotExist: + return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) + serializer = PotSerializer(pot) + return Response(serializer.data) + + +class PotApplicationsAPI(APIView, LimitOffsetPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("pot_id", str, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=PotApplicationSerializer(many=True), + description="Returns applications for the pot", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for pot applications", + value=SIMPLE_POT_APPLICATION_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Pot not found"), + }, + ) + def get(self, request: Request, *args, **kwargs): + pot_id = kwargs.get("pot_id") + try: + pot = Pot.objects.get(id=pot_id) + except Pot.DoesNotExist: + return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) + + applications = pot.applications.all() + results = self.paginate_queryset(applications, request, view=self) + serializer = PotApplicationSerializer(results, many=True) + return self.get_paginated_response(serializer.data) + + +class PotDonationsAPI(APIView, LimitOffsetPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("pot_id", str, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=DonationSerializer(many=True), + description="Returns donations for the pot", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for donations", + value=SIMPLE_DONATION_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Pot not found"), + }, + ) + def get(self, request: Request, *args, **kwargs): + pot_id = kwargs.get("pot_id") + try: + pot = Pot.objects.get(id=pot_id) + except Pot.DoesNotExist: + return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) + + donations = pot.donations.all() + results = self.paginate_queryset(donations, request, view=self) + serializer = DonationSerializer(results, many=True) + return self.get_paginated_response(serializer.data) + + +class PotSponsorsAPI(APIView, LimitOffsetPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("pot_id", str, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=AccountSerializer(many=True), + description="Returns sponsors for the pot", + examples=[ + OpenApiExample( + "example-1", + summary="user.near", + description="Example response for sponsors", + value=SIMPLE_ACCOUNT_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Pot not found"), + }, + ) + def get(self, request: Request, *args, **kwargs): + pot_id = kwargs.get("pot_id") + try: + pot = Pot.objects.get(id=pot_id) + except Pot.DoesNotExist: + return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) + + sponsor_ids = ( + Donation.objects.filter(pot=pot, matching_pool=True) + .values_list("donor", flat=True) + .distinct() + ) + sponsors = Account.objects.filter(id__in=sponsor_ids) + results = self.paginate_queryset(sponsors, request, view=self) + serializer = AccountSerializer(results, many=True) + return self.get_paginated_response(serializer.data) + + +class PotPayoutsAPI(APIView, LimitOffsetPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("pot_id", str, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=PotPayoutSerializer(many=True), + description="Returns payouts for the pot", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for payouts", + value=SIMPLE_PAYOUT_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Pot not found"), + }, + ) + def get(self, request: Request, *args, **kwargs): + pot_id = kwargs.get("pot_id") + try: + pot = Pot.objects.get(id=pot_id) + except Pot.DoesNotExist: + return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) + + payouts = pot.payouts.all() + results = self.paginate_queryset(payouts, request, view=self) + serializer = PotPayoutSerializer(results, many=True) + return self.get_paginated_response(serializer.data) diff --git a/pots/serializers.py b/pots/serializers.py index 232fbd3..f7533b1 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -1,12 +1,53 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField +from base.serializers import TwoDecimalStringField + from .models import Pot, PotApplication, PotPayout class PotSerializer(ModelSerializer): + total_matching_pool_usd = TwoDecimalStringField(max_digits=20, decimal_places=2) + total_public_donations_usd = TwoDecimalStringField(max_digits=20, decimal_places=2) + class Meta: model = Pot - fields = "__all__" # TODO: potentially adjust this e.g. for formatting of datetimes, adding convenience fields etc + fields = [ + "id", + "pot_factory", + "deployer", + "deployed_at", + "source_metadata", + "owner", + "admins", + "chef", + "name", + "description", + "max_approved_applicants", + "base_currency", + "application_start", + "application_end", + "matching_round_start", + "matching_round_end", + "registry_provider", + "min_matching_pool_donation_amount", + "sybil_wrapper_provider", + "custom_sybil_checks", + "custom_min_threshold_score", + "referral_fee_matching_pool_basis_points", + "referral_fee_public_round_basis_points", + "chef_fee_basis_points", + "total_matching_pool", + "total_matching_pool_usd", + "matching_pool_balance", + "matching_pool_donations_count", + "total_public_donations", + "total_public_donations_usd", + "public_donations_count", + "cooldown_end", + "cooldown_period_ms", + "all_paid_out", + "protocol_config_provider", + ] class PotApplicationSerializer(ModelSerializer): @@ -20,3 +61,70 @@ class PotPayoutSerializer(ModelSerializer): class Meta: model = PotPayout fields = "__all__" # TODO: potentially adjust this e.g. for formatting of datetimes, adding convenience fields etc + + +EXAMPLE_POT_ID = "some-pot.v1.potfactory.potlock.near" + +SIMPLE_POT_EXAMPLE = { + "id": EXAMPLE_POT_ID, + "deployed_at": "2024-02-16T17:45:03.600845Z", + "source_metadata": { + "link": "https://github.com/PotLock/core", + "version": "0.1.0", + "commit_hash": "2db43b1182eb97d34e1b67f21b44c7084b364358", + }, + "name": "My Pot Name", + "description": "To support impactful open source software projects.", + "max_approved_applicants": 50, + "base_currency": "near", + "application_start": "2024-04-08T04:00:00Z", + "application_end": "2024-04-22T03:59:00Z", + "matching_round_start": "2024-04-22T04:00:00Z", + "matching_round_end": "2024-05-06T03:59:00Z", + "registry_provider": "registry.potlock.near:is_registered", + "min_matching_pool_donation_amount": "0", + "sybil_wrapper_provider": "v1.nadabot.near:is_human", + "custom_sybil_checks": None, + "custom_min_threshold_score": None, + "referral_fee_matching_pool_basis_points": 500, + "referral_fee_public_round_basis_points": 500, + "chef_fee_basis_points": 500, + "total_matching_pool": "10000000000000000000", + "total_matching_pool_usd": "100.17", + "matching_pool_balance": "10000000000000000000", + "matching_pool_donations_count": 0, + "total_public_donations": "100000000000000000", + "total_public_donations_usd": "1.17", + "public_donations_count": 0, + "cooldown_end": None, + "cooldown_period_ms": None, + "all_paid_out": False, + "protocol_config_provider": "v1.potfactory.potlock.near:get_protocol_config", + "pot_factory": "v1.potfactory.potlock.near", + "deployer": "ossround.near", + "owner": "ossround.near", + "chef": "plugrel.near", + "admins": [], +} + +SIMPLE_POT_APPLICATION_EXAMPLE = { + "id": 2, + "message": "Hi, I'm a great project and I'd like to apply for this pot.", + "status": "Pending", + "submitted_at": "2024-06-05T18:06:45.519Z", + "updated_at": "2024-06-05T18:06:45.519Z", + "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", + "pot": EXAMPLE_POT_ID, + "applicant": "applicant.near", +} + +SIMPLE_PAYOUT_EXAMPLE = { + "id": 4, + "amount": "1000000000000000000000000", + "amount_paid_usd": "1.27", + "paid_at": "2024-06-05T18:12:39.014Z", + "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", + "pot": EXAMPLE_POT_ID, + "recipient": "someproject.near", + "ft": "near", +} From e445440ce8f53cbf930cc1a316ca5456028cb377 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:35:18 -0400 Subject: [PATCH 155/454] add social profile data indexing (#31) * add social profile data indexing * remove unused imports --- .../0002_account_near_social_profile_data.py | 22 +++++ accounts/models.py | 84 ++++++++++++++++++- accounts/serializers.py | 1 + base/settings.py | 10 +++ indexer_app/handler.py | 22 +++-- indexer_app/utils.py | 18 +++- .../0003_alter_listupvote_options.py | 17 ++++ ...3_alter_potapplication_options_and_more.py | 33 ++++++++ pyproject.toml | 2 +- 9 files changed, 200 insertions(+), 9 deletions(-) create mode 100644 accounts/migrations/0002_account_near_social_profile_data.py create mode 100644 lists/migrations/0003_alter_listupvote_options.py create mode 100644 pots/migrations/0003_alter_potapplication_options_and_more.py diff --git a/accounts/migrations/0002_account_near_social_profile_data.py b/accounts/migrations/0002_account_near_social_profile_data.py new file mode 100644 index 0000000..7641235 --- /dev/null +++ b/accounts/migrations/0002_account_near_social_profile_data.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.4 on 2024-06-05 14:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="account", + name="near_social_profile_data", + field=models.JSONField( + help_text="NEAR social data contained under 'profile' key.", + null=True, + verbose_name="NEAR social profile data", + ), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 834c6de..ec301d5 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -1,7 +1,11 @@ +import requests from django import db +from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ +from base.logging import logger + class Account(models.Model): id = models.CharField( @@ -39,5 +43,83 @@ class Account(models.Model): default=0, help_text=_("Number of donors."), ) + near_social_profile_data = models.JSONField( + _("NEAR social profile data"), + null=True, + help_text=_("NEAR social data contained under 'profile' key."), + ) + + def fetch_near_social_profile_data(self, should_save=True): + # Fetch social profile data from NEAR blockchain + try: + url = f"{settings.FASTNEAR_RPC_URL}/account/{settings.NEAR_SOCIAL_CONTRACT_ADDRESS}/view/get" + keys_value = f'["{self.id}/profile/**"]' + params = {"keys.json": keys_value} + response = requests.get(url, params=params) + if response.status_code == 200: + data = response.json() + if self.id in data and "profile" in data[self.id]: + profile_data = data[self.id][ + "profile" + ] # TODO: validate/sanitize profile data? + # fetch NFT URLs if applicable + for image_type in ["image", "backgroundImage"]: + if ( + image_type in profile_data + and "nft" in profile_data[image_type] + and "contractId" in profile_data[image_type]["nft"] + and "tokenId" in profile_data[image_type]["nft"] + ): + contract_id = profile_data[image_type]["nft"]["contractId"] + token_id = profile_data[image_type]["nft"]["tokenId"] + # get base_uri + url = f"https://rpc.web4.near.page/account/{contract_id}/view/nft_metadata" + response = requests.get(url) + if response.status_code == 200: + metadata = response.json() + if "base_uri" in metadata: + base_uri = metadata["base_uri"] + # store baseUri in profile_data + profile_data[image_type]["nft"][ + "baseUri" + ] = base_uri + else: + logger.error( + f"Request for NFT metadata failed ({response.status_code}) with message: {response.text}" + ) + # get token metadata + url = f"https://rpc.web4.near.page/account/{contract_id}/view/nft_token" + json_data = {"token_id": token_id} + response = requests.post( + url, json=json_data + ) # using a POST request here so that token_id is not coerced into an integer on fastnear's side, causing a contract view error + if response.status_code == 200: + token_metadata = response.json() + if ( + "metadata" in token_metadata + and "media" in token_metadata["metadata"] + ): + # store media in profile_data + profile_data[image_type]["nft"]["media"] = ( + token_metadata["metadata"]["media"] + ) + else: + logger.error( + f"Request for NFT metadata failed ({response.status_code}) with message: {response.text}" + ) + self.near_social_profile_data = profile_data + if should_save: + self.save() + else: + logger.error( + f"Request for NEAR Social profile data failed ({response.status_code}) with message: {response.text}" + ) + except Exception as e: + logger.error(f"Error fetching NEAR social profile data: {e}") - # add Meta, properties & methods as necessary + def save(self, *args, **kwargs): + if self._state.adding: # If the account is being created (not updated) + self.fetch_near_social_profile_data( + False # don't save yet as we want to avoid infinite loop + ) + super().save(*args, **kwargs) diff --git a/accounts/serializers.py b/accounts/serializers.py index 4f330e9..d9c6c1d 100644 --- a/accounts/serializers.py +++ b/accounts/serializers.py @@ -13,6 +13,7 @@ class Meta: "total_donations_out_usd", "total_matching_pool_allocations_usd", "donors_count", + "near_social_profile_data", ] diff --git a/base/settings.py b/base/settings.py index dcfdc83..d086918 100644 --- a/base/settings.py +++ b/base/settings.py @@ -59,6 +59,16 @@ POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else "potlock.near" +NEAR_SOCIAL_CONTRACT_ADDRESS = ( + "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" +) + +FASTNEAR_RPC_URL = ( + "https://rpc.web4.testnet.page" + if ENVIRONMENT == "testnet" + else "https://rpc.web4.near.page" +) + BLOCK_SAVE_HEIGHT = os.environ.get("BLOCK_SAVE_HEIGHT") COINGECKO_URL = ( diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 1981fb3..6aa5bdd 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -26,6 +26,7 @@ handle_pot_application, handle_pot_application_status_change, handle_set_payouts, + handle_social_profile_update, handle_transfer_payout, ) @@ -46,19 +47,21 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess for shard in streamer_message.shards: for receipt_execution_outcome in shard.receipt_execution_outcomes: - # we only want to proceed if it's a potlock tx and it succeeded.... (unreadable if statement?) - lists_contract = "lists." + settings.POTLOCK_TLA - if not receipt_execution_outcome.receipt.receiver_id.endswith( - settings.POTLOCK_TLA - ) or ( + # we only want to proceed if the tx succeeded + if ( "SuccessReceiptId" not in receipt_execution_outcome.execution_outcome.outcome.status and "SuccessValue" not in receipt_execution_outcome.execution_outcome.outcome.status ): continue + receiver_id = receipt_execution_outcome.receipt.receiver_id + if ( + receiver_id != settings.NEAR_SOCIAL_CONTRACT_ADDRESS + and not receiver_id.endswith(settings.POTLOCK_TLA) + ): + continue # 1. HANDLE LOGS - log_data = [] for log_index, log in enumerate( @@ -89,6 +92,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess # # consider logging failures to logging service; for now, just skip # print("here we are...") # continue + lists_contract = "lists." + settings.POTLOCK_TLA for index, action in enumerate( receipt_execution_outcome.receipt.receipt["Action"]["actions"] @@ -125,6 +129,12 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess args_dict = {} match method_name: + case "set": # handle near social profile data updates + if receiver_id == settings.NEAR_SOCIAL_CONTRACT_ADDRESS: + logger.info(f"setting profile data: {args_dict}") + await handle_social_profile_update( + args_dict, receiver_id, signer_id + ) case "new": if match_pot_factory_pattern(receipt.receiver_id): logger.info(f"matched for factory pattern: {args_dict}") diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 3e8ee85..caf9603 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1,5 +1,4 @@ import base64 -import decimal import json from datetime import datetime @@ -29,6 +28,23 @@ # GECKO_URL = "https://api.coingecko.com/api/v3" # TODO: move to settings +async def handle_social_profile_update(args_dict, receiver_id, signer_id): + logger.info(f"handling social profile update for {signer_id}") + if ( + "data" in args_dict + and signer_id in args_dict["data"] + and "profile" in args_dict["data"][signer_id] + ): + try: + # only proceed if this account already exists in db + account = await Account.objects.filter(id=signer_id).first() + if account: + logger.info(f"updating social profile for {signer_id}") + account.fetch_near_social_profile_data() + except Exception as e: + logger.error(f"Error in handle_social_profile_update: {e}") + + async def handle_new_pot( data: dict, receiverId: str, diff --git a/lists/migrations/0003_alter_listupvote_options.py b/lists/migrations/0003_alter_listupvote_options.py new file mode 100644 index 0000000..96ef6e5 --- /dev/null +++ b/lists/migrations/0003_alter_listupvote_options.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.4 on 2024-06-05 14:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("lists", "0002_alter_listupvote_options_and_more"), + ] + + operations = [ + migrations.AlterModelOptions( + name="listupvote", + options={"verbose_name_plural": "List Upvotes"}, + ), + ] diff --git a/pots/migrations/0003_alter_potapplication_options_and_more.py b/pots/migrations/0003_alter_potapplication_options_and_more.py new file mode 100644 index 0000000..48bb30a --- /dev/null +++ b/pots/migrations/0003_alter_potapplication_options_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.4 on 2024-06-05 14:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("pots", "0002_alter_potapplication_options_and_more"), + ] + + operations = [ + migrations.AlterModelOptions( + name="potapplication", + options={"verbose_name_plural": "Pot Applications"}, + ), + migrations.AlterModelOptions( + name="potapplicationreview", + options={"verbose_name_plural": "Pot Application Reviews"}, + ), + migrations.AlterModelOptions( + name="potfactory", + options={"verbose_name_plural": "Pot Factories"}, + ), + migrations.AlterModelOptions( + name="potpayoutchallenge", + options={"verbose_name_plural": "Payout Challenges"}, + ), + migrations.AlterModelOptions( + name="potpayoutchallengeadminresponse", + options={"verbose_name_plural": "Payout Challenge Responses"}, + ), + ] diff --git a/pyproject.toml b/pyproject.toml index dca95a0..fee60dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ djangorestframework = "^3.15.1" django-cachalot = "^2.6.2" django-redis = "^5.4.0" gunicorn = "^22.0.0" -sentry-sdk = {extras = ["django"], version = "^1.45.0"} +sentry-sdk = { extras = ["django"], version = "^1.45.0" } watchtower = "^3.1.0" django-cors-headers = "^4.3.1" drf-spectacular = "^0.27.2" From 5b7c576191b13f7621908cb688d96ed1f8b21f9e Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:36:39 -0400 Subject: [PATCH 156/454] Testnet (#30) * add deploy-testnet.yml * add testnet deployment workflow & appspec * testnet workflow debugging * upload appspec as artifact * upload zipped codebase to s3 in workflow * continue working on after_install_testnet * separate watchtower log groups by django env * add testnet potlock TLA to indexer handler * add POTLOCK_TLA to settings * add dev.potlock.io and test-dev.potlock.io to allowed hosts * remove admin dashboard edit access * remove admin edit permission for all models * add timestamp to deployment zip filename * add env vars to deploy-testnet * add after_install_dev script * add after_install debug logs * handle testnet potfactory & pot contract patterns * test throttle rate * update throttling to 100/minute * update readme with API urls * add poetry lock --no-update to after_install_testnet * add poetry lock --no-update to after_install_dev --- scripts/after_install_dev.sh | 1 + scripts/after_install_testnet.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh index d9b54f1..01bed73 100755 --- a/scripts/after_install_dev.sh +++ b/scripts/after_install_dev.sh @@ -34,6 +34,7 @@ source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-Y-SQFfhb-py3.1 # Install dependencies using Poetry echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +poetry lock --no-update >> "$LOG_FILE" poetry install >> "$LOG_FILE" echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" diff --git a/scripts/after_install_testnet.sh b/scripts/after_install_testnet.sh index 94c517b..32e4356 100755 --- a/scripts/after_install_testnet.sh +++ b/scripts/after_install_testnet.sh @@ -34,6 +34,7 @@ source "/home/ec2-user/.cache/pypoetry/virtualenvs/django-indexer-AhfQkQzj-py3.1 # Install dependencies using Poetry echo "$(date '+%Y-%m-%d %H:%M:%S') - Installing dependencies with Poetry" >> "$LOG_FILE" +poetry lock --no-update >> "$LOG_FILE" poetry install >> "$LOG_FILE" echo "$(date '+%Y-%m-%d %H:%M:%S') - Dependencies installed" >> "$LOG_FILE" From 2ea615ee91094a9f3a8b2bbc33d24705165ddc1c Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:42:39 -0400 Subject: [PATCH 157/454] add near_social_profile_data to accounts admin --- accounts/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/accounts/admin.py b/accounts/admin.py index 3b8aca7..e59c081 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -11,6 +11,7 @@ class AccountAdmin(admin.ModelAdmin): "total_donations_out_usd", "total_matching_pool_allocations_usd", "donors_count", + "near_social_profile_data", ) search_fields = ("id",) # Allow searching by account address list_filter = ( From cf89c726e552435a5796024f4387139f7a965138 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:47:00 -0400 Subject: [PATCH 158/454] add fetchsocialprofiledata command --- accounts/management/__init__.py | 0 accounts/management/commands/__init__.py | 0 .../commands/fetchsocialprofiledata.py | 23 +++++++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 accounts/management/__init__.py create mode 100644 accounts/management/commands/__init__.py create mode 100644 accounts/management/commands/fetchsocialprofiledata.py diff --git a/accounts/management/__init__.py b/accounts/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounts/management/commands/__init__.py b/accounts/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounts/management/commands/fetchsocialprofiledata.py b/accounts/management/commands/fetchsocialprofiledata.py new file mode 100644 index 0000000..030910d --- /dev/null +++ b/accounts/management/commands/fetchsocialprofiledata.py @@ -0,0 +1,23 @@ +import time + +from django.core.management.base import BaseCommand + +from accounts.models import Account + + +class Command(BaseCommand): + help = "Fetch social profile data for all accounts." + + def handle(self, *args, **options): + # Get all account addresses + account_addresses = [account.id for account in Account.objects.all()] + + # Fetch social profile data for each account + for address in account_addresses: + account = Account.objects.get(id=address) + account.fetch_near_social_profile_data() + self.stdout.write( + self.style.SUCCESS(f"Fetched social profile data for {address}") + ) + # wait for 1 second to avoid rate limiting + time.sleep(1) From f2ca3d01555187198500aa739df5777f02209246 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 6 Jun 2024 14:47:30 -0400 Subject: [PATCH 159/454] add fetch_near_social_profile_data_async --- accounts/models.py | 5 +++++ indexer_app/utils.py | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/accounts/models.py b/accounts/models.py index ec301d5..fd88ad2 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -1,4 +1,5 @@ import requests +from asgiref.sync import sync_to_async from django import db from django.conf import settings from django.db import models @@ -49,6 +50,10 @@ class Account(models.Model): help_text=_("NEAR social data contained under 'profile' key."), ) + async def fetch_near_social_profile_data_async(self): + fetch_profile_data = sync_to_async(self.fetch_near_social_profile_data) + await fetch_profile_data() + def fetch_near_social_profile_data(self, should_save=True): # Fetch social profile data from NEAR blockchain try: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index caf9603..94b53fe 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -40,7 +40,8 @@ async def handle_social_profile_update(args_dict, receiver_id, signer_id): account = await Account.objects.filter(id=signer_id).first() if account: logger.info(f"updating social profile for {signer_id}") - account.fetch_near_social_profile_data() + await account.fetch_near_social_profile_data_async() + # account.fetch_near_social_profile_data() except Exception as e: logger.error(f"Error in handle_social_profile_update: {e}") From 1ebd5778325d3f2e4e3a85f23730b784b8daa63f Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:28:02 -0400 Subject: [PATCH 160/454] update SIMPLE_ACCOUNT_EXAMPLE in OpenAPI schema to include near_social_profile_data --- accounts/serializers.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/accounts/serializers.py b/accounts/serializers.py index d9c6c1d..686e489 100644 --- a/accounts/serializers.py +++ b/accounts/serializers.py @@ -23,4 +23,26 @@ class Meta: "total_donations_out_usd": "1234.56", "total_matching_pool_allocations_usd": "800.01", "donors_count": 321, + "near_social_profile_data": { + "name": "Illia", + "image": { + "nft": { + "media": "https://ipfs.nftstorage.link/ipfs/bafybeie6mpnk6iya3wvwtxtogzmzpprw5734dydoeujo5esqqxmmirug6y", + "baseUri": "https://arweave.net/q8IenkSo5aogF-bIphzedrom24OFYGECZYUs9gEfM0A", + "tokenId": "8120", + "contractId": "citizen.bodega-lab.near", + } + }, + "linktree": { + "github": "ilblackdragon", + "twitter": "ilblackdragon", + "website": "near.org", + "telegram": "", + }, + "description": "Bringing 1B users to web3", + "horizon_tnc": "true", + "backgroundImage": { + "ipfs_cid": "bafkreiemktmsdhpdoomwlvfi2ztm7c5sdqdmb2z5mg4bjssoqkz7wunaoi" + }, + }, } From 74e98ad438283a6793b9a654544b67c2a7a21da2 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 7 Jun 2024 09:45:59 -0400 Subject: [PATCH 161/454] fix pot pattern matching --- pots/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pots/utils.py b/pots/utils.py index 0d98146..ef041d2 100644 --- a/pots/utils.py +++ b/pots/utils.py @@ -3,19 +3,19 @@ from django.conf import settings BASE_PATTERN = ( - r"^potlock\.testnet$" + r"potlock\.testnet" if settings.ENVIRONMENT == "testnet" - else r"v\d+\.potfactory\.potlock\.near$" + else r"v\d+\.potfactory\.potlock\.near" ) def match_pot_factory_pattern(receiver): """Matches the base pot factory version pattern without a subaccount. NB: does not currently handle testnet factory.""" - pattern = f"^{BASE_PATTERN}" + pattern = f"^{BASE_PATTERN}$" return bool(re.match(pattern, receiver)) def match_pot_subaccount_pattern(receiver): """Matches the pot factory version pattern with a subaccount. NB: does not currently handle testnet factory.""" - pattern = f"^[a-zA-Z0-9_]+\.{BASE_PATTERN}" + pattern = f"^[a-zA-Z0-9_-]+\.{BASE_PATTERN}$" return bool(re.match(pattern, receiver)) From fdc27c53eca1135a811fcf61908698d5e5d53cbe Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 7 Jun 2024 10:23:22 -0400 Subject: [PATCH 162/454] fix fastnear rpc url --- accounts/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accounts/models.py b/accounts/models.py index fd88ad2..2feba43 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -78,7 +78,7 @@ def fetch_near_social_profile_data(self, should_save=True): contract_id = profile_data[image_type]["nft"]["contractId"] token_id = profile_data[image_type]["nft"]["tokenId"] # get base_uri - url = f"https://rpc.web4.near.page/account/{contract_id}/view/nft_metadata" + url = f"{settings.FASTNEAR_RPC_URL}/account/{contract_id}/view/nft_metadata" response = requests.get(url) if response.status_code == 200: metadata = response.json() @@ -93,7 +93,7 @@ def fetch_near_social_profile_data(self, should_save=True): f"Request for NFT metadata failed ({response.status_code}) with message: {response.text}" ) # get token metadata - url = f"https://rpc.web4.near.page/account/{contract_id}/view/nft_token" + url = f"{settings.FASTNEAR_RPC_URL}/account/{contract_id}/view/nft_token" json_data = {"token_id": token_id} response = requests.post( url, json=json_data From bc1fceafafc44ad72c6951a7aa59bc8444b29be1 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:05:48 -0400 Subject: [PATCH 163/454] fix async error in handle_social_profile_update --- indexer_app/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 94b53fe..ddfac47 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -37,7 +37,8 @@ async def handle_social_profile_update(args_dict, receiver_id, signer_id): ): try: # only proceed if this account already exists in db - account = await Account.objects.filter(id=signer_id).first() + account = await Account.objects.filter(id=signer_id).afirst() + logger.info(f"account: {account}") if account: logger.info(f"updating social profile for {signer_id}") await account.fetch_near_social_profile_data_async() From d854ff3ec2ee91b543ad3284ccb280a29edb9d35 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 08:36:52 -0400 Subject: [PATCH 164/454] allow write admin actions for Accounts & Activities --- accounts/admin.py | 12 ++++++------ activities/admin.py | 12 ++++++------ pots/admin.py | 24 ++++++++++++------------ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/accounts/admin.py b/accounts/admin.py index e59c081..ec0b3c5 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -43,11 +43,11 @@ def total_matching_pool_allocations_usd_display(self, obj): "Total Matching Pool Allocations (USD)" ) - def has_add_permission(self, request): - return False + # def has_add_permission(self, request): + # return False - def has_change_permission(self, request, obj=None): - return False + # def has_change_permission(self, request, obj=None): + # return False - def has_delete_permission(self, request, obj=None): - return False + # def has_delete_permission(self, request, obj=None): + # return False diff --git a/activities/admin.py b/activities/admin.py index 219e369..3dc69e1 100644 --- a/activities/admin.py +++ b/activities/admin.py @@ -42,11 +42,11 @@ def transaction_link(self, obj): # return "Has Result" if obj.action_result else "No Result" # action_result_summary.short_description = 'Action Result' - def has_add_permission(self, request): - return False + # def has_add_permission(self, request): + # return False - def has_change_permission(self, request, obj=None): - return False + # def has_change_permission(self, request, obj=None): + # return False - def has_delete_permission(self, request, obj=None): - return False + # def has_delete_permission(self, request, obj=None): + # return False diff --git a/pots/admin.py b/pots/admin.py index 41243af..6ffeec9 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -108,14 +108,14 @@ class PotApplicationAdmin(admin.ModelAdmin): search_fields = ("pot__id", "applicant__id") list_filter = ("status", "submitted_at") - def has_add_permission(self, request): - return False + # def has_add_permission(self, request): + # return False - def has_change_permission(self, request, obj=None): - return False + # def has_change_permission(self, request, obj=None): + # return False - def has_delete_permission(self, request, obj=None): - return False + # def has_delete_permission(self, request, obj=None): + # return False @admin.register(PotApplicationReview) @@ -132,14 +132,14 @@ class PotApplicationReviewAdmin(admin.ModelAdmin): search_fields = ("application__id", "reviewer__id") list_filter = ("status", "reviewed_at") - def has_add_permission(self, request): - return False + # def has_add_permission(self, request): + # return False - def has_change_permission(self, request, obj=None): - return False + # def has_change_permission(self, request, obj=None): + # return False - def has_delete_permission(self, request, obj=None): - return False + # def has_delete_permission(self, request, obj=None): + # return False @admin.register(PotPayout) From ec06d1ef63621cf06e0ba325e1a59c042d0b10f0 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 08:55:46 -0400 Subject: [PATCH 165/454] add autocomplete field for applicant --- pots/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pots/admin.py b/pots/admin.py index 6ffeec9..35199c6 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -107,6 +107,7 @@ class PotApplicationAdmin(admin.ModelAdmin): list_display = ("id", "pot", "applicant", "status", "submitted_at") search_fields = ("pot__id", "applicant__id") list_filter = ("status", "submitted_at") + autocomplete_fields = ["applicant"] # def has_add_permission(self, request): # return False From 1c6be1695c55d149ff7a012334ee7b2ec9949559 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:06:17 -0400 Subject: [PATCH 166/454] add application applicant id to admin --- pots/admin.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pots/admin.py b/pots/admin.py index 35199c6..63d1f77 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -123,7 +123,7 @@ class PotApplicationAdmin(admin.ModelAdmin): class PotApplicationReviewAdmin(admin.ModelAdmin): list_display = ( "id", - "application", + "application_applicant_id", "reviewer", "notes", "status", @@ -133,6 +133,11 @@ class PotApplicationReviewAdmin(admin.ModelAdmin): search_fields = ("application__id", "reviewer__id") list_filter = ("status", "reviewed_at") + def application_applicant_id(self, obj): + return obj.application.applicant.id + + application_applicant_id.short_description = "Application Applicant ID" + # def has_add_permission(self, request): # return False From c8e0c6c488ae03e3bc24bbe204517b1f4f0002e9 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:11:33 -0400 Subject: [PATCH 167/454] add application autocomplete field to PotApplicationReviewAdmin --- pots/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pots/admin.py b/pots/admin.py index 63d1f77..8cc7b92 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -132,6 +132,7 @@ class PotApplicationReviewAdmin(admin.ModelAdmin): ) search_fields = ("application__id", "reviewer__id") list_filter = ("status", "reviewed_at") + autocomplete_fields = ["application"] def application_applicant_id(self, obj): return obj.application.applicant.id From 688d0137ca465849b511f55a6a69d47fee95ea33 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:16:31 -0400 Subject: [PATCH 168/454] update PotApplicationAdmin --- pots/admin.py | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/pots/admin.py b/pots/admin.py index 8cc7b92..226e1f6 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -102,13 +102,46 @@ def has_delete_permission(self, request, obj=None): return False +class PotApplicationAdminForm(forms.ModelForm): + class Meta: + model = PotApplication + fields = "__all__" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["applicant"].widget.attrs.update( + {"data-placeholder": "Search by applicant ID..."} + ) + + @admin.register(PotApplication) class PotApplicationAdmin(admin.ModelAdmin): + form = PotApplicationAdminForm list_display = ("id", "pot", "applicant", "status", "submitted_at") search_fields = ("pot__id", "applicant__id") list_filter = ("status", "submitted_at") autocomplete_fields = ["applicant"] + def get_search_results(self, request, queryset, search_term): + queryset, use_distinct = super().get_search_results( + request, queryset, search_term + ) + try: + queryset |= self.model.objects.filter(applicant__id__icontains=search_term) + except ValueError: + pass + return queryset, use_distinct + + def get_queryset(self, request): + qs = super().get_queryset(request) + qs = qs.select_related("applicant") + return qs + + def get_label(self, obj): + return f"{obj.applicant.id} - {obj.pot}" + + get_label.short_description = "Application" + # def has_add_permission(self, request): # return False @@ -123,7 +156,7 @@ class PotApplicationAdmin(admin.ModelAdmin): class PotApplicationReviewAdmin(admin.ModelAdmin): list_display = ( "id", - "application_applicant_id", + "application_applicant_id", # Use the custom method here "reviewer", "notes", "status", @@ -132,7 +165,9 @@ class PotApplicationReviewAdmin(admin.ModelAdmin): ) search_fields = ("application__id", "reviewer__id") list_filter = ("status", "reviewed_at") - autocomplete_fields = ["application"] + autocomplete_fields = [ + "application" + ] # Enable autocomplete for the application field def application_applicant_id(self, obj): return obj.application.applicant.id From e48948861cceff8d4e33c848491768fcdf484d87 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:21:21 -0400 Subject: [PATCH 169/454] update PotApplication __str__ --- pots/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pots/models.py b/pots/models.py index a495aea..ba1405e 100644 --- a/pots/models.py +++ b/pots/models.py @@ -340,6 +340,9 @@ class Meta: unique_together = (("pot", "applicant"),) + def __str__(self): + return f"{self.applicant.id} - {self.pot}" + class PotApplicationReview(models.Model): id = models.AutoField( From 51b257bd319e923d9fe11165a198095b29e9f0be Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:27:47 -0400 Subject: [PATCH 170/454] fix bad queries in admin --- pots/admin.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/pots/admin.py b/pots/admin.py index 226e1f6..93097df 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -122,25 +122,25 @@ class PotApplicationAdmin(admin.ModelAdmin): list_filter = ("status", "submitted_at") autocomplete_fields = ["applicant"] - def get_search_results(self, request, queryset, search_term): - queryset, use_distinct = super().get_search_results( - request, queryset, search_term - ) - try: - queryset |= self.model.objects.filter(applicant__id__icontains=search_term) - except ValueError: - pass - return queryset, use_distinct - - def get_queryset(self, request): - qs = super().get_queryset(request) - qs = qs.select_related("applicant") - return qs - - def get_label(self, obj): - return f"{obj.applicant.id} - {obj.pot}" - - get_label.short_description = "Application" + # def get_search_results(self, request, queryset, search_term): + # queryset, use_distinct = super().get_search_results( + # request, queryset, search_term + # ) + # try: + # queryset |= self.model.objects.filter(applicant__id__icontains=search_term) + # except ValueError: + # pass + # return queryset, use_distinct + + # def get_queryset(self, request): + # qs = super().get_queryset(request) + # qs = qs.select_related("applicant") + # return qs + + # def get_label(self, obj): + # return f"{obj.applicant.id} - {obj.pot}" + + # get_label.short_description = "Application" # def has_add_permission(self, request): # return False From 84c3559696bf9a9db91363bf5afb5499771c28c8 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 11:49:43 -0400 Subject: [PATCH 171/454] add reviewer autocomplete --- pots/admin.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/pots/admin.py b/pots/admin.py index 93097df..18ff16c 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -53,14 +53,14 @@ def get_form(self, request, obj=None, **kwargs): pass return form - def has_add_permission(self, request): - return False + # def has_add_permission(self, request): + # return False - def has_change_permission(self, request, obj=None): - return False + # def has_change_permission(self, request, obj=None): + # return False - def has_delete_permission(self, request, obj=None): - return False + # def has_delete_permission(self, request, obj=None): + # return False class PotForm(forms.ModelForm): @@ -92,14 +92,14 @@ def get_form(self, request, obj=None, **kwargs): pass return form - def has_add_permission(self, request): - return False + # def has_add_permission(self, request): + # return False - def has_change_permission(self, request, obj=None): - return False + # def has_change_permission(self, request, obj=None): + # return False - def has_delete_permission(self, request, obj=None): - return False + # def has_delete_permission(self, request, obj=None): + # return False class PotApplicationAdminForm(forms.ModelForm): @@ -166,7 +166,8 @@ class PotApplicationReviewAdmin(admin.ModelAdmin): search_fields = ("application__id", "reviewer__id") list_filter = ("status", "reviewed_at") autocomplete_fields = [ - "application" + "application", + "reviewer", ] # Enable autocomplete for the application field def application_applicant_id(self, obj): From c0c36f0604c966e298a063820ed9f1ae49c8bb64 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:52:58 -0400 Subject: [PATCH 172/454] add autocomplete for activities admin --- activities/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/activities/admin.py b/activities/admin.py index 3dc69e1..e1ade60 100644 --- a/activities/admin.py +++ b/activities/admin.py @@ -19,6 +19,7 @@ class ActivityAdmin(admin.ModelAdmin): search_fields = ("signer__id", "receiver__id", "tx_hash") date_hierarchy = "timestamp" ordering = ("-timestamp",) + autocomplete_fields = ["signer", "receiver"] def signer_address(self, obj): return obj.signer.id From 13769f4d163734686befa5b9b9112cb54278b424 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:17:44 -0400 Subject: [PATCH 173/454] add pot__id to Donation admin list filter --- donations/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/donations/admin.py b/donations/admin.py index f27994d..2af9a47 100644 --- a/donations/admin.py +++ b/donations/admin.py @@ -21,7 +21,7 @@ class DonationAdmin(admin.ModelAdmin): "message", "donor__id", ) # Correct field name from 'donor__address' to 'donor__id' if 'id' is used in the model - list_filter = ("donated_at", "donor", "pot") + list_filter = ("donated_at", "donor", "pot", "pot__id") date_hierarchy = "donated_at" ordering = ("-donated_at",) From 981bae71f47321b0fa3a8fbe00fe57b495b2096e Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:34:59 -0400 Subject: [PATCH 174/454] switch order of handle_protocol_fee_callback and donate handlers --- indexer_app/handler.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 6aa5bdd..859ca1b 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -183,6 +183,23 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess ) break + case ( + "handle_protocol_fee_callback" + ): # NB: It's important that this comes before the donate case + logger.info( + f"donations to pool incoming: {args_dict}, {receipt}, {receipt_execution_outcome}" + ) + await handle_new_donations( + args_dict, + receiver_id, + signer_id, + "pot", + receipt, + status_obj, + log_data, + ) + break + case "donate": # TODO: donate that produces result logger.info( f"switching bazooka to knifee works!! donate his blood: {args_dict}, {receipt}, {action}, {log_data}" @@ -203,21 +220,6 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess ) break - case "handle_protocol_fee_callback": - logger.info( - f"donations to pool incoming: {args_dict}, {receipt}, {receipt_execution_outcome}" - ) - await handle_new_donations( - args_dict, - receiver_id, - signer_id, - "pot", - receipt, - status_obj, - log_data, - ) - break - case "transfer_funds_callback": logger.info( f"new version donations to pool incoming: {args_dict}, {action}" From ae8a241133fc303d2556fa0ccdf5886eafb45068 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:44:19 -0400 Subject: [PATCH 175/454] add debug logs for pot donations --- donations/models.py | 4 ++++ indexer_app/utils.py | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/donations/models.py b/donations/models.py index b101250..9979486 100644 --- a/donations/models.py +++ b/donations/models.py @@ -5,6 +5,7 @@ from asgiref.sync import sync_to_async from django.conf import settings from django.db import models +from django.forms.models import model_to_dict from django.utils.translation import gettext_lazy as _ from accounts.models import Account @@ -176,6 +177,9 @@ class Meta: ), ] + def to_dict(self): + return model_to_dict(self) + def get_ft_token(self): token, created = Token.objects.get_or_create( id=self.ft, diff --git a/indexer_app/utils.py b/indexer_app/utils.py index ddfac47..f1aab1e 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -5,6 +5,7 @@ import requests from django.conf import settings from django.core.cache import cache +from django.forms.models import model_to_dict from django.utils import timezone from near_lake_framework.near_primitives import ExecutionOutcome, Receipt @@ -682,6 +683,7 @@ async def handle_new_donations( return # Handle non-direct donation donation_data = json.loads(base64.b64decode(result).decode("utf-8")) + logger.info(f"Donation data: {donation_data}") net_amount = int(donation_data["net_amount"]) donated_at = datetime.fromtimestamp( @@ -784,17 +786,19 @@ async def handle_new_donations( "referrer_fee": donation_data.get("referrer_fee"), "tx_hash": receipt_obj.receipt_id, } - logger.info(f"default donation data: {default_data}") if actionName != "direct": logger.info("selecting pot to make public donation update") pot = await Pot.objects.aget(id=receiverId) default_data["pot"] = pot + logger.info(f"default donation data: {default_data}") + donation, donation_created = await Donation.objects.aupdate_or_create( on_chain_id=donation_data["id"], defaults={}, create_defaults=default_data ) logger.info(f"Created donation? {donation_created}") + logger.info(f"donation: {donation.to_dict()}") # fetch USD prices await donation.fetch_usd_prices_async() # might not need to await this? From 1d88f4331deb2f5b7948cf972ec8f0c46f93d401 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:57:02 -0400 Subject: [PATCH 176/454] remove use of deprecated create_defaults in new pot donation --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index f1aab1e..1325f24 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -795,7 +795,7 @@ async def handle_new_donations( logger.info(f"default donation data: {default_data}") donation, donation_created = await Donation.objects.aupdate_or_create( - on_chain_id=donation_data["id"], defaults={}, create_defaults=default_data + on_chain_id=donation_data["id"], defaults=default_data ) logger.info(f"Created donation? {donation_created}") logger.info(f"donation: {donation.to_dict()}") From db66d5f95d299b4bcee8b72d1b1b25ff06e2be42 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 11 Jun 2024 14:41:09 -0400 Subject: [PATCH 177/454] rework donations handling --- indexer_app/examples.txt | 57 +++++++ indexer_app/handler.py | 88 +++++------ indexer_app/utils.py | 323 ++++++++++++++++----------------------- 3 files changed, 235 insertions(+), 233 deletions(-) create mode 100644 indexer_app/examples.txt diff --git a/indexer_app/examples.txt b/indexer_app/examples.txt new file mode 100644 index 0000000..eec31e9 --- /dev/null +++ b/indexer_app/examples.txt @@ -0,0 +1,57 @@ +## DONATION EXAMPLES + +# POT DONATION: + +pub struct DonationExternal { + /// ID of the donation + pub id: DonationId, + /// ID of the donor + pub donor_id: AccountId, + /// Amount donated + pub total_amount: U128, + /// Amount after all fees/expenses (incl. storage) + pub net_amount: U128, + /// Optional message from the donor + pub message: Option, + /// Timestamp when the donation was made + pub donated_at: TimestampMs, + /// ID of the project receiving the donation, if applicable (matching pool donations will contain `None`) + pub project_id: Option, + /// Referrer ID + pub referrer_id: Option, + /// Referrer fee + pub referrer_fee: Option, + /// Protocol fee + pub protocol_fee: U128, + /// Indicates whether this is matching pool donation + pub matching_pool: bool, + /// Chef ID + pub chef_id: Option, + /// Chef fee + pub chef_fee: Option, +} + +# DIRECT DONATION: + +pub struct DonationExternal { + /// Unique identifier for the donation + pub id: DonationId, + /// ID of the donor + pub donor_id: AccountId, + /// Amount donated + pub total_amount: U128, + /// FT id (e.g. "near") + pub ft_id: AccountId, + /// Optional message from the donor + pub message: Option, + /// Timestamp when the donation was made + pub donated_at_ms: TimestampMs, + /// ID of the account receiving the donation + pub recipient_id: AccountId, + /// Protocol fee + pub protocol_fee: U128, + /// Referrer ID + pub referrer_id: Option, + /// Referrer fee + pub referrer_fee: Option, +} \ No newline at end of file diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 859ca1b..2196d12 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -10,13 +10,12 @@ from pots.utils import match_pot_factory_pattern, match_pot_subaccount_pattern from .logging import logger -from .utils import ( - handle_batch_donations, +from .utils import ( # handle_batch_donations, handle_default_list_status_change, handle_list_admin_removal, handle_list_registration_update, handle_list_upvote, - handle_new_donations, + handle_new_donation, handle_new_list, handle_new_list_registration, handle_new_pot, @@ -111,6 +110,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess signer_id = receipt.receipt["Action"]["signer_id"] receiver_id = receipt.receiver_id predecessor_id = receipt.predecessor_id + result = status_obj.status.get("SuccessValue") # Assuming the decoded data is UTF-8 text try: decoded_text = decoded_bytes.decode("utf-8") @@ -183,55 +183,55 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess ) break - case ( - "handle_protocol_fee_callback" - ): # NB: It's important that this comes before the donate case - logger.info( - f"donations to pool incoming: {args_dict}, {receipt}, {receipt_execution_outcome}" - ) - await handle_new_donations( - args_dict, - receiver_id, - signer_id, - "pot", - receipt, - status_obj, - log_data, - ) - break + # Donation cases + # SCENARIOS: + # 1. Pot donations + # tl;dr: only handle method calls that have a result, aka the final call in the chain. This could be "donate", "handle_protocol_fee_callback", or "sybil_callback". + # - handle_protocol_fee_callback (NOT called if protocol fee is bypassed) + # - check result (will ALWAYS return DonationExternal) + # - sybil_callback (NOT called if there are no sybil requirements for the Pot) + # - check result (MAY return DonationExternal) + # - if result is not None, handle donation. + # - donate + # - check result (will either return `DonationExternal`, if no CC calls, or `None` if CC calls were involved) + # - if result is not None, handle donation. Otherwise ignore & listen for either handle_protocol_fee_callback or sybil_callback + # - Example with result: https://nearblocks.io/txns/9beSPiZzR9Yu1951gC6AfQVCXiGPnBRxRFQsyfxUQr3H?tab=execution + # - Example with no result: https://nearblocks.io/txns/7p9m3D2Ao3TX9BXXCKTFbBk51F2iEuSCi8r5gSesdkZ2?tab=execution + # 2. Direct donations + # - transfer_funds_callback + # - check result (will always return DonationExternal IF it is a DonationTransfer) + # - if result is not None, handle donation + # - NB: this method was not implemented until early 2024; for older donations, use donate method (we aren't handling at this point since these donations have already been indexed. this would only be necessary if donations are re-indexed from PotLock genesis.) - case "donate": # TODO: donate that produces result - logger.info( - f"switching bazooka to knifee works!! donate his blood: {args_dict}, {receipt}, {action}, {log_data}" + case ( + "donate" + | "handle_protocol_fee_callback" + | "sybil_callback" + | "transfer_funds_callback" + ): + pot_methods = [ + "handle_protocol_fee_callback", + "sybil_callback", + "donate", + ] + direct_methods = ["transfer_funds_callback"] + donation_type = ( + "pot" if method_name in pot_methods else "direct" ) - if len(log_data) > 1: - await handle_batch_donations( - receiver_id, signer_id, "direct", receipt, log_data - ) - else: - await handle_new_donations( - args_dict, - receiver_id, - signer_id, - "direct", - receipt, - status_obj, - log_data, - ) - break - - case "transfer_funds_callback": logger.info( - f"new version donations to pool incoming: {args_dict}, {action}" + f"New {donation_type} donation ({method_name}) --- ARGS: {args_dict}, RECEIPT: {receipt}, STATUS: {status_obj}, OUTCOME: {receipt_execution_outcome}, LOGS: {log_data}" ) - await handle_new_donations( + if not result: + logger.info("No result found. Skipping...") + break + await handle_new_donation( args_dict, receiver_id, signer_id, - "direct", + donation_type, receipt, - status_obj, - log_data, + result, + # log_data, ) break diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 1325f24..d9ddb00 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -50,8 +50,8 @@ async def handle_social_profile_update(args_dict, receiver_id, signer_id): async def handle_new_pot( data: dict, - receiverId: str, - signerId: str, + receiver_id: str, + signer_id: str, predecessorId: str, receiptId: str, created_at: datetime, @@ -62,11 +62,11 @@ async def handle_new_pot( # Upsert accounts owner_id = ( - data.get("owner") or signerId + data.get("owner") or signer_id ) # owner is optional; if not provided, owner will be transaction signer (this logic is implemented by Pot contract's "new" method) owner, _ = await Account.objects.aget_or_create(id=owner_id) - signer, _ = await Account.objects.aget_or_create(id=signerId) - receiver, _ = await Account.objects.aget_or_create(id=receiverId) + signer, _ = await Account.objects.aget_or_create(id=signer_id) + receiver, _ = await Account.objects.aget_or_create(id=receiver_id) logger.info("upsert chef") if data.get("chef"): @@ -131,8 +131,8 @@ async def handle_new_pot( pot.admins.aadd(admin) defaults = { - "signer_id": signerId, - "receiver_id": receiverId, + "signer_id": signer_id, + "receiver_id": receiver_id, "timestamp": created_at, "tx_hash": receiptId, } @@ -144,7 +144,7 @@ async def handle_new_pot( logger.error(f"Failed to handle new pot, Error: {e}") -async def handle_new_pot_factory(data: dict, receiverId: str, created_at: datetime): +async def handle_new_pot_factory(data: dict, receiver_id: str, created_at: datetime): try: logger.info("upserting accounts...") @@ -158,7 +158,7 @@ async def handle_new_pot_factory(data: dict, receiverId: str, created_at: dateti ) receiver, _ = await Account.objects.aget_or_create( - id=receiverId, + id=receiver_id, ) logger.info("creating factory....") @@ -192,12 +192,16 @@ async def handle_new_pot_factory(data: dict, receiverId: str, created_at: dateti logger.error(f"Failed to handle new pot Factory, Error: {e}") -async def handle_new_list(signerId: str, receiverId: str, status_obj: ExecutionOutcome): +async def handle_new_list( + signer_id: str, receiver_id: str, status_obj: ExecutionOutcome +): # receipt = block.receipts().filter(receiptId=receiptId)[0] try: data = json.loads( - base64.b64decode(status_obj.status.get("SuccessValue")).decode("utf-8") + base64.b64decode(status_obj.status.get("SuccessValue")).decode( + "utf-8" + ) # TODO: RECEIVE AS A FUNCTION ARGUMENT ) logger.info(f"creating list..... {data}") @@ -218,9 +222,9 @@ async def handle_new_list(signerId: str, receiverId: str, status_obj: ExecutionO await Account.objects.aget_or_create(id=data["owner"]) - await Account.objects.aget_or_create(id=signerId) + await Account.objects.aget_or_create(id=signer_id) - await Account.objects.aget_or_create(id=receiverId) + await Account.objects.aget_or_create(id=receiver_id) if data.get("admins"): for admin_id in data["admins"]: @@ -234,19 +238,21 @@ async def handle_new_list(signerId: str, receiverId: str, status_obj: ExecutionO async def handle_new_list_registration( data: dict, - receiverId: str, - signerId: str, + receiver_id: str, + signer_id: str, receipt: Receipt, status_obj: ExecutionOutcome, ): - logger.info(f"new Project data: {data}, {receiverId}") + logger.info(f"new Project data: {data}, {receiver_id}") # Retrieve receipt data if receipt is None: return reg_data = json.loads( - base64.b64decode(status_obj.status["SuccessValue"]).decode("utf-8") + base64.b64decode(status_obj.status["SuccessValue"]).decode( + "utf-8" + ) # TODO: RECEIVE AS A FUNCTION ARGUMENT ) # Prepare data for insertion project_list = [] @@ -272,7 +278,7 @@ async def handle_new_list_registration( await Account.objects.abulk_create( objs=[Account(**data) for data in project_list], ignore_conflicts=True ) - await Account.objects.aget_or_create(id=signerId) + await Account.objects.aget_or_create(id=signer_id) logger.info("Upserted accounts/registrants(signer)") except Exception as e: logger.error(f"Encountered error trying to get create acct: {e}") @@ -287,8 +293,8 @@ async def handle_new_list_registration( # Insert activity try: defaults = { - "signer_id": signerId, - "receiver_id": receiverId, + "signer_id": signer_id, + "receiver_id": receiver_id, "timestamp": datetime.fromtimestamp(insert_data[0]["submitted_at"] / 1000), "tx_hash": receipt.receipt_id, } @@ -301,12 +307,14 @@ async def handle_new_list_registration( async def handle_list_registration_update( - data: dict, receiverId: str, status_obj: ExecutionOutcome + data: dict, receiver_id: str, status_obj: ExecutionOutcome ): - logger.info(f"new Project data: {data}, {receiverId}") + logger.info(f"new Project data: {data}, {receiver_id}") data = json.loads( - base64.b64decode(status_obj.status.get("SuccessValue")).decode("utf-8") + base64.b64decode(status_obj.status.get("SuccessValue")).decode( + "utf-8" + ) # TODO: RECEIVE AS A FUNCTION ARGUMENT ) # Prepare data for update @@ -325,8 +333,8 @@ async def handle_list_registration_update( async def handle_pot_application( data: dict, - receiverId: str, - signerId: str, + receiver_id: str, + signer_id: str, receipt: Receipt, status_obj: ExecutionOutcome, created_at: datetime, @@ -338,7 +346,9 @@ async def handle_pot_application( if not result: return - appl_data = json.loads(base64.b64decode(result).decode("utf-8")) + appl_data = json.loads( + base64.b64decode(result).decode("utf-8") + ) # TODO: RECEIVE AS A FUNCTION ARGUMENT logger.info(f"new pot application data: {data}, {appl_data}") # Update or create the account @@ -347,7 +357,7 @@ async def handle_pot_application( ) signer, _ = await Account.objects.aget_or_create( - id=signerId, + id=signer_id, ) # Create the PotApplication object @@ -362,7 +372,7 @@ async def handle_pot_application( application, application_created = ( await PotApplication.objects.aupdate_or_create( applicant=project, - pot_id=receiverId, + pot_id=receiver_id, defaults=appl_defaults, ) ) @@ -371,8 +381,8 @@ async def handle_pot_application( logger.info("creating activity for action....") defaults = { - "signer_id": signerId, - "receiver_id": receiverId, + "signer_id": signer_id, + "receiver_id": receiver_id, "timestamp": created_at, "tx_hash": receipt.receipt_id, } @@ -390,18 +400,20 @@ async def handle_pot_application( async def handle_pot_application_status_change( data: dict, - receiverId: str, - signerId: str, + receiver_id: str, + signer_id: str, receipt: Receipt, status_obj: ExecutionOutcome, ): try: - logger.info(f"pot application update data: {data}, {receiverId}") + logger.info(f"pot application update data: {data}, {receiver_id}") # receipt = next(receipt for receipt in block.receipts() if receipt.receiptId == receiptId) update_data = json.loads( - base64.b64decode(status_obj.status["SuccessValue"]).decode("utf-8") + base64.b64decode(status_obj.status["SuccessValue"]).decode( + "utf-8" + ) # TODO: RECEIVE AS A FUNCTION ARGUMENT ) # Retrieve the PotApplication object @@ -427,7 +439,7 @@ async def handle_pot_application_status_change( await PotApplicationReview.objects.aupdate_or_create( application_id=appl.id, - reviewer_id=signerId, + reviewer_id=signer_id, reviewed_at=updated_at, defaults=defaults, ) @@ -443,14 +455,16 @@ async def handle_pot_application_status_change( async def handle_default_list_status_change( - data: dict, receiverId: str, status_obj: ExecutionOutcome + data: dict, receiver_id: str, status_obj: ExecutionOutcome ): try: - logger.info(f"update project data: {data}, {receiverId}") + logger.info(f"update project data: {data}, {receiver_id}") result_data = json.loads( - base64.b64decode(status_obj.status.get("SuccessValue")).decode("utf-8") + base64.b64decode(status_obj.status.get("SuccessValue")).decode( + "utf-8" + ) # TODO: RECEIVE AS A FUNCTION ARGUMENT ) list_id = data.get("registration_id") @@ -474,26 +488,26 @@ async def handle_default_list_status_change( async def handle_list_upvote( - data: dict, receiverId: str, signerId: str, receiptId: str + data: dict, receiver_id: str, signer_id: str, receiptId: str ): try: - logger.info(f"upvote list: {data}, {receiverId}") + logger.info(f"upvote list: {data}, {receiver_id}") acct, _ = await Account.objects.aget_or_create( - id=signerId, + id=signer_id, ) created_at = datetime.now() await ListUpvote.objects.aupdate_or_create( - list_id=data.get("list_id") or receiverId, - account_id=signerId, + list_id=data.get("list_id") or receiver_id, + account_id=signer_id, ) defaults = { - "signer_id": signerId, - "receiver_id": receiverId, + "signer_id": signer_id, + "receiver_id": receiver_id, "timestamp": created_at, "tx_hash": receiptId, } @@ -509,10 +523,10 @@ async def handle_list_upvote( logger.error(f"Failed to upvote list, Error: {e}") -async def handle_set_payouts(data: dict, receiverId: str, receipt: Receipt): +async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): try: - logger.info(f"set payout data: {data}, {receiverId}") + logger.info(f"set payout data: {data}, {receiver_id}") payouts = data.get("payouts", []) insertion_data = [] @@ -532,12 +546,12 @@ async def handle_set_payouts(data: dict, receiverId: str, receipt: Receipt): async def handle_transfer_payout( - data: dict, receiverId: str, receiptId: str, created_at: datetime + data: dict, receiver_id: str, receiptId: str, created_at: datetime ): try: data = data["payout"] - logger.info(f"fulfill payout data: {data}, {receiverId}") + logger.info(f"fulfill payout data: {data}, {receiver_id}") payout = { "recipient_id": data["project_id"], "amount": data["amount"], @@ -552,23 +566,23 @@ async def handle_transfer_payout( async def handle_payout_challenge( - data: dict, receiverId: str, signerId: str, receiptId: str, created_at: datetime + data: dict, receiver_id: str, signer_id: str, receiptId: str, created_at: datetime ): try: - logger.info(f"challenging payout..: {data}, {receiverId}") + logger.info(f"challenging payout..: {data}, {receiver_id}") payoutChallenge = { "created_at": created_at, "message": data["reason"], "tx_hash": receiptId, } await PotPayoutChallenge.objects.aupdate_or_create( - challenger_id=signerId, pot_id=receiverId, defaults=payoutChallenge + challenger_id=signer_id, pot_id=receiver_id, defaults=payoutChallenge ) defaults = { - "signer_id": signerId, - "receiver_id": receiverId, + "signer_id": signer_id, + "receiver_id": receiver_id, "timestamp": created_at, "tx_hash": receiptId, } @@ -581,19 +595,19 @@ async def handle_payout_challenge( async def handle_payout_challenge_response( - data: dict, receiverId: str, signerId: str, receiptId: str, created_at: datetime + data: dict, receiver_id: str, signer_id: str, receiptId: str, created_at: datetime ): try: - logger.info(f"responding to payout challenge..: {data}, {receiverId}") + logger.info(f"responding to payout challenge..: {data}, {receiver_id}") response_defaults = { - "admin": signerId, + "admin": signer_id, "message": data.get("notes"), "resolved": data.get("resolve_challenge"), "tx_hash": receiptId, } await PotPayoutChallengeAdminResponse.objects.aupdate_or_create( challenger_id=data["challenger_id"], - pot_id=receiverId, + pot_id=receiver_id, created_at=created_at, defaults=response_defaults, ) @@ -601,18 +615,18 @@ async def handle_payout_challenge_response( logger.error(f"Failed to handle admin challeneg response, Error: {e}") -async def handle_list_admin_removal(data, receiverId, signerId, receiptId): +async def handle_list_admin_removal(data, receiver_id, signer_id, receiptId): try: - logger.info(f"removing admin...: {data}, {receiverId}") + logger.info(f"removing admin...: {data}, {receiver_id}") list_obj = await List.objects.aget(id=data["list_id"]) for acct in data["admins"]: list_obj.admins.remove({"admins_id": acct}) # maybe check activity = { - "signer_id": signerId, - "receiver_id": receiverId, + "signer_id": signer_id, + "receiver_id": receiver_id, "timestamp": datetime.now(), "tx_hash": receiptId, } @@ -624,83 +638,67 @@ async def handle_list_admin_removal(data, receiverId, signerId, receiptId): logger.error(f"Failed to remove list admin, Error: {e}") -# TODO: Need to abstract some actions. -async def handle_batch_donations( - receiverId: str, - signerId: str, - actionName: str, - receipt_obj: Receipt, - log_data: list, -): - logger.info("BAtch Transaction for donation...") - for event_data in log_data: - await handle_new_donations( - event_data["donation"], - receiverId, - signerId, - actionName, - receipt_obj, - status_obj=None, - log_data=[event_data], - ) - - -async def handle_new_donations( +# # TODO: Need to abstract some actions. +# async def handle_batch_donations( +# receiver_id: str, +# signer_id: str, +# action_name: str, +# receipt_obj: Receipt, +# log_data: list, +# ): +# logger.info("Batch Transaction for donation...") +# for event_data in log_data: +# await handle_new_donations( +# event_data["donation"], +# receiver_id, +# signer_id, +# action_name, +# receipt_obj, +# status_obj=None, +# log_data=[event_data], +# ) + +# TODO: create handle_new_pot_donation & handle_new_direct_donation functions & share common logic with _handle_new_donation function + + +async def handle_new_donation( data: dict, - receiverId: str, - signerId: str, - actionName: str, + receiver_id: str, + signer_id: str, + donation_type: str, # "direct" or "pot" receipt_obj: Receipt, - status_obj: ExecutionOutcome, - log_data: list, + result: dict, # Donation object (note that these vary between direct and pot donations - see examples of each in ./examples.txt) ): - logger.info(f"new donation data: {data}, {receiverId}") - donate_contract_addr = "donate." + settings.POTLOCK_TLA - - if ( - actionName == "direct" - ) and receiverId == donate_contract_addr: # early pot donations followed similarly to direct donations i.e they returned result instead of events. - logger.info("calling donate contract...") - # Handle direct donation - - if not log_data: - return - - logger.info(f"event after possible filtering: {log_data}") - - event_data = log_data[0] - donation_data = event_data["donation"] - net_amount = int(donation_data["total_amount"]) - int( - donation_data["protocol_fee"] - ) - logger.info(f"Donation data: {donation_data}, {net_amount}") - # insert donate contract which is the receiver id(because of activitry relationship mainly) - donate_contract, _ = await Account.objects.aget_or_create(id=receiverId) + logger.info(f"handle_new_donation data: {data}, {receiver_id}") + donation_data = json.loads(base64.b64decode(result).decode("utf-8")) + logger.info(f"decoded result: {donation_data}") - else: - result = status_obj.status.get("SuccessValue") - if not result: - return - # Handle non-direct donation - donation_data = json.loads(base64.b64decode(result).decode("utf-8")) - logger.info(f"Donation data: {donation_data}") + if "net_amount" in donation_data: net_amount = int(donation_data["net_amount"]) + else: + # direct donations don't have net_amount property, so have to calculate it here + net_amount = ( + int(donation_data["total_amount"]) + - int(donation_data["protocol_fee"]) + - donation_data.get("referrer_fee", 0) + ) donated_at = datetime.fromtimestamp( (donation_data.get("donated_at") or donation_data.get("donated_at_ms")) / 1000 ) try: - # Upsert donor account donor, _ = await Account.objects.aget_or_create(id=donation_data["donor_id"]) recipient = None + referrer = None + chef = None - if donation_data.get("recipient_id"): + if donation_data.get("recipient_id"): # direct donations have recipient_id recipient, _ = await Account.objects.aget_or_create( id=donation_data["recipient_id"] ) - if donation_data.get("project_id"): + if donation_data.get("project_id"): # pot donations have project_id recipient, _ = await Account.objects.aget_or_create( id=donation_data["project_id"] ) @@ -710,6 +708,9 @@ async def handle_new_donations( id=donation_data["referrer_id"] ) + if donation_data.get("chef_id"): + chef, _ = await Account.objects.aget_or_create(id=donation_data["chef_id"]) + # Upsert token account token_acct, _ = await Account.objects.aget_or_create( id=(donation_data.get("ft_id") or "near") @@ -717,63 +718,15 @@ async def handle_new_donations( except Exception as e: logger.error(f"Failed to create/get an account involved in donation: {e}") - # # Upsert token - # try: - # token = await Token.objects.aget(id=token_acct) - # except Token.DoesNotExist: - # # TODO: fetch metadata from token contract (ft_metadata) and add decimals to token record. For now adding 12 which is most common - # token = await Token.objects.acreate(id=token_acct, decimals=12) - - # # Fetch historical token data - # # late_p = await token.get_most_recent_price() - # try: - # logger.info("fetching historical price...") - # logger.info(f"donated at: {donated_at}") - # endpoint = f"{GECKO_URL}/coins/{donation_data.get('ft_id', 'near')}/history?date={format_date(donated_at)}&localization=false" - # logger.info(f"endpoint: {endpoint}") - # response = requests.get(endpoint) - # logger.info(f"response: {response}") - # if response.status_code == 429: - # logger.error("Coingecko rate limit exceeded") - # price_data = response.json() - # except Exception as e: - # logger.error(f"Failed to fetch price data: {e}") - # logger.info(f"price data: {price_data}") - # unit_price = price_data.get("market_data", {}).get("current_price", {}).get("usd") - # logger.info(f"unit price: {unit_price}") - # if unit_price: - # try: - # await TokenHistoricalPrice.objects.acreate( # need to change token model to use token as id - # token=token, - # price_usd=unit_price, - # timestamp=donated_at, - # ) - # except Exception as e: - # logger.error( - # f"Error creating TokenHistoricalPrice: {e} token: {token} unit_price: {unit_price}" - # ) - # # historical_price = await token.get_most_recent_price() # to use model methods, we might have to use asgiref sync_to_async - # historical = await TokenHistoricalPrice.objects.aget(token=token) - # unit_price = historical.price_usd - - # total_amount = donation_data["total_amount"] - # net_amount = net_amount - int(donation_data.get("referrer_fee") or 0) - - # # Calculate and format amounts - # total_near_amount = format_to_near(total_amount) - # net_near_amount = format_to_near(net_amount) - # total_amount_usd = None if not unit_price else unit_price * total_near_amount - # net_amount_usd = None if not unit_price else unit_price * net_near_amount - try: total_amount = donation_data["total_amount"] - logger.info(f"inserting donations... by {actionName}") + logger.info(f"inserting {donation_type} donation") default_data = { "donor": donor, "total_amount": total_amount, - "total_amount_usd": None, # USD amounts will be added later (could be in pre-save hook) + "total_amount_usd": None, # USD amounts will be added later "net_amount_usd": None, "net_amount": net_amount, "ft": token_acct, @@ -782,15 +735,15 @@ async def handle_new_donations( "matching_pool": donation_data.get("matching_pool", False), "recipient": recipient, "protocol_fee": donation_data["protocol_fee"], - "referrer": referrer if donation_data.get("referrer_id") else None, + "referrer": referrer, "referrer_fee": donation_data.get("referrer_fee"), + "chef": chef, + "chef_fee": donation_data.get("chef_fee"), "tx_hash": receipt_obj.receipt_id, } - if actionName != "direct": - logger.info("selecting pot to make public donation update") - pot = await Pot.objects.aget(id=receiverId) - default_data["pot"] = pot + if donation_type == "pot": + default_data["pot"] = await Pot.objects.aget(id=receiver_id) logger.info(f"default donation data: {default_data}") @@ -803,17 +756,10 @@ async def handle_new_donations( # fetch USD prices await donation.fetch_usd_prices_async() # might not need to await this? - # # convert total_amount_usd and net_amount_usd from None - # if total_amount_usd is None: - # total_amount_usd = 0.0 - # if net_amount_usd is None: - # net_amount_usd = 0.0 - - logger.info(f"Created donation? {donation_created}") # Insert or update activity record activity_type = ( "Donate_Direct" - if actionName == "direct" + if donation_type == "direct" else ( "Donate_Pot_Matching_Pool" if donation.matching_pool @@ -821,13 +767,12 @@ async def handle_new_donations( ) ) defaults = { - "signer_id": signerId, - "receiver_id": receiverId, + "signer_id": signer_id, + "receiver_id": receiver_id, "timestamp": donation.donated_at, "tx_hash": receipt_obj.receipt_id, } try: - activity, activity_created = await Activity.objects.aupdate_or_create( action_result=donation_data, type=activity_type, defaults=defaults ) @@ -844,7 +789,7 @@ async def handle_new_donations( ### CAN ALWAYS ADD BACK IF DESIRED # if donation_created: # only do stats updates if donation object was created - # if actionName != "direct": + # if action_name != "direct": # potUpdate = { # "total_public_donations": str( @@ -878,7 +823,7 @@ async def handle_new_donations( # pot.public_donations_count or 0 # ) + 1 - # await Pot.objects.filter(id=receiverId).aupdate(**potUpdate) + # await Pot.objects.filter(id=receiver_id).aupdate(**potUpdate) # # donation_recipient = donation_data.get('project_id', donation_data['recipient_id']) # logger.info( From 7efe70aeb2b9cb6bc8b5da6a3500fa6d34a56081 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:05:41 -0400 Subject: [PATCH 178/454] fix unsupported operand type error --- indexer_app/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index d9ddb00..ba8273c 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -680,7 +680,8 @@ async def handle_new_donation( net_amount = ( int(donation_data["total_amount"]) - int(donation_data["protocol_fee"]) - - donation_data.get("referrer_fee", 0) + - donation_data["referrer_fee"] + or 0 ) donated_at = datetime.fromtimestamp( From 264cf4b3fb865164f0b3dde32156facd2f81e3de Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:12:09 -0400 Subject: [PATCH 179/454] fix operand issue again --- indexer_app/utils.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index ba8273c..178556f 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -677,12 +677,11 @@ async def handle_new_donation( net_amount = int(donation_data["net_amount"]) else: # direct donations don't have net_amount property, so have to calculate it here - net_amount = ( - int(donation_data["total_amount"]) - - int(donation_data["protocol_fee"]) - - donation_data["referrer_fee"] - or 0 - ) + total_amount = int(donation_data["total_amount"]) + protocol_fee = int(donation_data["protocol_fee"]) + referrer_fee = int(donation_data["referrer_fee"] or 0) + + net_amount = total_amount - protocol_fee - referrer_fee donated_at = datetime.fromtimestamp( (donation_data.get("donated_at") or donation_data.get("donated_at_ms")) / 1000 From 6d4858974d241d7c951f84cffe6d6e1de12c107b Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:48:20 -0400 Subject: [PATCH 180/454] update donation handler --- indexer_app/handler.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 2196d12..8895132 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -92,6 +92,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess # print("here we are...") # continue lists_contract = "lists." + settings.POTLOCK_TLA + donate_contract = "donate." + settings.POTLOCK_TLA for index, action in enumerate( receipt_execution_outcome.receipt.receipt["Action"]["actions"] @@ -198,6 +199,8 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess # - Example with result: https://nearblocks.io/txns/9beSPiZzR9Yu1951gC6AfQVCXiGPnBRxRFQsyfxUQr3H?tab=execution # - Example with no result: https://nearblocks.io/txns/7p9m3D2Ao3TX9BXXCKTFbBk51F2iEuSCi8r5gSesdkZ2?tab=execution # 2. Direct donations + # - donate + # - if result is not None, handle donation. # - transfer_funds_callback # - check result (will always return DonationExternal IF it is a DonationTransfer) # - if result is not None, handle donation @@ -209,14 +212,8 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess | "sybil_callback" | "transfer_funds_callback" ): - pot_methods = [ - "handle_protocol_fee_callback", - "sybil_callback", - "donate", - ] - direct_methods = ["transfer_funds_callback"] donation_type = ( - "pot" if method_name in pot_methods else "direct" + "direct" if receiver_id == donate_contract else "pot" ) logger.info( f"New {donation_type} donation ({method_name}) --- ARGS: {args_dict}, RECEIPT: {receipt}, STATUS: {status_obj}, OUTCOME: {receipt_execution_outcome}, LOGS: {log_data}" From 148707d3e246cdc5f276d5337b5feaaf40b504c7 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 12 Jun 2024 10:27:04 -0400 Subject: [PATCH 181/454] add near social profile data schema --- accounts/serializers.py | 60 +++++++++++++++++++++++++++++++++++++++++ indexer_app/handler.py | 21 +++++++-------- 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/accounts/serializers.py b/accounts/serializers.py index 686e489..c4b4e70 100644 --- a/accounts/serializers.py +++ b/accounts/serializers.py @@ -3,6 +3,54 @@ from .models import Account +# near social profile data serializers (for Swagger schema) + + +class NFTSerializer(serializers.Serializer): + media = serializers.URLField(required=False) + baseUri = serializers.URLField(required=False) + tokenId = serializers.CharField(required=False) + contractId = serializers.CharField(required=False) + + +class ImageSerializer(serializers.Serializer): + url = serializers.URLField(required=False) + ipfs_cid = serializers.CharField(required=False) + nft = NFTSerializer(required=False) + + +class LinktreeSerializer(serializers.Serializer): + github = serializers.CharField(required=False, allow_blank=True) + twitter = serializers.CharField(required=False, allow_blank=True) + website = serializers.CharField(required=False, allow_blank=True) + telegram = serializers.CharField(required=False, allow_blank=True) + + +class NearSocialProfileDataSerializer(serializers.Serializer): + name = serializers.CharField(required=False) + image = ImageSerializer(required=False) + backgroundImage = ImageSerializer(required=False) + description = serializers.CharField(required=False) + linktree = LinktreeSerializer(required=False) + plPublicGoodReason = serializers.CharField(required=False) + plCategories = serializers.CharField( + required=False, help_text="JSON-stringified array of category strings" + ) + plGithubRepos = serializers.CharField( + required=False, help_text="JSON-stringified array of URLs" + ) + plSmartContracts = serializers.CharField( + required=False, + help_text="JSON-stringified object with chain names as keys that map to nested objects of contract addresses", + ) + plFundingSources = serializers.CharField( + required=False, help_text="JSON-stringified array of funding source objects" + ) + plTeam = serializers.CharField( + required=False, + help_text="JSON-stringified array of team member account ID strings", + ) + class AccountSerializer(serializers.ModelSerializer): class Meta: @@ -16,6 +64,18 @@ class Meta: "near_social_profile_data", ] + total_donations_in_usd = serializers.DecimalField( + max_digits=20, decimal_places=2, coerce_to_string=False + ) + total_donations_out_usd = serializers.DecimalField( + max_digits=20, decimal_places=2, coerce_to_string=False + ) + total_matching_pool_allocations_usd = serializers.DecimalField( + max_digits=20, decimal_places=2, coerce_to_string=False + ) + donors_count = serializers.IntegerField() + near_social_profile_data = NearSocialProfileDataSerializer(required=False) + SIMPLE_ACCOUNT_EXAMPLE = { "id": "user.near", diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 8895132..48098e8 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -91,8 +91,8 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess # # consider logging failures to logging service; for now, just skip # print("here we are...") # continue - lists_contract = "lists." + settings.POTLOCK_TLA - donate_contract = "donate." + settings.POTLOCK_TLA + LISTS_CONTRACT = "lists." + settings.POTLOCK_TLA + DONATE_CONTRACT = "donate." + settings.POTLOCK_TLA for index, action in enumerate( receipt_execution_outcome.receipt.receipt["Action"]["actions"] @@ -184,8 +184,8 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess ) break - # Donation cases - # SCENARIOS: + ### Donation cases + ## SCENARIOS: # 1. Pot donations # tl;dr: only handle method calls that have a result, aka the final call in the chain. This could be "donate", "handle_protocol_fee_callback", or "sybil_callback". # - handle_protocol_fee_callback (NOT called if protocol fee is bypassed) @@ -204,8 +204,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess # - transfer_funds_callback # - check result (will always return DonationExternal IF it is a DonationTransfer) # - if result is not None, handle donation - # - NB: this method was not implemented until early 2024; for older donations, use donate method (we aren't handling at this point since these donations have already been indexed. this would only be necessary if donations are re-indexed from PotLock genesis.) - + # - NB: this method was not implemented until early 2024; for older donations, use donate method case ( "donate" | "handle_protocol_fee_callback" @@ -213,7 +212,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess | "transfer_funds_callback" ): donation_type = ( - "direct" if receiver_id == donate_contract else "pot" + "direct" if receiver_id == DONATE_CONTRACT else "pot" ) logger.info( f"New {donation_type} donation ({method_name}) --- ARGS: {args_dict}, RECEIPT: {receipt}, STATUS: {status_obj}, OUTCOME: {receipt_execution_outcome}, LOGS: {log_data}" @@ -238,7 +237,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess logger.info( f"registrations incoming: {args_dict}, {action}" ) - if receiver_id != lists_contract: + if receiver_id != LISTS_CONTRACT: break await handle_new_list_registration( args_dict, receiver_id, signer_id, receipt, status_obj @@ -312,7 +311,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess "owner_remove_admins" ): # TODO: use update_admins event instead of method call to handle all cases logger.info(f"attempting to remove admins....: {args_dict}") - if receiver_id != lists_contract: + if receiver_id != LISTS_CONTRACT: break await handle_list_admin_removal( args_dict, receiver_id, signer_id, receipt.receipt_id @@ -321,14 +320,14 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess case "create_list": logger.info(f"creating list... {args_dict}, {action}") - if receiver_id != lists_contract: + if receiver_id != LISTS_CONTRACT: break await handle_new_list(signer_id, receiver_id, status_obj) break case "upvote": logger.info(f"up voting... {args_dict}") - if receiver_id != lists_contract: + if receiver_id != LISTS_CONTRACT: break await handle_list_upvote( args_dict, receiver_id, signer_id, receipt.receipt_id From 237df5335c2839ff60e284eac0796064e5792493 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 13 Jun 2024 09:46:07 -0400 Subject: [PATCH 182/454] fix Donation aupdate_or_create query to include pot --- indexer_app/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 178556f..7ac64a6 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -748,7 +748,9 @@ async def handle_new_donation( logger.info(f"default donation data: {default_data}") donation, donation_created = await Donation.objects.aupdate_or_create( - on_chain_id=donation_data["id"], defaults=default_data + on_chain_id=donation_data["id"], + pot=default_data["pot"], + defaults=default_data, ) logger.info(f"Created donation? {donation_created}") logger.info(f"donation: {donation.to_dict()}") From 66a0ffe217646893bd1097453ebddb994b9ee087 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:43:17 -0400 Subject: [PATCH 183/454] add populatedata script --- .../migrations/0004_alter_activity_tx_hash.py | 23 + activities/models.py | 2 +- .../migrations/0008_alter_donation_tx_hash.py | 23 + donations/models.py | 2 +- .../management/commands/populatedata.py | 414 ++++++++++++++++++ pots/admin.py | 4 +- ...4_alter_potapplication_tx_hash_and_more.py | 53 +++ .../0005_alter_potapplication_updated_at.py | 22 + pots/models.py | 22 +- 9 files changed, 544 insertions(+), 21 deletions(-) create mode 100644 activities/migrations/0004_alter_activity_tx_hash.py create mode 100644 donations/migrations/0008_alter_donation_tx_hash.py create mode 100644 indexer_app/management/commands/populatedata.py create mode 100644 pots/migrations/0004_alter_potapplication_tx_hash_and_more.py create mode 100644 pots/migrations/0005_alter_potapplication_updated_at.py diff --git a/activities/migrations/0004_alter_activity_tx_hash.py b/activities/migrations/0004_alter_activity_tx_hash.py new file mode 100644 index 0000000..7dbece2 --- /dev/null +++ b/activities/migrations/0004_alter_activity_tx_hash.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.4 on 2024-06-13 14:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("activities", "0003_alter_activity_unique_together"), + ] + + operations = [ + migrations.AlterField( + model_name="activity", + name="tx_hash", + field=models.CharField( + help_text="Transaction hash.", + max_length=64, + null=True, + verbose_name="transaction hash", + ), + ), + ] diff --git a/activities/models.py b/activities/models.py index b1bab6d..3eefb5b 100644 --- a/activities/models.py +++ b/activities/models.py @@ -53,7 +53,7 @@ class Activity(models.Model): tx_hash = models.CharField( _("transaction hash"), max_length=64, - null=False, + null=True, help_text=_("Transaction hash."), ) type = models.CharField( diff --git a/donations/migrations/0008_alter_donation_tx_hash.py b/donations/migrations/0008_alter_donation_tx_hash.py new file mode 100644 index 0000000..7e7fc37 --- /dev/null +++ b/donations/migrations/0008_alter_donation_tx_hash.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.4 on 2024-06-13 14:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("donations", "0007_auto_20240507_1857"), + ] + + operations = [ + migrations.AlterField( + model_name="donation", + name="tx_hash", + field=models.CharField( + help_text="Transaction hash.", + max_length=64, + null=True, + verbose_name="transaction hash", + ), + ), + ] diff --git a/donations/models.py b/donations/models.py index 9979486..10c63a9 100644 --- a/donations/models.py +++ b/donations/models.py @@ -159,7 +159,7 @@ class Donation(models.Model): tx_hash = models.CharField( _("transaction hash"), max_length=64, - null=False, + null=True, help_text=_("Transaction hash."), ) diff --git a/indexer_app/management/commands/populatedata.py b/indexer_app/management/commands/populatedata.py new file mode 100644 index 0000000..489b70b --- /dev/null +++ b/indexer_app/management/commands/populatedata.py @@ -0,0 +1,414 @@ +import time +from datetime import date, datetime + +import requests +from django.conf import settings +from django.core.management.base import BaseCommand + +from accounts.models import Account +from activities.models import Activity +from donations.models import Donation +from pots.models import ( + Pot, + PotApplication, + PotApplicationReview, + PotPayout, + PotPayoutChallenge, + PotPayoutChallengeAdminResponse, +) + + +class Command(BaseCommand): + help = "Pull donations data from contract & populate donations table." + + def handle(self, *args, **options): + # direct donations + # get direct donations in pages + DONATE_CONTRACT_ID = "donate.potlock.near" + page = 0 + limit = 300 + while True: + time.sleep(1) + url = f"https://rpc.web4.near.page/account/{DONATE_CONTRACT_ID}/view/get_donations" + params = {"from_index.json": page * limit, "limit.json": limit} + donations = requests.get(url, params=params) + if donations.status_code != 200: + print( + f"Request for direct donations data failed ({donations.status_code}) with message: {donations.text}" + ) + return + donations = donations.json() + print(f"adding {len(donations)} direct donations") + for donation in donations: + donor, _ = Account.objects.get_or_create(id=donation["donor_id"]) + referrer = None + recipient, _ = Account.objects.get_or_create( + id=donation["recipient_id"] + ) + ft_acct, _ = Account.objects.get_or_create(id=donation["ft_id"]) + if donation.get("referrer_id"): + referrer, _ = Account.objects.get_or_create( + id=donation["referrer_id"] + ) + + total_amount = int(donation["total_amount"]) + protocol_fee = int(donation["protocol_fee"]) + referrer_fee = int(donation["referrer_fee"] or 0) + net_amount = total_amount - protocol_fee - referrer_fee + + donation_defaults = { + "donor": donor, + "total_amount": donation["total_amount"], + "total_amount_usd": None, # USD amounts will be added later + "net_amount_usd": None, + "net_amount": str(net_amount), + "ft": ft_acct, + "message": donation["message"], + "donated_at": datetime.fromtimestamp( + donation["donated_at_ms"] / 1000 + ), + "matching_pool": False, + "recipient": recipient, + "protocol_fee": donation["protocol_fee"], + "referrer": referrer, + "referrer_fee": donation["referrer_fee"], + "tx_hash": None, + } + donation, created = Donation.objects.update_or_create( + on_chain_id=donation["id"], pot=None, defaults=donation_defaults + ) + print("created donation: ", donation.on_chain_id, created) + # # add donation activity + # activity_type = ( + # "Donate_Pot_Matching_Pool" + # if donation.matching_pool + # else "Donate_Pot_Public" + # ) + # defaults = { + # "signer_id": donor.id, + # "receiver_id": pot_id, + # "timestamp": donation.donated_at, + # "tx_hash": None, + # } + # activity, _ = Activity.objects.update_or_create( + # type=activity_type, + # action_result=None, + # defaults=defaults, + # ) + if len(donations) == limit: + page += 1 + else: + break + # pot factory + POTFACTORY_ID = "v1.potfactory.potlock.near" + # pots + near_acct, _ = Account.objects.get_or_create(id="near") + url = f"{settings.FASTNEAR_RPC_URL}/account/{POTFACTORY_ID}/view/get_pots" + response = requests.get(url) + if response.status_code != 200: + print( + f"Request for donations data failed ({response.status_code}) with message: {response.text}" + ) + return + pots = response.json() + print("pots data; ", pots) + for pot in pots: + # if pot["id"] != "marketing.v1.potfactory.potlock.near": + # continue # TODO: REMOVE THIS + print("sleeping for 1 second") + time.sleep(1) + pot_id = pot["id"] + deployed_by = pot["deployed_by"] + deployed_at_ms = pot["deployed_at_ms"] + print("handling pot: ", pot_id) + # fetch config for pot + url = f"{settings.FASTNEAR_RPC_URL}/account/{pot_id}/view/get_config" + response = requests.get(url) + if response.status_code != 200: + print( + f"Request for pot config failed ({response.status_code}) with message: {response.text}" + ) + return + config = response.json() + print("config data; ", config) + url = f"{settings.FASTNEAR_RPC_URL}/account/{pot_id}/view/get_contract_source_metadata" + response = requests.get(url) + if response.status_code != 200: + print( + f"Request for pot source metadata failed ({response.status_code}) with message: {response.text}" + ) + return + source_metadata = response.json() + print("source metadata; ", source_metadata) + # add or update pot + pot_acct, _ = Account.objects.get_or_create(id=pot_id) + owner, _ = Account.objects.get_or_create(id=config["owner"]) + if config.get("chef"): + chef, _ = Account.objects.get_or_create(id=config["chef"]) + pot_defaults = { + "pot_factory_id": POTFACTORY_ID, + "owner_id": config["owner"], + "chef_id": config["chef"], + "name": config["pot_name"], + "description": config["pot_description"], + "max_approved_applicants": config["max_projects"], + "base_currency": config["base_currency"], + "application_start": datetime.fromtimestamp( + config["application_start_ms"] / 1000 + ), + "application_end": datetime.fromtimestamp( + config["application_end_ms"] / 1000 + ), + "matching_round_start": datetime.fromtimestamp( + config["public_round_start_ms"] / 1000 + ), + "matching_round_end": datetime.fromtimestamp( + config["public_round_end_ms"] / 1000 + ), + "deployer_id": config["deployed_by"], + "deployed_at": datetime.fromtimestamp(deployed_at_ms / 1000), + "source_metadata": source_metadata, + "registry_provider": config["registry_provider"], + "min_matching_pool_donation_amount": config[ + "min_matching_pool_donation_amount" + ], + "sybil_wrapper_provider": config["sybil_wrapper_provider"], + "custom_sybil_checks": config["custom_sybil_checks"], + "custom_min_threshold_score": config["custom_min_threshold_score"], + "referral_fee_matching_pool_basis_points": config[ + "referral_fee_matching_pool_basis_points" + ], + "referral_fee_public_round_basis_points": config[ + "referral_fee_public_round_basis_points" + ], + "chef_fee_basis_points": config["chef_fee_basis_points"], + "total_matching_pool": "0", + "matching_pool_balance": "0", + "matching_pool_donations_count": 0, + "total_public_donations": "0", + "public_donations_count": 0, + "cooldown_period_ms": None, + "cooldown_end": ( + None + if not config["cooldown_end_ms"] + else datetime.fromtimestamp(config["cooldown_end_ms"] / 1000) + ), + "all_paid_out": config["all_paid_out"], + "protocol_config_provider": config["protocol_config_provider"], + } + pot, created = Pot.objects.update_or_create( + id=pot_acct, defaults=pot_defaults + ) + print("pot created? ", created) + print("adding admins") + for admin in config["admins"]: + admin_obj, _ = Account.objects.get_or_create(id=admin) + pot.admins.add(admin_obj) + # add activity + activity_defaults = { + "signer_id": owner.id, + "receiver_id": POTFACTORY_ID, + "timestamp": datetime.fromtimestamp(deployed_at_ms / 1000), + "tx_hash": None, + } + activity, _ = Activity.objects.update_or_create( + type="Deploy_Pot", action_result=None, defaults=activity_defaults + ) + # get applications in pages + page = 0 + limit = 300 + while True: + time.sleep(1) + url = ( + f"https://rpc.web4.near.page/account/{pot_id}/view/get_applications" + ) + params = {"from_index.json": page * limit, "limit.json": limit} + applications = requests.get(url, params=params) + if applications.status_code != 200: + print( + f"Request for applications data failed ({applications.status_code}) with message: {applications.text}" + ) + return + applications = applications.json() + print(f"adding {len(applications)} applications") + for appl in applications: + applicant, _ = Account.objects.get_or_create(id=appl["project_id"]) + application_defaults = { + "applicant": applicant, + "pot": pot, + "message": appl["message"], + "status": appl["status"], + "submitted_at": datetime.fromtimestamp( + appl["submitted_at"] / 1000 + ), + "updated_at": ( + None + if not appl["updated_at"] + else datetime.fromtimestamp(appl["updated_at"] / 1000) + ), + } + application, _ = PotApplication.objects.update_or_create( + pot=pot, applicant=applicant, defaults=application_defaults + ) + # if status != Pending, add PotApplicationReview also, default to pot owner + if application.status != "Pending": + review_defaults = { + "application": application, + "reviewer": owner, + "notes": appl["review_notes"], + "status": application.status, + "reviewed_at": application.updated_at, + "tx_hash": None, + } + review, _ = PotApplicationReview.objects.update_or_create( + application=application, + reviewer=owner, + defaults=review_defaults, + ) + if len(applications) == limit: + page += 1 + else: + break + # get pot donations in pages + page = 0 + limit = 300 + while True: + time.sleep(1) + url = f"https://rpc.web4.near.page/account/{pot_id}/view/get_donations" + params = {"from_index.json": page * limit, "limit.json": limit} + donations = requests.get(url, params=params) + if donations.status_code != 200: + print( + f"Request for donations data failed ({donations.status_code}) with message: {donations.text}" + ) + return + donations = donations.json() + print(f"adding {len(donations)} donations") + for donation in donations: + donor, _ = Account.objects.get_or_create(id=donation["donor_id"]) + project = None + referrer = None + chef = None + if donation.get("project_id"): + project, _ = Account.objects.get_or_create( + id=donation["project_id"] + ) + if donation.get("referrer_id"): + referrer, _ = Account.objects.get_or_create( + id=donation["referrer_id"] + ) + if donation.get("chef_id"): + chef, _ = Account.objects.get_or_create(id=donation["chef_id"]) + donation_defaults = { + "donor": donor, + "total_amount": donation["total_amount"], + "total_amount_usd": None, # USD amounts will be added later + "net_amount_usd": None, + "net_amount": donation["net_amount"], + "ft_id": "near", + "message": donation["message"], + "donated_at": datetime.fromtimestamp( + donation["donated_at"] / 1000 + ), + "matching_pool": donation["matching_pool"], + "recipient": project, + "protocol_fee": donation["protocol_fee"], + "referrer": referrer, + "referrer_fee": donation["referrer_fee"], + "chef": chef, + "chef_fee": donation["chef_fee"], + "tx_hash": None, + } + donation, _ = Donation.objects.update_or_create( + on_chain_id=donation["id"], pot=pot, defaults=donation_defaults + ) + print("added donation: ", donation.on_chain_id) + # # add donation activity + # activity_type = ( + # "Donate_Pot_Matching_Pool" + # if donation.matching_pool + # else "Donate_Pot_Public" + # ) + # defaults = { + # "signer_id": donor.id, + # "receiver_id": pot_id, + # "timestamp": donation.donated_at, + # "tx_hash": None, + # } + # activity, _ = Activity.objects.update_or_create( + # type=activity_type, + # action_result=None, + # defaults=defaults, + # ) + if len(donations) == limit: + page += 1 + else: + break + if "payouts" in config: + for payout in config["payouts"]: + if not payout["paid_at"]: + continue + recipient, _ = Account.objects.get_or_create( + id=payout["project_id"] + ) + payout_defaults = { + "pot": pot, + "recipient": recipient, + "amount": payout["amount"], + "amount_paid_usd": None, # TODO: add this in task + "ft_id": "near", + "paid_at": datetime.fromtimestamp(payout["paid_at"] / 1000), + "tx_hash": None, + } + payout, _ = PotPayout.objects.update_or_create( + pot=pot, recipient=recipient, defaults=payout_defaults + ) + # get pot challenges & admin responses + page = 0 + limit = 300 + while True: + time.sleep(1) + url = f"https://rpc.web4.near.page/account/{pot_id}/view/get_payouts_challenges" + params = {"from_index.json": page * limit, "limit.json": limit} + challenges = requests.get(url, params=params) + if challenges.status_code != 200: + print( + f"Request for challenges data failed ({challenges.status_code}) with message: {challenges.text}" + ) + return + challenges = challenges.json() + print(f"adding {len(challenges)} challenges") + for c in challenges: + challenger, _ = Account.objects.get_or_create(id=c["challenger_id"]) + challenge_defaults = { + "challenger": challenger, + "pot": pot, + "message": c["reason"], + "created_at": datetime.fromtimestamp(c["created_at"] / 1000), + } + challenge, _ = PotPayoutChallenge.objects.update_or_create( + pot=pot, challenger=challenger, defaults=challenge_defaults + ) + # if status != Pending, add PotApplicationReview also, default to pot owner + if c["admin_notes"] or c["resolved"]: + response_defaults = { + "challenger": challenger, + "pot": pot, + "admin": owner, # don't actually know who did it, just using owner for this backfilling + "created_at": datetime.fromtimestamp( + c["created_at"] / 1000 + ), # not exactly correct but we don't know currently + "message": c["admin_notes"], + "resolved": c["resolved"], + } + review, _ = ( + PotPayoutChallengeAdminResponse.objects.update_or_create( + challenger=challenger, + pot=pot, + defaults=response_defaults, + ) + ) + if len(challenges) == limit: + page += 1 + else: + break diff --git a/pots/admin.py b/pots/admin.py index 18ff16c..ace2d5a 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -203,7 +203,7 @@ def has_delete_permission(self, request, obj=None): @admin.register(PotPayoutChallenge) class PotPayoutChallengeAdmin(admin.ModelAdmin): - list_display = ("id", "challenger", "pot", "created_at") + list_display = ("id", "challenger", "pot", "message", "created_at") search_fields = ("challenger__id", "pot__id") list_filter = ("created_at",) @@ -219,7 +219,7 @@ def has_delete_permission(self, request, obj=None): @admin.register(PotPayoutChallengeAdminResponse) class PotPayoutChallengeAdminResponseAdmin(admin.ModelAdmin): - list_display = ("id", "pot", "admin", "created_at", "resolved") + list_display = ("id", "pot", "admin", "message", "created_at", "resolved") search_fields = ("admin__id", "challenge__id") list_filter = ("created_at", "resolved") diff --git a/pots/migrations/0004_alter_potapplication_tx_hash_and_more.py b/pots/migrations/0004_alter_potapplication_tx_hash_and_more.py new file mode 100644 index 0000000..4cf6ca3 --- /dev/null +++ b/pots/migrations/0004_alter_potapplication_tx_hash_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 5.0.4 on 2024-06-13 15:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("pots", "0003_alter_potapplication_options_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="potapplication", + name="tx_hash", + field=models.CharField( + help_text="Transaction hash.", + max_length=64, + null=True, + verbose_name="transaction hash", + ), + ), + migrations.AlterField( + model_name="potapplicationreview", + name="tx_hash", + field=models.CharField( + help_text="Transaction hash.", + max_length=64, + null=True, + verbose_name="transaction hash", + ), + ), + migrations.AlterField( + model_name="potpayout", + name="tx_hash", + field=models.CharField( + help_text="Transaction hash.", + max_length=64, + null=True, + verbose_name="transaction hash", + ), + ), + migrations.AlterField( + model_name="potpayoutchallengeadminresponse", + name="tx_hash", + field=models.CharField( + help_text="Transaction hash.", + max_length=64, + null=True, + verbose_name="transaction hash", + ), + ), + ] diff --git a/pots/migrations/0005_alter_potapplication_updated_at.py b/pots/migrations/0005_alter_potapplication_updated_at.py new file mode 100644 index 0000000..5d91e47 --- /dev/null +++ b/pots/migrations/0005_alter_potapplication_updated_at.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.4 on 2024-06-13 15:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("pots", "0004_alter_potapplication_tx_hash_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="potapplication", + name="updated_at", + field=models.DateTimeField( + help_text="Application last update date.", + null=True, + verbose_name="updated at", + ), + ), + ] diff --git a/pots/models.py b/pots/models.py index ba1405e..db1ddb5 100644 --- a/pots/models.py +++ b/pots/models.py @@ -157,25 +157,21 @@ class Pot(models.Model): ) registry_provider = models.CharField( _("registry provider"), - max_length=64, null=True, help_text=_("Registry provider."), ) min_matching_pool_donation_amount = models.CharField( _("min matching pool donation amount"), - max_length=64, null=False, help_text=_("Min matching pool donation amount."), ) sybil_wrapper_provider = models.CharField( _("sybil wrapper provider"), - max_length=64, null=True, help_text=_("Sybil wrapper provider."), ) custom_sybil_checks = models.CharField( _("custom sybil checks"), - max_length=64, null=True, help_text=_("Custom sybil checks."), ) @@ -201,7 +197,6 @@ class Pot(models.Model): ) total_matching_pool = models.CharField( _("total matching pool"), - max_length=64, null=False, help_text=_("Total matching pool."), ) @@ -214,7 +209,6 @@ class Pot(models.Model): ) matching_pool_balance = models.CharField( _("matching pool balance"), - max_length=64, null=False, help_text=_("Matching pool balance."), ) @@ -225,7 +219,6 @@ class Pot(models.Model): ) total_public_donations = models.CharField( _("total public donations"), - max_length=64, null=False, help_text=_("Total public donations."), ) @@ -258,7 +251,6 @@ class Pot(models.Model): ) protocol_config_provider = models.CharField( _("protocol config provider"), - max_length=64, null=True, help_text=_("Protocol config provider."), ) @@ -326,12 +318,12 @@ class PotApplication(models.Model): ) updated_at = models.DateTimeField( _("updated at"), + null=True, help_text=_("Application last update date."), ) tx_hash = models.CharField( _("transaction hash"), - max_length=64, - null=False, + null=True, help_text=_("Transaction hash."), ) @@ -384,8 +376,7 @@ class PotApplicationReview(models.Model): ) tx_hash = models.CharField( _("transaction hash"), - max_length=64, - null=False, + null=True, help_text=_("Transaction hash."), ) @@ -419,7 +410,6 @@ class PotPayout(models.Model): ) amount = models.CharField( _("amount"), - max_length=64, null=False, help_text=_("Payout amount."), ) @@ -446,8 +436,7 @@ class PotPayout(models.Model): ) tx_hash = models.CharField( _("transaction hash"), - max_length=64, - null=False, + null=True, help_text=_("Transaction hash."), ) @@ -537,8 +526,7 @@ class PotPayoutChallengeAdminResponse(models.Model): ) tx_hash = models.CharField( _("transaction hash"), - max_length=64, - null=False, + null=True, help_text=_("Transaction hash."), ) From b16658c171898404d332e307f026b4e9f4b36b0e Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 14 Jun 2024 11:54:04 -0400 Subject: [PATCH 184/454] add donations_received & donations_sent endpoints --- accounts/api.py | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ api/urls.py | 12 ++++++++ 2 files changed, 93 insertions(+) diff --git a/accounts/api.py b/accounts/api.py index dbd51e9..bb1a620 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -15,6 +15,7 @@ from base.logging import logger from donations.models import Donation +from donations.serializers import SIMPLE_DONATION_EXAMPLE, DonationSerializer from pots.models import Pot, PotApplication, PotApplicationStatus from pots.serializers import SIMPLE_POT_EXAMPLE, PotSerializer @@ -185,3 +186,83 @@ def get(self, request: Request, *args, **kwargs): results = self.paginate_queryset(pots, request, view=self) serializer = PotSerializer(results, many=True) return self.get_paginated_response(serializer.data) + + +@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins +class AccountDonationsReceivedAPI(APIView, LimitOffsetPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("account_id", str, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=DonationSerializer(many=True), + description="Returns paginated donations received by the account", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for donations received", + value=SIMPLE_DONATION_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Account not found"), + 500: OpenApiResponse(description="Internal server error"), + }, + ) + def get(self, request: Request, *args, **kwargs): + account_id = kwargs.get("account_id") + try: + account = Account.objects.get(id=account_id) + except Account.DoesNotExist: + return Response( + {"message": f"Account with ID {account_id} not found."}, status=404 + ) + + donations = Donation.objects.filter(recipient=account) + results = self.paginate_queryset(donations, request, view=self) + serializer = DonationSerializer(results, many=True) + return self.get_paginated_response(serializer.data) + + +@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins +class AccountDonationsSentAPI(APIView, LimitOffsetPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("account_id", str, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=DonationSerializer(many=True), + description="Returns paginated donations sent by the account", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for donations received", + value=SIMPLE_DONATION_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Account not found"), + 500: OpenApiResponse(description="Internal server error"), + }, + ) + def get(self, request: Request, *args, **kwargs): + account_id = kwargs.get("account_id") + try: + account = Account.objects.get(id=account_id) + except Account.DoesNotExist: + return Response( + {"message": f"Account with ID {account_id} not found."}, status=404 + ) + + donations = Donation.objects.filter(donor=account) + results = self.paginate_queryset(donations, request, view=self) + serializer = DonationSerializer(results, many=True) + return self.get_paginated_response(serializer.data) diff --git a/api/urls.py b/api/urls.py index 6509a4f..76dc434 100644 --- a/api/urls.py +++ b/api/urls.py @@ -8,6 +8,8 @@ from accounts.api import ( AccountActivePotsAPI, AccountDetailAPI, + AccountDonationsReceivedAPI, + AccountDonationsSentAPI, AccountsListAPI, DonorsAPI, ) @@ -45,6 +47,16 @@ AccountActivePotsAPI.as_view(), name="accounts_api_by_id_active_pots", ), + path( + "v1/accounts//donations_received", + AccountDonationsReceivedAPI.as_view(), + name="accounts_api_by_id_donations_received", + ), + path( + "v1/accounts//donations_sent", + AccountDonationsSentAPI.as_view(), + name="accounts_api_by_id_donations_sent", + ), # donors path("v1/donors", DonorsAPI.as_view(), name="donors_api"), # lists From 1cb8ca1c8e6c8926d9d5e27be50b8b739d862ee6 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:03:13 -0400 Subject: [PATCH 185/454] fix caching --- accounts/api.py | 12 ++++++------ lists/api.py | 4 +++- pots/api.py | 7 ++++++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/accounts/api.py b/accounts/api.py index bb1a620..af71abe 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -25,7 +25,6 @@ class DonorsAPI(APIView, LimitOffsetPagination): - @method_decorator(cache_page(60 * 15)) # Cache for 15 mins @extend_schema( parameters=[ OpenApiParameter( @@ -52,6 +51,7 @@ class DonorsAPI(APIView, LimitOffsetPagination): 500: OpenApiResponse(description="Internal server error"), }, ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): # Return all donors donations_subquery = Donation.objects.filter(donor_id=OuterRef("pk")) @@ -67,7 +67,6 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins class AccountsListAPI(APIView, LimitOffsetPagination): @extend_schema( @@ -88,6 +87,7 @@ class AccountsListAPI(APIView, LimitOffsetPagination): 500: OpenApiResponse(description="Internal server error"), } ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): accounts = Account.objects.all() results = self.paginate_queryset(accounts, request, view=self) @@ -95,7 +95,6 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins class AccountDetailAPI(APIView): @extend_schema( @@ -120,6 +119,7 @@ class AccountDetailAPI(APIView): 500: OpenApiResponse(description="Internal server error"), }, ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): account_id = kwargs.get("account_id") try: @@ -132,7 +132,6 @@ def get(self, request: Request, *args, **kwargs): return Response(serializer.data) -@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins class AccountActivePotsAPI(APIView, LimitOffsetPagination): @extend_schema( @@ -164,6 +163,7 @@ class AccountActivePotsAPI(APIView, LimitOffsetPagination): 500: OpenApiResponse(description="Internal server error"), }, ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): account_id = kwargs.get("account_id") try: @@ -188,7 +188,6 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins class AccountDonationsReceivedAPI(APIView, LimitOffsetPagination): @extend_schema( @@ -213,6 +212,7 @@ class AccountDonationsReceivedAPI(APIView, LimitOffsetPagination): 500: OpenApiResponse(description="Internal server error"), }, ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): account_id = kwargs.get("account_id") try: @@ -228,7 +228,6 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins class AccountDonationsSentAPI(APIView, LimitOffsetPagination): @extend_schema( @@ -253,6 +252,7 @@ class AccountDonationsSentAPI(APIView, LimitOffsetPagination): 500: OpenApiResponse(description="Internal server error"), }, ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): account_id = kwargs.get("account_id") try: diff --git a/lists/api.py b/lists/api.py index eb4885b..c4a09e2 100644 --- a/lists/api.py +++ b/lists/api.py @@ -43,7 +43,6 @@ } -@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins class ListsListAPI(APIView, LimitOffsetPagination): @extend_schema( @@ -64,6 +63,7 @@ class ListsListAPI(APIView, LimitOffsetPagination): 500: OpenApiResponse(description="Internal server error"), } ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): lists = List.objects.all() results = self.paginate_queryset(lists, request, view=self) @@ -95,6 +95,7 @@ class ListDetailAPI(APIView): 500: OpenApiResponse(description="Internal server error"), }, ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") try: @@ -131,6 +132,7 @@ class ListRegistrationsAPI(APIView, LimitOffsetPagination): 500: OpenApiResponse(description="Internal server error"), }, ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") try: diff --git a/pots/api.py b/pots/api.py index 19742f7..623ce82 100644 --- a/pots/api.py +++ b/pots/api.py @@ -29,7 +29,6 @@ ) -@method_decorator(cache_page(60 * 15), name="dispatch") # Cache for 15 mins class PotsListAPI(APIView, LimitOffsetPagination): @extend_schema( @@ -49,6 +48,7 @@ class PotsListAPI(APIView, LimitOffsetPagination): ), } ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): pots = Pot.objects.all() results = self.paginate_queryset(pots, request, view=self) @@ -79,6 +79,7 @@ class PotDetailAPI(APIView): 404: OpenApiResponse(description="Pot not found"), }, ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): pot_id = kwargs.get("pot_id") try: @@ -112,6 +113,7 @@ class PotApplicationsAPI(APIView, LimitOffsetPagination): 404: OpenApiResponse(description="Pot not found"), }, ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): pot_id = kwargs.get("pot_id") try: @@ -148,6 +150,7 @@ class PotDonationsAPI(APIView, LimitOffsetPagination): 404: OpenApiResponse(description="Pot not found"), }, ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): pot_id = kwargs.get("pot_id") try: @@ -184,6 +187,7 @@ class PotSponsorsAPI(APIView, LimitOffsetPagination): 404: OpenApiResponse(description="Pot not found"), }, ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): pot_id = kwargs.get("pot_id") try: @@ -225,6 +229,7 @@ class PotPayoutsAPI(APIView, LimitOffsetPagination): 404: OpenApiResponse(description="Pot not found"), }, ) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): pot_id = kwargs.get("pot_id") try: From 182732e0b9a2af4769d2ac9ed4b888af643032c5 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:04:18 -0400 Subject: [PATCH 186/454] update readme --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fc89206..5cf7d39 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,16 @@ - [Steps to run:](#steps-to-run) - [Env vars example](#env-vars-example) - [API Basics](#api-basics) - - [Base URL](#base-url) - - [Authorization](#authorization) - - [Error Responses](#error-responses) - - [Pagination](#pagination) + - [Base URL](#base-url) + - [Authorization](#authorization) + - [Error Responses](#error-responses) + - [Pagination](#pagination) - [API Endpoints](#api-endpoints) - [`Account` endpoints](#account-endpoints) - [✅ Get all accounts: `GET /accounts` (paginated)](#-get-all-accounts-get-accounts-paginated) - [✅ Get account by ID (address): `GET /accounts/{ACCOUNT_ID}`](#-get-account-by-id-address-get-accountsaccount_id) + - [✅ Get donations received for account: `GET /accounts/{ACCOUNT_ID}/donations_received` (paginated)](#-get-donations-received-for-account-get-accountsaccount_iddonations_received-paginated) + - [✅ Get donations sent for account: `GET /accounts/{ACCOUNT_ID}/donations_sent` (paginated)](#-get-donations-sent-for-account-get-accountsaccount_iddonations_sent-paginated) - [✅ Get pots for account: `GET /accounts/{ACCOUNT_ID}/active_pots` (paginated)](#-get-pots-for-account-get-accountsaccount_idactive_pots-paginated) - [`List` endpoints](#list-endpoints) - [✅ Get all lists: `GET /lists` (paginated)](#-get-all-lists-get-lists-paginated) @@ -121,6 +123,10 @@ _NB: These endpoints are what is required to integrate with BOS app & replace cu #### ✅ Get account by ID (address): `GET /accounts/{ACCOUNT_ID}` +#### ✅ Get donations received for account: `GET /accounts/{ACCOUNT_ID}/donations_received` (paginated) + +#### ✅ Get donations sent for account: `GET /accounts/{ACCOUNT_ID}/donations_sent` (paginated) + #### ✅ Get pots for account: `GET /accounts/{ACCOUNT_ID}/active_pots` (paginated) Can specify `status=live` query param to retrieve only pots that are currently active (live matching round) From 6ff9be3e9d30683f5dec2c10dacb02af7c9930e1 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:13:26 -0400 Subject: [PATCH 187/454] populate lists data --- .../management/commands/populatedata.py | 824 ++++++++++-------- indexer_app/utils.py | 20 +- lists/admin.py | 4 + lists/migrations/0004_list_on_chain_id.py | 24 + lists/migrations/0005_alter_list_id.py | 23 + ..._alter_listregistration_unique_together.py | 18 + lists/models.py | 12 +- ..._alter_pot_custom_sybil_checks_and_more.py | 120 +++ 8 files changed, 665 insertions(+), 380 deletions(-) create mode 100644 lists/migrations/0004_list_on_chain_id.py create mode 100644 lists/migrations/0005_alter_list_id.py create mode 100644 lists/migrations/0006_alter_listregistration_unique_together.py create mode 100644 pots/migrations/0006_alter_pot_custom_sybil_checks_and_more.py diff --git a/indexer_app/management/commands/populatedata.py b/indexer_app/management/commands/populatedata.py index 489b70b..2688742 100644 --- a/indexer_app/management/commands/populatedata.py +++ b/indexer_app/management/commands/populatedata.py @@ -8,6 +8,7 @@ from accounts.models import Account from activities.models import Activity from donations.models import Donation +from lists.models import List, ListRegistration from pots.models import ( Pot, PotApplication, @@ -22,393 +23,480 @@ class Command(BaseCommand): help = "Pull donations data from contract & populate donations table." def handle(self, *args, **options): - # direct donations - # get direct donations in pages - DONATE_CONTRACT_ID = "donate.potlock.near" - page = 0 - limit = 300 - while True: - time.sleep(1) - url = f"https://rpc.web4.near.page/account/{DONATE_CONTRACT_ID}/view/get_donations" - params = {"from_index.json": page * limit, "limit.json": limit} - donations = requests.get(url, params=params) - if donations.status_code != 200: - print( - f"Request for direct donations data failed ({donations.status_code}) with message: {donations.text}" - ) - return - donations = donations.json() - print(f"adding {len(donations)} direct donations") - for donation in donations: - donor, _ = Account.objects.get_or_create(id=donation["donor_id"]) - referrer = None - recipient, _ = Account.objects.get_or_create( - id=donation["recipient_id"] - ) - ft_acct, _ = Account.objects.get_or_create(id=donation["ft_id"]) - if donation.get("referrer_id"): - referrer, _ = Account.objects.get_or_create( - id=donation["referrer_id"] - ) - - total_amount = int(donation["total_amount"]) - protocol_fee = int(donation["protocol_fee"]) - referrer_fee = int(donation["referrer_fee"] or 0) - net_amount = total_amount - protocol_fee - referrer_fee - - donation_defaults = { - "donor": donor, - "total_amount": donation["total_amount"], - "total_amount_usd": None, # USD amounts will be added later - "net_amount_usd": None, - "net_amount": str(net_amount), - "ft": ft_acct, - "message": donation["message"], - "donated_at": datetime.fromtimestamp( - donation["donated_at_ms"] / 1000 - ), - "matching_pool": False, - "recipient": recipient, - "protocol_fee": donation["protocol_fee"], - "referrer": referrer, - "referrer_fee": donation["referrer_fee"], - "tx_hash": None, - } - donation, created = Donation.objects.update_or_create( - on_chain_id=donation["id"], pot=None, defaults=donation_defaults - ) - print("created donation: ", donation.on_chain_id, created) - # # add donation activity - # activity_type = ( - # "Donate_Pot_Matching_Pool" - # if donation.matching_pool - # else "Donate_Pot_Public" - # ) - # defaults = { - # "signer_id": donor.id, - # "receiver_id": pot_id, - # "timestamp": donation.donated_at, - # "tx_hash": None, - # } - # activity, _ = Activity.objects.update_or_create( - # type=activity_type, - # action_result=None, - # defaults=defaults, - # ) - if len(donations) == limit: - page += 1 - else: - break - # pot factory - POTFACTORY_ID = "v1.potfactory.potlock.near" - # pots - near_acct, _ = Account.objects.get_or_create(id="near") - url = f"{settings.FASTNEAR_RPC_URL}/account/{POTFACTORY_ID}/view/get_pots" - response = requests.get(url) - if response.status_code != 200: + # lists + LISTS_CONTRACT_ID = "lists.potlock.near" + url = f"https://rpc.web4.near.page/account/{LISTS_CONTRACT_ID}/view/get_lists" + lists = requests.get(url) + if lists.status_code != 200: print( - f"Request for donations data failed ({response.status_code}) with message: {response.text}" + f"Request for lists data failed ({lists.status_code}) with message: {lists.text}" ) return - pots = response.json() - print("pots data; ", pots) - for pot in pots: - # if pot["id"] != "marketing.v1.potfactory.potlock.near": - # continue # TODO: REMOVE THIS - print("sleeping for 1 second") - time.sleep(1) - pot_id = pot["id"] - deployed_by = pot["deployed_by"] - deployed_at_ms = pot["deployed_at_ms"] - print("handling pot: ", pot_id) - # fetch config for pot - url = f"{settings.FASTNEAR_RPC_URL}/account/{pot_id}/view/get_config" - response = requests.get(url) - if response.status_code != 200: - print( - f"Request for pot config failed ({response.status_code}) with message: {response.text}" - ) - return - config = response.json() - print("config data; ", config) - url = f"{settings.FASTNEAR_RPC_URL}/account/{pot_id}/view/get_contract_source_metadata" - response = requests.get(url) - if response.status_code != 200: - print( - f"Request for pot source metadata failed ({response.status_code}) with message: {response.text}" - ) - return - source_metadata = response.json() - print("source metadata; ", source_metadata) - # add or update pot - pot_acct, _ = Account.objects.get_or_create(id=pot_id) - owner, _ = Account.objects.get_or_create(id=config["owner"]) - if config.get("chef"): - chef, _ = Account.objects.get_or_create(id=config["chef"]) - pot_defaults = { - "pot_factory_id": POTFACTORY_ID, - "owner_id": config["owner"], - "chef_id": config["chef"], - "name": config["pot_name"], - "description": config["pot_description"], - "max_approved_applicants": config["max_projects"], - "base_currency": config["base_currency"], - "application_start": datetime.fromtimestamp( - config["application_start_ms"] / 1000 - ), - "application_end": datetime.fromtimestamp( - config["application_end_ms"] / 1000 - ), - "matching_round_start": datetime.fromtimestamp( - config["public_round_start_ms"] / 1000 - ), - "matching_round_end": datetime.fromtimestamp( - config["public_round_end_ms"] / 1000 - ), - "deployer_id": config["deployed_by"], - "deployed_at": datetime.fromtimestamp(deployed_at_ms / 1000), - "source_metadata": source_metadata, - "registry_provider": config["registry_provider"], - "min_matching_pool_donation_amount": config[ - "min_matching_pool_donation_amount" - ], - "sybil_wrapper_provider": config["sybil_wrapper_provider"], - "custom_sybil_checks": config["custom_sybil_checks"], - "custom_min_threshold_score": config["custom_min_threshold_score"], - "referral_fee_matching_pool_basis_points": config[ - "referral_fee_matching_pool_basis_points" - ], - "referral_fee_public_round_basis_points": config[ - "referral_fee_public_round_basis_points" - ], - "chef_fee_basis_points": config["chef_fee_basis_points"], - "total_matching_pool": "0", - "matching_pool_balance": "0", - "matching_pool_donations_count": 0, - "total_public_donations": "0", - "public_donations_count": 0, - "cooldown_period_ms": None, - "cooldown_end": ( - None - if not config["cooldown_end_ms"] - else datetime.fromtimestamp(config["cooldown_end_ms"] / 1000) - ), - "all_paid_out": config["all_paid_out"], - "protocol_config_provider": config["protocol_config_provider"], + lists = lists.json() + print("lists: ", lists) + print(f"adding {len(lists)} direct lists") + for l in lists: + list_id = l["id"] + owner, _ = Account.objects.get_or_create(id=l["owner"]) + list_defaults = { + "on_chain_id": list_id, + "owner": owner, + "name": l["name"], + "description": l["description"], + "cover_image_url": l["cover_image_url"], + "admin_only_registrations": l["admin_only_registrations"], + "default_registration_status": l["default_registration_status"], + "created_at": datetime.fromtimestamp(l["created_at"] / 1000), + "updated_at": datetime.fromtimestamp(l["updated_at"] / 1000), } - pot, created = Pot.objects.update_or_create( - id=pot_acct, defaults=pot_defaults + list, created = List.objects.update_or_create( + on_chain_id=list_id, defaults=list_defaults ) - print("pot created? ", created) - print("adding admins") - for admin in config["admins"]: + print("created list: ", list.on_chain_id, created) + # add admins + for admin in l["admins"]: admin_obj, _ = Account.objects.get_or_create(id=admin) - pot.admins.add(admin_obj) - # add activity - activity_defaults = { - "signer_id": owner.id, - "receiver_id": POTFACTORY_ID, - "timestamp": datetime.fromtimestamp(deployed_at_ms / 1000), - "tx_hash": None, - } - activity, _ = Activity.objects.update_or_create( - type="Deploy_Pot", action_result=None, defaults=activity_defaults - ) - # get applications in pages + list.admins.add(admin_obj) + # get registrations in pages page = 0 limit = 300 while True: time.sleep(1) - url = ( - f"https://rpc.web4.near.page/account/{pot_id}/view/get_applications" - ) - params = {"from_index.json": page * limit, "limit.json": limit} - applications = requests.get(url, params=params) - if applications.status_code != 200: + url = f"https://rpc.web4.near.page/account/{LISTS_CONTRACT_ID}/view/get_registrations_for_list" + params = { + "list_id.json": list_id, + "from_index.json": page * limit, + "limit.json": limit, + } + registrations = requests.get(url, params=params) + if registrations.status_code != 200: print( - f"Request for applications data failed ({applications.status_code}) with message: {applications.text}" + f"Request for registrations data failed ({registrations.status_code}) with message: {registrations.text}" ) return - applications = applications.json() - print(f"adding {len(applications)} applications") - for appl in applications: - applicant, _ = Account.objects.get_or_create(id=appl["project_id"]) - application_defaults = { - "applicant": applicant, - "pot": pot, - "message": appl["message"], - "status": appl["status"], - "submitted_at": datetime.fromtimestamp( - appl["submitted_at"] / 1000 - ), - "updated_at": ( - None - if not appl["updated_at"] - else datetime.fromtimestamp(appl["updated_at"] / 1000) - ), - } - application, _ = PotApplication.objects.update_or_create( - pot=pot, applicant=applicant, defaults=application_defaults + registrations = registrations.json() + print("registrations: ", registrations) + print(f"adding {len(registrations)} registrations") + for reg in registrations: + registrant, _ = Account.objects.get_or_create( + id=reg["registrant_id"] ) - # if status != Pending, add PotApplicationReview also, default to pot owner - if application.status != "Pending": - review_defaults = { - "application": application, - "reviewer": owner, - "notes": appl["review_notes"], - "status": application.status, - "reviewed_at": application.updated_at, - "tx_hash": None, - } - review, _ = PotApplicationReview.objects.update_or_create( - application=application, - reviewer=owner, - defaults=review_defaults, - ) - if len(applications) == limit: - page += 1 - else: - break - # get pot donations in pages - page = 0 - limit = 300 - while True: - time.sleep(1) - url = f"https://rpc.web4.near.page/account/{pot_id}/view/get_donations" - params = {"from_index.json": page * limit, "limit.json": limit} - donations = requests.get(url, params=params) - if donations.status_code != 200: - print( - f"Request for donations data failed ({donations.status_code}) with message: {donations.text}" - ) - return - donations = donations.json() - print(f"adding {len(donations)} donations") - for donation in donations: - donor, _ = Account.objects.get_or_create(id=donation["donor_id"]) - project = None - referrer = None - chef = None - if donation.get("project_id"): - project, _ = Account.objects.get_or_create( - id=donation["project_id"] + registrar = registrant + if "registered_by" in reg: + registrar, _ = Account.objects.get_or_create( + id=reg["registered_by"] ) - if donation.get("referrer_id"): - referrer, _ = Account.objects.get_or_create( - id=donation["referrer_id"] - ) - if donation.get("chef_id"): - chef, _ = Account.objects.get_or_create(id=donation["chef_id"]) - donation_defaults = { - "donor": donor, - "total_amount": donation["total_amount"], - "total_amount_usd": None, # USD amounts will be added later - "net_amount_usd": None, - "net_amount": donation["net_amount"], - "ft_id": "near", - "message": donation["message"], - "donated_at": datetime.fromtimestamp( - donation["donated_at"] / 1000 + registration_defaults = { + "list": list, + "registrant": registrant, + "registered_by": registrar, + "status": reg["status"], + "submitted_at": datetime.fromtimestamp( + reg["submitted_ms"] / 1000 ), - "matching_pool": donation["matching_pool"], - "recipient": project, - "protocol_fee": donation["protocol_fee"], - "referrer": referrer, - "referrer_fee": donation["referrer_fee"], - "chef": chef, - "chef_fee": donation["chef_fee"], + "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), + "registrant_notes": reg["registrant_notes"], + "admin_notes": reg["admin_notes"], "tx_hash": None, } - donation, _ = Donation.objects.update_or_create( - on_chain_id=donation["id"], pot=pot, defaults=donation_defaults + registration, created = ListRegistration.objects.update_or_create( + registrant=registrant, + list=list, + defaults=registration_defaults, ) - print("added donation: ", donation.on_chain_id) - # # add donation activity - # activity_type = ( - # "Donate_Pot_Matching_Pool" - # if donation.matching_pool - # else "Donate_Pot_Public" - # ) - # defaults = { - # "signer_id": donor.id, - # "receiver_id": pot_id, - # "timestamp": donation.donated_at, - # "tx_hash": None, - # } - # activity, _ = Activity.objects.update_or_create( - # type=activity_type, - # action_result=None, - # defaults=defaults, - # ) - if len(donations) == limit: - page += 1 - else: - break - if "payouts" in config: - for payout in config["payouts"]: - if not payout["paid_at"]: - continue - recipient, _ = Account.objects.get_or_create( - id=payout["project_id"] - ) - payout_defaults = { - "pot": pot, - "recipient": recipient, - "amount": payout["amount"], - "amount_paid_usd": None, # TODO: add this in task - "ft_id": "near", - "paid_at": datetime.fromtimestamp(payout["paid_at"] / 1000), - "tx_hash": None, - } - payout, _ = PotPayout.objects.update_or_create( - pot=pot, recipient=recipient, defaults=payout_defaults - ) - # get pot challenges & admin responses - page = 0 - limit = 300 - while True: - time.sleep(1) - url = f"https://rpc.web4.near.page/account/{pot_id}/view/get_payouts_challenges" - params = {"from_index.json": page * limit, "limit.json": limit} - challenges = requests.get(url, params=params) - if challenges.status_code != 200: - print( - f"Request for challenges data failed ({challenges.status_code}) with message: {challenges.text}" - ) - return - challenges = challenges.json() - print(f"adding {len(challenges)} challenges") - for c in challenges: - challenger, _ = Account.objects.get_or_create(id=c["challenger_id"]) - challenge_defaults = { - "challenger": challenger, - "pot": pot, - "message": c["reason"], - "created_at": datetime.fromtimestamp(c["created_at"] / 1000), - } - challenge, _ = PotPayoutChallenge.objects.update_or_create( - pot=pot, challenger=challenger, defaults=challenge_defaults - ) - # if status != Pending, add PotApplicationReview also, default to pot owner - if c["admin_notes"] or c["resolved"]: - response_defaults = { - "challenger": challenger, - "pot": pot, - "admin": owner, # don't actually know who did it, just using owner for this backfilling - "created_at": datetime.fromtimestamp( - c["created_at"] / 1000 - ), # not exactly correct but we don't know currently - "message": c["admin_notes"], - "resolved": c["resolved"], - } - review, _ = ( - PotPayoutChallengeAdminResponse.objects.update_or_create( - challenger=challenger, - pot=pot, - defaults=response_defaults, - ) - ) - if len(challenges) == limit: + print("created registration: ", registration.id, created) + if len(registrations) == limit: page += 1 else: break + # TODO: get upvotes (none currently so don't have to worry about it at this point) + # # direct donations + # # get direct donations in pages + # DONATE_CONTRACT_ID = "donate.potlock.near" + # page = 0 + # limit = 300 + # while True: + # time.sleep(1) + # url = f"https://rpc.web4.near.page/account/{DONATE_CONTRACT_ID}/view/get_donations" + # params = {"from_index.json": page * limit, "limit.json": limit} + # donations = requests.get(url, params=params) + # if donations.status_code != 200: + # print( + # f"Request for direct donations data failed ({donations.status_code}) with message: {donations.text}" + # ) + # return + # donations = donations.json() + # print(f"adding {len(donations)} direct donations") + # for donation in donations: + # donor, _ = Account.objects.get_or_create(id=donation["donor_id"]) + # referrer = None + # recipient, _ = Account.objects.get_or_create( + # id=donation["recipient_id"] + # ) + # ft_acct, _ = Account.objects.get_or_create(id=donation["ft_id"]) + # if donation.get("referrer_id"): + # referrer, _ = Account.objects.get_or_create( + # id=donation["referrer_id"] + # ) + + # total_amount = int(donation["total_amount"]) + # protocol_fee = int(donation["protocol_fee"]) + # referrer_fee = int(donation["referrer_fee"] or 0) + # net_amount = total_amount - protocol_fee - referrer_fee + + # donation_defaults = { + # "donor": donor, + # "total_amount": donation["total_amount"], + # "total_amount_usd": None, # USD amounts will be added later + # "net_amount_usd": None, + # "net_amount": str(net_amount), + # "ft": ft_acct, + # "message": donation["message"], + # "donated_at": datetime.fromtimestamp( + # donation["donated_at_ms"] / 1000 + # ), + # "matching_pool": False, + # "recipient": recipient, + # "protocol_fee": donation["protocol_fee"], + # "referrer": referrer, + # "referrer_fee": donation["referrer_fee"], + # "tx_hash": None, + # } + # donation, created = Donation.objects.update_or_create( + # on_chain_id=donation["id"], pot=None, defaults=donation_defaults + # ) + # print("created donation: ", donation.on_chain_id, created) + # # # add donation activity + # # activity_type = ( + # # "Donate_Pot_Matching_Pool" + # # if donation.matching_pool + # # else "Donate_Pot_Public" + # # ) + # # defaults = { + # # "signer_id": donor.id, + # # "receiver_id": pot_id, + # # "timestamp": donation.donated_at, + # # "tx_hash": None, + # # } + # # activity, _ = Activity.objects.update_or_create( + # # type=activity_type, + # # action_result=None, + # # defaults=defaults, + # # ) + # if len(donations) == limit: + # page += 1 + # else: + # break + # # pot factory + # POTFACTORY_ID = "v1.potfactory.potlock.near" + # # pots + # near_acct, _ = Account.objects.get_or_create(id="near") + # url = f"{settings.FASTNEAR_RPC_URL}/account/{POTFACTORY_ID}/view/get_pots" + # response = requests.get(url) + # if response.status_code != 200: + # print( + # f"Request for donations data failed ({response.status_code}) with message: {response.text}" + # ) + # return + # pots = response.json() + # print("pots data; ", pots) + # for pot in pots: + # # if pot["id"] != "marketing.v1.potfactory.potlock.near": + # # continue # TODO: REMOVE THIS + # print("sleeping for 1 second") + # time.sleep(1) + # pot_id = pot["id"] + # deployed_by = pot["deployed_by"] + # deployed_at_ms = pot["deployed_at_ms"] + # print("handling pot: ", pot_id) + # # fetch config for pot + # url = f"{settings.FASTNEAR_RPC_URL}/account/{pot_id}/view/get_config" + # response = requests.get(url) + # if response.status_code != 200: + # print( + # f"Request for pot config failed ({response.status_code}) with message: {response.text}" + # ) + # return + # config = response.json() + # print("config data; ", config) + # url = f"{settings.FASTNEAR_RPC_URL}/account/{pot_id}/view/get_contract_source_metadata" + # response = requests.get(url) + # if response.status_code != 200: + # print( + # f"Request for pot source metadata failed ({response.status_code}) with message: {response.text}" + # ) + # return + # source_metadata = response.json() + # print("source metadata; ", source_metadata) + # # add or update pot + # pot_acct, _ = Account.objects.get_or_create(id=pot_id) + # owner, _ = Account.objects.get_or_create(id=config["owner"]) + # if config.get("chef"): + # chef, _ = Account.objects.get_or_create(id=config["chef"]) + # pot_defaults = { + # "pot_factory_id": POTFACTORY_ID, + # "owner_id": config["owner"], + # "chef_id": config["chef"], + # "name": config["pot_name"], + # "description": config["pot_description"], + # "max_approved_applicants": config["max_projects"], + # "base_currency": config["base_currency"], + # "application_start": datetime.fromtimestamp( + # config["application_start_ms"] / 1000 + # ), + # "application_end": datetime.fromtimestamp( + # config["application_end_ms"] / 1000 + # ), + # "matching_round_start": datetime.fromtimestamp( + # config["public_round_start_ms"] / 1000 + # ), + # "matching_round_end": datetime.fromtimestamp( + # config["public_round_end_ms"] / 1000 + # ), + # "deployer_id": config["deployed_by"], + # "deployed_at": datetime.fromtimestamp(deployed_at_ms / 1000), + # "source_metadata": source_metadata, + # "registry_provider": config["registry_provider"], + # "min_matching_pool_donation_amount": config[ + # "min_matching_pool_donation_amount" + # ], + # "sybil_wrapper_provider": config["sybil_wrapper_provider"], + # "custom_sybil_checks": config["custom_sybil_checks"], + # "custom_min_threshold_score": config["custom_min_threshold_score"], + # "referral_fee_matching_pool_basis_points": config[ + # "referral_fee_matching_pool_basis_points" + # ], + # "referral_fee_public_round_basis_points": config[ + # "referral_fee_public_round_basis_points" + # ], + # "chef_fee_basis_points": config["chef_fee_basis_points"], + # "total_matching_pool": "0", + # "matching_pool_balance": "0", + # "matching_pool_donations_count": 0, + # "total_public_donations": "0", + # "public_donations_count": 0, + # "cooldown_period_ms": None, + # "cooldown_end": ( + # None + # if not config["cooldown_end_ms"] + # else datetime.fromtimestamp(config["cooldown_end_ms"] / 1000) + # ), + # "all_paid_out": config["all_paid_out"], + # "protocol_config_provider": config["protocol_config_provider"], + # } + # pot, created = Pot.objects.update_or_create( + # id=pot_acct, defaults=pot_defaults + # ) + # print("pot created? ", created) + # print("adding admins") + # for admin in config["admins"]: + # admin_obj, _ = Account.objects.get_or_create(id=admin) + # pot.admins.add(admin_obj) + # # add activity + # activity_defaults = { + # "signer_id": owner.id, + # "receiver_id": POTFACTORY_ID, + # "timestamp": datetime.fromtimestamp(deployed_at_ms / 1000), + # "tx_hash": None, + # } + # activity, _ = Activity.objects.update_or_create( + # type="Deploy_Pot", action_result=None, defaults=activity_defaults + # ) + # # get applications in pages + # page = 0 + # limit = 300 + # while True: + # time.sleep(1) + # url = ( + # f"https://rpc.web4.near.page/account/{pot_id}/view/get_applications" + # ) + # params = {"from_index.json": page * limit, "limit.json": limit} + # applications = requests.get(url, params=params) + # if applications.status_code != 200: + # print( + # f"Request for applications data failed ({applications.status_code}) with message: {applications.text}" + # ) + # return + # applications = applications.json() + # print(f"adding {len(applications)} applications") + # for appl in applications: + # applicant, _ = Account.objects.get_or_create(id=appl["project_id"]) + # application_defaults = { + # "applicant": applicant, + # "pot": pot, + # "message": appl["message"], + # "status": appl["status"], + # "submitted_at": datetime.fromtimestamp( + # appl["submitted_at"] / 1000 + # ), + # "updated_at": ( + # None + # if not appl["updated_at"] + # else datetime.fromtimestamp(appl["updated_at"] / 1000) + # ), + # } + # application, _ = PotApplication.objects.update_or_create( + # pot=pot, applicant=applicant, defaults=application_defaults + # ) + # # if status != Pending, add PotApplicationReview also, default to pot owner + # if application.status != "Pending": + # review_defaults = { + # "application": application, + # "reviewer": owner, + # "notes": appl["review_notes"], + # "status": application.status, + # "reviewed_at": application.updated_at, + # "tx_hash": None, + # } + # review, _ = PotApplicationReview.objects.update_or_create( + # application=application, + # reviewer=owner, + # defaults=review_defaults, + # ) + # if len(applications) == limit: + # page += 1 + # else: + # break + # # get pot donations in pages + # page = 0 + # limit = 300 + # while True: + # time.sleep(1) + # url = f"https://rpc.web4.near.page/account/{pot_id}/view/get_donations" + # params = {"from_index.json": page * limit, "limit.json": limit} + # donations = requests.get(url, params=params) + # if donations.status_code != 200: + # print( + # f"Request for donations data failed ({donations.status_code}) with message: {donations.text}" + # ) + # return + # donations = donations.json() + # print(f"adding {len(donations)} donations") + # for donation in donations: + # donor, _ = Account.objects.get_or_create(id=donation["donor_id"]) + # project = None + # referrer = None + # chef = None + # if donation.get("project_id"): + # project, _ = Account.objects.get_or_create( + # id=donation["project_id"] + # ) + # if donation.get("referrer_id"): + # referrer, _ = Account.objects.get_or_create( + # id=donation["referrer_id"] + # ) + # if donation.get("chef_id"): + # chef, _ = Account.objects.get_or_create(id=donation["chef_id"]) + # donation_defaults = { + # "donor": donor, + # "total_amount": donation["total_amount"], + # "total_amount_usd": None, # USD amounts will be added later + # "net_amount_usd": None, + # "net_amount": donation["net_amount"], + # "ft_id": "near", + # "message": donation["message"], + # "donated_at": datetime.fromtimestamp( + # donation["donated_at"] / 1000 + # ), + # "matching_pool": donation["matching_pool"], + # "recipient": project, + # "protocol_fee": donation["protocol_fee"], + # "referrer": referrer, + # "referrer_fee": donation["referrer_fee"], + # "chef": chef, + # "chef_fee": donation["chef_fee"], + # "tx_hash": None, + # } + # donation, _ = Donation.objects.update_or_create( + # on_chain_id=donation["id"], pot=pot, defaults=donation_defaults + # ) + # print("added donation: ", donation.on_chain_id) + # # # add donation activity + # # activity_type = ( + # # "Donate_Pot_Matching_Pool" + # # if donation.matching_pool + # # else "Donate_Pot_Public" + # # ) + # # defaults = { + # # "signer_id": donor.id, + # # "receiver_id": pot_id, + # # "timestamp": donation.donated_at, + # # "tx_hash": None, + # # } + # # activity, _ = Activity.objects.update_or_create( + # # type=activity_type, + # # action_result=None, + # # defaults=defaults, + # # ) + # if len(donations) == limit: + # page += 1 + # else: + # break + # if "payouts" in config: + # for payout in config["payouts"]: + # if not payout["paid_at"]: + # continue + # recipient, _ = Account.objects.get_or_create( + # id=payout["project_id"] + # ) + # payout_defaults = { + # "pot": pot, + # "recipient": recipient, + # "amount": payout["amount"], + # "amount_paid_usd": None, # TODO: add this in task + # "ft_id": "near", + # "paid_at": datetime.fromtimestamp(payout["paid_at"] / 1000), + # "tx_hash": None, + # } + # payout, _ = PotPayout.objects.update_or_create( + # pot=pot, recipient=recipient, defaults=payout_defaults + # ) + # # get pot challenges & admin responses + # page = 0 + # limit = 300 + # while True: + # time.sleep(1) + # url = f"https://rpc.web4.near.page/account/{pot_id}/view/get_payouts_challenges" + # params = {"from_index.json": page * limit, "limit.json": limit} + # challenges = requests.get(url, params=params) + # if challenges.status_code != 200: + # print( + # f"Request for challenges data failed ({challenges.status_code}) with message: {challenges.text}" + # ) + # return + # challenges = challenges.json() + # print(f"adding {len(challenges)} challenges") + # for c in challenges: + # challenger, _ = Account.objects.get_or_create(id=c["challenger_id"]) + # challenge_defaults = { + # "challenger": challenger, + # "pot": pot, + # "message": c["reason"], + # "created_at": datetime.fromtimestamp(c["created_at"] / 1000), + # } + # challenge, _ = PotPayoutChallenge.objects.update_or_create( + # pot=pot, challenger=challenger, defaults=challenge_defaults + # ) + # # if status != Pending, add PotApplicationReview also, default to pot owner + # if c["admin_notes"] or c["resolved"]: + # response_defaults = { + # "challenger": challenger, + # "pot": pot, + # "admin": owner, # don't actually know who did it, just using owner for this backfilling + # "created_at": datetime.fromtimestamp( + # c["created_at"] / 1000 + # ), # not exactly correct but we don't know currently + # "message": c["admin_notes"], + # "resolved": c["resolved"], + # } + # review, _ = ( + # PotPayoutChallengeAdminResponse.objects.update_or_create( + # challenger=challenger, + # pot=pot, + # defaults=response_defaults, + # ) + # ) + # if len(challenges) == limit: + # page += 1 + # else: + # break diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 7ac64a6..4372fa7 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -853,16 +853,16 @@ async def cache_block_height( ) -> int: await cache.aset(key, height) # the cache os the default go to for the restart block, the db is a backup if the redis server crashes. - if (block_count % int(settings.BLOCK_SAVE_HEIGHT or 400)) == 0: - logger.info(f"saving daylight, {height}") - await BlockHeight.objects.aupdate_or_create( - id=1, - defaults={ - "block_height": height, - "block_timestamp": datetime.fromtimestamp(block_timestamp / 1000000000), - "updated_at": timezone.now(), - }, - ) # better than ovverriding model's save method to get a singleton? we need only one entry + # if (block_count % int(settings.BLOCK_SAVE_HEIGHT or 400)) == 0: + # logger.info(f"saving daylight, {height}") + await BlockHeight.objects.aupdate_or_create( + id=1, + defaults={ + "block_height": height, + "block_timestamp": datetime.fromtimestamp(block_timestamp / 1000000000), + "updated_at": timezone.now(), + }, + ) # better than ovverriding model's save method to get a singleton? we need only one entry return height diff --git a/lists/admin.py b/lists/admin.py index db244ed..b665bd2 100644 --- a/lists/admin.py +++ b/lists/admin.py @@ -7,8 +7,12 @@ class ListAdmin(admin.ModelAdmin): list_display = ( "id", + "on_chain_id", "name", + "description", + "cover_image_url", "owner", + "admin_only_registrations", "default_registration_status", "created_at", "updated_at", diff --git a/lists/migrations/0004_list_on_chain_id.py b/lists/migrations/0004_list_on_chain_id.py new file mode 100644 index 0000000..5b4f085 --- /dev/null +++ b/lists/migrations/0004_list_on_chain_id.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.4 on 2024-06-14 17:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("lists", "0003_alter_listupvote_options"), + ] + + operations = [ + migrations.AddField( + model_name="list", + name="on_chain_id", + field=models.IntegerField( + default=1, + help_text="List ID in contract", + unique=True, + verbose_name="contract list ID", + ), + preserve_default=False, + ), + ] diff --git a/lists/migrations/0005_alter_list_id.py b/lists/migrations/0005_alter_list_id.py new file mode 100644 index 0000000..f70e260 --- /dev/null +++ b/lists/migrations/0005_alter_list_id.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.4 on 2024-06-14 17:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("lists", "0004_list_on_chain_id"), + ] + + operations = [ + migrations.AlterField( + model_name="list", + name="id", + field=models.AutoField( + help_text="List ID in DB (does not necessarily correspond to on-chain ID).", + primary_key=True, + serialize=False, + verbose_name="list id", + ), + ), + ] diff --git a/lists/migrations/0006_alter_listregistration_unique_together.py b/lists/migrations/0006_alter_listregistration_unique_together.py new file mode 100644 index 0000000..9dfae2d --- /dev/null +++ b/lists/migrations/0006_alter_listregistration_unique_together.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-06-14 17:59 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0002_account_near_social_profile_data"), + ("lists", "0005_alter_list_id"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="listregistration", + unique_together={("list", "registrant")}, + ), + ] diff --git a/lists/models.py b/lists/models.py index ad412d5..d27c613 100644 --- a/lists/models.py +++ b/lists/models.py @@ -13,10 +13,16 @@ class ListRegistrationStatus(models.TextChoices): class List(models.Model): - id = models.PositiveIntegerField( + id = models.AutoField( _("list id"), primary_key=True, - help_text=_("List id."), + help_text=_("List ID in DB (does not necessarily correspond to on-chain ID)."), + ) + on_chain_id = models.IntegerField( + _("contract list ID"), + null=False, + unique=True, + help_text=_("List ID in contract"), ) owner = models.ForeignKey( Account, @@ -172,3 +178,5 @@ class ListRegistration(models.Model): class Meta: indexes = [models.Index(fields=["id", "status"], name="idx_list_id_status")] + + unique_together = (("list", "registrant"),) diff --git a/pots/migrations/0006_alter_pot_custom_sybil_checks_and_more.py b/pots/migrations/0006_alter_pot_custom_sybil_checks_and_more.py new file mode 100644 index 0000000..be07aad --- /dev/null +++ b/pots/migrations/0006_alter_pot_custom_sybil_checks_and_more.py @@ -0,0 +1,120 @@ +# Generated by Django 5.0.4 on 2024-06-14 17:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("pots", "0005_alter_potapplication_updated_at"), + ] + + operations = [ + migrations.AlterField( + model_name="pot", + name="custom_sybil_checks", + field=models.CharField( + help_text="Custom sybil checks.", + null=True, + verbose_name="custom sybil checks", + ), + ), + migrations.AlterField( + model_name="pot", + name="matching_pool_balance", + field=models.CharField( + help_text="Matching pool balance.", verbose_name="matching pool balance" + ), + ), + migrations.AlterField( + model_name="pot", + name="min_matching_pool_donation_amount", + field=models.CharField( + help_text="Min matching pool donation amount.", + verbose_name="min matching pool donation amount", + ), + ), + migrations.AlterField( + model_name="pot", + name="protocol_config_provider", + field=models.CharField( + help_text="Protocol config provider.", + null=True, + verbose_name="protocol config provider", + ), + ), + migrations.AlterField( + model_name="pot", + name="registry_provider", + field=models.CharField( + help_text="Registry provider.", + null=True, + verbose_name="registry provider", + ), + ), + migrations.AlterField( + model_name="pot", + name="sybil_wrapper_provider", + field=models.CharField( + help_text="Sybil wrapper provider.", + null=True, + verbose_name="sybil wrapper provider", + ), + ), + migrations.AlterField( + model_name="pot", + name="total_matching_pool", + field=models.CharField( + help_text="Total matching pool.", verbose_name="total matching pool" + ), + ), + migrations.AlterField( + model_name="pot", + name="total_public_donations", + field=models.CharField( + help_text="Total public donations.", + verbose_name="total public donations", + ), + ), + migrations.AlterField( + model_name="potapplication", + name="tx_hash", + field=models.CharField( + help_text="Transaction hash.", + null=True, + verbose_name="transaction hash", + ), + ), + migrations.AlterField( + model_name="potapplicationreview", + name="tx_hash", + field=models.CharField( + help_text="Transaction hash.", + null=True, + verbose_name="transaction hash", + ), + ), + migrations.AlterField( + model_name="potpayout", + name="amount", + field=models.CharField(help_text="Payout amount.", verbose_name="amount"), + ), + migrations.AlterField( + model_name="potpayout", + name="tx_hash", + field=models.CharField( + help_text="Transaction hash.", + null=True, + verbose_name="transaction hash", + ), + ), + migrations.AlterField( + model_name="potpayoutchallengeadminresponse", + name="tx_hash", + field=models.CharField( + help_text="Transaction hash.", + null=True, + verbose_name="transaction hash", + ), + ), + ] From a6ed92489b8156bc97fb7029c666a09d81911d5c Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:13:44 -0400 Subject: [PATCH 188/454] add pot_applications endpoint for accounts --- README.md | 14 +++++++++++++ accounts/api.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++- api/urls.py | 6 ++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cf7d39..518f5ec 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ - [✅ Get donations received for account: `GET /accounts/{ACCOUNT_ID}/donations_received` (paginated)](#-get-donations-received-for-account-get-accountsaccount_iddonations_received-paginated) - [✅ Get donations sent for account: `GET /accounts/{ACCOUNT_ID}/donations_sent` (paginated)](#-get-donations-sent-for-account-get-accountsaccount_iddonations_sent-paginated) - [✅ Get pots for account: `GET /accounts/{ACCOUNT_ID}/active_pots` (paginated)](#-get-pots-for-account-get-accountsaccount_idactive_pots-paginated) + - [✅ Get applications for account: `GET /accounts/{ACCOUNT_ID}/active_pots` (paginated)](#-get-applications-for-account-get-accountsaccount_idactive_pots-paginated) - [`List` endpoints](#list-endpoints) - [✅ Get all lists: `GET /lists` (paginated)](#-get-all-lists-get-lists-paginated) - [✅ Get list by ID: `GET /lists/{LIST_ID}` (paginated)](#-get-list-by-id-get-listslist_id-paginated) @@ -131,6 +132,19 @@ _NB: These endpoints are what is required to integrate with BOS app & replace cu Can specify `status=live` query param to retrieve only pots that are currently active (live matching round) +#### ✅ Get applications for account: `GET /accounts/{ACCOUNT_ID}/active_pots` (paginated) + +Can specify `status={PotApplicationStatus}` query param to retrieve applications with a given status: + +```py +enum PotApplicationStatus { + Pending, + Approved, + Rejected, + InReview, +} +``` + ### `List` endpoints #### ✅ Get all lists: `GET /lists` (paginated) diff --git a/accounts/api.py b/accounts/api.py index af71abe..7e40048 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -17,7 +17,13 @@ from donations.models import Donation from donations.serializers import SIMPLE_DONATION_EXAMPLE, DonationSerializer from pots.models import Pot, PotApplication, PotApplicationStatus -from pots.serializers import SIMPLE_POT_EXAMPLE, PotSerializer +from pots.serializers import ( + SIMPLE_PAYOUT_EXAMPLE, + SIMPLE_POT_APPLICATION_EXAMPLE, + SIMPLE_POT_EXAMPLE, + PotApplicationSerializer, + PotSerializer, +) from .models import Account from .serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer @@ -188,6 +194,54 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) +class AccountPotApplicationsAPI(APIView, LimitOffsetPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("account_id", str, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=PotApplicationSerializer(many=True), + description="Returns paginated pot applications for the account", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for pot applications", + value=SIMPLE_POT_APPLICATION_EXAMPLE, + response_only=True, + ), + ], + ), + 400: OpenApiResponse(description="Invalid status value"), + 404: OpenApiResponse(description="Account not found"), + 500: OpenApiResponse(description="Internal server error"), + }, + ) + @method_decorator(cache_page(60 * 5)) + def get(self, request: Request, *args, **kwargs): + account_id = kwargs.get("account_id") + try: + account = Account.objects.get(id=account_id) + except Account.DoesNotExist: + return Response( + {"message": f"Account with ID {account_id} not found."}, status=404 + ) + + applications = PotApplication.objects.filter(applicant=account) + status_param = request.query_params.get("status") + if status_param: + if status_param not in PotApplicationStatus.values: + return Response( + {"message": f"Invalid status value: {status_param}"}, status=400 + ) + applications = applications.filter(status=status_param) + results = self.paginate_queryset(applications, request, view=self) + serializer = PotApplicationSerializer(results, many=True) + return self.get_paginated_response(serializer.data) + + class AccountDonationsReceivedAPI(APIView, LimitOffsetPagination): @extend_schema( diff --git a/api/urls.py b/api/urls.py index 76dc434..5b20f86 100644 --- a/api/urls.py +++ b/api/urls.py @@ -10,6 +10,7 @@ AccountDetailAPI, AccountDonationsReceivedAPI, AccountDonationsSentAPI, + AccountPotApplicationsAPI, AccountsListAPI, DonorsAPI, ) @@ -47,6 +48,11 @@ AccountActivePotsAPI.as_view(), name="accounts_api_by_id_active_pots", ), + path( + "v1/accounts//pot_applications", + AccountPotApplicationsAPI.as_view(), + name="accounts_api_by_id_pot_applications", + ), path( "v1/accounts//donations_received", AccountDonationsReceivedAPI.as_view(), From 0f30d2b3c0c983f5c69a285159e872ae6787c0f4 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:14:19 -0400 Subject: [PATCH 189/454] fix readme typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 518f5ec..12ce198 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - [✅ Get donations received for account: `GET /accounts/{ACCOUNT_ID}/donations_received` (paginated)](#-get-donations-received-for-account-get-accountsaccount_iddonations_received-paginated) - [✅ Get donations sent for account: `GET /accounts/{ACCOUNT_ID}/donations_sent` (paginated)](#-get-donations-sent-for-account-get-accountsaccount_iddonations_sent-paginated) - [✅ Get pots for account: `GET /accounts/{ACCOUNT_ID}/active_pots` (paginated)](#-get-pots-for-account-get-accountsaccount_idactive_pots-paginated) - - [✅ Get applications for account: `GET /accounts/{ACCOUNT_ID}/active_pots` (paginated)](#-get-applications-for-account-get-accountsaccount_idactive_pots-paginated) + - [✅ Get applications for account: `GET /accounts/{ACCOUNT_ID}/pot_applications` (paginated)](#-get-applications-for-account-get-accountsaccount_idpot_applications-paginated) - [`List` endpoints](#list-endpoints) - [✅ Get all lists: `GET /lists` (paginated)](#-get-all-lists-get-lists-paginated) - [✅ Get list by ID: `GET /lists/{LIST_ID}` (paginated)](#-get-list-by-id-get-listslist_id-paginated) @@ -132,7 +132,7 @@ _NB: These endpoints are what is required to integrate with BOS app & replace cu Can specify `status=live` query param to retrieve only pots that are currently active (live matching round) -#### ✅ Get applications for account: `GET /accounts/{ACCOUNT_ID}/active_pots` (paginated) +#### ✅ Get applications for account: `GET /accounts/{ACCOUNT_ID}/pot_applications` (paginated) Can specify `status={PotApplicationStatus}` query param to retrieve applications with a given status: From c3936a4f9cfe2267f603cbeb60281fad30d68883 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:29:37 -0400 Subject: [PATCH 190/454] add related pot record to PotApplicationSerializer --- accounts/api.py | 2 +- pots/serializers.py | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/accounts/api.py b/accounts/api.py index 7e40048..d41b38e 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -219,7 +219,7 @@ class AccountPotApplicationsAPI(APIView, LimitOffsetPagination): 500: OpenApiResponse(description="Internal server error"), }, ) - @method_decorator(cache_page(60 * 5)) + # @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): account_id = kwargs.get("account_id") try: diff --git a/pots/serializers.py b/pots/serializers.py index f7533b1..0bbd720 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -51,10 +51,24 @@ class Meta: class PotApplicationSerializer(ModelSerializer): + class Meta: model = PotApplication - fields = "__all__" # TODO: potentially adjust this e.g. for formatting of datetimes, adding convenience fields etc - # TODO: add reviews + fields = [ + "id", + "pot", + "applicant", + "message", + "status", + "submitted_at", + "updated_at", + "tx_hash", + ] + + pot = SerializerMethodField() + + def get_pot(self, obj): + return PotSerializer(obj.pot).data class PotPayoutSerializer(ModelSerializer): From 0bec9a1638e75e665452d6cc0310c26b0f871ccd Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:30:00 -0400 Subject: [PATCH 191/454] add cache back in --- accounts/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/api.py b/accounts/api.py index d41b38e..7e40048 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -219,7 +219,7 @@ class AccountPotApplicationsAPI(APIView, LimitOffsetPagination): 500: OpenApiResponse(description="Internal server error"), }, ) - # @method_decorator(cache_page(60 * 5)) + @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): account_id = kwargs.get("account_id") try: From 03fdf76262a6bf96848dcce6b0b7b766e93141c5 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:41:38 -0400 Subject: [PATCH 192/454] add pagination to openAPI schema --- accounts/api.py | 24 ++++++++++++------------ accounts/serializers.py | 7 +++++++ donations/serializers.py | 9 ++++++++- lists/api.py | 20 +++++++++++++++++--- pots/api.py | 22 +++++++++++----------- pots/serializers.py | 37 ++++++++++++++++++++++++++++++++++--- 6 files changed, 89 insertions(+), 30 deletions(-) diff --git a/accounts/api.py b/accounts/api.py index 7e40048..20d4edf 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -15,18 +15,18 @@ from base.logging import logger from donations.models import Donation -from donations.serializers import SIMPLE_DONATION_EXAMPLE, DonationSerializer +from donations.serializers import PAGINATED_DONATION_EXAMPLE, DonationSerializer from pots.models import Pot, PotApplication, PotApplicationStatus from pots.serializers import ( - SIMPLE_PAYOUT_EXAMPLE, - SIMPLE_POT_APPLICATION_EXAMPLE, - SIMPLE_POT_EXAMPLE, + PAGINATED_PAYOUT_EXAMPLE, + PAGINATED_POT_APPLICATION_EXAMPLE, + PAGINATED_POT_EXAMPLE, PotApplicationSerializer, PotSerializer, ) from .models import Account -from .serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer +from .serializers import PAGINATED_ACCOUNT_EXAMPLE, AccountSerializer class DonorsAPI(APIView, LimitOffsetPagination): @@ -49,7 +49,7 @@ class DonorsAPI(APIView, LimitOffsetPagination): "example-1", summary="user.near", description="Example response for donor accounts", - value=SIMPLE_ACCOUNT_EXAMPLE, + value=PAGINATED_ACCOUNT_EXAMPLE, response_only=True, ), ], @@ -85,7 +85,7 @@ class AccountsListAPI(APIView, LimitOffsetPagination): "example-1", summary="Simple example", description="Example response for accounts data", - value=SIMPLE_ACCOUNT_EXAMPLE, + value=PAGINATED_ACCOUNT_EXAMPLE, response_only=True, ), ], @@ -116,7 +116,7 @@ class AccountDetailAPI(APIView): "example-1", summary="user.near", description="Example response for account detail", - value=SIMPLE_ACCOUNT_EXAMPLE, + value=PAGINATED_ACCOUNT_EXAMPLE, response_only=True, ), ], @@ -160,7 +160,7 @@ class AccountActivePotsAPI(APIView, LimitOffsetPagination): "example-1", summary="Simple example", description="Example response for active pots", - value=SIMPLE_POT_EXAMPLE, + value=PAGINATED_POT_EXAMPLE, response_only=True, ), ], @@ -209,7 +209,7 @@ class AccountPotApplicationsAPI(APIView, LimitOffsetPagination): "example-1", summary="Simple example", description="Example response for pot applications", - value=SIMPLE_POT_APPLICATION_EXAMPLE, + value=PAGINATED_POT_APPLICATION_EXAMPLE, response_only=True, ), ], @@ -257,7 +257,7 @@ class AccountDonationsReceivedAPI(APIView, LimitOffsetPagination): "example-1", summary="Simple example", description="Example response for donations received", - value=SIMPLE_DONATION_EXAMPLE, + value=PAGINATED_DONATION_EXAMPLE, response_only=True, ), ], @@ -297,7 +297,7 @@ class AccountDonationsSentAPI(APIView, LimitOffsetPagination): "example-1", summary="Simple example", description="Example response for donations received", - value=SIMPLE_DONATION_EXAMPLE, + value=PAGINATED_DONATION_EXAMPLE, response_only=True, ), ], diff --git a/accounts/serializers.py b/accounts/serializers.py index c4b4e70..a990b48 100644 --- a/accounts/serializers.py +++ b/accounts/serializers.py @@ -106,3 +106,10 @@ class Meta: }, }, } + +PAGINATED_ACCOUNT_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_ACCOUNT_EXAMPLE], +} diff --git a/donations/serializers.py b/donations/serializers.py index 9fd8783..087f34d 100644 --- a/donations/serializers.py +++ b/donations/serializers.py @@ -30,8 +30,15 @@ class Meta: "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", "donor": "donor.near", "ft": "near", - "pot": EXAMPLE_POT_ID, + "pot": "some-pot.v1.potfactory.potlock.near", "recipient": "someproject.near", "referrer": "somereferrer.near", "chef": "chef.near", } + +PAGINATED_DONATION_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_DONATION_EXAMPLE], +} diff --git a/lists/api.py b/lists/api.py index c4a09e2..7401d68 100644 --- a/lists/api.py +++ b/lists/api.py @@ -29,6 +29,13 @@ "admins": ["plugrel.near"], } +PAGINATED_LIST_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_LIST_EXAMPLE], +} + SIMPLE_LIST_REGISTRATION_EXAMPLE = { "id": 10, "status": "Approved", @@ -42,6 +49,13 @@ "registered_by": "project-admin.near", } +PAGINATED_LIST_REGISTRATION_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_LIST_REGISTRATION_EXAMPLE], +} + class ListsListAPI(APIView, LimitOffsetPagination): @@ -55,7 +69,7 @@ class ListsListAPI(APIView, LimitOffsetPagination): "example-1", summary="Simple example", description="Example response for lists", - value=SIMPLE_LIST_EXAMPLE, + value=PAGINATED_LIST_EXAMPLE, response_only=True, ), ], @@ -86,7 +100,7 @@ class ListDetailAPI(APIView): "example-1", summary="Simple list example", description="Example response for list detail", - value=SIMPLE_LIST_EXAMPLE, + value=PAGINATED_LIST_EXAMPLE, response_only=True, ), ], @@ -123,7 +137,7 @@ class ListRegistrationsAPI(APIView, LimitOffsetPagination): "example-1", summary="Simple registration example", description="Example response for list registrations", - value=SIMPLE_LIST_REGISTRATION_EXAMPLE, + value=PAGINATED_LIST_REGISTRATION_EXAMPLE, response_only=True, ), ], diff --git a/pots/api.py b/pots/api.py index 623ce82..78129be 100644 --- a/pots/api.py +++ b/pots/api.py @@ -14,15 +14,15 @@ from rest_framework.views import APIView from accounts.models import Account -from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer +from accounts.serializers import PAGINATED_ACCOUNT_EXAMPLE, AccountSerializer from donations.models import Donation -from donations.serializers import SIMPLE_DONATION_EXAMPLE, DonationSerializer +from donations.serializers import PAGINATED_DONATION_EXAMPLE, DonationSerializer from .models import Pot, PotApplication, PotApplicationStatus from .serializers import ( - SIMPLE_PAYOUT_EXAMPLE, - SIMPLE_POT_APPLICATION_EXAMPLE, - SIMPLE_POT_EXAMPLE, + PAGINATED_PAYOUT_EXAMPLE, + PAGINATED_POT_APPLICATION_EXAMPLE, + PAGINATED_POT_EXAMPLE, PotApplicationSerializer, PotPayoutSerializer, PotSerializer, @@ -41,7 +41,7 @@ class PotsListAPI(APIView, LimitOffsetPagination): "example-1", summary="Simple example", description="Example response for pots", - value=SIMPLE_POT_EXAMPLE, + value=PAGINATED_POT_EXAMPLE, response_only=True, ), ], @@ -71,7 +71,7 @@ class PotDetailAPI(APIView): "example-1", summary="Simple pot example", description="Example response for pot detail", - value=SIMPLE_POT_EXAMPLE, + value=PAGINATED_POT_EXAMPLE, response_only=True, ), ], @@ -105,7 +105,7 @@ class PotApplicationsAPI(APIView, LimitOffsetPagination): "example-1", summary="Simple example", description="Example response for pot applications", - value=SIMPLE_POT_APPLICATION_EXAMPLE, + value=PAGINATED_POT_APPLICATION_EXAMPLE, response_only=True, ), ], @@ -142,7 +142,7 @@ class PotDonationsAPI(APIView, LimitOffsetPagination): "example-1", summary="Simple example", description="Example response for donations", - value=SIMPLE_DONATION_EXAMPLE, + value=PAGINATED_DONATION_EXAMPLE, response_only=True, ), ], @@ -179,7 +179,7 @@ class PotSponsorsAPI(APIView, LimitOffsetPagination): "example-1", summary="user.near", description="Example response for sponsors", - value=SIMPLE_ACCOUNT_EXAMPLE, + value=PAGINATED_ACCOUNT_EXAMPLE, response_only=True, ), ], @@ -221,7 +221,7 @@ class PotPayoutsAPI(APIView, LimitOffsetPagination): "example-1", summary="Simple example", description="Example response for payouts", - value=SIMPLE_PAYOUT_EXAMPLE, + value=PAGINATED_PAYOUT_EXAMPLE, response_only=True, ), ], diff --git a/pots/serializers.py b/pots/serializers.py index 0bbd720..39fe620 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -80,7 +80,7 @@ class Meta: EXAMPLE_POT_ID = "some-pot.v1.potfactory.potlock.near" SIMPLE_POT_EXAMPLE = { - "id": EXAMPLE_POT_ID, + "id": "some-pot.v1.potfactory.potlock.near", "deployed_at": "2024-02-16T17:45:03.600845Z", "source_metadata": { "link": "https://github.com/PotLock/core", @@ -121,6 +121,13 @@ class Meta: "admins": [], } +PAGINATED_POT_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_POT_EXAMPLE], +} + SIMPLE_POT_APPLICATION_EXAMPLE = { "id": 2, "message": "Hi, I'm a great project and I'd like to apply for this pot.", @@ -128,17 +135,41 @@ class Meta: "submitted_at": "2024-06-05T18:06:45.519Z", "updated_at": "2024-06-05T18:06:45.519Z", "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", - "pot": EXAMPLE_POT_ID, + "pot": { + "id": "some-pot.v1.potfactory.potlock.near", + "name": "My Pot Name", + "description": "To support impactful open source software projects.", + "owner": "ossround.near", + }, "applicant": "applicant.near", } +PAGINATED_POT_APPLICATION_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_POT_APPLICATION_EXAMPLE], +} + SIMPLE_PAYOUT_EXAMPLE = { "id": 4, "amount": "1000000000000000000000000", "amount_paid_usd": "1.27", "paid_at": "2024-06-05T18:12:39.014Z", "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", - "pot": EXAMPLE_POT_ID, + "pot": { + "id": "some-pot.v1.potfactory.potlock.near", + "name": "My Pot Name", + "description": "To support impactful open source software projects.", + "owner": "ossround.near", + }, "recipient": "someproject.near", "ft": "near", } + +PAGINATED_PAYOUT_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_PAYOUT_EXAMPLE], +} From 2eb93763d1efd60ba97883925138a2c721b5f2af Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:46:24 -0400 Subject: [PATCH 193/454] fix api schema pagination --- accounts/api.py | 12 ++++++++---- lists/api.py | 4 ++-- pots/api.py | 5 +++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/accounts/api.py b/accounts/api.py index 20d4edf..d06172c 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -26,7 +26,11 @@ ) from .models import Account -from .serializers import PAGINATED_ACCOUNT_EXAMPLE, AccountSerializer +from .serializers import ( + PAGINATED_ACCOUNT_EXAMPLE, + SIMPLE_ACCOUNT_EXAMPLE, + AccountSerializer, +) class DonorsAPI(APIView, LimitOffsetPagination): @@ -43,7 +47,7 @@ class DonorsAPI(APIView, LimitOffsetPagination): responses={ 200: OpenApiResponse( response=AccountSerializer(many=True), - description="Returns a list of donor accounts", + description="Returns a paginated list of donor accounts", examples=[ OpenApiExample( "example-1", @@ -79,7 +83,7 @@ class AccountsListAPI(APIView, LimitOffsetPagination): responses={ 200: OpenApiResponse( response=AccountSerializer(many=True), - description="Returns a list of accounts", + description="Returns a paginated list of accounts", examples=[ OpenApiExample( "example-1", @@ -116,7 +120,7 @@ class AccountDetailAPI(APIView): "example-1", summary="user.near", description="Example response for account detail", - value=PAGINATED_ACCOUNT_EXAMPLE, + value=SIMPLE_ACCOUNT_EXAMPLE, response_only=True, ), ], diff --git a/lists/api.py b/lists/api.py index 7401d68..da98f4c 100644 --- a/lists/api.py +++ b/lists/api.py @@ -63,7 +63,7 @@ class ListsListAPI(APIView, LimitOffsetPagination): responses={ 200: OpenApiResponse( response=ListSerializer(many=True), - description="Returns a list of lists", + description="Returns a paginated list of lists", examples=[ OpenApiExample( "example-1", @@ -100,7 +100,7 @@ class ListDetailAPI(APIView): "example-1", summary="Simple list example", description="Example response for list detail", - value=PAGINATED_LIST_EXAMPLE, + value=SIMPLE_LIST_EXAMPLE, response_only=True, ), ], diff --git a/pots/api.py b/pots/api.py index 78129be..d6840fc 100644 --- a/pots/api.py +++ b/pots/api.py @@ -23,6 +23,7 @@ PAGINATED_PAYOUT_EXAMPLE, PAGINATED_POT_APPLICATION_EXAMPLE, PAGINATED_POT_EXAMPLE, + SIMPLE_POT_EXAMPLE, PotApplicationSerializer, PotPayoutSerializer, PotSerializer, @@ -35,7 +36,7 @@ class PotsListAPI(APIView, LimitOffsetPagination): responses={ 200: OpenApiResponse( response=PotSerializer(many=True), - description="Returns a list of pots", + description="Returns a paginated list of pots", examples=[ OpenApiExample( "example-1", @@ -71,7 +72,7 @@ class PotDetailAPI(APIView): "example-1", summary="Simple pot example", description="Example response for pot detail", - value=PAGINATED_POT_EXAMPLE, + value=SIMPLE_POT_EXAMPLE, response_only=True, ), ], From 7fba810269d5b03db3168c641fea0c76d0b5de42 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:49:22 -0400 Subject: [PATCH 194/454] add payouts for acct endpoint; change ft FK to token --- accounts/api.py | 43 ++- api/urls.py | 6 + donations/admin.py | 10 +- donations/migrations/0009_donation_token.py | 26 ++ .../migrations/0010_migrate_ft_to_token.py | 23 ++ ...remove_donation_ft_alter_donation_token.py | 29 ++ donations/models.py | 17 +- donations/serializers.py | 73 +++- .../management/commands/populatedata.py | 344 ++++++++++-------- indexer_app/utils.py | 32 +- pots/migrations/0007_potpayout_token.py | 26 ++ pots/migrations/0008_migrate_ft_to_token.py | 23 ++ ...move_potpayout_ft_alter_potpayout_token.py | 29 ++ pots/models.py | 10 +- pots/serializers.py | 17 +- tokens/admin.py | 26 +- ...0002_token_icon_token_name_token_symbol.py | 39 ++ tokens/models.py | 18 + tokens/serializers.py | 13 + 19 files changed, 592 insertions(+), 212 deletions(-) create mode 100644 donations/migrations/0009_donation_token.py create mode 100644 donations/migrations/0010_migrate_ft_to_token.py create mode 100644 donations/migrations/0011_remove_donation_ft_alter_donation_token.py create mode 100644 pots/migrations/0007_potpayout_token.py create mode 100644 pots/migrations/0008_migrate_ft_to_token.py create mode 100644 pots/migrations/0009_remove_potpayout_ft_alter_potpayout_token.py create mode 100644 tokens/migrations/0002_token_icon_token_name_token_symbol.py create mode 100644 tokens/serializers.py diff --git a/accounts/api.py b/accounts/api.py index d06172c..8a45463 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -16,12 +16,13 @@ from base.logging import logger from donations.models import Donation from donations.serializers import PAGINATED_DONATION_EXAMPLE, DonationSerializer -from pots.models import Pot, PotApplication, PotApplicationStatus +from pots.models import Pot, PotApplication, PotApplicationStatus, PotPayout from pots.serializers import ( PAGINATED_PAYOUT_EXAMPLE, PAGINATED_POT_APPLICATION_EXAMPLE, PAGINATED_POT_EXAMPLE, PotApplicationSerializer, + PotPayoutSerializer, PotSerializer, ) @@ -324,3 +325,43 @@ def get(self, request: Request, *args, **kwargs): results = self.paginate_queryset(donations, request, view=self) serializer = DonationSerializer(results, many=True) return self.get_paginated_response(serializer.data) + + +class AccountPayoutsReceivedAPI(APIView, LimitOffsetPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("account_id", str, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=DonationSerializer(many=True), + description="Returns paginated payouts received by the account", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for payouts received", + value=PAGINATED_PAYOUT_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Account not found"), + 500: OpenApiResponse(description="Internal server error"), + }, + ) + @method_decorator(cache_page(60 * 5)) + def get(self, request: Request, *args, **kwargs): + account_id = kwargs.get("account_id") + try: + account = Account.objects.get(id=account_id) + except Account.DoesNotExist: + return Response( + {"message": f"Account with ID {account_id} not found."}, status=404 + ) + + payouts = PotPayout.objects.filter(recipient=account) + results = self.paginate_queryset(payouts, request, view=self) + serializer = PotPayoutSerializer(results, many=True) + return self.get_paginated_response(serializer.data) diff --git a/api/urls.py b/api/urls.py index 5b20f86..84d8bef 100644 --- a/api/urls.py +++ b/api/urls.py @@ -10,6 +10,7 @@ AccountDetailAPI, AccountDonationsReceivedAPI, AccountDonationsSentAPI, + AccountPayoutsReceivedAPI, AccountPotApplicationsAPI, AccountsListAPI, DonorsAPI, @@ -63,6 +64,11 @@ AccountDonationsSentAPI.as_view(), name="accounts_api_by_id_donations_sent", ), + path( + "v1/accounts//payouts_received", + AccountPayoutsReceivedAPI.as_view(), + name="accounts_api_by_id_payouts_received", + ), # donors path("v1/donors", DonorsAPI.as_view(), name="donors_api"), # lists diff --git a/donations/admin.py b/donations/admin.py index 2af9a47..a9b1769 100644 --- a/donations/admin.py +++ b/donations/admin.py @@ -12,7 +12,7 @@ class DonationAdmin(admin.ModelAdmin): [ "donor_address", "recipient_address", - "ft_address", + "token_address", "referrer_address", "chef_address", ] @@ -26,11 +26,11 @@ class DonationAdmin(admin.ModelAdmin): ordering = ("-donated_at",) def get_queryset(self, request): - # Prefetch related donor, recipient, ft, referrer, and chef to prevent N+1 queries + # Prefetch related donor, recipient, token, referrer, and chef to prevent N+1 queries return ( super() .get_queryset(request) - .prefetch_related("donor", "recipient", "ft", "referrer", "chef") + .prefetch_related("donor", "recipient", "token", "referrer", "chef") ) def donor_address(self, obj): @@ -42,8 +42,8 @@ def donor_address(self, obj): def recipient_address(self, obj): return obj.recipient.id if obj.recipient else None - def ft_address(self, obj): - return obj.ft.id + def token_address(self, obj): + return obj.token.id def referrer_address(self, obj): return obj.referrer.id if obj.referrer else None diff --git a/donations/migrations/0009_donation_token.py b/donations/migrations/0009_donation_token.py new file mode 100644 index 0000000..81a5fb3 --- /dev/null +++ b/donations/migrations/0009_donation_token.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.4 on 2024-06-20 13:03 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("donations", "0008_alter_donation_tx_hash"), + ("tokens", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="donation", + name="token", + field=models.ForeignKey( + help_text="Donation token.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="donations", + to="tokens.token", + ), + ), + ] diff --git a/donations/migrations/0010_migrate_ft_to_token.py b/donations/migrations/0010_migrate_ft_to_token.py new file mode 100644 index 0000000..8b02236 --- /dev/null +++ b/donations/migrations/0010_migrate_ft_to_token.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.4 on 2024-06-20 13:04 + +from django.db import migrations + + +def migrate_ft_to_token(apps, schema_editor): + Donation = apps.get_model("donations", "Donation") + Token = apps.get_model("tokens", "Token") + for donation in Donation.objects.all(): + token = Token.objects.get(id=donation.ft) + donation.token = token + donation.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("donations", "0009_donation_token"), + ] + + operations = [ + migrations.RunPython(migrate_ft_to_token), + ] diff --git a/donations/migrations/0011_remove_donation_ft_alter_donation_token.py b/donations/migrations/0011_remove_donation_ft_alter_donation_token.py new file mode 100644 index 0000000..fe047bf --- /dev/null +++ b/donations/migrations/0011_remove_donation_ft_alter_donation_token.py @@ -0,0 +1,29 @@ +# Generated by Django 5.0.4 on 2024-06-20 13:56 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("donations", "0010_migrate_ft_to_token"), + ("tokens", "0001_initial"), + ] + + operations = [ + migrations.RemoveField( + model_name="donation", + name="ft", + ), + migrations.AlterField( + model_name="donation", + name="token", + field=models.ForeignKey( + help_text="Donation token.", + on_delete=django.db.models.deletion.CASCADE, + related_name="donations", + to="tokens.token", + ), + ), + ] diff --git a/donations/models.py b/donations/models.py index 10c63a9..34cafbe 100644 --- a/donations/models.py +++ b/donations/models.py @@ -1,3 +1,4 @@ +import token from datetime import timedelta from decimal import Decimal @@ -62,12 +63,20 @@ class Donation(models.Model): null=True, help_text=_("Net amount in USD."), ) - ft = models.ForeignKey( - Account, # should probably be Token + # ft = models.ForeignKey( + # Account, # should probably be Token + # on_delete=models.CASCADE, + # related_name="ft_donations", + # null=False, + # help_text=_("Donation FT."), + # ) + token = models.ForeignKey( + Token, on_delete=models.CASCADE, - related_name="ft_donations", + related_name="donations", null=False, - help_text=_("Donation FT."), + help_text=_("Donation token."), + db_index=True, ) pot = models.ForeignKey( Pot, diff --git a/donations/serializers.py b/donations/serializers.py index 087f34d..795dbae 100644 --- a/donations/serializers.py +++ b/donations/serializers.py @@ -1,14 +1,69 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField -from pots.serializers import EXAMPLE_POT_ID +from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer +from pots.serializers import EXAMPLE_POT_ID, SIMPLE_POT_EXAMPLE, PotSerializer +from tokens.serializers import SIMPLE_TOKEN_EXAMPLE, TokenSerializer from .models import Donation class DonationSerializer(ModelSerializer): + class Meta: model = Donation - fields = "__all__" # TODO: potentially adjust this e.g. for formatting of datetimes, adding convenience fields etc + fields = [ + "id", + "on_chain_id", + "total_amount", + "total_amount_usd", + "net_amount", + "net_amount_usd", + "matching_pool", + "message", + "donated_at", + "protocol_fee", + "protocol_fee_usd", + "referrer_fee", + "referrer_fee_usd", + "chef_fee", + "chef_fee_usd", + "tx_hash", + "donor", + "token", + "pot", + "recipient", + "referrer", + "chef", + ] + + donor = SerializerMethodField() + token = SerializerMethodField() + pot = SerializerMethodField() + recipient = SerializerMethodField() + referrer = SerializerMethodField() + chef = SerializerMethodField() + + def get_donor(self, obj): + return AccountSerializer(obj.donor).data + + def get_token(self, obj): + return TokenSerializer(obj.token).data + + def get_pot(self, obj): + if obj.pot: + return PotSerializer(obj.pot).data + + def get_recipient(self, obj): + if obj.recipient: + return AccountSerializer(obj.recipient).data + + def get_referrer(self, obj): + if obj.referrer: + return AccountSerializer(obj.referrer).data + + def get_chef(self, obj): + if obj.chef: + return AccountSerializer(obj.chef).data SIMPLE_DONATION_EXAMPLE = { @@ -18,7 +73,7 @@ class Meta: "total_amount_usd": "1.17", "net_amount": "1000000000000000000000000", "net_amount_usd": "1.02", - "matching_pool": True, + "matching_pool": False, "message": None, "donated_at": "2024-06-05T18:08:40.751Z", "protocol_fee": "10000000000000000000000", @@ -28,12 +83,12 @@ class Meta: "chef_fee": "10000000000000000000000", "chef_fee_usd": "0.27", "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", - "donor": "donor.near", - "ft": "near", - "pot": "some-pot.v1.potfactory.potlock.near", - "recipient": "someproject.near", - "referrer": "somereferrer.near", - "chef": "chef.near", + "donor": SIMPLE_ACCOUNT_EXAMPLE, + "token": SIMPLE_TOKEN_EXAMPLE, + "pot": SIMPLE_POT_EXAMPLE, + "recipient": SIMPLE_ACCOUNT_EXAMPLE, + "referrer": SIMPLE_ACCOUNT_EXAMPLE, + "chef": SIMPLE_ACCOUNT_EXAMPLE, } PAGINATED_DONATION_EXAMPLE = { diff --git a/indexer_app/management/commands/populatedata.py b/indexer_app/management/commands/populatedata.py index 2688742..6a4f3c9 100644 --- a/indexer_app/management/commands/populatedata.py +++ b/indexer_app/management/commands/populatedata.py @@ -17,176 +17,202 @@ PotPayoutChallenge, PotPayoutChallengeAdminResponse, ) +from tokens.models import Token class Command(BaseCommand): help = "Pull donations data from contract & populate donations table." def handle(self, *args, **options): - # lists - LISTS_CONTRACT_ID = "lists.potlock.near" - url = f"https://rpc.web4.near.page/account/{LISTS_CONTRACT_ID}/view/get_lists" - lists = requests.get(url) - if lists.status_code != 200: - print( - f"Request for lists data failed ({lists.status_code}) with message: {lists.text}" - ) - return - lists = lists.json() - print("lists: ", lists) - print(f"adding {len(lists)} direct lists") - for l in lists: - list_id = l["id"] - owner, _ = Account.objects.get_or_create(id=l["owner"]) - list_defaults = { - "on_chain_id": list_id, - "owner": owner, - "name": l["name"], - "description": l["description"], - "cover_image_url": l["cover_image_url"], - "admin_only_registrations": l["admin_only_registrations"], - "default_registration_status": l["default_registration_status"], - "created_at": datetime.fromtimestamp(l["created_at"] / 1000), - "updated_at": datetime.fromtimestamp(l["updated_at"] / 1000), - } - list, created = List.objects.update_or_create( - on_chain_id=list_id, defaults=list_defaults - ) - print("created list: ", list.on_chain_id, created) - # add admins - for admin in l["admins"]: - admin_obj, _ = Account.objects.get_or_create(id=admin) - list.admins.add(admin_obj) - # get registrations in pages - page = 0 - limit = 300 - while True: - time.sleep(1) - url = f"https://rpc.web4.near.page/account/{LISTS_CONTRACT_ID}/view/get_registrations_for_list" - params = { - "list_id.json": list_id, - "from_index.json": page * limit, - "limit.json": limit, + # # lists + # LISTS_CONTRACT_ID = "lists.potlock.near" + # url = f"https://rpc.web4.near.page/account/{LISTS_CONTRACT_ID}/view/get_lists" + # lists = requests.get(url) + # if lists.status_code != 200: + # print( + # f"Request for lists data failed ({lists.status_code}) with message: {lists.text}" + # ) + # return + # lists = lists.json() + # print("lists: ", lists) + # print(f"adding {len(lists)} direct lists") + # for l in lists: + # list_id = l["id"] + # owner, _ = Account.objects.get_or_create(id=l["owner"]) + # list_defaults = { + # "on_chain_id": list_id, + # "owner": owner, + # "name": l["name"], + # "description": l["description"], + # "cover_image_url": l["cover_image_url"], + # "admin_only_registrations": l["admin_only_registrations"], + # "default_registration_status": l["default_registration_status"], + # "created_at": datetime.fromtimestamp(l["created_at"] / 1000), + # "updated_at": datetime.fromtimestamp(l["updated_at"] / 1000), + # } + # list, created = List.objects.update_or_create( + # on_chain_id=list_id, defaults=list_defaults + # ) + # print("created list: ", list.on_chain_id, created) + # # add admins + # for admin in l["admins"]: + # admin_obj, _ = Account.objects.get_or_create(id=admin) + # list.admins.add(admin_obj) + # # get registrations in pages + # page = 0 + # limit = 300 + # while True: + # time.sleep(1) + # url = f"https://rpc.web4.near.page/account/{LISTS_CONTRACT_ID}/view/get_registrations_for_list" + # params = { + # "list_id.json": list_id, + # "from_index.json": page * limit, + # "limit.json": limit, + # } + # registrations = requests.get(url, params=params) + # if registrations.status_code != 200: + # print( + # f"Request for registrations data failed ({registrations.status_code}) with message: {registrations.text}" + # ) + # return + # registrations = registrations.json() + # print("registrations: ", registrations) + # print(f"adding {len(registrations)} registrations") + # for reg in registrations: + # registrant, _ = Account.objects.get_or_create( + # id=reg["registrant_id"] + # ) + # registrar = registrant + # if "registered_by" in reg: + # registrar, _ = Account.objects.get_or_create( + # id=reg["registered_by"] + # ) + # registration_defaults = { + # "list": list, + # "registrant": registrant, + # "registered_by": registrar, + # "status": reg["status"], + # "submitted_at": datetime.fromtimestamp( + # reg["submitted_ms"] / 1000 + # ), + # "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), + # "registrant_notes": reg["registrant_notes"], + # "admin_notes": reg["admin_notes"], + # "tx_hash": None, + # } + # registration, created = ListRegistration.objects.update_or_create( + # registrant=registrant, + # list=list, + # defaults=registration_defaults, + # ) + # print("created registration: ", registration.id, created) + # if len(registrations) == limit: + # page += 1 + # else: + # break + # # TODO: get upvotes (none currently so don't have to worry about it at this point) + # direct donations + # get direct donations in pages + DONATE_CONTRACT_ID = "donate.potlock.near" + page = 0 + limit = 300 + while True: + time.sleep(1) + url = f"https://rpc.web4.near.page/account/{DONATE_CONTRACT_ID}/view/get_donations" + params = {"from_index.json": page * limit, "limit.json": limit} + donations = requests.get(url, params=params) + if donations.status_code != 200: + print( + f"Request for direct donations data failed ({donations.status_code}) with message: {donations.text}" + ) + return + donations = donations.json() + print(f"adding {len(donations)} direct donations") + for donation in donations: + donor, _ = Account.objects.get_or_create(id=donation["donor_id"]) + referrer = None + recipient, _ = Account.objects.get_or_create( + id=donation["recipient_id"] + ) + ft_id = donation["ft_id"] + ft_acct, _ = Account.objects.get_or_create(id=ft_id) + token_defaults = { + "decimals": 24, } - registrations = requests.get(url, params=params) - if registrations.status_code != 200: - print( - f"Request for registrations data failed ({registrations.status_code}) with message: {registrations.text}" - ) - return - registrations = registrations.json() - print("registrations: ", registrations) - print(f"adding {len(registrations)} registrations") - for reg in registrations: - registrant, _ = Account.objects.get_or_create( - id=reg["registrant_id"] - ) - registrar = registrant - if "registered_by" in reg: - registrar, _ = Account.objects.get_or_create( - id=reg["registered_by"] + if ft_id != "near": + time.sleep(1) + url = f"https://rpc.web4.near.page/account/{ft_id}/view/ft_metadata" + ft_metadata = requests.get(url) + if ft_metadata.status_code != 200: + print( + f"Request for ft_metadata failed ({ft_metadata.status_code}) with message: {ft_metadata.text}" ) - registration_defaults = { - "list": list, - "registrant": registrant, - "registered_by": registrar, - "status": reg["status"], - "submitted_at": datetime.fromtimestamp( - reg["submitted_ms"] / 1000 - ), - "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), - "registrant_notes": reg["registrant_notes"], - "admin_notes": reg["admin_notes"], - "tx_hash": None, - } - registration, created = ListRegistration.objects.update_or_create( - registrant=registrant, - list=list, - defaults=registration_defaults, + else: + ft_metadata = ft_metadata.json() + if "name" in ft_metadata: + token_defaults["name"] = ft_metadata["name"] + if "symbol" in ft_metadata: + token_defaults["symbol"] = ft_metadata["symbol"] + if "icon" in ft_metadata: + token_defaults["icon"] = ft_metadata["icon"] + if "decimals" in ft_metadata: + token_defaults["decimals"] = ft_metadata["decimals"] + ft_token, _ = Token.objects.update_or_create( + id=ft_acct, defaults=token_defaults + ) + if donation.get("referrer_id"): + referrer, _ = Account.objects.get_or_create( + id=donation["referrer_id"] ) - print("created registration: ", registration.id, created) - if len(registrations) == limit: - page += 1 - else: - break - # TODO: get upvotes (none currently so don't have to worry about it at this point) - # # direct donations - # # get direct donations in pages - # DONATE_CONTRACT_ID = "donate.potlock.near" - # page = 0 - # limit = 300 - # while True: - # time.sleep(1) - # url = f"https://rpc.web4.near.page/account/{DONATE_CONTRACT_ID}/view/get_donations" - # params = {"from_index.json": page * limit, "limit.json": limit} - # donations = requests.get(url, params=params) - # if donations.status_code != 200: - # print( - # f"Request for direct donations data failed ({donations.status_code}) with message: {donations.text}" - # ) - # return - # donations = donations.json() - # print(f"adding {len(donations)} direct donations") - # for donation in donations: - # donor, _ = Account.objects.get_or_create(id=donation["donor_id"]) - # referrer = None - # recipient, _ = Account.objects.get_or_create( - # id=donation["recipient_id"] - # ) - # ft_acct, _ = Account.objects.get_or_create(id=donation["ft_id"]) - # if donation.get("referrer_id"): - # referrer, _ = Account.objects.get_or_create( - # id=donation["referrer_id"] - # ) - # total_amount = int(donation["total_amount"]) - # protocol_fee = int(donation["protocol_fee"]) - # referrer_fee = int(donation["referrer_fee"] or 0) - # net_amount = total_amount - protocol_fee - referrer_fee + total_amount = int(donation["total_amount"]) + protocol_fee = int(donation["protocol_fee"]) + referrer_fee = int(donation["referrer_fee"] or 0) + net_amount = total_amount - protocol_fee - referrer_fee - # donation_defaults = { - # "donor": donor, - # "total_amount": donation["total_amount"], - # "total_amount_usd": None, # USD amounts will be added later - # "net_amount_usd": None, - # "net_amount": str(net_amount), - # "ft": ft_acct, - # "message": donation["message"], - # "donated_at": datetime.fromtimestamp( - # donation["donated_at_ms"] / 1000 - # ), - # "matching_pool": False, - # "recipient": recipient, - # "protocol_fee": donation["protocol_fee"], - # "referrer": referrer, - # "referrer_fee": donation["referrer_fee"], - # "tx_hash": None, - # } - # donation, created = Donation.objects.update_or_create( - # on_chain_id=donation["id"], pot=None, defaults=donation_defaults - # ) - # print("created donation: ", donation.on_chain_id, created) - # # # add donation activity - # # activity_type = ( - # # "Donate_Pot_Matching_Pool" - # # if donation.matching_pool - # # else "Donate_Pot_Public" - # # ) - # # defaults = { - # # "signer_id": donor.id, - # # "receiver_id": pot_id, - # # "timestamp": donation.donated_at, - # # "tx_hash": None, - # # } - # # activity, _ = Activity.objects.update_or_create( - # # type=activity_type, - # # action_result=None, - # # defaults=defaults, - # # ) - # if len(donations) == limit: - # page += 1 - # else: - # break + donation_defaults = { + "donor": donor, + "total_amount": donation["total_amount"], + "total_amount_usd": None, # USD amounts will be added later + "net_amount_usd": None, + "net_amount": str(net_amount), + "token": ft_token, + "message": donation["message"], + "donated_at": datetime.fromtimestamp( + donation["donated_at_ms"] / 1000 + ), + "matching_pool": False, + "recipient": recipient, + "protocol_fee": donation["protocol_fee"], + "referrer": referrer, + "referrer_fee": donation["referrer_fee"], + "tx_hash": None, + } + donation, created = Donation.objects.update_or_create( + on_chain_id=donation["id"], pot=None, defaults=donation_defaults + ) + print("created donation: ", donation.on_chain_id, created) + # # add donation activity + # activity_type = ( + # "Donate_Pot_Matching_Pool" + # if donation.matching_pool + # else "Donate_Pot_Public" + # ) + # defaults = { + # "signer_id": donor.id, + # "receiver_id": pot_id, + # "timestamp": donation.donated_at, + # "tx_hash": None, + # } + # activity, _ = Activity.objects.update_or_create( + # type=activity_type, + # action_result=None, + # defaults=defaults, + # ) + if len(donations) == limit: + page += 1 + else: + break # # pot factory # POTFACTORY_ID = "v1.potfactory.potlock.near" # # pots diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 4372fa7..ec4f077 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -23,6 +23,7 @@ PotPayoutChallenge, PotPayoutChallengeAdminResponse, ) +from tokens.models import Token from .logging import logger @@ -712,9 +713,34 @@ async def handle_new_donation( chef, _ = await Account.objects.aget_or_create(id=donation_data["chef_id"]) # Upsert token account - token_acct, _ = await Account.objects.aget_or_create( - id=(donation_data.get("ft_id") or "near") + ft_id = donation_data.get("ft_id") or "near" + token_acct, token_acct_created = await Account.objects.aget_or_create(id=ft_id) + token_defaults = { + "decimals": 24, + } + if token_acct_created: + logger.info(f"Created new token account: {token_acct}") + if ft_id != "near": + url = f"{settings.FASTNEAR_RPC_URL}/account/{ft_id}/view/ft_metadata" + ft_metadata = requests.get(url) + if ft_metadata.status_code != 200: + logger.error( + f"Request for ft_metadata failed ({ft_metadata.status_code}) with message: {ft_metadata.text}" + ) + else: + ft_metadata = ft_metadata.json() + if "name" in ft_metadata: + token_defaults["name"] = ft_metadata["name"] + if "symbol" in ft_metadata: + token_defaults["symbol"] = ft_metadata["symbol"] + if "icon" in ft_metadata: + token_defaults["icon"] = ft_metadata["icon"] + if "decimals" in ft_metadata: + token_defaults["decimals"] = ft_metadata["decimals"] + token, _ = await Token.objects.aupdate_or_create( + id=token_acct, defaults=token_defaults ) + except Exception as e: logger.error(f"Failed to create/get an account involved in donation: {e}") @@ -729,7 +755,7 @@ async def handle_new_donation( "total_amount_usd": None, # USD amounts will be added later "net_amount_usd": None, "net_amount": net_amount, - "ft": token_acct, + "token": token, "message": donation_data.get("message"), "donated_at": donated_at, "matching_pool": donation_data.get("matching_pool", False), diff --git a/pots/migrations/0007_potpayout_token.py b/pots/migrations/0007_potpayout_token.py new file mode 100644 index 0000000..c34c813 --- /dev/null +++ b/pots/migrations/0007_potpayout_token.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.4 on 2024-06-20 14:10 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("pots", "0006_alter_pot_custom_sybil_checks_and_more"), + ("tokens", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="potpayout", + name="token", + field=models.ForeignKey( + help_text="Payout token.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="pot_payouts", + to="tokens.token", + ), + ), + ] diff --git a/pots/migrations/0008_migrate_ft_to_token.py b/pots/migrations/0008_migrate_ft_to_token.py new file mode 100644 index 0000000..b04b159 --- /dev/null +++ b/pots/migrations/0008_migrate_ft_to_token.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.4 on 2024-06-20 13:04 + +from django.db import migrations + + +def migrate_ft_to_token(apps, schema_editor): + PotPayout = apps.get_model("pots", "PotPayout") + Token = apps.get_model("tokens", "Token") + for payout in PotPayout.objects.all(): + token = Token.objects.get(id=payout.ft) + payout.token = token + payout.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("pots", "0007_potpayout_token"), + ] + + operations = [ + migrations.RunPython(migrate_ft_to_token), + ] diff --git a/pots/migrations/0009_remove_potpayout_ft_alter_potpayout_token.py b/pots/migrations/0009_remove_potpayout_ft_alter_potpayout_token.py new file mode 100644 index 0000000..ef926c4 --- /dev/null +++ b/pots/migrations/0009_remove_potpayout_ft_alter_potpayout_token.py @@ -0,0 +1,29 @@ +# Generated by Django 5.0.4 on 2024-06-20 14:14 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("pots", "0008_migrate_ft_to_token"), + ("tokens", "0001_initial"), + ] + + operations = [ + migrations.RemoveField( + model_name="potpayout", + name="ft", + ), + migrations.AlterField( + model_name="potpayout", + name="token", + field=models.ForeignKey( + help_text="Payout token.", + on_delete=django.db.models.deletion.CASCADE, + related_name="pot_payouts", + to="tokens.token", + ), + ), + ] diff --git a/pots/models.py b/pots/models.py index db1ddb5..cc0c98d 100644 --- a/pots/models.py +++ b/pots/models.py @@ -2,6 +2,7 @@ from django.utils.translation import gettext_lazy as _ from accounts.models import Account +from tokens.models import Token class PotFactory(models.Model): @@ -420,13 +421,12 @@ class PotPayout(models.Model): null=True, help_text=_("Payout amount in USD."), ) - ft = models.ForeignKey( - Account, + token = models.ForeignKey( + Token, on_delete=models.CASCADE, - related_name="ft_pot_payouts", + related_name="pot_payouts", null=False, - help_text=_("Payout FT."), - db_index=True, + help_text=_("Payout token."), ) paid_at = models.DateTimeField( _("paid at"), diff --git a/pots/serializers.py b/pots/serializers.py index 39fe620..251a44b 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -1,6 +1,7 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField from base.serializers import TwoDecimalStringField +from tokens.serializers import SIMPLE_TOKEN_EXAMPLE from .models import Pot, PotApplication, PotPayout @@ -135,12 +136,7 @@ class Meta: "submitted_at": "2024-06-05T18:06:45.519Z", "updated_at": "2024-06-05T18:06:45.519Z", "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", - "pot": { - "id": "some-pot.v1.potfactory.potlock.near", - "name": "My Pot Name", - "description": "To support impactful open source software projects.", - "owner": "ossround.near", - }, + "pot": SIMPLE_POT_EXAMPLE, "applicant": "applicant.near", } @@ -157,14 +153,9 @@ class Meta: "amount_paid_usd": "1.27", "paid_at": "2024-06-05T18:12:39.014Z", "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", - "pot": { - "id": "some-pot.v1.potfactory.potlock.near", - "name": "My Pot Name", - "description": "To support impactful open source software projects.", - "owner": "ossround.near", - }, + "pot": SIMPLE_POT_EXAMPLE, "recipient": "someproject.near", - "ft": "near", + "token": SIMPLE_TOKEN_EXAMPLE, } PAGINATED_PAYOUT_EXAMPLE = { diff --git a/tokens/admin.py b/tokens/admin.py index f783399..76aedc9 100644 --- a/tokens/admin.py +++ b/tokens/admin.py @@ -5,7 +5,7 @@ @admin.register(Token) class TokenAdmin(admin.ModelAdmin): - list_display = ("id", "decimals", "get_most_recent_price") + list_display = ("id", "name", "symbol", "icon", "decimals", "get_most_recent_price") search_fields = ("id",) def get_most_recent_price(self, obj): @@ -14,14 +14,14 @@ def get_most_recent_price(self, obj): get_most_recent_price.short_description = "Most Recent Price (USD)" - def has_add_permission(self, request): - return False + # def has_add_permission(self, request): + # return False - def has_change_permission(self, request, obj=None): - return False + # def has_change_permission(self, request, obj=None): + # return False - def has_delete_permission(self, request, obj=None): - return False + # def has_delete_permission(self, request, obj=None): + # return False @admin.register(TokenHistoricalPrice) @@ -30,11 +30,11 @@ class TokenHistoricalPriceAdmin(admin.ModelAdmin): search_fields = ("token__id",) list_filter = ("timestamp",) - def has_add_permission(self, request): - return False + # def has_add_permission(self, request): + # return False - def has_change_permission(self, request, obj=None): - return False + # def has_change_permission(self, request, obj=None): + # return False - def has_delete_permission(self, request, obj=None): - return False + # def has_delete_permission(self, request, obj=None): + # return False diff --git a/tokens/migrations/0002_token_icon_token_name_token_symbol.py b/tokens/migrations/0002_token_icon_token_name_token_symbol.py new file mode 100644 index 0000000..129f89a --- /dev/null +++ b/tokens/migrations/0002_token_icon_token_name_token_symbol.py @@ -0,0 +1,39 @@ +# Generated by Django 5.0.4 on 2024-06-20 17:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("tokens", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="token", + name="icon", + field=models.TextField( + help_text="Token icon (base64 data URL).", + null=True, + verbose_name="icon", + ), + ), + migrations.AddField( + model_name="token", + name="name", + field=models.CharField( + help_text="Token name.", max_length=255, null=True, verbose_name="name" + ), + ), + migrations.AddField( + model_name="token", + name="symbol", + field=models.CharField( + help_text="Token symbol.", + max_length=255, + null=True, + verbose_name="symbol", + ), + ), + ] diff --git a/tokens/models.py b/tokens/models.py index 1b4d071..90a7a8d 100644 --- a/tokens/models.py +++ b/tokens/models.py @@ -1,4 +1,5 @@ from decimal import Decimal +from os import name from django.db import models from django.utils import timezone @@ -14,6 +15,23 @@ class Token(models.Model): primary_key=True, help_text=_("Token ID (address)."), ) + name = models.CharField( + _("name"), + max_length=255, + null=True, + help_text=_("Token name."), + ) + symbol = models.CharField( + _("symbol"), + max_length=255, + null=True, + help_text=_("Token symbol."), + ) + icon = models.TextField( + _("icon"), + null=True, + help_text=_("Token icon (base64 data URL)."), + ) decimals = models.PositiveIntegerField( _("decimals"), null=False, diff --git a/tokens/serializers.py b/tokens/serializers.py new file mode 100644 index 0000000..82b6f74 --- /dev/null +++ b/tokens/serializers.py @@ -0,0 +1,13 @@ +from rest_framework.serializers import ModelSerializer + +from tokens.models import Token + + +class TokenSerializer(ModelSerializer): + + class Meta: + model = Token + fields = "__all__" + + +SIMPLE_TOKEN_EXAMPLE = {"id": "near", "decimals": 24} From 90c023b5f63ebc4b1adab6df1e505825e6439459 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:01:25 -0400 Subject: [PATCH 195/454] fix remaining references to donation.ft --- donations/models.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/donations/models.py b/donations/models.py index 34cafbe..d8dfe66 100644 --- a/donations/models.py +++ b/donations/models.py @@ -189,16 +189,6 @@ class Meta: def to_dict(self): return model_to_dict(self) - def get_ft_token(self): - token, created = Token.objects.get_or_create( - id=self.ft, - defaults={"decimals": 12}, # Default values for new Token creation - ) - if created: - # TODO: fetch token metadata and add correct decimals, possibly other metadata - pass - return token - async def fetch_usd_prices_async(self): fetch_prices = sync_to_async(self.fetch_usd_prices) await fetch_prices() @@ -212,7 +202,7 @@ def fetch_usd_prices(self): existing_referrer_fee_usd = self.referrer_fee_usd existing_chef_fee_usd = self.chef_fee_usd # first, see if there is a TokenHistoricalPrice within 1 day (or HISTORICAL_PRICE_QUERY_HOURS) of self.donated_at - token = self.get_ft_token() + token = self.token time_window = timedelta(hours=settings.HISTORICAL_PRICE_QUERY_HOURS or 24) token_prices = TokenHistoricalPrice.objects.filter( token=token, @@ -253,7 +243,7 @@ def fetch_usd_prices(self): logger.info( "No existing price within acceptable time period; fetching historical price..." ) - endpoint = f"{settings.COINGECKO_URL}/coins/{self.ft.id}/history?date={format_date(self.donated_at)}&localization=false" + endpoint = f"{settings.COINGECKO_URL}/coins/{self.token.id.id}/history?date={format_date(self.donated_at)}&localization=false" if settings.COINGECKO_API_KEY: endpoint += f"&x_cg_pro_api_key={settings.COINGECKO_API_KEY}" logger.info(f"coingecko endpoint: {endpoint}") From 1876ea16df6113dd6c49846dbefe95506fd69aae Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:33:57 -0400 Subject: [PATCH 196/454] add related records to API responses --- lists/api.py | 48 ++++------------------ lists/models.py | 10 ++--- lists/serializers.py | 94 +++++++++++++++++++++++++++++++++++++++++++- pots/serializers.py | 59 +++++++++++++++++++++++---- 4 files changed, 156 insertions(+), 55 deletions(-) diff --git a/lists/api.py b/lists/api.py index da98f4c..bbe26e6 100644 --- a/lists/api.py +++ b/lists/api.py @@ -14,47 +14,13 @@ from rest_framework.views import APIView from .models import List -from .serializers import ListRegistrationSerializer, ListSerializer - -SIMPLE_LIST_EXAMPLE = { - "id": 1, - "name": "PotLock Public Goods Registry", - "description": "The official registry of public goods on NEAR Protocol", - "cover_image_url": "https://images.unsplash.com/photo-1593642532973-d31b6557fa68", - "admin_only_registrations": True, - "default_registration_status": "Approved", - "created_at": "2024-06-05T17:55:14.030Z", - "updated_at": "2024-06-05T17:55:14.030Z", - "owner": "potlock.near", - "admins": ["plugrel.near"], -} - -PAGINATED_LIST_EXAMPLE = { - "count": 1, - "next": None, - "previous": None, - "results": [SIMPLE_LIST_EXAMPLE], -} - -SIMPLE_LIST_REGISTRATION_EXAMPLE = { - "id": 10, - "status": "Approved", - "submitted_at": "2024-06-05T18:01:02.319Z", - "updated_at": "2024-06-05T18:01:02.319Z", - "registrant_notes": "I'm excited to apply for this list", - "admin_notes": "This is a great project that I want on my list.", - "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", - "list": 1, - "registrant": "project.near", - "registered_by": "project-admin.near", -} - -PAGINATED_LIST_REGISTRATION_EXAMPLE = { - "count": 1, - "next": None, - "previous": None, - "results": [SIMPLE_LIST_REGISTRATION_EXAMPLE], -} +from .serializers import ( + PAGINATED_LIST_EXAMPLE, + PAGINATED_LIST_REGISTRATION_EXAMPLE, + SIMPLE_LIST_EXAMPLE, + ListRegistrationSerializer, + ListSerializer, +) class ListsListAPI(APIView, LimitOffsetPagination): diff --git a/lists/models.py b/lists/models.py index d27c613..953b1fe 100644 --- a/lists/models.py +++ b/lists/models.py @@ -31,6 +31,11 @@ class List(models.Model): null=False, help_text=_("List owner."), ) + admins = models.ManyToManyField( + Account, + related_name="admin_lists", + help_text=_("List admins."), + ) name = models.CharField( _("name"), max_length=64, @@ -69,11 +74,6 @@ class List(models.Model): _("updated at"), help_text=_("List last update date."), ) - admins = models.ManyToManyField( - Account, - related_name="admin_lists", - help_text=_("List admins."), - ) class Meta: indexes = [ diff --git a/lists/serializers.py b/lists/serializers.py index a6cef59..90d7dcb 100644 --- a/lists/serializers.py +++ b/lists/serializers.py @@ -1,15 +1,105 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField +from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer + from .models import List, ListRegistration class ListSerializer(ModelSerializer): class Meta: model = List - fields = "__all__" # TODO: potentially adjust this e.g. for formatting of datetimes, adding convenience fields etc + fields = [ + "id", + "on_chain_id", + "owner", + "admins", + "name", + "description", + "cover_image_url", + "admin_only_registrations", + "default_registration_status", + "created_at", + "updated_at", + ] + + owner = SerializerMethodField() + admins = SerializerMethodField() + + def get_owner(self, obj): + return AccountSerializer(obj.owner).data + + def get_admins(self, obj): + return AccountSerializer(obj.admins.all(), many=True).data class ListRegistrationSerializer(ModelSerializer): class Meta: model = ListRegistration - fields = "__all__" # TODO: potentially adjust this e.g. for formatting of datetimes, adding convenience fields etc + fields = [ + "id", + "list", + "registrant", + "registered_by", + "status", + "submitted_at", + "updated_at", + "registrant_notes", + "admin_notes", + "tx_hash", + ] + + list = SerializerMethodField() + registrant = SerializerMethodField() + registered_by = SerializerMethodField() + + def get_list(self, obj): + return ListSerializer(obj.list).data + + def get_registrant(self, obj): + return AccountSerializer(obj.registrant).data + + def get_registered_by(self, obj): + return AccountSerializer(obj.registered_by).data + + +SIMPLE_LIST_EXAMPLE = { + "id": 1, + "on_chain_id": 1, + "owner": SIMPLE_ACCOUNT_EXAMPLE, + "admins": [SIMPLE_ACCOUNT_EXAMPLE], + "name": "Potlock Public Goods Registry", + "description": "The official NEAR Protocol Public Goods Registry", + "cover_image_url": None, + "admin_only_registrations": False, + "default_registration_status": "Approved", + "created_at": "2024-03-27T15:24:46.104000Z", + "updated_at": "2024-04-30T19:00:51.002000Z", +} + +PAGINATED_LIST_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_LIST_EXAMPLE], +} + + +SIMPLE_LIST_REGISTRATION_EXAMPLE = { + "id": 10, + "status": "Approved", + "submitted_at": "2024-06-05T18:01:02.319Z", + "updated_at": "2024-06-05T18:01:02.319Z", + "registrant_notes": "I'm excited to apply for this list", + "admin_notes": "This is a great project that I want on my list.", + "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", + "list": SIMPLE_LIST_EXAMPLE, + "registrant": "project.near", + "registered_by": "project-admin.near", +} + +PAGINATED_LIST_REGISTRATION_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_LIST_REGISTRATION_EXAMPLE], +} diff --git a/pots/serializers.py b/pots/serializers.py index 251a44b..b66f0f9 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -1,7 +1,8 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField +from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer from base.serializers import TwoDecimalStringField -from tokens.serializers import SIMPLE_TOKEN_EXAMPLE +from tokens.serializers import SIMPLE_TOKEN_EXAMPLE, TokenSerializer from .models import Pot, PotApplication, PotPayout @@ -50,6 +51,24 @@ class Meta: "protocol_config_provider", ] + deployer = SerializerMethodField() + owner = SerializerMethodField() + admins = SerializerMethodField() + chef = SerializerMethodField() + + def get_deployer(self, obj): + return AccountSerializer(obj.deployer).data + + def get_owner(self, obj): + return AccountSerializer(obj.owner).data + + def get_admins(self, obj): + return AccountSerializer(obj.admins.all(), many=True).data + + def get_chef(self, obj): + if obj.chef: + return AccountSerializer(obj.chef).data + class PotApplicationSerializer(ModelSerializer): @@ -67,15 +86,41 @@ class Meta: ] pot = SerializerMethodField() + applicant = SerializerMethodField() def get_pot(self, obj): return PotSerializer(obj.pot).data + def get_applicant(self, obj): + return AccountSerializer(obj.applicant).data + class PotPayoutSerializer(ModelSerializer): class Meta: model = PotPayout - fields = "__all__" # TODO: potentially adjust this e.g. for formatting of datetimes, adding convenience fields etc + fields = [ + "id", + "pot", + "recipient", + "amount", + "amount_paid_usd", + "token", + "paid_at", + "tx_hash", + ] + + pot = SerializerMethodField() + recipient = SerializerMethodField() + token = SerializerMethodField() + + def get_pot(self, obj): + return PotSerializer(obj.pot).data + + def get_recipient(self, obj): + return AccountSerializer(obj.recipient).data + + def get_token(self, obj): + return TokenSerializer(obj.token).data EXAMPLE_POT_ID = "some-pot.v1.potfactory.potlock.near" @@ -116,10 +161,10 @@ class Meta: "all_paid_out": False, "protocol_config_provider": "v1.potfactory.potlock.near:get_protocol_config", "pot_factory": "v1.potfactory.potlock.near", - "deployer": "ossround.near", - "owner": "ossround.near", - "chef": "plugrel.near", - "admins": [], + "deployer": SIMPLE_ACCOUNT_EXAMPLE, + "owner": SIMPLE_ACCOUNT_EXAMPLE, + "chef": SIMPLE_ACCOUNT_EXAMPLE, + "admins": [SIMPLE_ACCOUNT_EXAMPLE], } PAGINATED_POT_EXAMPLE = { @@ -137,7 +182,7 @@ class Meta: "updated_at": "2024-06-05T18:06:45.519Z", "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", "pot": SIMPLE_POT_EXAMPLE, - "applicant": "applicant.near", + "applicant": SIMPLE_ACCOUNT_EXAMPLE, } PAGINATED_POT_APPLICATION_EXAMPLE = { From cbc849798c481347b0db28939b894af9774e5bdb Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 20 Jun 2024 15:12:20 -0400 Subject: [PATCH 197/454] add update_pot_statistics task --- base/celery.py | 6 +++ indexer_app/tasks.py | 91 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/base/celery.py b/base/celery.py index 1aa4d8f..68d0724 100644 --- a/base/celery.py +++ b/base/celery.py @@ -35,9 +35,15 @@ "schedule": crontab(minute="*/5"), # Executes every 5 minutes "options": {"queue": "beat_tasks"}, }, + "update_pot_statistics_every_5_minutes": { + "task": "indexer_app.tasks.update_pot_statistics", + "schedule": crontab(minute="*/5"), # Executes every 5 minutes + "options": {"queue": "beat_tasks"}, + }, } app.conf.task_routes = { "indexer_app.tasks.update_account_statistics": {"queue": "beat_tasks"}, "indexer_app.tasks.fetch_usd_prices": {"queue": "beat_tasks"}, + "indexer_app.tasks.update_pot_statistics": {"queue": "beat_tasks"}, } diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 4d3f179..b8dfe39 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -2,18 +2,20 @@ import logging from pathlib import Path +import requests from billiard.exceptions import WorkerLostError from celery import shared_task from celery.signals import task_revoked, worker_shutdown from django.conf import settings from django.db import transaction -from django.db.models import Count, Q, Sum +from django.db.models import Count, DecimalField, Q, Sum, Value +from django.db.models.functions import Cast, NullIf from near_lake_framework import LakeConfig, streamer from accounts.models import Account from donations.models import Donation from indexer_app.handler import handle_streamer_message -from pots.models import PotPayout +from pots.models import Pot, PotPayout from .logging import logger from .utils import cache_block_height, get_block_height @@ -121,6 +123,91 @@ def fetch_usd_prices(): jobs_logger.info(f"USD prices fetched for {donations_count} donations.") +@shared_task +def update_pot_statistics(): + pots = Pot.objects.all() + pots_count = pots.count() + jobs_logger.info(f"Updating statistics for {pots_count} pots...") + for pot in pots: + matching_pool_donations = Donation.objects.filter(pot=pot, matching_pool=True) + public_donations = Donation.objects.filter(pot=pot, matching_pool=False) + try: + print(f"Processing pot: {pot.id}") + + # total matching pool + pot.total_matching_pool = sum( + int(donation.total_amount) + for donation in matching_pool_donations + if donation.total_amount.isdigit() + ) + jobs_logger.info(f"Total matching pool: {pot.total_matching_pool}") + + # total matching pool usd + pot.total_matching_pool_usd = sum( + donation.total_amount_usd + for donation in matching_pool_donations + if donation.total_amount_usd + ) + jobs_logger.info(f"Total matching pool USD: {pot.total_matching_pool_usd}") + + # matching pool balance (get from contract) + url = f"{settings.FASTNEAR_RPC_URL}/account/{pot.id.id}/view/get_config" + response = requests.get(url) + if response.status_code != 200: + jobs_logger.error( + f"Failed to get matching pool balance for pot {pot.id}: {response.text}" + ) + else: + data = response.json() + pot.matching_pool_balance = data["matching_pool_balance"] + jobs_logger.info( + f"Matching pool balance for pot {pot.id}: {pot.matching_pool_balance}" + ) + + # matching pool donations count + pot.matching_pool_donations_count = matching_pool_donations.count() + jobs_logger.info( + f"Matching pool donations count: {pot.matching_pool_donations_count}" + ) + + # total public donations + pot.total_public_donations = sum( + int(donation.total_amount) + for donation in public_donations + if donation.total_amount.isdigit() + ) + jobs_logger.info(f"Total public donations: {pot.total_public_donations}") + + # total public donations usd + pot.total_public_donations_usd = sum( + donation.total_amount_usd + for donation in public_donations + if donation.total_amount_usd + ) + jobs_logger.info( + f"Total public donations USD: {pot.total_public_donations_usd}" + ) + + # public donations count + pot.public_donations_count = public_donations.count() + jobs_logger.info(f"Public donations count: {pot.public_donations_count}") + + # Save changes + pot.save( + update_fields=[ + "total_matching_pool", + "total_matching_pool_usd", + "matching_pool_balance", + "matching_pool_donations_count", + "total_public_donations", + "total_public_donations_usd", + "public_donations_count", + ] + ) + except Exception as e: + jobs_logger.error(f"Failed to update statistics for pot {pot.id}: {e}") + + @shared_task def update_account_statistics(): From a5aab5de7aef12cba3eba836124def02fb2ce0e4 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 20 Jun 2024 16:55:55 -0400 Subject: [PATCH 198/454] fetch usd prices for payouts --- donations/models.py | 1 + indexer_app/tasks.py | 12 ++++++++ pots/models.py | 72 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/donations/models.py b/donations/models.py index d8dfe66..a97573c 100644 --- a/donations/models.py +++ b/donations/models.py @@ -195,6 +195,7 @@ async def fetch_usd_prices_async(self): ### Fetches USD prices for the Donation record and saves USD totals def fetch_usd_prices(self): + # TODO: remove duplicate logic with PotPayout.fetch_usd_prices # get existing values for stats adjustments later existing_total_amount_usd = self.total_amount_usd existing_net_amount_usd = self.net_amount_usd diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index b8dfe39..ffefee9 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -122,6 +122,18 @@ def fetch_usd_prices(): ) jobs_logger.info(f"USD prices fetched for {donations_count} donations.") + # payouts + payouts = PotPayout.objects.filter(amount_paid_usd__isnull=True) + payouts_count = payouts.count() + jobs_logger.info(f"Fetching USD prices for {payouts_count} payouts...") + for payout in payouts: + try: + payout.fetch_usd_prices() + except Exception as e: + jobs_logger.error(f"Failed to fetch USD prices for payout {payout.id}: {e}") + + jobs_logger.info(f"USD prices fetched for {payouts_count} payouts.") + @shared_task def update_pot_statistics(): diff --git a/pots/models.py b/pots/models.py index cc0c98d..e17a59f 100644 --- a/pots/models.py +++ b/pots/models.py @@ -1,8 +1,15 @@ +from datetime import timedelta +from decimal import Decimal + +import requests +from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ from accounts.models import Account -from tokens.models import Token +from base.logging import logger +from base.utils import format_date +from tokens.models import Token, TokenHistoricalPrice class PotFactory(models.Model): @@ -440,6 +447,69 @@ class PotPayout(models.Model): help_text=_("Transaction hash."), ) + ### Fetches USD prices for the Donation record and saves USD totals + def fetch_usd_prices(self): + # first, see if there is a TokenHistoricalPrice within 1 day (or HISTORICAL_PRICE_QUERY_HOURS) of self.paid_at + token = self.token + time_window = timedelta(hours=settings.HISTORICAL_PRICE_QUERY_HOURS or 24) + token_prices = TokenHistoricalPrice.objects.filter( + token=token, + timestamp__gte=self.paid_at - time_window, + timestamp__lte=self.paid_at + time_window, + ) + existing_token_price = token_prices.first() + amount = token.format_price(self.amount) + if existing_token_price: + try: + price_usd = existing_token_price.price_usd + self.amount_paid_usd = amount * price_usd + self.save() + except Exception as e: + logger.error( + f"Failed to calculate and save USD prices for payout using existing TokenHistoricalPrice: {e}" + ) + else: + # no existing price within acceptable time period; fetch from coingecko + try: + logger.info( + "No existing price within acceptable time period; fetching historical price..." + ) + endpoint = f"{settings.COINGECKO_URL}/coins/{self.token.id.id}/history?date={format_date(self.paid_at)}&localization=false" + if settings.COINGECKO_API_KEY: + endpoint += f"&x_cg_pro_api_key={settings.COINGECKO_API_KEY}" + response = requests.get(endpoint) + logger.info(f"coingecko response: {response}") + if response.status_code == 429: + logger.warning("Coingecko rate limit exceeded") + price_data = response.json() + except Exception as e: + logger.warning(f"Failed to fetch coingecko price data: {e}") + logger.info(f"coingecko price data: {price_data}") + price_usd = ( + price_data.get("market_data", {}).get("current_price", {}).get("usd") + ) + logger.info(f"unit price: {price_usd}") + if price_usd: + try: + # convert price_usd to decimal + price_usd = Decimal(price_usd) + self.amount_paid_usd = amount * price_usd + self.save() + except Exception as e: + logger.error( + f"Failed to calculate and save USD prices using fetched price: {e}" + ) + try: + TokenHistoricalPrice.objects.create( + token=token, + price_usd=price_usd, + timestamp=self.paid_at, + ) + except Exception as e: + logger.warning( + f"Error creating TokenHistoricalPrice: {e} token: {token} price_usd: {price_usd}" + ) + class PotPayoutChallenge(models.Model): id = models.AutoField( From 18b47fcc757512656d46c8d26e2b2dc6b3c49d50 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 20 Jun 2024 17:03:06 -0400 Subject: [PATCH 199/454] add amount_paid_usd to payout admin --- pots/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pots/admin.py b/pots/admin.py index ace2d5a..54159b4 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -187,7 +187,7 @@ def application_applicant_id(self, obj): @admin.register(PotPayout) class PotPayoutAdmin(admin.ModelAdmin): - list_display = ("id", "pot", "recipient", "amount", "paid_at") + list_display = ("id", "pot", "recipient", "amount", "amount_paid_usd", "paid_at") search_fields = ("pot__id", "recipient__id") list_filter = ("paid_at",) From 935e1a10778926739ffdb11479e61a758570f0dc Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 20 Jun 2024 17:15:31 -0400 Subject: [PATCH 200/454] add donate_contract_config endpoint --- README.md | 6 ++++ api/urls.py | 7 +++++ donations/api.py | 65 ++++++++++++++++++++++++++++++++++++++++ donations/serializers.py | 14 ++++++++- 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 donations/api.py diff --git a/README.md b/README.md index 12ce198..5855b5b 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ - [✅ Get all lists: `GET /lists` (paginated)](#-get-all-lists-get-lists-paginated) - [✅ Get list by ID: `GET /lists/{LIST_ID}` (paginated)](#-get-list-by-id-get-listslist_id-paginated) - [✅ Get registrations for list: `GET /lists/{LIST_ID}/registrations` (paginated)](#-get-registrations-for-list-get-listslist_idregistrations-paginated) + - [Donate Contract Config endpoint](#donate-contract-config-endpoint) + - [✅ Get donate contract config: `GET /donate_contract_config`](#-get-donate-contract-config-get-donate_contract_config) - [`Donors` endpoints](#donors-endpoints) - [✅ Get all donors: `GET /donors` (paginated)](#-get-all-donors-get-donors-paginated) - [`Pots` endpoints](#pots-endpoints) @@ -155,6 +157,10 @@ enum PotApplicationStatus { Can specify status to filter by using `status` query param if desired, e.g. `status=Approved` +### Donate Contract Config endpoint + +#### ✅ Get donate contract config: `GET /donate_contract_config` + ### `Donors` endpoints #### ✅ Get all donors: `GET /donors` (paginated) diff --git a/api/urls.py b/api/urls.py index 84d8bef..6c25665 100644 --- a/api/urls.py +++ b/api/urls.py @@ -16,6 +16,7 @@ DonorsAPI, ) from base.api import StatsAPI +from donations.api import DonationContractConfigAPI from lists.api import ListDetailAPI, ListRegistrationsAPI, ListsListAPI from pots.api import ( PotApplicationsAPI, @@ -69,6 +70,12 @@ AccountPayoutsReceivedAPI.as_view(), name="accounts_api_by_id_payouts_received", ), + # donate contract config + path( + "v1/donate_contract_config", + DonationContractConfigAPI.as_view(), + name="donate_contract_config_api", + ), # donors path("v1/donors", DonorsAPI.as_view(), name="donors_api"), # lists diff --git a/donations/api.py b/donations/api.py new file mode 100644 index 0000000..6053c4d --- /dev/null +++ b/donations/api.py @@ -0,0 +1,65 @@ +import requests +from django.conf import settings +from django.utils.decorators import method_decorator +from django.views.decorators.cache import cache_page +from drf_spectacular.utils import ( + OpenApiExample, + OpenApiParameter, + OpenApiResponse, + extend_schema, +) +from rest_framework.pagination import LimitOffsetPagination +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.views import APIView + +from base.logging import logger + +from .serializers import DonationContractConfigSerializer + +DONATE_CONTRACT = "donate." + settings.POTLOCK_TLA + + +class DonationContractConfigAPI(APIView, LimitOffsetPagination): + + @extend_schema( + responses={ + 200: OpenApiResponse( + response=DonationContractConfigSerializer, + description=f"Returns config for {DONATE_CONTRACT}", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for donate contract config", + value={ + "owner": "potlock.near", + "protocol_fee_basis_points": 250, + "referral_fee_basis_points": 1500, + "protocol_fee_recipient_account": "impact.sputnik-dao.near", + }, + response_only=True, + ), + ], + ), + 500: OpenApiResponse(description="Internal server error"), + } + ) + @method_decorator(cache_page(60 * 5)) + def get(self, request: Request, *args, **kwargs): + url = f"{settings.FASTNEAR_RPC_URL}/account/{DONATE_CONTRACT}/view/get_config" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + data.pop("total_donations_amount") + data.pop("net_donations_amount") + data.pop("total_donations_count") + data.pop("total_protocol_fees") + data.pop("total_referrer_fees") + + return Response(data) + else: + logger.error( + f"Request for {DONATE_CONTRACT} config failed ({response.status_code}) with message: {response.text}" + ) + return Response({"message": response.text}, status=response.status_code) diff --git a/donations/serializers.py b/donations/serializers.py index 795dbae..f5c7087 100644 --- a/donations/serializers.py +++ b/donations/serializers.py @@ -1,4 +1,9 @@ -from rest_framework.serializers import ModelSerializer, SerializerMethodField +from rest_framework import serializers +from rest_framework.serializers import ( + ModelSerializer, + Serializer, + SerializerMethodField, +) from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer from pots.serializers import EXAMPLE_POT_ID, SIMPLE_POT_EXAMPLE, PotSerializer @@ -97,3 +102,10 @@ def get_chef(self, obj): "previous": None, "results": [SIMPLE_DONATION_EXAMPLE], } + + +class DonationContractConfigSerializer(Serializer): + owner = serializers.CharField() + protocol_fee_basis_points = serializers.IntegerField() + referral_fee_basis_points = serializers.IntegerField() + protocol_fee_recipient_account = serializers.CharField() From c9c9d77994a63ad9f87ae4ab89be45a7a73f8931 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:47:39 -0400 Subject: [PATCH 201/454] fix paginated response schemas --- accounts/api.py | 25 +++++++++++++++++-------- accounts/serializers.py | 7 +++++++ donations/serializers.py | 7 +++++++ lists/api.py | 6 ++++-- lists/serializers.py | 15 +++++++++++++++ pots/api.py | 25 ++++++++++++++++++------- pots/serializers.py | 24 ++++++++++++++++++++++++ 7 files changed, 92 insertions(+), 17 deletions(-) diff --git a/accounts/api.py b/accounts/api.py index 8a45463..5bc62ef 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -6,6 +6,7 @@ OpenApiExample, OpenApiParameter, OpenApiResponse, + OpenApiTypes, extend_schema, ) from rest_framework.pagination import LimitOffsetPagination @@ -15,12 +16,19 @@ from base.logging import logger from donations.models import Donation -from donations.serializers import PAGINATED_DONATION_EXAMPLE, DonationSerializer +from donations.serializers import ( + PAGINATED_DONATION_EXAMPLE, + DonationSerializer, + PaginatedDonationsResponseSerializer, +) from pots.models import Pot, PotApplication, PotApplicationStatus, PotPayout from pots.serializers import ( PAGINATED_PAYOUT_EXAMPLE, PAGINATED_POT_APPLICATION_EXAMPLE, PAGINATED_POT_EXAMPLE, + PaginatedPotApplicationsResponseSerializer, + PaginatedPotPayoutsResponseSerializer, + PaginatedPotsResponseSerializer, PotApplicationSerializer, PotPayoutSerializer, PotSerializer, @@ -31,6 +39,7 @@ PAGINATED_ACCOUNT_EXAMPLE, SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer, + PaginatedAccountsResponseSerializer, ) @@ -47,7 +56,7 @@ class DonorsAPI(APIView, LimitOffsetPagination): ], responses={ 200: OpenApiResponse( - response=AccountSerializer(many=True), + response=PaginatedAccountsResponseSerializer, description="Returns a paginated list of donor accounts", examples=[ OpenApiExample( @@ -83,7 +92,7 @@ class AccountsListAPI(APIView, LimitOffsetPagination): @extend_schema( responses={ 200: OpenApiResponse( - response=AccountSerializer(many=True), + response=PaginatedAccountsResponseSerializer, description="Returns a paginated list of accounts", examples=[ OpenApiExample( @@ -158,7 +167,7 @@ class AccountActivePotsAPI(APIView, LimitOffsetPagination): ], responses={ 200: OpenApiResponse( - response=PotSerializer(many=True), + response=PaginatedPotsResponseSerializer, description="Returns paginated active pots for the account", examples=[ OpenApiExample( @@ -207,7 +216,7 @@ class AccountPotApplicationsAPI(APIView, LimitOffsetPagination): ], responses={ 200: OpenApiResponse( - response=PotApplicationSerializer(many=True), + response=PaginatedPotApplicationsResponseSerializer, description="Returns paginated pot applications for the account", examples=[ OpenApiExample( @@ -255,7 +264,7 @@ class AccountDonationsReceivedAPI(APIView, LimitOffsetPagination): ], responses={ 200: OpenApiResponse( - response=DonationSerializer(many=True), + response=PaginatedDonationsResponseSerializer, description="Returns paginated donations received by the account", examples=[ OpenApiExample( @@ -295,7 +304,7 @@ class AccountDonationsSentAPI(APIView, LimitOffsetPagination): ], responses={ 200: OpenApiResponse( - response=DonationSerializer(many=True), + response=PaginatedDonationsResponseSerializer, description="Returns paginated donations sent by the account", examples=[ OpenApiExample( @@ -335,7 +344,7 @@ class AccountPayoutsReceivedAPI(APIView, LimitOffsetPagination): ], responses={ 200: OpenApiResponse( - response=DonationSerializer(many=True), + response=PaginatedPotPayoutsResponseSerializer, description="Returns paginated payouts received by the account", examples=[ OpenApiExample( diff --git a/accounts/serializers.py b/accounts/serializers.py index a990b48..e3fa2b0 100644 --- a/accounts/serializers.py +++ b/accounts/serializers.py @@ -113,3 +113,10 @@ class Meta: "previous": None, "results": [SIMPLE_ACCOUNT_EXAMPLE], } + + +class PaginatedAccountsResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = AccountSerializer(many=True) diff --git a/donations/serializers.py b/donations/serializers.py index f5c7087..ecc1e76 100644 --- a/donations/serializers.py +++ b/donations/serializers.py @@ -104,6 +104,13 @@ def get_chef(self, obj): } +class PaginatedDonationsResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = DonationSerializer(many=True) + + class DonationContractConfigSerializer(Serializer): owner = serializers.CharField() protocol_fee_basis_points = serializers.IntegerField() diff --git a/lists/api.py b/lists/api.py index bbe26e6..1f63f78 100644 --- a/lists/api.py +++ b/lists/api.py @@ -20,6 +20,8 @@ SIMPLE_LIST_EXAMPLE, ListRegistrationSerializer, ListSerializer, + PaginatedListRegistrationsResponseSerializer, + PaginatedListsResponseSerializer, ) @@ -28,7 +30,7 @@ class ListsListAPI(APIView, LimitOffsetPagination): @extend_schema( responses={ 200: OpenApiResponse( - response=ListSerializer(many=True), + response=PaginatedListsResponseSerializer, description="Returns a paginated list of lists", examples=[ OpenApiExample( @@ -96,7 +98,7 @@ class ListRegistrationsAPI(APIView, LimitOffsetPagination): ], responses={ 200: OpenApiResponse( - response=ListRegistrationSerializer(many=True), + response=PaginatedListRegistrationsResponseSerializer, description="Returns registrations for the list", examples=[ OpenApiExample( diff --git a/lists/serializers.py b/lists/serializers.py index 90d7dcb..56b1a97 100644 --- a/lists/serializers.py +++ b/lists/serializers.py @@ -1,3 +1,4 @@ +from rest_framework import serializers from rest_framework.serializers import ModelSerializer, SerializerMethodField from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer @@ -84,6 +85,13 @@ def get_registered_by(self, obj): } +class PaginatedListsResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = ListSerializer(many=True) + + SIMPLE_LIST_REGISTRATION_EXAMPLE = { "id": 10, "status": "Approved", @@ -103,3 +111,10 @@ def get_registered_by(self, obj): "previous": None, "results": [SIMPLE_LIST_REGISTRATION_EXAMPLE], } + + +class PaginatedListRegistrationsResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = ListRegistrationSerializer(many=True) diff --git a/pots/api.py b/pots/api.py index d6840fc..0fd624f 100644 --- a/pots/api.py +++ b/pots/api.py @@ -14,9 +14,17 @@ from rest_framework.views import APIView from accounts.models import Account -from accounts.serializers import PAGINATED_ACCOUNT_EXAMPLE, AccountSerializer +from accounts.serializers import ( + PAGINATED_ACCOUNT_EXAMPLE, + AccountSerializer, + PaginatedAccountsResponseSerializer, +) from donations.models import Donation -from donations.serializers import PAGINATED_DONATION_EXAMPLE, DonationSerializer +from donations.serializers import ( + PAGINATED_DONATION_EXAMPLE, + DonationSerializer, + PaginatedDonationsResponseSerializer, +) from .models import Pot, PotApplication, PotApplicationStatus from .serializers import ( @@ -24,6 +32,9 @@ PAGINATED_POT_APPLICATION_EXAMPLE, PAGINATED_POT_EXAMPLE, SIMPLE_POT_EXAMPLE, + PaginatedPotApplicationsResponseSerializer, + PaginatedPotPayoutsResponseSerializer, + PaginatedPotsResponseSerializer, PotApplicationSerializer, PotPayoutSerializer, PotSerializer, @@ -35,7 +46,7 @@ class PotsListAPI(APIView, LimitOffsetPagination): @extend_schema( responses={ 200: OpenApiResponse( - response=PotSerializer(many=True), + response=PaginatedPotsResponseSerializer, description="Returns a paginated list of pots", examples=[ OpenApiExample( @@ -99,7 +110,7 @@ class PotApplicationsAPI(APIView, LimitOffsetPagination): ], responses={ 200: OpenApiResponse( - response=PotApplicationSerializer(many=True), + response=PaginatedPotApplicationsResponseSerializer, description="Returns applications for the pot", examples=[ OpenApiExample( @@ -136,7 +147,7 @@ class PotDonationsAPI(APIView, LimitOffsetPagination): ], responses={ 200: OpenApiResponse( - response=DonationSerializer(many=True), + response=PaginatedDonationsResponseSerializer, description="Returns donations for the pot", examples=[ OpenApiExample( @@ -173,7 +184,7 @@ class PotSponsorsAPI(APIView, LimitOffsetPagination): ], responses={ 200: OpenApiResponse( - response=AccountSerializer(many=True), + response=PaginatedAccountsResponseSerializer, description="Returns sponsors for the pot", examples=[ OpenApiExample( @@ -215,7 +226,7 @@ class PotPayoutsAPI(APIView, LimitOffsetPagination): ], responses={ 200: OpenApiResponse( - response=PotPayoutSerializer(many=True), + response=PaginatedPotPayoutsResponseSerializer, description="Returns payouts for the pot", examples=[ OpenApiExample( diff --git a/pots/serializers.py b/pots/serializers.py index b66f0f9..c8f52e4 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -1,3 +1,4 @@ +from rest_framework import serializers from rest_framework.serializers import ModelSerializer, SerializerMethodField from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer @@ -174,6 +175,14 @@ def get_token(self, obj): "results": [SIMPLE_POT_EXAMPLE], } + +class PaginatedPotsResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = PotSerializer(many=True) + + SIMPLE_POT_APPLICATION_EXAMPLE = { "id": 2, "message": "Hi, I'm a great project and I'd like to apply for this pot.", @@ -192,6 +201,14 @@ def get_token(self, obj): "results": [SIMPLE_POT_APPLICATION_EXAMPLE], } + +class PaginatedPotApplicationsResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = PotApplicationSerializer(many=True) + + SIMPLE_PAYOUT_EXAMPLE = { "id": 4, "amount": "1000000000000000000000000", @@ -209,3 +226,10 @@ def get_token(self, obj): "previous": None, "results": [SIMPLE_PAYOUT_EXAMPLE], } + + +class PaginatedPotPayoutsResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = PotPayoutSerializer(many=True) From 6c21a4b7f35980eb8613acb6c254abc5963b4fcb Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:54:49 -0400 Subject: [PATCH 202/454] fix related records in OpenAPI schema --- donations/serializers.py | 34 ++++++----------------------- lists/serializers.py | 27 ++++++++--------------- pots/serializers.py | 46 ++++++++-------------------------------- 3 files changed, 24 insertions(+), 83 deletions(-) diff --git a/donations/serializers.py b/donations/serializers.py index ecc1e76..bc5d122 100644 --- a/donations/serializers.py +++ b/donations/serializers.py @@ -41,34 +41,12 @@ class Meta: "chef", ] - donor = SerializerMethodField() - token = SerializerMethodField() - pot = SerializerMethodField() - recipient = SerializerMethodField() - referrer = SerializerMethodField() - chef = SerializerMethodField() - - def get_donor(self, obj): - return AccountSerializer(obj.donor).data - - def get_token(self, obj): - return TokenSerializer(obj.token).data - - def get_pot(self, obj): - if obj.pot: - return PotSerializer(obj.pot).data - - def get_recipient(self, obj): - if obj.recipient: - return AccountSerializer(obj.recipient).data - - def get_referrer(self, obj): - if obj.referrer: - return AccountSerializer(obj.referrer).data - - def get_chef(self, obj): - if obj.chef: - return AccountSerializer(obj.chef).data + donor = AccountSerializer() + token = TokenSerializer() + pot = PotSerializer() + recipient = AccountSerializer() + referrer = AccountSerializer() + chef = AccountSerializer() SIMPLE_DONATION_EXAMPLE = { diff --git a/lists/serializers.py b/lists/serializers.py index 56b1a97..b223315 100644 --- a/lists/serializers.py +++ b/lists/serializers.py @@ -23,14 +23,14 @@ class Meta: "updated_at", ] - owner = SerializerMethodField() - admins = SerializerMethodField() + owner = AccountSerializer() + admins = AccountSerializer(many=True) - def get_owner(self, obj): - return AccountSerializer(obj.owner).data + # def get_owner(self, obj): + # return AccountSerializer(obj.owner).data - def get_admins(self, obj): - return AccountSerializer(obj.admins.all(), many=True).data + # def get_admins(self, obj): + # return AccountSerializer(obj.admins.all(), many=True).data class ListRegistrationSerializer(ModelSerializer): @@ -49,18 +49,9 @@ class Meta: "tx_hash", ] - list = SerializerMethodField() - registrant = SerializerMethodField() - registered_by = SerializerMethodField() - - def get_list(self, obj): - return ListSerializer(obj.list).data - - def get_registrant(self, obj): - return AccountSerializer(obj.registrant).data - - def get_registered_by(self, obj): - return AccountSerializer(obj.registered_by).data + list = ListSerializer() + registrant = AccountSerializer() + registered_by = AccountSerializer() SIMPLE_LIST_EXAMPLE = { diff --git a/pots/serializers.py b/pots/serializers.py index c8f52e4..b0c6581 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -52,23 +52,10 @@ class Meta: "protocol_config_provider", ] - deployer = SerializerMethodField() - owner = SerializerMethodField() - admins = SerializerMethodField() - chef = SerializerMethodField() - - def get_deployer(self, obj): - return AccountSerializer(obj.deployer).data - - def get_owner(self, obj): - return AccountSerializer(obj.owner).data - - def get_admins(self, obj): - return AccountSerializer(obj.admins.all(), many=True).data - - def get_chef(self, obj): - if obj.chef: - return AccountSerializer(obj.chef).data + deployer = AccountSerializer() + owner = AccountSerializer() + admins = AccountSerializer(many=True) + chef = AccountSerializer() class PotApplicationSerializer(ModelSerializer): @@ -86,14 +73,8 @@ class Meta: "tx_hash", ] - pot = SerializerMethodField() - applicant = SerializerMethodField() - - def get_pot(self, obj): - return PotSerializer(obj.pot).data - - def get_applicant(self, obj): - return AccountSerializer(obj.applicant).data + pot = PotSerializer() + applicant = AccountSerializer() class PotPayoutSerializer(ModelSerializer): @@ -110,18 +91,9 @@ class Meta: "tx_hash", ] - pot = SerializerMethodField() - recipient = SerializerMethodField() - token = SerializerMethodField() - - def get_pot(self, obj): - return PotSerializer(obj.pot).data - - def get_recipient(self, obj): - return AccountSerializer(obj.recipient).data - - def get_token(self, obj): - return TokenSerializer(obj.token).data + pot = PotSerializer() + recipient = AccountSerializer() + token = TokenSerializer() EXAMPLE_POT_ID = "some-pot.v1.potfactory.potlock.near" From ed33537694b9ca3c9c3ae380714d42119dd9a827 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:09:30 -0400 Subject: [PATCH 203/454] add random_registration endpoint --- README.md | 5 ++++ accounts/api.py | 6 ++++ api/urls.py | 12 +++++++- lists/api.py | 66 +++++++++++++++++++++++++++++++++++++++++++- lists/serializers.py | 4 +-- 5 files changed, 89 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5855b5b..91fc06d 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ - [✅ Get all lists: `GET /lists` (paginated)](#-get-all-lists-get-lists-paginated) - [✅ Get list by ID: `GET /lists/{LIST_ID}` (paginated)](#-get-list-by-id-get-listslist_id-paginated) - [✅ Get registrations for list: `GET /lists/{LIST_ID}/registrations` (paginated)](#-get-registrations-for-list-get-listslist_idregistrations-paginated) + - [✅ Get random registration for list: `GET /lists/{LIST_ID}/random_registration`](#-get-random-registration-for-list-get-listslist_idrandom_registration) - [Donate Contract Config endpoint](#donate-contract-config-endpoint) - [✅ Get donate contract config: `GET /donate_contract_config`](#-get-donate-contract-config-get-donate_contract_config) - [`Donors` endpoints](#donors-endpoints) @@ -157,6 +158,10 @@ enum PotApplicationStatus { Can specify status to filter by using `status` query param if desired, e.g. `status=Approved` +#### ✅ Get random registration for list: `GET /lists/{LIST_ID}/random_registration` + +Can specify status to filter by using `status` query param if desired, e.g. `status=Approved` + ### Donate Contract Config endpoint #### ✅ Get donate contract config: `GET /donate_contract_config` diff --git a/accounts/api.py b/accounts/api.py index 5bc62ef..fe95982 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -213,6 +213,12 @@ class AccountPotApplicationsAPI(APIView, LimitOffsetPagination): @extend_schema( parameters=[ OpenApiParameter("account_id", str, OpenApiParameter.PATH), + OpenApiParameter( + "status", + str, + OpenApiParameter.QUERY, + description="Filter pot applications by status", + ), ], responses={ 200: OpenApiResponse( diff --git a/api/urls.py b/api/urls.py index 6c25665..ea956f6 100644 --- a/api/urls.py +++ b/api/urls.py @@ -17,7 +17,12 @@ ) from base.api import StatsAPI from donations.api import DonationContractConfigAPI -from lists.api import ListDetailAPI, ListRegistrationsAPI, ListsListAPI +from lists.api import ( + ListDetailAPI, + ListRandomRegistrationAPI, + ListRegistrationsAPI, + ListsListAPI, +) from pots.api import ( PotApplicationsAPI, PotDetailAPI, @@ -86,6 +91,11 @@ ListRegistrationsAPI.as_view(), name="lists_api_by_id_registrations", ), + path( + "v1/lists//random_registration", + ListRandomRegistrationAPI.as_view(), + name="lists_api_by_id_registrations", + ), # pots path("v1/pots", PotsListAPI.as_view(), name="pots_api"), path("v1/pots//", PotDetailAPI.as_view(), name="pots_api_by_id"), diff --git a/lists/api.py b/lists/api.py index 1f63f78..4c59126 100644 --- a/lists/api.py +++ b/lists/api.py @@ -1,3 +1,5 @@ +import random + from django.db.models import Exists, OuterRef from django.utils import timezone from django.utils.decorators import method_decorator @@ -13,11 +15,12 @@ from rest_framework.response import Response from rest_framework.views import APIView -from .models import List +from .models import List, ListRegistrationStatus from .serializers import ( PAGINATED_LIST_EXAMPLE, PAGINATED_LIST_REGISTRATION_EXAMPLE, SIMPLE_LIST_EXAMPLE, + SIMPLE_LIST_REGISTRATION_EXAMPLE, ListRegistrationSerializer, ListSerializer, PaginatedListRegistrationsResponseSerializer, @@ -128,3 +131,64 @@ def get(self, request: Request, *args, **kwargs): results = self.paginate_queryset(registrations, request, view=self) serializer = ListRegistrationSerializer(results, many=True) return self.get_paginated_response(serializer.data) + + +class ListRandomRegistrationAPI(APIView): + + @extend_schema( + parameters=[ + OpenApiParameter("list_id", int, OpenApiParameter.PATH), + OpenApiParameter( + "status", + str, + OpenApiParameter.QUERY, + description="Filter registrations by status", + ), + ], + responses={ + 200: OpenApiResponse( + response=ListRegistrationSerializer, + description="Returns a random registration for the list", + examples=[ + OpenApiExample( + "example-1", + summary="Simple registration example", + description="Example response for list registration", + value=SIMPLE_LIST_REGISTRATION_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="List not found"), + 500: OpenApiResponse(description="Internal server error"), + }, + ) + def get(self, request: Request, *args, **kwargs): + list_id = kwargs.get("list_id") + try: + list_obj = List.objects.get(id=list_id) + except List.DoesNotExist: + return Response( + {"message": f"List with ID {list_id} not found."}, status=404 + ) + + registrations = list_obj.registrations.all() + status_param = request.query_params.get("status") + if status_param: + if status_param not in ListRegistrationStatus.values: + return Response( + {"message": f"Invalid status value: {status_param}"}, status=400 + ) + registrations = registrations.filter(status=status_param) + + # Get a random registration + registrations_list = list(registrations) + if not registrations_list: + return Response( + {"message": "No registrations found for the given criteria."}, + status=404, + ) + + registration = random.choice(registrations_list) + serializer = ListRegistrationSerializer(registration) + return Response(serializer.data) diff --git a/lists/serializers.py b/lists/serializers.py index b223315..b9a495a 100644 --- a/lists/serializers.py +++ b/lists/serializers.py @@ -92,8 +92,8 @@ class PaginatedListsResponseSerializer(serializers.Serializer): "admin_notes": "This is a great project that I want on my list.", "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", "list": SIMPLE_LIST_EXAMPLE, - "registrant": "project.near", - "registered_by": "project-admin.near", + "registrant": SIMPLE_ACCOUNT_EXAMPLE, + "registered_by": SIMPLE_ACCOUNT_EXAMPLE, } PAGINATED_LIST_REGISTRATION_EXAMPLE = { From 8d6c56291bb896d6cca1f4728a407eda6b405661 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 24 Jun 2024 13:53:22 -0400 Subject: [PATCH 204/454] fix price_data reference error --- donations/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/donations/models.py b/donations/models.py index a97573c..bb756f9 100644 --- a/donations/models.py +++ b/donations/models.py @@ -240,6 +240,7 @@ def fetch_usd_prices(self): # TODO: update totals for relevant accounts else: # no existing price within acceptable time period; fetch from coingecko + price_data = {} try: logger.info( "No existing price within acceptable time period; fetching historical price..." From 3f54b31fc89dac7c83a954aee5c0b4e2064393f8 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:09:11 -0400 Subject: [PATCH 205/454] add debug log for payout processing paid_at: None issue --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index ec4f077..387cade 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -552,7 +552,7 @@ async def handle_transfer_payout( try: data = data["payout"] - logger.info(f"fulfill payout data: {data}, {receiver_id}") + logger.info(f"fulfill payout data: {data}, {receiver_id}, {created_at}") payout = { "recipient_id": data["project_id"], "amount": data["amount"], From d2a0aad61d9c24b1ce28aed36c856ec16e5ddb76 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:19:36 -0400 Subject: [PATCH 206/454] fix project_id error in new pot application --- indexer_app/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 387cade..823fd10 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -354,9 +354,10 @@ async def handle_pot_application( # Update or create the account project, _ = await Account.objects.aget_or_create( - id=data["project_id"], + id=appl_data["project_id"], ) + # TODO: wouldn't this be the same as the project_id? should inspect signer, _ = await Account.objects.aget_or_create( id=signer_id, ) From 974ace3bb5e1b4833c8010e863c63142e354ea22 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:35:57 -0400 Subject: [PATCH 207/454] add debug logs for new list registrations --- indexer_app/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 823fd10..f085bfb 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -259,6 +259,7 @@ async def handle_new_list_registration( project_list = [] insert_data = [] for dt in reg_data: + logger.info(f"dt: {dt}") project_list.append({"id": dt["registrant_id"]}) insert_data.append( { @@ -274,6 +275,7 @@ async def handle_new_list_registration( "tx_hash": receipt.receipt_id, } ) + logger.info(f"insert_data: {insert_data}") try: await Account.objects.abulk_create( From d0fa8b00a994c934a50badf5c126438cc3dcaae8 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:50:26 -0400 Subject: [PATCH 208/454] attempt to resolve None error on new Donation --- indexer_app/handler.py | 18 ++++++++++++++++-- indexer_app/utils.py | 7 +++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 48098e8..04ed609 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -220,14 +220,28 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess if not result: logger.info("No result found. Skipping...") break + decoded_success_val = base64.b64decode(result).decode( + "utf-8" + ) + logger.info(f"Decoded success value: {decoded_success_val}") + if ( + decoded_success_val == "null" + ): # weird edge case that sometimes occurs where the response is a literal string "null" + logger.info("Result is null. Skipping...") + try: + donation_data = json.loads(decoded_success_val) + except json.JSONDecodeError: + logger.error( + f"Error decoding result to JSON: {decoded_success_val}" + ) + donation_data = {} await handle_new_donation( args_dict, receiver_id, signer_id, donation_type, receipt, - result, - # log_data, + donation_data, ) break diff --git a/indexer_app/utils.py b/indexer_app/utils.py index f085bfb..02a0f4d 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -671,11 +671,10 @@ async def handle_new_donation( signer_id: str, donation_type: str, # "direct" or "pot" receipt_obj: Receipt, - result: dict, # Donation object (note that these vary between direct and pot donations - see examples of each in ./examples.txt) + donation_data: dict, # Donation object (note that these vary between direct and pot donations - see examples of each in ./examples.txt) ): - logger.info(f"handle_new_donation data: {data}, {receiver_id}") - donation_data = json.loads(base64.b64decode(result).decode("utf-8")) - logger.info(f"decoded result: {donation_data}") + logger.info(f"handle_new_donation args data: {data}, {receiver_id}") + logger.info(f"donation data: {donation_data}") if "net_amount" in donation_data: net_amount = int(donation_data["net_amount"]) From b0258bb4ecf4571dc062d8c0ab034ca4003f4f7d Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:52:41 -0400 Subject: [PATCH 209/454] fix error Failed to create/update donation: 'pot' --- indexer_app/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 02a0f4d..785c161 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -753,6 +753,7 @@ async def handle_new_donation( logger.info(f"inserting {donation_type} donation") default_data = { "donor": donor, + "pot": None, "total_amount": total_amount, "total_amount_usd": None, # USD amounts will be added later "net_amount_usd": None, From 86699015beb6dffeb36a3f7201987ca5e35134ef Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 24 Jun 2024 15:16:48 -0400 Subject: [PATCH 210/454] update populatedata command --- .../management/commands/populatedata.py | 805 +++++++++--------- 1 file changed, 405 insertions(+), 400 deletions(-) diff --git a/indexer_app/management/commands/populatedata.py b/indexer_app/management/commands/populatedata.py index 6a4f3c9..ac7128b 100644 --- a/indexer_app/management/commands/populatedata.py +++ b/indexer_app/management/commands/populatedata.py @@ -24,93 +24,93 @@ class Command(BaseCommand): help = "Pull donations data from contract & populate donations table." def handle(self, *args, **options): - # # lists - # LISTS_CONTRACT_ID = "lists.potlock.near" - # url = f"https://rpc.web4.near.page/account/{LISTS_CONTRACT_ID}/view/get_lists" - # lists = requests.get(url) - # if lists.status_code != 200: - # print( - # f"Request for lists data failed ({lists.status_code}) with message: {lists.text}" - # ) - # return - # lists = lists.json() - # print("lists: ", lists) - # print(f"adding {len(lists)} direct lists") - # for l in lists: - # list_id = l["id"] - # owner, _ = Account.objects.get_or_create(id=l["owner"]) - # list_defaults = { - # "on_chain_id": list_id, - # "owner": owner, - # "name": l["name"], - # "description": l["description"], - # "cover_image_url": l["cover_image_url"], - # "admin_only_registrations": l["admin_only_registrations"], - # "default_registration_status": l["default_registration_status"], - # "created_at": datetime.fromtimestamp(l["created_at"] / 1000), - # "updated_at": datetime.fromtimestamp(l["updated_at"] / 1000), - # } - # list, created = List.objects.update_or_create( - # on_chain_id=list_id, defaults=list_defaults - # ) - # print("created list: ", list.on_chain_id, created) - # # add admins - # for admin in l["admins"]: - # admin_obj, _ = Account.objects.get_or_create(id=admin) - # list.admins.add(admin_obj) - # # get registrations in pages - # page = 0 - # limit = 300 - # while True: - # time.sleep(1) - # url = f"https://rpc.web4.near.page/account/{LISTS_CONTRACT_ID}/view/get_registrations_for_list" - # params = { - # "list_id.json": list_id, - # "from_index.json": page * limit, - # "limit.json": limit, - # } - # registrations = requests.get(url, params=params) - # if registrations.status_code != 200: - # print( - # f"Request for registrations data failed ({registrations.status_code}) with message: {registrations.text}" - # ) - # return - # registrations = registrations.json() - # print("registrations: ", registrations) - # print(f"adding {len(registrations)} registrations") - # for reg in registrations: - # registrant, _ = Account.objects.get_or_create( - # id=reg["registrant_id"] - # ) - # registrar = registrant - # if "registered_by" in reg: - # registrar, _ = Account.objects.get_or_create( - # id=reg["registered_by"] - # ) - # registration_defaults = { - # "list": list, - # "registrant": registrant, - # "registered_by": registrar, - # "status": reg["status"], - # "submitted_at": datetime.fromtimestamp( - # reg["submitted_ms"] / 1000 - # ), - # "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), - # "registrant_notes": reg["registrant_notes"], - # "admin_notes": reg["admin_notes"], - # "tx_hash": None, - # } - # registration, created = ListRegistration.objects.update_or_create( - # registrant=registrant, - # list=list, - # defaults=registration_defaults, - # ) - # print("created registration: ", registration.id, created) - # if len(registrations) == limit: - # page += 1 - # else: - # break - # # TODO: get upvotes (none currently so don't have to worry about it at this point) + # lists + LISTS_CONTRACT_ID = "lists.potlock.near" + url = f"https://rpc.web4.near.page/account/{LISTS_CONTRACT_ID}/view/get_lists" + lists = requests.get(url) + if lists.status_code != 200: + print( + f"Request for lists data failed ({lists.status_code}) with message: {lists.text}" + ) + return + lists = lists.json() + print("lists: ", lists) + print(f"adding {len(lists)} direct lists") + for l in lists: + list_id = l["id"] + owner, _ = Account.objects.get_or_create(id=l["owner"]) + list_defaults = { + "on_chain_id": list_id, + "owner": owner, + "name": l["name"], + "description": l["description"], + "cover_image_url": l["cover_image_url"], + "admin_only_registrations": l["admin_only_registrations"], + "default_registration_status": l["default_registration_status"], + "created_at": datetime.fromtimestamp(l["created_at"] / 1000), + "updated_at": datetime.fromtimestamp(l["updated_at"] / 1000), + } + list, created = List.objects.update_or_create( + on_chain_id=list_id, defaults=list_defaults + ) + print("created list: ", list.on_chain_id, created) + # add admins + for admin in l["admins"]: + admin_obj, _ = Account.objects.get_or_create(id=admin) + list.admins.add(admin_obj) + # get registrations in pages + page = 0 + limit = 300 + while True: + time.sleep(1) + url = f"https://rpc.web4.near.page/account/{LISTS_CONTRACT_ID}/view/get_registrations_for_list" + params = { + "list_id.json": list_id, + "from_index.json": page * limit, + "limit.json": limit, + } + registrations = requests.get(url, params=params) + if registrations.status_code != 200: + print( + f"Request for registrations data failed ({registrations.status_code}) with message: {registrations.text}" + ) + return + registrations = registrations.json() + print("registrations: ", registrations) + print(f"adding {len(registrations)} registrations") + for reg in registrations: + registrant, _ = Account.objects.get_or_create( + id=reg["registrant_id"] + ) + registrar = registrant + if "registered_by" in reg: + registrar, _ = Account.objects.get_or_create( + id=reg["registered_by"] + ) + registration_defaults = { + "list": list, + "registrant": registrant, + "registered_by": registrar, + "status": reg["status"], + "submitted_at": datetime.fromtimestamp( + reg["submitted_ms"] / 1000 + ), + "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), + "registrant_notes": reg["registrant_notes"], + "admin_notes": reg["admin_notes"], + "tx_hash": None, + } + registration, created = ListRegistration.objects.update_or_create( + registrant=registrant, + list=list, + defaults=registration_defaults, + ) + print("created registration: ", registration.id, created) + if len(registrations) == limit: + page += 1 + else: + break + # TODO: get upvotes (none currently so don't have to worry about it at this point) # direct donations # get direct donations in pages DONATE_CONTRACT_ID = "donate.potlock.near" @@ -213,316 +213,321 @@ def handle(self, *args, **options): page += 1 else: break - # # pot factory - # POTFACTORY_ID = "v1.potfactory.potlock.near" - # # pots - # near_acct, _ = Account.objects.get_or_create(id="near") - # url = f"{settings.FASTNEAR_RPC_URL}/account/{POTFACTORY_ID}/view/get_pots" - # response = requests.get(url) - # if response.status_code != 200: - # print( - # f"Request for donations data failed ({response.status_code}) with message: {response.text}" - # ) - # return - # pots = response.json() - # print("pots data; ", pots) - # for pot in pots: - # # if pot["id"] != "marketing.v1.potfactory.potlock.near": - # # continue # TODO: REMOVE THIS - # print("sleeping for 1 second") - # time.sleep(1) - # pot_id = pot["id"] - # deployed_by = pot["deployed_by"] - # deployed_at_ms = pot["deployed_at_ms"] - # print("handling pot: ", pot_id) - # # fetch config for pot - # url = f"{settings.FASTNEAR_RPC_URL}/account/{pot_id}/view/get_config" - # response = requests.get(url) - # if response.status_code != 200: - # print( - # f"Request for pot config failed ({response.status_code}) with message: {response.text}" - # ) - # return - # config = response.json() - # print("config data; ", config) - # url = f"{settings.FASTNEAR_RPC_URL}/account/{pot_id}/view/get_contract_source_metadata" - # response = requests.get(url) - # if response.status_code != 200: - # print( - # f"Request for pot source metadata failed ({response.status_code}) with message: {response.text}" - # ) - # return - # source_metadata = response.json() - # print("source metadata; ", source_metadata) - # # add or update pot - # pot_acct, _ = Account.objects.get_or_create(id=pot_id) - # owner, _ = Account.objects.get_or_create(id=config["owner"]) - # if config.get("chef"): - # chef, _ = Account.objects.get_or_create(id=config["chef"]) - # pot_defaults = { - # "pot_factory_id": POTFACTORY_ID, - # "owner_id": config["owner"], - # "chef_id": config["chef"], - # "name": config["pot_name"], - # "description": config["pot_description"], - # "max_approved_applicants": config["max_projects"], - # "base_currency": config["base_currency"], - # "application_start": datetime.fromtimestamp( - # config["application_start_ms"] / 1000 - # ), - # "application_end": datetime.fromtimestamp( - # config["application_end_ms"] / 1000 - # ), - # "matching_round_start": datetime.fromtimestamp( - # config["public_round_start_ms"] / 1000 - # ), - # "matching_round_end": datetime.fromtimestamp( - # config["public_round_end_ms"] / 1000 - # ), - # "deployer_id": config["deployed_by"], - # "deployed_at": datetime.fromtimestamp(deployed_at_ms / 1000), - # "source_metadata": source_metadata, - # "registry_provider": config["registry_provider"], - # "min_matching_pool_donation_amount": config[ - # "min_matching_pool_donation_amount" - # ], - # "sybil_wrapper_provider": config["sybil_wrapper_provider"], - # "custom_sybil_checks": config["custom_sybil_checks"], - # "custom_min_threshold_score": config["custom_min_threshold_score"], - # "referral_fee_matching_pool_basis_points": config[ - # "referral_fee_matching_pool_basis_points" - # ], - # "referral_fee_public_round_basis_points": config[ - # "referral_fee_public_round_basis_points" - # ], - # "chef_fee_basis_points": config["chef_fee_basis_points"], - # "total_matching_pool": "0", - # "matching_pool_balance": "0", - # "matching_pool_donations_count": 0, - # "total_public_donations": "0", - # "public_donations_count": 0, - # "cooldown_period_ms": None, - # "cooldown_end": ( - # None - # if not config["cooldown_end_ms"] - # else datetime.fromtimestamp(config["cooldown_end_ms"] / 1000) - # ), - # "all_paid_out": config["all_paid_out"], - # "protocol_config_provider": config["protocol_config_provider"], - # } - # pot, created = Pot.objects.update_or_create( - # id=pot_acct, defaults=pot_defaults - # ) - # print("pot created? ", created) - # print("adding admins") - # for admin in config["admins"]: - # admin_obj, _ = Account.objects.get_or_create(id=admin) - # pot.admins.add(admin_obj) - # # add activity - # activity_defaults = { - # "signer_id": owner.id, - # "receiver_id": POTFACTORY_ID, - # "timestamp": datetime.fromtimestamp(deployed_at_ms / 1000), - # "tx_hash": None, - # } - # activity, _ = Activity.objects.update_or_create( - # type="Deploy_Pot", action_result=None, defaults=activity_defaults - # ) - # # get applications in pages - # page = 0 - # limit = 300 - # while True: - # time.sleep(1) - # url = ( - # f"https://rpc.web4.near.page/account/{pot_id}/view/get_applications" - # ) - # params = {"from_index.json": page * limit, "limit.json": limit} - # applications = requests.get(url, params=params) - # if applications.status_code != 200: - # print( - # f"Request for applications data failed ({applications.status_code}) with message: {applications.text}" - # ) - # return - # applications = applications.json() - # print(f"adding {len(applications)} applications") - # for appl in applications: - # applicant, _ = Account.objects.get_or_create(id=appl["project_id"]) - # application_defaults = { - # "applicant": applicant, - # "pot": pot, - # "message": appl["message"], - # "status": appl["status"], - # "submitted_at": datetime.fromtimestamp( - # appl["submitted_at"] / 1000 - # ), - # "updated_at": ( - # None - # if not appl["updated_at"] - # else datetime.fromtimestamp(appl["updated_at"] / 1000) - # ), - # } - # application, _ = PotApplication.objects.update_or_create( - # pot=pot, applicant=applicant, defaults=application_defaults - # ) - # # if status != Pending, add PotApplicationReview also, default to pot owner - # if application.status != "Pending": - # review_defaults = { - # "application": application, - # "reviewer": owner, - # "notes": appl["review_notes"], - # "status": application.status, - # "reviewed_at": application.updated_at, - # "tx_hash": None, - # } - # review, _ = PotApplicationReview.objects.update_or_create( - # application=application, - # reviewer=owner, - # defaults=review_defaults, - # ) - # if len(applications) == limit: - # page += 1 - # else: - # break - # # get pot donations in pages - # page = 0 - # limit = 300 - # while True: - # time.sleep(1) - # url = f"https://rpc.web4.near.page/account/{pot_id}/view/get_donations" - # params = {"from_index.json": page * limit, "limit.json": limit} - # donations = requests.get(url, params=params) - # if donations.status_code != 200: - # print( - # f"Request for donations data failed ({donations.status_code}) with message: {donations.text}" - # ) - # return - # donations = donations.json() - # print(f"adding {len(donations)} donations") - # for donation in donations: - # donor, _ = Account.objects.get_or_create(id=donation["donor_id"]) - # project = None - # referrer = None - # chef = None - # if donation.get("project_id"): - # project, _ = Account.objects.get_or_create( - # id=donation["project_id"] - # ) - # if donation.get("referrer_id"): - # referrer, _ = Account.objects.get_or_create( - # id=donation["referrer_id"] - # ) - # if donation.get("chef_id"): - # chef, _ = Account.objects.get_or_create(id=donation["chef_id"]) - # donation_defaults = { - # "donor": donor, - # "total_amount": donation["total_amount"], - # "total_amount_usd": None, # USD amounts will be added later - # "net_amount_usd": None, - # "net_amount": donation["net_amount"], - # "ft_id": "near", - # "message": donation["message"], - # "donated_at": datetime.fromtimestamp( - # donation["donated_at"] / 1000 - # ), - # "matching_pool": donation["matching_pool"], - # "recipient": project, - # "protocol_fee": donation["protocol_fee"], - # "referrer": referrer, - # "referrer_fee": donation["referrer_fee"], - # "chef": chef, - # "chef_fee": donation["chef_fee"], - # "tx_hash": None, - # } - # donation, _ = Donation.objects.update_or_create( - # on_chain_id=donation["id"], pot=pot, defaults=donation_defaults - # ) - # print("added donation: ", donation.on_chain_id) - # # # add donation activity - # # activity_type = ( - # # "Donate_Pot_Matching_Pool" - # # if donation.matching_pool - # # else "Donate_Pot_Public" - # # ) - # # defaults = { - # # "signer_id": donor.id, - # # "receiver_id": pot_id, - # # "timestamp": donation.donated_at, - # # "tx_hash": None, - # # } - # # activity, _ = Activity.objects.update_or_create( - # # type=activity_type, - # # action_result=None, - # # defaults=defaults, - # # ) - # if len(donations) == limit: - # page += 1 - # else: - # break - # if "payouts" in config: - # for payout in config["payouts"]: - # if not payout["paid_at"]: - # continue - # recipient, _ = Account.objects.get_or_create( - # id=payout["project_id"] - # ) - # payout_defaults = { - # "pot": pot, - # "recipient": recipient, - # "amount": payout["amount"], - # "amount_paid_usd": None, # TODO: add this in task - # "ft_id": "near", - # "paid_at": datetime.fromtimestamp(payout["paid_at"] / 1000), - # "tx_hash": None, - # } - # payout, _ = PotPayout.objects.update_or_create( - # pot=pot, recipient=recipient, defaults=payout_defaults - # ) - # # get pot challenges & admin responses - # page = 0 - # limit = 300 - # while True: - # time.sleep(1) - # url = f"https://rpc.web4.near.page/account/{pot_id}/view/get_payouts_challenges" - # params = {"from_index.json": page * limit, "limit.json": limit} - # challenges = requests.get(url, params=params) - # if challenges.status_code != 200: - # print( - # f"Request for challenges data failed ({challenges.status_code}) with message: {challenges.text}" - # ) - # return - # challenges = challenges.json() - # print(f"adding {len(challenges)} challenges") - # for c in challenges: - # challenger, _ = Account.objects.get_or_create(id=c["challenger_id"]) - # challenge_defaults = { - # "challenger": challenger, - # "pot": pot, - # "message": c["reason"], - # "created_at": datetime.fromtimestamp(c["created_at"] / 1000), - # } - # challenge, _ = PotPayoutChallenge.objects.update_or_create( - # pot=pot, challenger=challenger, defaults=challenge_defaults - # ) - # # if status != Pending, add PotApplicationReview also, default to pot owner - # if c["admin_notes"] or c["resolved"]: - # response_defaults = { - # "challenger": challenger, - # "pot": pot, - # "admin": owner, # don't actually know who did it, just using owner for this backfilling - # "created_at": datetime.fromtimestamp( - # c["created_at"] / 1000 - # ), # not exactly correct but we don't know currently - # "message": c["admin_notes"], - # "resolved": c["resolved"], - # } - # review, _ = ( - # PotPayoutChallengeAdminResponse.objects.update_or_create( - # challenger=challenger, - # pot=pot, - # defaults=response_defaults, - # ) - # ) - # if len(challenges) == limit: - # page += 1 - # else: - # break + # pot factory + POTFACTORY_ID = "v1.potfactory.potlock.near" + # pots + near_acct, _ = Account.objects.get_or_create(id="near") + url = f"{settings.FASTNEAR_RPC_URL}/account/{POTFACTORY_ID}/view/get_pots" + response = requests.get(url) + if response.status_code != 200: + print( + f"Request for donations data failed ({response.status_code}) with message: {response.text}" + ) + return + pots = response.json() + print("pots data; ", pots) + for pot in pots: + # if pot["id"] != "marketing.v1.potfactory.potlock.near": + # continue # TODO: REMOVE THIS + print("sleeping for 1 second") + time.sleep(1) + pot_id = pot["id"] + deployed_by = pot["deployed_by"] + deployed_at_ms = pot["deployed_at_ms"] + print("handling pot: ", pot_id) + # fetch config for pot + url = f"{settings.FASTNEAR_RPC_URL}/account/{pot_id}/view/get_config" + response = requests.get(url) + if response.status_code != 200: + print( + f"Request for pot config failed ({response.status_code}) with message: {response.text}" + ) + return + config = response.json() + print("config data; ", config) + url = f"{settings.FASTNEAR_RPC_URL}/account/{pot_id}/view/get_contract_source_metadata" + response = requests.get(url) + if response.status_code != 200: + print( + f"Request for pot source metadata failed ({response.status_code}) with message: {response.text}" + ) + return + source_metadata = response.json() + print("source metadata; ", source_metadata) + # add or update pot + pot_acct, _ = Account.objects.get_or_create(id=pot_id) + owner, _ = Account.objects.get_or_create(id=config["owner"]) + if config.get("chef"): + chef, _ = Account.objects.get_or_create(id=config["chef"]) + pot_defaults = { + "pot_factory_id": POTFACTORY_ID, + "owner_id": config["owner"], + "chef_id": config["chef"], + "name": config["pot_name"], + "description": config["pot_description"], + "max_approved_applicants": config["max_projects"], + "base_currency": config["base_currency"], + "application_start": datetime.fromtimestamp( + config["application_start_ms"] / 1000 + ), + "application_end": datetime.fromtimestamp( + config["application_end_ms"] / 1000 + ), + "matching_round_start": datetime.fromtimestamp( + config["public_round_start_ms"] / 1000 + ), + "matching_round_end": datetime.fromtimestamp( + config["public_round_end_ms"] / 1000 + ), + "deployer_id": config["deployed_by"], + "deployed_at": datetime.fromtimestamp(deployed_at_ms / 1000), + "source_metadata": source_metadata, + "registry_provider": config["registry_provider"], + "min_matching_pool_donation_amount": config[ + "min_matching_pool_donation_amount" + ], + "sybil_wrapper_provider": config["sybil_wrapper_provider"], + "custom_sybil_checks": config["custom_sybil_checks"], + "custom_min_threshold_score": config["custom_min_threshold_score"], + "referral_fee_matching_pool_basis_points": config[ + "referral_fee_matching_pool_basis_points" + ], + "referral_fee_public_round_basis_points": config[ + "referral_fee_public_round_basis_points" + ], + "chef_fee_basis_points": config["chef_fee_basis_points"], + "total_matching_pool": "0", + "matching_pool_balance": "0", + "matching_pool_donations_count": 0, + "total_public_donations": "0", + "public_donations_count": 0, + "cooldown_period_ms": None, + "cooldown_end": ( + None + if not config["cooldown_end_ms"] + else datetime.fromtimestamp(config["cooldown_end_ms"] / 1000) + ), + "all_paid_out": config["all_paid_out"], + "protocol_config_provider": config["protocol_config_provider"], + } + pot, created = Pot.objects.update_or_create( + id=pot_acct, defaults=pot_defaults + ) + print("pot created? ", created) + print("adding admins") + for admin in config["admins"]: + admin_obj, _ = Account.objects.get_or_create(id=admin) + pot.admins.add(admin_obj) + # add activity + activity_defaults = { + "signer_id": owner.id, + "receiver_id": POTFACTORY_ID, + "timestamp": datetime.fromtimestamp(deployed_at_ms / 1000), + "tx_hash": None, + } + activity, _ = Activity.objects.update_or_create( + type="Deploy_Pot", action_result=None, defaults=activity_defaults + ) + # get applications in pages + page = 0 + limit = 300 + while True: + time.sleep(1) + url = ( + f"https://rpc.web4.near.page/account/{pot_id}/view/get_applications" + ) + params = {"from_index.json": page * limit, "limit.json": limit} + applications = requests.get(url, params=params) + if applications.status_code != 200: + print( + f"Request for applications data failed ({applications.status_code}) with message: {applications.text}" + ) + return + applications = applications.json() + print(f"adding {len(applications)} applications") + for appl in applications: + applicant, _ = Account.objects.get_or_create(id=appl["project_id"]) + application_defaults = { + "applicant": applicant, + "pot": pot, + "message": appl["message"], + "status": appl["status"], + "submitted_at": datetime.fromtimestamp( + appl["submitted_at"] / 1000 + ), + "updated_at": ( + None + if not appl["updated_at"] + else datetime.fromtimestamp(appl["updated_at"] / 1000) + ), + } + application, _ = PotApplication.objects.update_or_create( + pot=pot, applicant=applicant, defaults=application_defaults + ) + # if status != Pending, add PotApplicationReview also, default to pot owner + if application.status != "Pending": + review_defaults = { + "application": application, + "reviewer": owner, + "notes": appl["review_notes"], + "status": application.status, + "reviewed_at": application.updated_at, + "tx_hash": None, + } + review, _ = PotApplicationReview.objects.update_or_create( + application=application, + reviewer=owner, + defaults=review_defaults, + ) + if len(applications) == limit: + page += 1 + else: + break + # get pot donations in pages + page = 0 + limit = 300 + while True: + time.sleep(1) + url = f"https://rpc.web4.near.page/account/{pot_id}/view/get_donations" + params = {"from_index.json": page * limit, "limit.json": limit} + donations = requests.get(url, params=params) + if donations.status_code != 200: + print( + f"Request for donations data failed ({donations.status_code}) with message: {donations.text}" + ) + return + donations = donations.json() + print(f"adding {len(donations)} donations") + for donation in donations: + donor, _ = Account.objects.get_or_create(id=donation["donor_id"]) + project = None + referrer = None + chef = None + if donation.get("project_id"): + project, _ = Account.objects.get_or_create( + id=donation["project_id"] + ) + if donation.get("referrer_id"): + referrer, _ = Account.objects.get_or_create( + id=donation["referrer_id"] + ) + if donation.get("chef_id"): + chef, _ = Account.objects.get_or_create(id=donation["chef_id"]) + + # pot donations always NEAR + ft_acct, _ = Account.objects.get_or_create(id="near") + ft_token, _ = Token.objects.get_or_create(id=ft_acct) + donation_defaults = { + "donor": donor, + "total_amount": donation["total_amount"], + "total_amount_usd": None, # USD amounts will be added later + "net_amount_usd": None, + "net_amount": donation["net_amount"], + "token": ft_token, + "message": donation["message"], + "donated_at": datetime.fromtimestamp( + donation["donated_at"] / 1000 + ), + "matching_pool": donation["matching_pool"], + "recipient": project, + "protocol_fee": donation["protocol_fee"], + "referrer": referrer, + "referrer_fee": donation["referrer_fee"], + "chef": chef, + "chef_fee": donation["chef_fee"], + "tx_hash": None, + } + donation, _ = Donation.objects.update_or_create( + on_chain_id=donation["id"], pot=pot, defaults=donation_defaults + ) + print("added donation: ", donation.on_chain_id) + # # add donation activity + # activity_type = ( + # "Donate_Pot_Matching_Pool" + # if donation.matching_pool + # else "Donate_Pot_Public" + # ) + # defaults = { + # "signer_id": donor.id, + # "receiver_id": pot_id, + # "timestamp": donation.donated_at, + # "tx_hash": None, + # } + # activity, _ = Activity.objects.update_or_create( + # type=activity_type, + # action_result=None, + # defaults=defaults, + # ) + if len(donations) == limit: + page += 1 + else: + break + if "payouts" in config: + for payout in config["payouts"]: + if not payout["paid_at"]: + continue + recipient, _ = Account.objects.get_or_create( + id=payout["project_id"] + ) + near_token, _ = Token.objects.get_or_create(id=near_acct) + payout_defaults = { + "pot": pot, + "recipient": recipient, + "amount": payout["amount"], + "amount_paid_usd": None, + "token": near_token, # pots only support native NEAR + "paid_at": datetime.fromtimestamp(payout["paid_at"] / 1000), + "tx_hash": None, + } + payout, _ = PotPayout.objects.update_or_create( + pot=pot, recipient=recipient, defaults=payout_defaults + ) + # get pot challenges & admin responses + page = 0 + limit = 300 + while True: + time.sleep(1) + url = f"https://rpc.web4.near.page/account/{pot_id}/view/get_payouts_challenges" + params = {"from_index.json": page * limit, "limit.json": limit} + challenges = requests.get(url, params=params) + if challenges.status_code != 200: + print( + f"Request for challenges data failed ({challenges.status_code}) with message: {challenges.text}" + ) + return + challenges = challenges.json() + print(f"adding {len(challenges)} challenges") + for c in challenges: + challenger, _ = Account.objects.get_or_create(id=c["challenger_id"]) + challenge_defaults = { + "challenger": challenger, + "pot": pot, + "message": c["reason"], + "created_at": datetime.fromtimestamp(c["created_at"] / 1000), + } + challenge, _ = PotPayoutChallenge.objects.update_or_create( + pot=pot, challenger=challenger, defaults=challenge_defaults + ) + # if status != Pending, add PotApplicationReview also, default to pot owner + if c["admin_notes"] or c["resolved"]: + response_defaults = { + "challenger": challenger, + "pot": pot, + "admin": owner, # don't actually know who did it, just using owner for this backfilling + "created_at": datetime.fromtimestamp( + c["created_at"] / 1000 + ), # not exactly correct but we don't know currently + "message": c["admin_notes"], + "resolved": c["resolved"], + } + review, _ = ( + PotPayoutChallengeAdminResponse.objects.update_or_create( + challenger=challenger, + pot=pot, + defaults=response_defaults, + ) + ) + if len(challenges) == limit: + page += 1 + else: + break From 202d77a8e65103437285894e025afa6f85bcc85c Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 25 Jun 2024 16:16:59 -0400 Subject: [PATCH 211/454] add break to null donation result handling --- indexer_app/handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 04ed609..00ee284 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -226,8 +226,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess logger.info(f"Decoded success value: {decoded_success_val}") if ( decoded_success_val == "null" - ): # weird edge case that sometimes occurs where the response is a literal string "null" + ): # edge case that sometimes occurs where the response is a literal string "null", appears to be due to transfer_funds_callback returning None e.g. in the case of a ProtocolFeeCallback (see https://pikespeak.ai/transaction-viewer/78M3HCiBCeCu7jEk6KiVSJGr4utnV2aze8S5ZdEu16t8/detailed for example) logger.info("Result is null. Skipping...") + break try: donation_data = json.loads(decoded_success_val) except json.JSONDecodeError: From 14bf19738d219649bbaa8dbc485699be9c0d340d Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 26 Jun 2024 09:50:58 -0400 Subject: [PATCH 212/454] add spotindexer --- base/celery.py | 2 ++ .../management/commands/killspotindexer.py | 33 +++++++++++++++++++ .../management/commands/runspotindexer.py | 27 +++++++++++++++ indexer_app/tasks.py | 16 +++++++++ 4 files changed, 78 insertions(+) create mode 100644 indexer_app/management/commands/killspotindexer.py create mode 100644 indexer_app/management/commands/runspotindexer.py diff --git a/base/celery.py b/base/celery.py index 68d0724..a08ab54 100644 --- a/base/celery.py +++ b/base/celery.py @@ -47,3 +47,5 @@ "indexer_app.tasks.fetch_usd_prices": {"queue": "beat_tasks"}, "indexer_app.tasks.update_pot_statistics": {"queue": "beat_tasks"}, } + +SPOT_INDEXER_QUEUE_NAME = "spot_indexing" diff --git a/indexer_app/management/commands/killspotindexer.py b/indexer_app/management/commands/killspotindexer.py new file mode 100644 index 0000000..d0f5883 --- /dev/null +++ b/indexer_app/management/commands/killspotindexer.py @@ -0,0 +1,33 @@ +from celery.app.control import Inspect +from django.core.management.base import BaseCommand + +from base.celery import SPOT_INDEXER_QUEUE_NAME, app + + +class Command(BaseCommand): + help = "Kill all running Celery tasks in the spot queue for the indexer" + + def handle(self, *args, **options): + i = Inspect(app=app) + active_tasks = i.active() + + if active_tasks is None: + self.stdout.write( + self.style.WARNING( + "No active tasks found or unable to connect to workers." + ) + ) + return + + for worker, tasks in active_tasks.items(): + for task in tasks: + queue = task.get("delivery_info", {}).get("routing_key") + if queue == SPOT_INDEXER_QUEUE_NAME: + app.control.revoke(task_id=task["id"], terminate=True) + self.stdout.write(self.style.SUCCESS(f"Task {task['id']} revoked.")) + + self.stdout.write( + self.style.SUCCESS( + f"All tasks in {SPOT_INDEXER_QUEUE_NAME} queue have been revoked." + ) + ) diff --git a/indexer_app/management/commands/runspotindexer.py b/indexer_app/management/commands/runspotindexer.py new file mode 100644 index 0000000..a78fb26 --- /dev/null +++ b/indexer_app/management/commands/runspotindexer.py @@ -0,0 +1,27 @@ +from django.core.management.base import BaseCommand + +from indexer_app.tasks import spot_index_near_events + + +class Command(BaseCommand): + help = "Invoke the Near Data Lake indexer task" + + def add_arguments(self, parser): + parser.add_argument( + "start_block", + type=int, + help="The starting block number for the Near Data Lake indexer task", + ) + + def handle(self, *args, **options): + start_block = options["start_block"] + # Invoke Celery task + try: + spot_index_near_events.delay(start_block=start_block) + self.stdout.write( + self.style.SUCCESS( + "Successfully invoked the Near Data Lake indexer task" + ) + ) + except Exception as e: + self.stdout.write(self.style.ERROR(f"Failed to invoke task: {str(e)}")) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index ffefee9..7040491 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -13,6 +13,7 @@ from near_lake_framework import LakeConfig, streamer from accounts.models import Account +from base.celery import SPOT_INDEXER_QUEUE_NAME from donations.models import Donation from indexer_app.handler import handle_streamer_message from pots.models import Pot, PotPayout @@ -78,6 +79,21 @@ def listen_to_near_events(): loop.close() +@shared_task(queue=SPOT_INDEXER_QUEUE_NAME) +def spot_index_near_events(start_block): + logger.info("Spot indexing NEAR events...") + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + try: + logger.info(f"Spot index start block: {start_block-1}") + loop.run_until_complete(indexer(start_block - 1, None)) + except WorkerLostError: + pass # don't log to Sentry + finally: + loop.close() + + # @worker_shutdown.connect # def worker_shutdown_handler(sig, how, exitcode, **kwargs): # if sig == 15: From d55f392167f5064d4a5eff7f381f15349f742212 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 26 Jun 2024 11:49:31 -0400 Subject: [PATCH 213/454] add alpha.potlock.io to allowed hosts --- base/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/base/settings.py b/base/settings.py index d086918..3003792 100644 --- a/base/settings.py +++ b/base/settings.py @@ -35,6 +35,7 @@ "127.0.0.1", "dev.potlock.io", "test-dev.potlock.io", + "alpha.potlock.io", ] # Env vars From af83d4f3b99fd8d48a1d0edca700da7e0042228f Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:07:29 -0400 Subject: [PATCH 214/454] add alpha.potlock.io to cors allowed origins --- base/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index 3003792..97a4d7f 100644 --- a/base/settings.py +++ b/base/settings.py @@ -35,7 +35,7 @@ "127.0.0.1", "dev.potlock.io", "test-dev.potlock.io", - "alpha.potlock.io", + # "alpha.potlock.io", ] # Env vars @@ -161,6 +161,7 @@ CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", + "https://alpha.potlock.io", ] # REDIS / CACHE CONFIGS From bb6f19f93603779b52a13df9def87fb17aadd5b9 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Mon, 1 Jul 2024 20:38:22 +0100 Subject: [PATCH 215/454] Increase rate limit (#33) --- README.md | 2 +- base/settings.py | 2 +- lists/api.py | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 91fc06d..aa4311b 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ export PL_SENTRY_DSN= This is a public, read-only API and as such does not currently implement authentication or authorization. -Rate limits of 100 requests/min are enforced to ensure service for all users. +Rate limits of 500 requests/min are enforced to ensure service for all users. #### Error Responses diff --git a/base/settings.py b/base/settings.py index 97a4d7f..d21f8a2 100644 --- a/base/settings.py +++ b/base/settings.py @@ -115,7 +115,7 @@ ], "DEFAULT_THROTTLE_RATES": { # "user": "100/day", - "anon": "100/minute", + "anon": "500/minute", }, "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", } diff --git a/lists/api.py b/lists/api.py index 4c59126..e9a5038 100644 --- a/lists/api.py +++ b/lists/api.py @@ -98,6 +98,12 @@ class ListRegistrationsAPI(APIView, LimitOffsetPagination): @extend_schema( parameters=[ OpenApiParameter("list_id", int, OpenApiParameter.PATH), + OpenApiParameter( + "status", + str, + OpenApiParameter.QUERY, + description="Filter registrations by status", + ), ], responses={ 200: OpenApiResponse( @@ -128,6 +134,13 @@ def get(self, request: Request, *args, **kwargs): ) registrations = list_obj.registrations.all() + status_param = request.query_params.get("status") + if status_param: + if status_param not in ListRegistrationStatus.values: + return Response( + {"message": f"Invalid status value: {status_param}"}, status=400 + ) + registrations = registrations.filter(status=status_param) results = self.paginate_queryset(registrations, request, view=self) serializer = ListRegistrationSerializer(results, many=True) return self.get_paginated_response(serializer.data) From f99f86822dc8a882ac4e7fe7797c7a1fc63072ad Mon Sep 17 00:00:00 2001 From: Prometheus Date: Tue, 2 Jul 2024 19:49:39 +0100 Subject: [PATCH 216/454] filter registration by plCategories (#35) --- README.md | 1 + lists/api.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index aa4311b..aeabe4f 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,7 @@ enum PotApplicationStatus { #### ✅ Get registrations for list: `GET /lists/{LIST_ID}/registrations` (paginated) Can specify status to filter by using `status` query param if desired, e.g. `status=Approved` +Can also specify project category to filter by using `category` query param if desired, e.g. `category=Education` #### ✅ Get random registration for list: `GET /lists/{LIST_ID}/random_registration` diff --git a/lists/api.py b/lists/api.py index e9a5038..af31fc8 100644 --- a/lists/api.py +++ b/lists/api.py @@ -135,12 +135,18 @@ def get(self, request: Request, *args, **kwargs): registrations = list_obj.registrations.all() status_param = request.query_params.get("status") + category_param = request.query_params.get("category") if status_param: if status_param not in ListRegistrationStatus.values: return Response( {"message": f"Invalid status value: {status_param}"}, status=400 ) registrations = registrations.filter(status=status_param) + if category_param: + category_regex_pattern = rf'\[.*?"{category_param}".*?\]' + registrations = registrations.filter( + registrant__near_social_profile_data__plCategories__iregex = category_regex_pattern + ) results = self.paginate_queryset(registrations, request, view=self) serializer = ListRegistrationSerializer(results, many=True) return self.get_paginated_response(serializer.data) From 9dbe9a67b84f1afdf3d46c9f475b97a64d80988b Mon Sep 17 00:00:00 2001 From: Prometheus Date: Wed, 3 Jul 2024 08:59:57 +0100 Subject: [PATCH 217/454] extend schema (#36) --- lists/api.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lists/api.py b/lists/api.py index af31fc8..d8a568e 100644 --- a/lists/api.py +++ b/lists/api.py @@ -104,6 +104,12 @@ class ListRegistrationsAPI(APIView, LimitOffsetPagination): OpenApiParameter.QUERY, description="Filter registrations by status", ), + OpenApiParameter( + "category", + str, + OpenApiParameter.QUERY, + description="Filter registrations by category", + ), ], responses={ 200: OpenApiResponse( From 2f15218067d224fb28dbcf27bfc7db8eb4191236 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Thu, 4 Jul 2024 17:06:13 +0100 Subject: [PATCH 218/454] add other tlds to cors whitelist (#38) --- base/settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/settings.py b/base/settings.py index d21f8a2..91737c7 100644 --- a/base/settings.py +++ b/base/settings.py @@ -162,6 +162,9 @@ CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", "https://alpha.potlock.io", + "https://alpha.potlock.org", + "https://alpha.potlock.xyz", + "https://alpha.potlock.app", # regex matching might not be advisable. ] # REDIS / CACHE CONFIGS From 7ed140387287fcdce76d9c7545a97aff7e163bca Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 8 Jul 2024 09:12:40 -0400 Subject: [PATCH 219/454] add dev.local to cors allowed origins --- base/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index 91737c7..b9ff677 100644 --- a/base/settings.py +++ b/base/settings.py @@ -164,7 +164,8 @@ "https://alpha.potlock.io", "https://alpha.potlock.org", "https://alpha.potlock.xyz", - "https://alpha.potlock.app", # regex matching might not be advisable. + "https://alpha.potlock.app", # regex matching might not be advisable. + "http://dev.local", ] # REDIS / CACHE CONFIGS From 12607cffcbc43aeae6abedd0672690102719dbd6 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 8 Jul 2024 09:25:14 -0400 Subject: [PATCH 220/454] fix payout-setting error --- indexer_app/utils.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 785c161..d3b23d1 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -536,13 +536,13 @@ async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): insertion_data = [] for payout in payouts: # General question: should we register projects as accounts? - potPayout = { - "recipient_id": payout.get("project_id"), - "amount": payout.get("amount"), - "ft_id": payout.get("ft_id", "near"), - "tx_hash": receipt.receipt_id, - } - insertion_data.append(potPayout) + pot_payout = PotPayout( + recipient_id=payout.get("project_id"), + amount=payout.get("amount"), + ft_id=payout.get("ft_id", "near"), + tx_hash=receipt.receipt_id, + ) + insertion_data.append(pot_payout) await PotPayout.objects.abulk_create(insertion_data, ignore_conflicts=True) except Exception as e: From 897df4ce3b3f3df7b5c186d1413bdd816a077806 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 8 Jul 2024 09:35:57 -0400 Subject: [PATCH 221/454] make PotPayout.paid_at nullable --- accounts/api.py | 2 +- .../management/commands/populatedata.py | 9 +++++--- indexer_app/tasks.py | 4 +++- indexer_app/utils.py | 9 +++++++- .../0010_alter_potpayout_paid_at.py | 23 +++++++++++++++++++ pots/models.py | 2 +- 6 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 pots/migrations/0010_alter_potpayout_paid_at.py diff --git a/accounts/api.py b/accounts/api.py index fe95982..97c182a 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -376,7 +376,7 @@ def get(self, request: Request, *args, **kwargs): {"message": f"Account with ID {account_id} not found."}, status=404 ) - payouts = PotPayout.objects.filter(recipient=account) + payouts = PotPayout.objects.filter(recipient=account, paid_at__isnull=False) results = self.paginate_queryset(payouts, request, view=self) serializer = PotPayoutSerializer(results, many=True) return self.get_paginated_response(serializer.data) diff --git a/indexer_app/management/commands/populatedata.py b/indexer_app/management/commands/populatedata.py index ac7128b..f542081 100644 --- a/indexer_app/management/commands/populatedata.py +++ b/indexer_app/management/commands/populatedata.py @@ -464,8 +464,11 @@ def handle(self, *args, **options): break if "payouts" in config: for payout in config["payouts"]: - if not payout["paid_at"]: - continue + paid_at = ( + None + if "paid_at" not in payout + else datetime.fromtimestamp(payout["paid_at"] / 1000) + ) recipient, _ = Account.objects.get_or_create( id=payout["project_id"] ) @@ -476,7 +479,7 @@ def handle(self, *args, **options): "amount": payout["amount"], "amount_paid_usd": None, "token": near_token, # pots only support native NEAR - "paid_at": datetime.fromtimestamp(payout["paid_at"] / 1000), + "paid_at": paid_at, "tx_hash": None, } payout, _ = PotPayout.objects.update_or_create( diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 7040491..b5d1795 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -139,7 +139,9 @@ def fetch_usd_prices(): jobs_logger.info(f"USD prices fetched for {donations_count} donations.") # payouts - payouts = PotPayout.objects.filter(amount_paid_usd__isnull=True) + payouts = PotPayout.objects.filter( + amount_paid_usd__isnull=True, paid_at__isnull=False + ) payouts_count = payouts.count() jobs_logger.info(f"Fetching USD prices for {payouts_count} payouts...") for payout in payouts: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index d3b23d1..f0deb8f 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -532,14 +532,21 @@ async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): logger.info(f"set payout data: {data}, {receiver_id}") payouts = data.get("payouts", []) + pot = await Pot.objects.aget(id=receiver_id) + near_acct, _ = Account.objects.get_or_create(id="near") + near_token, _ = Token.objects.get_or_create( + id=near_acct + ) # Pots only support native NEAR insertion_data = [] for payout in payouts: # General question: should we register projects as accounts? pot_payout = PotPayout( + pot=pot, recipient_id=payout.get("project_id"), amount=payout.get("amount"), - ft_id=payout.get("ft_id", "near"), + token=near_token, + paid_at=None, tx_hash=receipt.receipt_id, ) insertion_data.append(pot_payout) diff --git a/pots/migrations/0010_alter_potpayout_paid_at.py b/pots/migrations/0010_alter_potpayout_paid_at.py new file mode 100644 index 0000000..97c6304 --- /dev/null +++ b/pots/migrations/0010_alter_potpayout_paid_at.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.4 on 2024-07-08 13:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("pots", "0009_remove_potpayout_ft_alter_potpayout_token"), + ] + + operations = [ + migrations.AlterField( + model_name="potpayout", + name="paid_at", + field=models.DateTimeField( + db_index=True, + help_text="Payout date.", + null=True, + verbose_name="paid at", + ), + ), + ] diff --git a/pots/models.py b/pots/models.py index e17a59f..62051cf 100644 --- a/pots/models.py +++ b/pots/models.py @@ -437,7 +437,7 @@ class PotPayout(models.Model): ) paid_at = models.DateTimeField( _("paid at"), - null=False, + null=True, help_text=_("Payout date."), db_index=True, ) From 0a0db668977dd2134639d28e54b7af297a3f32e9 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 8 Jul 2024 10:43:29 -0400 Subject: [PATCH 222/454] fix sync db calls --- indexer_app/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index f0deb8f..ff36d68 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1,6 +1,7 @@ import base64 import json from datetime import datetime +from math import log import requests from django.conf import settings @@ -533,8 +534,8 @@ async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): logger.info(f"set payout data: {data}, {receiver_id}") payouts = data.get("payouts", []) pot = await Pot.objects.aget(id=receiver_id) - near_acct, _ = Account.objects.get_or_create(id="near") - near_token, _ = Token.objects.get_or_create( + near_acct, _ = Account.objects.aget_or_create(id="near") + near_token, _ = Token.objects.aget_or_create( id=near_acct ) # Pots only support native NEAR @@ -551,6 +552,7 @@ async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): ) insertion_data.append(pot_payout) + logger.info(f"pot payouts insertion data: {insertion_data}") await PotPayout.objects.abulk_create(insertion_data, ignore_conflicts=True) except Exception as e: logger.error(f"Failed to set payouts, Error: {e}") From b480a85cc668563b293c26fbe3abd1f0793410dc Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 8 Jul 2024 10:48:26 -0400 Subject: [PATCH 223/454] add awaits to async db methods --- indexer_app/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index ff36d68..d772747 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -534,8 +534,8 @@ async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): logger.info(f"set payout data: {data}, {receiver_id}") payouts = data.get("payouts", []) pot = await Pot.objects.aget(id=receiver_id) - near_acct, _ = Account.objects.aget_or_create(id="near") - near_token, _ = Token.objects.aget_or_create( + near_acct, _ = await Account.objects.aget_or_create(id="near") + near_token, _ = await Token.objects.aget_or_create( id=near_acct ) # Pots only support native NEAR From 1556511f31a0483e2707e9845490db14858978d1 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 8 Jul 2024 10:51:59 -0400 Subject: [PATCH 224/454] remove debug logs --- indexer_app/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index d772747..9c860ad 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -552,7 +552,6 @@ async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): ) insertion_data.append(pot_payout) - logger.info(f"pot payouts insertion data: {insertion_data}") await PotPayout.objects.abulk_create(insertion_data, ignore_conflicts=True) except Exception as e: logger.error(f"Failed to set payouts, Error: {e}") From d5871eafdaf8871f40e6b2576038c26d391bf5ec Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:14:31 -0400 Subject: [PATCH 225/454] add https://dev.local to cors allowed origins --- base/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/base/settings.py b/base/settings.py index b9ff677..79da600 100644 --- a/base/settings.py +++ b/base/settings.py @@ -166,6 +166,7 @@ "https://alpha.potlock.xyz", "https://alpha.potlock.app", # regex matching might not be advisable. "http://dev.local", + "https://dev.local", ] # REDIS / CACHE CONFIGS From 1a19b176af5495025bdee73a56b1e32b5801a005 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Mon, 8 Jul 2024 20:03:30 +0100 Subject: [PATCH 226/454] Paginate by page (#39) --- accounts/api.py | 16 ++++++++-------- accounts/models.py | 3 +++ base/api.py | 2 +- base/settings.py | 2 +- donations/api.py | 4 ++-- lists/api.py | 6 +++--- pots/api.py | 12 ++++++------ 7 files changed, 24 insertions(+), 21 deletions(-) diff --git a/accounts/api.py b/accounts/api.py index 97c182a..8d3b00b 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -9,7 +9,7 @@ OpenApiTypes, extend_schema, ) -from rest_framework.pagination import LimitOffsetPagination +from rest_framework.pagination import PageNumberPagination from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -43,7 +43,7 @@ ) -class DonorsAPI(APIView, LimitOffsetPagination): +class DonorsAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ @@ -87,7 +87,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountsListAPI(APIView, LimitOffsetPagination): +class AccountsListAPI(APIView, PageNumberPagination): @extend_schema( responses={ @@ -152,7 +152,7 @@ def get(self, request: Request, *args, **kwargs): return Response(serializer.data) -class AccountActivePotsAPI(APIView, LimitOffsetPagination): +class AccountActivePotsAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ @@ -208,7 +208,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountPotApplicationsAPI(APIView, LimitOffsetPagination): +class AccountPotApplicationsAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ @@ -262,7 +262,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountDonationsReceivedAPI(APIView, LimitOffsetPagination): +class AccountDonationsReceivedAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ @@ -302,7 +302,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountDonationsSentAPI(APIView, LimitOffsetPagination): +class AccountDonationsSentAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ @@ -342,7 +342,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountPayoutsReceivedAPI(APIView, LimitOffsetPagination): +class AccountPayoutsReceivedAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ diff --git a/accounts/models.py b/accounts/models.py index 2feba43..25e647b 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -50,6 +50,9 @@ class Account(models.Model): help_text=_("NEAR social data contained under 'profile' key."), ) + class Meta: + ordering = ["id"] + async def fetch_near_social_profile_data_async(self): fetch_profile_data = sync_to_async(self.fetch_near_social_profile_data) await fetch_profile_data() diff --git a/base/api.py b/base/api.py index 37bd407..602f810 100644 --- a/base/api.py +++ b/base/api.py @@ -9,7 +9,7 @@ extend_schema, ) from rest_framework import serializers -from rest_framework.pagination import LimitOffsetPagination +from rest_framework.pagination import PageNumberPagination from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView diff --git a/base/settings.py b/base/settings.py index 79da600..4c82261 100644 --- a/base/settings.py +++ b/base/settings.py @@ -107,7 +107,7 @@ DEFAULT_PAGE_SIZE = 30 REST_FRAMEWORK = { - "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", "PAGE_SIZE": DEFAULT_PAGE_SIZE, "DEFAULT_THROTTLE_CLASSES": [ # "rest_framework.throttling.UserRateThrottle", diff --git a/donations/api.py b/donations/api.py index 6053c4d..74f8d55 100644 --- a/donations/api.py +++ b/donations/api.py @@ -8,7 +8,7 @@ OpenApiResponse, extend_schema, ) -from rest_framework.pagination import LimitOffsetPagination +from rest_framework.pagination import PageNumberPagination from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -20,7 +20,7 @@ DONATE_CONTRACT = "donate." + settings.POTLOCK_TLA -class DonationContractConfigAPI(APIView, LimitOffsetPagination): +class DonationContractConfigAPI(APIView, PageNumberPagination): @extend_schema( responses={ diff --git a/lists/api.py b/lists/api.py index d8a568e..4cfa60a 100644 --- a/lists/api.py +++ b/lists/api.py @@ -10,7 +10,7 @@ OpenApiResponse, extend_schema, ) -from rest_framework.pagination import LimitOffsetPagination +from rest_framework.pagination import PageNumberPagination from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -28,7 +28,7 @@ ) -class ListsListAPI(APIView, LimitOffsetPagination): +class ListsListAPI(APIView, PageNumberPagination): @extend_schema( responses={ @@ -93,7 +93,7 @@ def get(self, request: Request, *args, **kwargs): return Response(serializer.data) -class ListRegistrationsAPI(APIView, LimitOffsetPagination): +class ListRegistrationsAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ diff --git a/pots/api.py b/pots/api.py index 0fd624f..b7aa8b5 100644 --- a/pots/api.py +++ b/pots/api.py @@ -8,7 +8,7 @@ OpenApiResponse, extend_schema, ) -from rest_framework.pagination import LimitOffsetPagination +from rest_framework.pagination import PageNumberPagination from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -41,7 +41,7 @@ ) -class PotsListAPI(APIView, LimitOffsetPagination): +class PotsListAPI(APIView, PageNumberPagination): @extend_schema( responses={ @@ -102,7 +102,7 @@ def get(self, request: Request, *args, **kwargs): return Response(serializer.data) -class PotApplicationsAPI(APIView, LimitOffsetPagination): +class PotApplicationsAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ @@ -139,7 +139,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class PotDonationsAPI(APIView, LimitOffsetPagination): +class PotDonationsAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ @@ -176,7 +176,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class PotSponsorsAPI(APIView, LimitOffsetPagination): +class PotSponsorsAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ @@ -218,7 +218,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class PotPayoutsAPI(APIView, LimitOffsetPagination): +class PotPayoutsAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ From 9ea4d9806141bf1d72b08786240ac9de5bf6dd2e Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:42:05 -0400 Subject: [PATCH 227/454] gunicorn graceful reload --- scripts/after_install_dev.sh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh index 01bed73..b9cf0bc 100755 --- a/scripts/after_install_dev.sh +++ b/scripts/after_install_dev.sh @@ -57,12 +57,20 @@ if [ "$PENDING_MIGRATIONS" -gt 0 ]; then echo 'Applying migrations...' >> "$LOG_FILE" poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 - echo 'Starting services...' >> "$LOG_FILE" - sudo systemctl start gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev + # echo 'Starting services...' >> "$LOG_FILE" + # sudo systemctl start gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev else - echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" + # echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" + echo 'No migrations found.' >> "$LOG_FILE" poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 - sudo systemctl restart gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev + # sudo systemctl restart gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev fi +# Gracefully reload Gunicorn to apply the changes without downtime +echo 'Reloading Gunicorn...' >> "$LOG_FILE" +sudo systemctl kill --signal=HUP gunicorn-dev + +echo 'Restarting services...' >> "$LOG_FILE" +sudo systemctl restart celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev + echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_dev.sh completed" >> "$LOG_FILE" From c0008b35706a8e53d73b20004e2656aeead84a7a Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:43:03 -0400 Subject: [PATCH 228/454] fix after install script --- scripts/after_install_dev.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh index b9cf0bc..43e46f9 100755 --- a/scripts/after_install_dev.sh +++ b/scripts/after_install_dev.sh @@ -62,10 +62,14 @@ if [ "$PENDING_MIGRATIONS" -gt 0 ]; then else # echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" echo 'No migrations found.' >> "$LOG_FILE" - poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 + # poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 # sudo systemctl restart gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev fi +# Collect static +echo 'Running collectstatic...' >> "$LOG_FILE" +poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 + # Gracefully reload Gunicorn to apply the changes without downtime echo 'Reloading Gunicorn...' >> "$LOG_FILE" sudo systemctl kill --signal=HUP gunicorn-dev From ecd1ebef6a86ea86cec4ad913051d984748f8f32 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:46:09 -0400 Subject: [PATCH 229/454] add graceful gunicorn restart to testnet after install script --- scripts/after_install_dev.sh | 6 ------ scripts/after_install_testnet.sh | 18 ++++++++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh index 43e46f9..9ddc0a1 100755 --- a/scripts/after_install_dev.sh +++ b/scripts/after_install_dev.sh @@ -56,14 +56,8 @@ if [ "$PENDING_MIGRATIONS" -gt 0 ]; then echo 'Applying migrations...' >> "$LOG_FILE" poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 - - # echo 'Starting services...' >> "$LOG_FILE" - # sudo systemctl start gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev else - # echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" echo 'No migrations found.' >> "$LOG_FILE" - # poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 - # sudo systemctl restart gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev fi # Collect static diff --git a/scripts/after_install_testnet.sh b/scripts/after_install_testnet.sh index 32e4356..a1788dc 100755 --- a/scripts/after_install_testnet.sh +++ b/scripts/after_install_testnet.sh @@ -56,13 +56,19 @@ if [ "$PENDING_MIGRATIONS" -gt 0 ]; then echo 'Applying migrations...' >> "$LOG_FILE" poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 - - echo 'Starting services...' >> "$LOG_FILE" - sudo systemctl start gunicorn-testnet celery-indexer-worker-testnet celery-beat-worker-testnet celery-beat-testnet else - echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" - poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 - sudo systemctl restart gunicorn-testnet celery-indexer-worker-testnet celery-beat-worker-testnet celery-beat-testnet + echo 'No migrations found.' >> "$LOG_FILE" fi +# Collect static +echo 'Running collectstatic...' >> "$LOG_FILE" +poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 + +# Gracefully reload Gunicorn to apply the changes without downtime +echo 'Reloading Gunicorn...' >> "$LOG_FILE" +sudo systemctl kill --signal=HUP gunicorn-testnet + +echo 'Restarting services...' >> "$LOG_FILE" +sudo systemctl restart celery-indexer-worker-testnet celery-beat-worker-testnet celery-beat-testnet + echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install_testnet.sh completed" >> "$LOG_FILE" From 60a430a06cd53462935e0d203b0d755f251c0b41 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:51:50 -0400 Subject: [PATCH 230/454] avoid stopping services before migrations in after_install scripts --- scripts/after_install_dev.sh | 5 +++-- scripts/after_install_testnet.sh | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/after_install_dev.sh b/scripts/after_install_dev.sh index 9ddc0a1..8088819 100755 --- a/scripts/after_install_dev.sh +++ b/scripts/after_install_dev.sh @@ -51,8 +51,9 @@ poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full o PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations if [ "$PENDING_MIGRATIONS" -gt 0 ]; then - echo "Migrations found; stopping services..." >> "$LOG_FILE" - sudo systemctl stop gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev + # COMMENTING OUT FOR NOW AS I BELIEVE STOPPING SERVICES CREATES UNNECESSARY DOWNTIME + # echo "Migrations found; stopping services..." >> "$LOG_FILE" + # sudo systemctl stop gunicorn-dev celery-indexer-worker-dev celery-beat-worker-dev celery-beat-dev echo 'Applying migrations...' >> "$LOG_FILE" poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 diff --git a/scripts/after_install_testnet.sh b/scripts/after_install_testnet.sh index a1788dc..628e056 100755 --- a/scripts/after_install_testnet.sh +++ b/scripts/after_install_testnet.sh @@ -51,8 +51,9 @@ poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full o PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations if [ "$PENDING_MIGRATIONS" -gt 0 ]; then - echo "Migrations found; stopping services..." >> "$LOG_FILE" - sudo systemctl stop gunicorn-testnet celery-indexer-worker-testnet celery-beat-worker-testnet celery-beat-testnet + # COMMENTING OUT FOR NOW AS I BELIEVE STOPPING SERVICES IS UNNECESSARY + # echo "Migrations found; stopping services..." >> "$LOG_FILE" + # sudo systemctl stop gunicorn-testnet celery-indexer-worker-testnet celery-beat-worker-testnet celery-beat-testnet echo 'Applying migrations...' >> "$LOG_FILE" poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 From 59dc627e96a77cee6c0d039c14313234d324e63e Mon Sep 17 00:00:00 2001 From: Prometheus Date: Tue, 9 Jul 2024 14:05:55 +0100 Subject: [PATCH 231/454] Pagination limit and readme update (#40) * update readme with pagination query param * customize pagination --- README.md | 2 +- accounts/api.py | 3 ++- api/pagination.py | 6 ++++++ base/serializers.py | 2 +- base/settings.py | 3 +-- pots/serializers.py | 6 +++--- 6 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 api/pagination.py diff --git a/README.md b/README.md index aeabe4f..7ab959d 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ Possible Error Codes: #### Pagination -Pagination available using `limit` and `offset` query params on endpoints that specify `paginated`. Default `limit` is 30. +Pagination available using `limit` and `page` as query param on endpoints that specify `paginated`. Default `limit` is 30. Endpoints that support pagination will return a success response containing the following: diff --git a/accounts/api.py b/accounts/api.py index 8d3b00b..3f2566a 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -41,6 +41,7 @@ AccountSerializer, PaginatedAccountsResponseSerializer, ) +from api.pagination import ResultPagination class DonorsAPI(APIView, PageNumberPagination): @@ -87,7 +88,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountsListAPI(APIView, PageNumberPagination): +class AccountsListAPI(APIView, ResultPagination): @extend_schema( responses={ diff --git a/api/pagination.py b/api/pagination.py new file mode 100644 index 0000000..21f4d65 --- /dev/null +++ b/api/pagination.py @@ -0,0 +1,6 @@ +from rest_framework.pagination import PageNumberPagination + +class ResultPagination(PageNumberPagination): + page_size = 30 + page_size_query_param = 'limit' + max_page_size = 200 diff --git a/base/serializers.py b/base/serializers.py index a47e77c..a27aa7e 100644 --- a/base/serializers.py +++ b/base/serializers.py @@ -1,7 +1,7 @@ from rest_framework import serializers -class TwoDecimalStringField(serializers.DecimalField): +class ResultPagination(serializers.DecimalField): def to_representation(self, value): if value is None: return value diff --git a/base/settings.py b/base/settings.py index 4c82261..b878872 100644 --- a/base/settings.py +++ b/base/settings.py @@ -107,7 +107,7 @@ DEFAULT_PAGE_SIZE = 30 REST_FRAMEWORK = { - "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "DEFAULT_PAGINATION_CLASS": "api.pagination.ResultPagination", "PAGE_SIZE": DEFAULT_PAGE_SIZE, "DEFAULT_THROTTLE_CLASSES": [ # "rest_framework.throttling.UserRateThrottle", @@ -166,7 +166,6 @@ "https://alpha.potlock.xyz", "https://alpha.potlock.app", # regex matching might not be advisable. "http://dev.local", - "https://dev.local", ] # REDIS / CACHE CONFIGS diff --git a/pots/serializers.py b/pots/serializers.py index b0c6581..901affd 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -2,15 +2,15 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer -from base.serializers import TwoDecimalStringField +from base.serializers import ResultPagination from tokens.serializers import SIMPLE_TOKEN_EXAMPLE, TokenSerializer from .models import Pot, PotApplication, PotPayout class PotSerializer(ModelSerializer): - total_matching_pool_usd = TwoDecimalStringField(max_digits=20, decimal_places=2) - total_public_donations_usd = TwoDecimalStringField(max_digits=20, decimal_places=2) + total_matching_pool_usd = ResultPagination(max_digits=20, decimal_places=2) + total_public_donations_usd = ResultPagination(max_digits=20, decimal_places=2) class Meta: model = Pot From 6a073952cb6da074cab320ea8fa3634e42c2adc8 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:10:56 -0400 Subject: [PATCH 232/454] add https://dev.local back to cors allowed origins --- base/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/base/settings.py b/base/settings.py index b878872..9b48b32 100644 --- a/base/settings.py +++ b/base/settings.py @@ -166,6 +166,7 @@ "https://alpha.potlock.xyz", "https://alpha.potlock.app", # regex matching might not be advisable. "http://dev.local", + "https://dev.local", ] # REDIS / CACHE CONFIGS From 0a78f1a058cf2f0db70d1fae5cbd14db23ab77ca Mon Sep 17 00:00:00 2001 From: Prometheus Date: Wed, 10 Jul 2024 20:21:03 +0100 Subject: [PATCH 233/454] handle pot config update (#37) * handle pot config update * review changes: pot cupdate_or-create * ensure chef exists, add admins --- indexer_app/handler.py | 5 +++- indexer_app/utils.py | 68 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 00ee284..20c8b3e 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -27,6 +27,7 @@ handle_set_payouts, handle_social_profile_update, handle_transfer_payout, + handle_pot_config_update ) @@ -70,6 +71,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess continue try: parsed_log = json.loads(log[len("EVENT_JSON:") :]) + event_name = parsed_log.get("event") + if event_name == "update_pot_config": + await handle_pot_config_update(parsed_log.get("data")[0], receiver_id) except json.JSONDecodeError: logger.warning( f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" @@ -349,7 +353,6 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess ) break # TODO: handle remove upvote - except Exception as e: logger.error(f"Error in indexer handler:\n{e}") # with open("indexer_error.txt", "a") as file: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 9c860ad..f6cac60 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -146,6 +146,74 @@ async def handle_new_pot( logger.error(f"Failed to handle new pot, Error: {e}") + +async def handle_pot_config_update( + receiver_id: str, + log_data: dict +): + try: + data = log_data + logger.info(f"asserting involved accts.... {receiver_id}") + if data.get("chef"): + chef, _ = await Account.objects.aget_or_create(id=data["chef"]) + owner, _ = await Account.objects.aget_or_create(id=data["owner"]) + logger.info(f"building updated config for pot {receiver_id}") + pot_config = { + "deployer": data["deployed_by"], + "source_metadata": data["source_metadata"], + "owner_id": data["owner"], + "chef_id": data.get("chef"), + "name": data["pot_name"], + "description": data["pot_description"], + "max_approved_applicants": data["max_projects"], + "base_currency": data["base_currency"], + "application_start": datetime.fromtimestamp( + data["application_start_ms"] / 1000 + ), + "application_end": datetime.fromtimestamp( + data["application_end_ms"] / 1000 + ), + "matching_round_start": datetime.fromtimestamp( + data["public_round_start_ms"] / 1000 + ), + "matching_round_end": datetime.fromtimestamp( + data["public_round_end_ms"] / 1000 + ), + "registry_provider": data["registry_provider"], + "min_matching_pool_donation_amount": data[ + "min_matching_pool_donation_amount" + ], + "sybil_wrapper_provider": data["sybil_wrapper_provider"], + "custom_sybil_checks": data.get("custom_sybil_checks"), + "custom_min_threshold_score": data.get("custom_min_threshold_score"), + "referral_fee_matching_pool_basis_points": data[ + "referral_fee_matching_pool_basis_points" + ], + "referral_fee_public_round_basis_points": data[ + "referral_fee_public_round_basis_points" + ], + "chef_fee_basis_points": data["chef_fee_basis_points"], + "matching_pool_balance": data["matching_pool_balance"], + "total_public_donations": data["total_public_donations"], + "public_donations_count": data["public_donations_count"], + "cooldown_period_ms": data["cooldown_end_ms"], + "all_paid_out": data["all_paid_out"], + "protocol_config_provider": data["protocol_config_provider"], + } + + pot, created = await Pot.objects.aupdate_or_create( + id=receiver_id, defaults=pot_config + ) + + if data.get("admins"): + for admin_id in data["admins"]: + admin, _ = await Account.objects.aget_or_create(id=admin_id) + pot.admins.aadd(admin) + # await Pot.objects.filter(id=receiver_id).aupdate(**pot_config) + except Exception as e: + logger.error(f"Failed to update Pot config, Error: {e}") + + async def handle_new_pot_factory(data: dict, receiver_id: str, created_at: datetime): try: From e52f2d954b2e19735705126463e7221af6ba5194 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Mon, 15 Jul 2024 15:42:43 +0100 Subject: [PATCH 234/454] New nadabot index (#34) * feat: index nadabot registry, provider and stamps * chore: add nadabot admin * chore: review fixes: print to log, model fixes * filter registration by plCategories (#35) * extend schema (#36) * add other tlds to cors whitelist (#38) * test groups and rules * add dev.local to cors allowed origins * fix payout-setting error * make PotPayout.paid_at nullable * fix sync db calls * add awaits to async db methods * remove debug logs * add https://dev.local to cors allowed origins * Paginate by page (#39) * gunicorn graceful reload * fix after install script * add graceful gunicorn restart to testnet after install script * avoid stopping services before migrations in after_install scripts * Pagination limit and readme update (#40) * update readme with pagination query param * customize pagination * add blacklist entry model * add https://dev.local back to cors allowed origins * handle pot config update (#37) * handle pot config update * review changes: pot cupdate_or-create * ensure chef exists, add admins * review changes to model * feat: index nadabot registry, provider and stamps * chore: add nadabot admin * chore: review fixes: print to log, model fixes * test groups and rules * add blacklist entry model * review changes to model * fix conflict issues * fix provider onchain_id reference * uncomment start block * add comment * fix group indexing and test --------- Co-authored-by: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> --- .gitignore | 5 +- .../migrations/0003_alter_account_options.py | 17 + base/settings.py | 2 + indexer_app/handler.py | 63 ++- indexer_app/tasks.py | 2 +- indexer_app/utils.py | 206 ++++++++- nadabot/__init__.py | 0 nadabot/admin.py | 3 + nadabot/api.py | 1 + nadabot/apps.py | 6 + nadabot/migrations/0001_initial.py | 421 ++++++++++++++++++ nadabot/migrations/__init__.py | 0 nadabot/models.py | 277 ++++++++++++ nadabot/serializers.py | 15 + nadabot/tests.py | 3 + nadabot/utils.py | 16 + nadabot/views.py | 3 + 17 files changed, 1019 insertions(+), 21 deletions(-) create mode 100644 accounts/migrations/0003_alter_account_options.py create mode 100644 nadabot/__init__.py create mode 100644 nadabot/admin.py create mode 100644 nadabot/api.py create mode 100644 nadabot/apps.py create mode 100644 nadabot/migrations/0001_initial.py create mode 100644 nadabot/migrations/__init__.py create mode 100644 nadabot/models.py create mode 100644 nadabot/serializers.py create mode 100644 nadabot/tests.py create mode 100644 nadabot/utils.py create mode 100644 nadabot/views.py diff --git a/.gitignore b/.gitignore index 5d0a2fb..923869c 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,7 @@ media/ # Celery beat schedule file celerybeat-schedule +dump.rdb # Flask stuff: instance/ @@ -99,7 +100,7 @@ poetry.lock # dotenv .env .env.* - +pyrightconfig.json # virtualenv .venv/ venv/ @@ -133,4 +134,4 @@ dmypy.json # static -/static/ \ No newline at end of file +/static/ diff --git a/accounts/migrations/0003_alter_account_options.py b/accounts/migrations/0003_alter_account_options.py new file mode 100644 index 0000000..d376807 --- /dev/null +++ b/accounts/migrations/0003_alter_account_options.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.6 on 2024-07-12 12:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0002_account_near_social_profile_data"), + ] + + operations = [ + migrations.AlterModelOptions( + name="account", + options={"ordering": ["id"]}, + ), + ] diff --git a/base/settings.py b/base/settings.py index 9b48b32..65667bc 100644 --- a/base/settings.py +++ b/base/settings.py @@ -59,6 +59,7 @@ SENTRY_DSN = os.environ.get("PL_SENTRY_DSN") POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else "potlock.near" +NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" @@ -102,6 +103,7 @@ "lists", "pots", "tokens", + "nadabot", ] DEFAULT_PAGE_SIZE = 30 diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 20c8b3e..e65d0b1 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -8,18 +8,23 @@ from base.utils import convert_ns_to_utc from pots.utils import match_pot_factory_pattern, match_pot_subaccount_pattern - +from nadabot.utils import match_nadabot_registry_pattern from .logging import logger from .utils import ( # handle_batch_donations, + handle_add_stamp, handle_default_list_status_change, handle_list_admin_removal, handle_list_registration_update, handle_list_upvote, + handle_add_nadabot_admin, + handle_new_nadabot_registry, handle_new_donation, + handle_new_group, handle_new_list, handle_new_list_registration, handle_new_pot, handle_new_pot_factory, + handle_new_provider, handle_payout_challenge, handle_payout_challenge_response, handle_pot_application, @@ -27,13 +32,17 @@ handle_set_payouts, handle_social_profile_update, handle_transfer_payout, - handle_pot_config_update + handle_update_default_human_threshold, + handle_registry_blacklist_action, + handle_registry_unblacklist_action, + handle_pot_config_update, ) async def handle_streamer_message(streamer_message: near_primitives.StreamerMessage): block_timestamp = streamer_message.block.header.timestamp block_height = streamer_message.block.header.height + now_datetime = datetime.fromtimestamp(block_timestamp / 1000000000) await cache.aset( "block_height", block_height ) # TODO: add custom timeout if it should be valid for longer than default (5 minutes) @@ -58,11 +67,13 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess receiver_id = receipt_execution_outcome.receipt.receiver_id if ( receiver_id != settings.NEAR_SOCIAL_CONTRACT_ADDRESS - and not receiver_id.endswith(settings.POTLOCK_TLA) + and not receiver_id.endswith((settings.POTLOCK_TLA, settings.NADABOT_TLA)) ): continue # 1. HANDLE LOGS log_data = [] + receipt = receipt_execution_outcome.receipt + signer_id = receipt.receipt["Action"]["signer_id"] for log_index, log in enumerate( receipt_execution_outcome.execution_outcome.outcome.logs, start=1 @@ -72,8 +83,22 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess try: parsed_log = json.loads(log[len("EVENT_JSON:") :]) event_name = parsed_log.get("event") + print("parsa parsa...", parsed_log) if event_name == "update_pot_config": await handle_pot_config_update(parsed_log.get("data")[0], receiver_id) + + if event_name == "add_or_update_provider": + await handle_new_provider(parsed_log.get("data")[0], receiver_id, signer_id) + elif event_name == "add_stamp": + await handle_add_stamp(parsed_log.get("data")[0], receiver_id, signer_id) + elif event_name == "update_default_human_threshold": + await handle_update_default_human_threshold(parsed_log.get("data")[0], receiver_id) + if event_name == "add_or_update_group": + await handle_new_group(parsed_log.get("data")[0], now_datetime) + if event_name == "blacklist_account": + await handle_registry_blacklist_action(parsed_log.get("data")[0], receiver_id, now_datetime) + if event_name == "unblacklist_account": + await handle_registry_unblacklist_action(parsed_log.get("data")[0], receiver_id, now_datetime) except json.JSONDecodeError: logger.warning( f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" @@ -103,17 +128,16 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess ): if "FunctionCall" not in action: continue - receipt = receipt_execution_outcome.receipt + # receipt = receipt_execution_outcome.receipt status_obj = receipt_execution_outcome.execution_outcome.outcome - created_at = datetime.fromtimestamp(block_timestamp / 1000000000) try: function_call = action["FunctionCall"] method_name = function_call["method_name"] args = function_call["args"] decoded_bytes = base64.b64decode(args) if args else b"{}" - signer_id = receipt.receipt["Action"]["signer_id"] - receiver_id = receipt.receiver_id + # signer_id = receipt.receipt["Action"]["signer_id"] + # receiver_id = receiver_id predecessor_id = receipt.predecessor_id result = status_obj.status.get("SuccessValue") # Assuming the decoded data is UTF-8 text @@ -141,12 +165,14 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess args_dict, receiver_id, signer_id ) case "new": - if match_pot_factory_pattern(receipt.receiver_id): + if match_pot_factory_pattern(receiver_id): logger.info(f"matched for factory pattern: {args_dict}") await handle_new_pot_factory( - args_dict, receiver_id, created_at + args_dict, receiver_id, now_datetime ) - elif match_pot_subaccount_pattern(receipt.receiver_id): + elif match_nadabot_registry_pattern(receiver_id): # matches registries in the pattern, version(v1).env(staging).nadabot.near + await handle_new_nadabot_registry(args_dict, receiver_id, now_datetime) + elif match_pot_subaccount_pattern(receiver_id): logger.info( f"new pot deployment: {args_dict}, {action}" ) @@ -156,7 +182,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess signer_id, predecessor_id, receipt.receipt_id, - created_at, + now_datetime, ) break # TODO: update to use handle_apply method?? @@ -170,7 +196,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess signer_id, receipt, status_obj, - created_at, + now_datetime, ) break @@ -184,7 +210,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess signer_id, receipt, status_obj, - created_at, + now_datetime, ) break @@ -304,7 +330,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess receiver_id, signer_id, receipt.receipt_id, - created_at, + now_datetime, ) break @@ -315,14 +341,14 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess receiver_id, signer_id, receipt.receipt_id, - created_at, + now_datetime, ) break case "transfer_payout_callback": logger.info(f"fulfilling payouts..... {args_dict}") await handle_transfer_payout( - args_dict, receiver_id, receipt.receipt_id, created_at + args_dict, receiver_id, receipt.receipt_id, now_datetime ) break @@ -352,7 +378,12 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess args_dict, receiver_id, signer_id, receipt.receipt_id ) break + case "owner_add_admins": + logger.info(f"adding admins.. {args_dict}") + await handle_add_nadabot_admin(args_dict, receiver_id) + break # TODO: handle remove upvote + except Exception as e: logger.error(f"Error in indexer handler:\n{e}") # with open("indexer_error.txt", "a") as file: diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index b5d1795..0cd5b27 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -70,7 +70,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height start_block = get_block_height("current_block_height") - # start_block = 112959664 + # start_block = 119_568_113 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer(start_block - 1, None)) except WorkerLostError: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index f6cac60..46aeed7 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -15,6 +15,7 @@ from donations.models import Donation from indexer_app.models import BlockHeight from lists.models import List, ListRegistration, ListUpvote +from nadabot.models import Group, NadabotRegistry, Provider, Stamp, BlackList from pots.models import ( Pot, PotApplication, @@ -50,6 +51,75 @@ async def handle_social_profile_update(args_dict, receiver_id, signer_id): logger.error(f"Error in handle_social_profile_update: {e}") +async def handle_new_nadabot_registry( + data: dict, + receiverId: str, + created_at: datetime +): + logger.info(f"nadabot registry init... {data}") + + try: + registry, _ = await Account.objects.aget_or_create(id=receiverId) + owner, _ = await Account.objects.aget_or_create(id=data["owner"]) + nadabot_registry, created = await NadabotRegistry.objects.aupdate_or_create( + id=registry, + owner=owner, + created_at=created_at, + updated_at=created_at, + source_metadata=data.get('source_metadata') + ) + + if data.get("admins"): + for admin_id in data["admins"]: + admin, _ = await Account.objects.aget_or_create(id=admin_id) + await nadabot_registry.admins.aadd(admin) + except Exception as e: + logger.error(f"Error in registry initiialization: {e}") + + +async def handle_registry_blacklist_action( + data: dict, + receiverId: str, + created_at: datetime +): + logger.info(f"Registry blacklist action....... {data}") + + try: + registry, _ = await Account.objects.aget_or_create(id=receiverId) + bulk_obj = [] + for acct in data["accounts"]: + account, _ = await Account.objects.aget_or_create(id=acct) + bulk_obj.append( + { + "registry": registry, + "account": account, + "reason": data.get("reason"), + "date_blacklisted": created_at + } + ) + await BlackList.objects.abulk_create( + objs = [BlackList(**data) for data in bulk_obj], ignore_conflicts=True + ) + except Exception as e: + logger.error(f"Error in adding acct to blacklist: {e}") + + +async def handle_registry_unblacklist_action( + data: dict, + receiverId: str, + created_at: datetime +): + logger.info(f"Registry remove blacklisted accts....... {data}") + + try: + registry, _ = await Account.objects.aget_or_create(id=receiverId) + entries = BlackList.objects.filter(account__in=data["accounts"]) + await entries.adelete() + except Exception as e: + logger.error(f"Error in removing acct from blacklist: {e}") + + + async def handle_new_pot( data: dict, receiver_id: str, @@ -148,8 +218,8 @@ async def handle_new_pot( async def handle_pot_config_update( - receiver_id: str, - log_data: dict + log_data: dict, + receiver_id: str, ): try: data = log_data @@ -718,6 +788,16 @@ async def handle_list_admin_removal(data, receiver_id, signer_id, receiptId): logger.error(f"Failed to remove list admin, Error: {e}") +async def handle_add_nadabot_admin(data, receiverId): + logger.info(f"adding admin...: {data}, {receiverId}") + try: + obj = await NadabotRegistry.objects.aget(id=receiverId) + + for acct in data["account_ids"]: + user, _ = await Account.objects.aget_or_create(id=acct) + await obj.admins.aadd(user) + except Exception as e: + logger.error(f"Failed to add nadabot admin, Error: {e}") # # TODO: Need to abstract some actions. # async def handle_batch_donations( # receiver_id: str, @@ -952,6 +1032,128 @@ async def handle_new_donation( # } # await Account.objects.filter(id=recipient.id).aupdate(**acctUpdate) +async def handle_update_default_human_threshold( + data: dict, + receiverId: str +): + logger.info(f"update threshold data... {data}") + + try: + + reg = await NadabotRegistry.objects.filter(id=receiverId).aupdate( + **{"default_human_threshold": data["default_human_threshold"]} + ) + logger.info("updated threshold..") + except Exception as e: + logger.error(f"Failed to update default threshold, Error: {e}") + + +async def handle_new_provider( + data: dict, + receiverId: str, + signerId: str +): + logger.info(f"new provider data: {data}, {receiverId}") + data = data["provider"] + + logger.info( + f"upserting accounts involved, {data['submitted_by']}, {data['contract_id']}" + ) + + try: + submitter, _ = await Account.objects.aget_or_create(id=data["submitted_by"]) + contract, _ = await Account.objects.aget_or_create(id=data["contract_id"]) + + provider_id = data["id"] + + # due to an event malfunction from the contract, the first 13 migrated(migrated from a previous contract) providers, + # had the same id emitted for them, the id `13`, so we have to catch it and manoeuvre aroubd it. + # TODO: REMOVE when next version contract is deployed, as this issue would be fixed. + if provider_id == 13: + provider_id = await cache.aget("last_id", 1) + await cache.aset("last_id", provider_id+1) + + provider = await Provider.objects.aupdate_or_create( + on_chain_id=provider_id, + contract=contract, + method_name=data["method_name"], + name=data["provider_name"], + description=data.get("description"), + status=data["status"], + admin_notes=data.get("admin_notes"), + default_weight=data["default_weight"], + gas=data.get("gas"), + tags=data.get("tags"), + icon_url=data.get("icon_url"), + external_url=data.get("external_url"), + submitted_by_id=data["submitted_by"], + submitted_at = datetime.fromtimestamp(data.get("submitted_at_ms") / 1000), + stamp_validity_ms = datetime.fromtimestamp(data.get("stamp_validity_ms") / 1000) if data.get("stamp_validity_ms") else None, + account_id_arg_name = data["account_id_arg_name"], + custom_args = data.get("custom_args"), + registry_id=receiverId + ) + except Exception as e: + logger.error(f"Failed to add new stamp provider: {e}") + + +async def handle_add_stamp( + data: dict, + receiverId: str, + signerId: str +): + logger.info(f"new stamp data: {data}, {receiverId}") + data = data["stamp"] + + logger.info(f"upserting accounts involved, {data['user_id']}") + + user, _ = await Account.objects.aget_or_create(id=data["user_id"]) + provider, _ = await Provider.objects.aget_or_create(on_chain_id=data["provider_id"]) + + try: + stamp = await Stamp.objects.aupdate_or_create( + user=user, + provider=provider, + verified_at = datetime.fromtimestamp(data["validated_at_ms"] / 1000) + ) + except Exception as e: + logger.error(f"Failed to create stamp: {e}") + + + +async def handle_new_group( + data: dict, + created_at: datetime +): + logger.info(f"new group data: {data}") + group_data = data.get('group', {}) + try: + # group enums can have values, they are represented as a dict in the events from the indexer, and enum choices without values are presented as normal strings: + # withValue: {'group': {'id': 5, 'name': 'Here we go again', 'providers': [8, 1, 4, 6], 'rule': {'IncreasingReturns': 10}}} + # noValue: {"id":6,"name":"Lachlan test group","providers":[1,2],"rule":"Highest"} + rule = group_data['rule'] + rule_key = rule + rule_val = None + if type(rule) == dict: + rule_key = next(iter(rule)) + rule_val = rule.get(rule_key) + + group = await Group.objects.acreate( + id=group_data["id"], + name=group_data["name"], + created_at=created_at, + updated_at=created_at, + rule_type = rule_key, + rule_val = rule_val + ) + + logger.info(f"addding provider.... : {group_data['providers']}") + if group_data.get("providers"): + for provider_id in group_data["providers"]: + provider, _ = await Provider.objects.aget_or_create(on_chain_id=provider_id) + await group.providers.aadd(provider) + except Exception as e: + logger.error(f"Failed to create group, because: {e}") async def cache_block_height( key: str, height: int, block_count: int, block_timestamp: int diff --git a/nadabot/__init__.py b/nadabot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nadabot/admin.py b/nadabot/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/nadabot/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/nadabot/api.py b/nadabot/api.py new file mode 100644 index 0000000..ea6a335 --- /dev/null +++ b/nadabot/api.py @@ -0,0 +1 @@ +# nadabot api \ No newline at end of file diff --git a/nadabot/apps.py b/nadabot/apps.py new file mode 100644 index 0000000..cc21c7d --- /dev/null +++ b/nadabot/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class NadabotConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "nadabot" diff --git a/nadabot/migrations/0001_initial.py b/nadabot/migrations/0001_initial.py new file mode 100644 index 0000000..2deb505 --- /dev/null +++ b/nadabot/migrations/0001_initial.py @@ -0,0 +1,421 @@ +# Generated by Django 5.0.6 on 2024-07-11 14:21 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("accounts", "0002_account_near_social_profile_data"), + ] + + operations = [ + migrations.CreateModel( + name="NadabotRegistry", + fields=[ + ( + "id", + models.OneToOneField( + help_text="Nadabot registry id.", + on_delete=django.db.models.deletion.CASCADE, + primary_key=True, + related_name="registry_id", + serialize=False, + to="accounts.account", + ), + ), + ( + "default_human_threshold", + models.PositiveIntegerField( + default=0, + help_text="default human threshold.", + verbose_name="default human threshold", + ), + ), + ( + "created_at", + models.DateTimeField( + help_text="Registry creation date.", verbose_name="created at" + ), + ), + ( + "updated_at", + models.DateTimeField( + help_text="Registry last update date.", + verbose_name="updated at", + ), + ), + ( + "source_metadata", + models.JSONField( + help_text="nadabot registry source metadata.", + verbose_name="source metadata", + ), + ), + ( + "admins", + models.ManyToManyField( + help_text="registry admins.", + related_name="nadabot_admin_registries", + to="accounts.account", + ), + ), + ( + "blacklisted_accounts", + models.ManyToManyField( + help_text="registry blacklisted accounts.", + related_name="registry_blacklisted_in", + to="accounts.account", + ), + ), + ( + "owner", + models.ForeignKey( + help_text="Nadabot Registry owner.", + on_delete=django.db.models.deletion.CASCADE, + related_name="nadabot_owner_registries", + to="accounts.account", + ), + ), + ], + ), + migrations.CreateModel( + name="Provider", + fields=[ + ( + "id", + models.AutoField( + help_text="Provider id.", + primary_key=True, + serialize=False, + verbose_name="provider id", + ), + ), + ( + "on_chain_id", + models.IntegerField( + help_text="Provider id in contract", + verbose_name="contract provider id", + ), + ), + ( + "method_name", + models.CharField( + help_text="Method name of the external contract that is the source of this provider.", + max_length=100, + verbose_name="method name", + ), + ), + ( + "name", + models.CharField( + help_text="Name of the provider, e.g. 'I Am Human'.", + max_length=64, + verbose_name="provider name", + ), + ), + ( + "description", + models.TextField( + blank=True, + help_text="Description of the provider.", + null=True, + verbose_name="description", + ), + ), + ( + "status", + models.CharField( + choices=[ + ("Pending", "Pending"), + ("Active", "Active"), + ("Deactivated", "Deactivated"), + ], + db_index=True, + help_text="Status of the provider.", + max_length=20, + verbose_name="status", + ), + ), + ( + "admin_notes", + models.TextField( + blank=True, + help_text="Admin notes, e.g. reason for flagging or marking inactive.", + null=True, + verbose_name="admin notes", + ), + ), + ( + "default_weight", + models.PositiveIntegerField( + help_text="Default weight for this provider, e.g. 100.", + verbose_name="default weight", + ), + ), + ( + "gas", + models.BigIntegerField( + blank=True, + help_text="Custom gas amount required.", + null=True, + verbose_name="gas", + ), + ), + ( + "tags", + models.JSONField( + blank=True, + help_text="Optional tags.", + null=True, + verbose_name="tags", + ), + ), + ( + "icon_url", + models.URLField( + blank=True, + help_text="Optional icon URL.", + null=True, + verbose_name="icon URL", + ), + ), + ( + "external_url", + models.URLField( + blank=True, + help_text="Optional external URL.", + null=True, + verbose_name="external URL", + ), + ), + ( + "submitted_at", + models.DateTimeField( + db_index=True, + help_text="Timestamp of when this provider was submitted.", + verbose_name="submitted at (milliseconds)", + ), + ), + ( + "stamp_validity_ms", + models.BigIntegerField( + blank=True, + db_index=True, + help_text="Milliseconds that stamps from this provider are valid for before they expire.", + null=True, + verbose_name="stamp validity", + ), + ), + ( + "account_id_arg_name", + models.CharField( + help_text="Name of account ID argument, e.g. 'account_id' or 'accountId' or 'account'.", + max_length=100, + verbose_name="account ID argument name", + ), + ), + ( + "custom_args", + models.CharField( + help_text="Custom args as Base64VecU8.", + null=True, + verbose_name="custom args", + ), + ), + ( + "contract", + models.ForeignKey( + help_text="Contract ID of the external contract that is the source of this provider.", + max_length=100, + on_delete=django.db.models.deletion.CASCADE, + related_name="provider_instances", + to="accounts.account", + verbose_name="contract ID", + ), + ), + ( + "registry", + models.ForeignKey( + help_text="registry under which provider was registered", + on_delete=django.db.models.deletion.CASCADE, + related_name="providers", + to="nadabot.nadabotregistry", + verbose_name="registry", + ), + ), + ( + "submitted_by", + models.ForeignKey( + help_text="User who submitted this provider.", + max_length=100, + on_delete=django.db.models.deletion.CASCADE, + related_name="providers_submitted", + to="accounts.account", + verbose_name="submitted by", + ), + ), + ], + ), + migrations.CreateModel( + name="Group", + fields=[ + ( + "id", + models.PositiveIntegerField( + help_text="Group id.", + primary_key=True, + serialize=False, + verbose_name="group id", + ), + ), + ( + "name", + models.CharField( + help_text="name given to the group", + max_length=100, + verbose_name="group name", + ), + ), + ( + "rule_type", + models.CharField( + choices=[ + ("Highest", "Highest"), + ("Lowest", "Lowest"), + ("Sum", "Sum"), + ("Diminishing_returns", "Diminishing Returns"), + ("Increasing_returns", "Increasing Returns"), + ], + help_text="The rule this group uses.", + max_length=100, + null=True, + verbose_name="rule type", + ), + ), + ( + "rule_val", + models.PositiveIntegerField( + help_text="An optional value that the group's rule choices might carry.", + null=True, + verbose_name="rule value", + ), + ), + ( + "created_at", + models.DateTimeField( + help_text="Group creation date.", verbose_name="created at" + ), + ), + ( + "updated_at", + models.DateTimeField( + help_text="Group last update date.", verbose_name="updated at" + ), + ), + ( + "providers", + models.ManyToManyField( + help_text="group providers.", + related_name="groups", + to="nadabot.provider", + ), + ), + ], + ), + migrations.CreateModel( + name="Stamp", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "verified_at", + models.DateField( + help_text="The date of verification.", + verbose_name="verification date", + ), + ), + ( + "provider", + models.ForeignKey( + help_text="The provider the user verified with.", + on_delete=django.db.models.deletion.CASCADE, + related_name="stamps", + to="nadabot.provider", + verbose_name="provider", + ), + ), + ( + "user", + models.ForeignKey( + help_text="The user who earned the stamp.", + on_delete=django.db.models.deletion.CASCADE, + related_name="stamps", + to="accounts.account", + verbose_name="user", + ), + ), + ], + ), + migrations.CreateModel( + name="BlackList", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "reason", + models.TextField( + blank=True, + help_text="Reason account is blacklisted", + null=True, + verbose_name="blacklist reason", + ), + ), + ( + "date_blacklisted", + models.DateTimeField( + help_text="Blacklist Entry Date.", verbose_name="blacklisted on" + ), + ), + ( + "account", + models.ForeignKey( + help_text="Nadabot Registry Blacklisted from.", + on_delete=django.db.models.deletion.CASCADE, + related_name="registries_blacklisted_from", + to="accounts.account", + ), + ), + ( + "registry", + models.ForeignKey( + help_text="blacklist entries by this registry", + on_delete=django.db.models.deletion.CASCADE, + related_name="blacklists", + to="nadabot.nadabotregistry", + verbose_name="registry", + ), + ), + ], + options={ + "unique_together": {("registry", "account")}, + }, + ), + ] diff --git a/nadabot/migrations/__init__.py b/nadabot/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nadabot/models.py b/nadabot/models.py new file mode 100644 index 0000000..971d9da --- /dev/null +++ b/nadabot/models.py @@ -0,0 +1,277 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from accounts.models import Account + + +class ProviderStatus(models.TextChoices): + PENDING = "Pending", "Pending" + ACTIVE = "Active", "Active" + DEACTIVATED = "Deactivated", "Deactivated" + + +class RuleType(models.TextChoices): + HIGHEST = 'Highest' + LOWEST = 'Lowest' + SUM = 'Sum' + DIMINISHING_RETURNS = 'Diminishing_returns' + INCREASING_RETURNS = 'Increasing_returns' + + + +class NadabotRegistry(models.Model): + id = models.OneToOneField( + Account, + related_name="registry_id", + on_delete=models.CASCADE, + primary_key=True, + help_text=_("Nadabot registry id."), + ) + owner = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="nadabot_owner_registries", + null=False, + help_text=_("Nadabot Registry owner."), + ) + default_human_threshold = models.PositiveIntegerField( + _("default human threshold"), + default=0, + help_text=_("default human threshold."), + ) + created_at = models.DateTimeField( + _("created at"), + null=False, + help_text=_("Registry creation date."), + ) + updated_at = models.DateTimeField( + _("updated at"), + help_text=_("Registry last update date."), + ) + admins = models.ManyToManyField( + Account, + related_name="nadabot_admin_registries", + help_text=_("registry admins."), + ) + blacklisted_accounts = models.ManyToManyField( + Account, + related_name="registry_blacklisted_in", + help_text=_("registry blacklisted accounts."), + ) + source_metadata = models.JSONField( + _("source metadata"), + null=False, + help_text=_("nadabot registry source metadata."), + ) + + +class BlackList(models.Model): + registry = models.ForeignKey( + NadabotRegistry, + on_delete=models.CASCADE, + related_name="blacklists", + verbose_name=_("registry"), + help_text=_("blacklist entries by this registry") + ) + account = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="registries_blacklisted_from", + null=False, + help_text=_("Nadabot Registry Blacklisted from."), + ) + reason = models.TextField( + _("blacklist reason"), + blank=True, + null=True, + help_text=_("Reason account is blacklisted") + ) + date_blacklisted = models.DateTimeField( + _("blacklisted on"), + null=False, + help_text=_("Blacklist Entry Date."), + ) + + class Meta: + unique_together = ['registry', 'account'] + +class Provider(models.Model): + id = models.AutoField( + _("provider id"), + primary_key=True, + help_text=_("Provider id.") + ) + on_chain_id = models.IntegerField( + _("contract provider id"), + null=False, + help_text=_("Provider id in contract"), + ) + contract = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="provider_instances", + verbose_name=_("contract ID"), + max_length=100, + null=False, + help_text=_("Contract ID of the external contract that is the source of this provider.") + ) + method_name = models.CharField( + _("method name"), + max_length=100, + null=False, + help_text=_("Method name of the external contract that is the source of this provider.") + ) + name = models.CharField( + _("provider name"), + max_length=64, + null=False, + help_text=_("Name of the provider, e.g. 'I Am Human'.") + ) + description = models.TextField( + _("description"), + blank=True, + null=True, + help_text=_("Description of the provider.") + ) + status = models.CharField( + _("status"), + max_length=20, + choices=ProviderStatus, + null=False, + db_index=True, + help_text=_("Status of the provider.") + ) + admin_notes = models.TextField( + _("admin notes"), + blank=True, + null=True, + help_text=_("Admin notes, e.g. reason for flagging or marking inactive.") + ) + default_weight = models.PositiveIntegerField( + _("default weight"), + null=False, + help_text=_("Default weight for this provider, e.g. 100.") + ) + gas = models.BigIntegerField( + _("gas"), + blank=True, + null=True, + help_text=_("Custom gas amount required.") + ) + tags = models.JSONField( + _("tags"), + blank=True, + null=True, + help_text=_("Optional tags.") + ) + icon_url = models.URLField( + _("icon URL"), + blank=True, + null=True, + help_text=_("Optional icon URL.") + ) + external_url = models.URLField( + _("external URL"), + blank=True, + null=True, + help_text=_("Optional external URL.") + ) + submitted_by = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="providers_submitted", + verbose_name=_("submitted by"), + max_length=100, + null=False, + help_text=_("User who submitted this provider.") + ) + submitted_at = models.DateTimeField( + _("submitted at (milliseconds)"), + null=False, + db_index=True, + help_text=_("Timestamp of when this provider was submitted.") + ) + stamp_validity_ms = models.BigIntegerField( + _("stamp validity"), + blank=True, + null=True, + db_index=True, + help_text=_("Milliseconds that stamps from this provider are valid for before they expire.") + ) + account_id_arg_name = models.CharField( + _("account ID argument name"), + max_length=100, + null=False, + help_text=_("Name of account ID argument, e.g. 'account_id' or 'accountId' or 'account'.") + ) + custom_args = models.CharField( + _("custom args"), + null=True, + help_text=_("Custom args as Base64VecU8.") + ) + registry = models.ForeignKey( + NadabotRegistry, + on_delete=models.CASCADE, + related_name="providers", + verbose_name=_("registry"), + help_text=_("registry under which provider was registered") + ) + +class Stamp(models.Model): + user = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="stamps", + verbose_name=_("user"), + help_text=_("The user who earned the stamp.") + ) + provider = models.ForeignKey( + Provider, + on_delete=models.CASCADE, + related_name="stamps", + verbose_name=_("provider"), + help_text=_("The provider the user verified with.") + ) + verified_at = models.DateField( + _("verification date"), + help_text=_("The date of verification.") + ) + +class Group(models.Model): + id = models.PositiveIntegerField( + _("group id"), + primary_key=True, + help_text=_("Group id."), + ) + name = models.CharField( + _("group name"), + max_length=100, + null=False, + help_text=_("name given to the group") + ) + rule_type = models.CharField( + _("rule type"), + choices=RuleType, + max_length=100, + null=True, + help_text=_("The rule this group uses.") + ) + rule_val = models.PositiveIntegerField( + _("rule value"), + null=True, + help_text=_("An optional value that the group's rule choices might carry."), + ) + providers = models.ManyToManyField( + Provider, + related_name="groups", + help_text=_("group providers."), + ) + created_at = models.DateTimeField( + _("created at"), + null=False, + help_text=_("Group creation date."), + ) + updated_at = models.DateTimeField( + _("updated at"), + help_text=_("Group last update date."), + ) diff --git a/nadabot/serializers.py b/nadabot/serializers.py new file mode 100644 index 0000000..270a1d4 --- /dev/null +++ b/nadabot/serializers.py @@ -0,0 +1,15 @@ +from rest_framework.serializers import ModelSerializer, SerializerMethodField + +from .models import NadabotRegistry, Provider, Stamp + + +class NadabotSerializer(ModelSerializer): + class Meta: + model = NadabotRegistry + fields = "__all__" # TODO: potentially adjust this e.g. for formatting of datetimes, adding convenience fields etc + + +class ProviderSerializer(ModelSerializer): + class Meta: + model = Provider + fields = "__all__" # TODO: potentially adjust this e.g. for formatting of datetimes, adding convenience fields etc diff --git a/nadabot/tests.py b/nadabot/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/nadabot/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/nadabot/utils.py b/nadabot/utils.py new file mode 100644 index 0000000..0aaa7a8 --- /dev/null +++ b/nadabot/utils.py @@ -0,0 +1,16 @@ +import re + +from django.conf import settings + +BASE_PATTERN = ( + r"nadabot\.testnet" + if settings.ENVIRONMENT == "testnet" +else r"v\d+(?:new)?\.[a-zA-Z]+\.nadabot\.near" +) + + +def match_nadabot_registry_pattern(receiver): + """Matches nadabot subaccounts for registry.""" + pattern = f"^{BASE_PATTERN}$" + print(F"the TLA {pattern}, {receiver}") + return bool(re.match(pattern, receiver)) diff --git a/nadabot/views.py b/nadabot/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/nadabot/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From f6745dc82e2a4557422012734688dcb6c27e4378 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:40:36 -0400 Subject: [PATCH 235/454] add chains app & Chain model (#41) --- accounts/migrations/0004_account_chain.py | 28 +++++++++ .../migrations/0005_alter_account_chain.py | 26 ++++++++ accounts/models.py | 10 ++++ base/settings.py | 2 + chains/__init__.py | 0 chains/admin.py | 18 ++++++ chains/apps.py | 6 ++ chains/migrations/0001_initial.py | 60 +++++++++++++++++++ chains/migrations/0002_add_near_chain.py | 25 ++++++++ chains/migrations/__init__.py | 0 chains/models.py | 48 +++++++++++++++ chains/tests.py | 3 + chains/views.py | 3 + pyproject.toml | 1 + 14 files changed, 230 insertions(+) create mode 100644 accounts/migrations/0004_account_chain.py create mode 100644 accounts/migrations/0005_alter_account_chain.py create mode 100644 chains/__init__.py create mode 100644 chains/admin.py create mode 100644 chains/apps.py create mode 100644 chains/migrations/0001_initial.py create mode 100644 chains/migrations/0002_add_near_chain.py create mode 100644 chains/migrations/__init__.py create mode 100644 chains/models.py create mode 100644 chains/tests.py create mode 100644 chains/views.py diff --git a/accounts/migrations/0004_account_chain.py b/accounts/migrations/0004_account_chain.py new file mode 100644 index 0000000..5fcbb89 --- /dev/null +++ b/accounts/migrations/0004_account_chain.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.4 on 2024-07-15 18:23 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0003_alter_account_options"), + ("chains", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="account", + name="chain", + field=models.ForeignKey( + blank=True, + help_text="Blockchain this account is located on.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="accounts", + related_query_name="account", + to="chains.chain", + ), + ), + ] diff --git a/accounts/migrations/0005_alter_account_chain.py b/accounts/migrations/0005_alter_account_chain.py new file mode 100644 index 0000000..2dbe9dd --- /dev/null +++ b/accounts/migrations/0005_alter_account_chain.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.4 on 2024-07-15 18:26 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0004_account_chain"), + ("chains", "0002_add_near_chain"), + ] + + operations = [ + migrations.AlterField( + model_name="account", + name="chain", + field=models.ForeignKey( + help_text="Blockchain this account is located on.", + on_delete=django.db.models.deletion.CASCADE, + related_name="accounts", + related_query_name="account", + to="chains.chain", + ), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 25e647b..35afcf4 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _ from base.logging import logger +from chains.models import Chain class Account(models.Model): @@ -17,6 +18,15 @@ class Account(models.Model): validators=[], help_text=_("On-chain account address."), ) + chain = models.ForeignKey( + Chain, + null=False, + blank=False, + on_delete=models.CASCADE, + related_name="accounts", + related_query_name="account", + help_text=_("Blockchain this account is located on."), + ) total_donations_in_usd = models.DecimalField( _("total donations received in USD"), max_digits=20, diff --git a/base/settings.py b/base/settings.py index 65667bc..3919a7e 100644 --- a/base/settings.py +++ b/base/settings.py @@ -92,6 +92,7 @@ "django.contrib.staticfiles", "rest_framework", "drf_spectacular", + "django_extensions", "corsheaders", # "cachalot", "celery", @@ -104,6 +105,7 @@ "pots", "tokens", "nadabot", + "chains", ] DEFAULT_PAGE_SIZE = 30 diff --git a/chains/__init__.py b/chains/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chains/admin.py b/chains/admin.py new file mode 100644 index 0000000..7a9ede9 --- /dev/null +++ b/chains/admin.py @@ -0,0 +1,18 @@ +from django.contrib import admin + +from .models import Chain + + +@admin.register(Chain) +class ChainAdmin(admin.ModelAdmin): + list_display = ( + "name", + "name_slug", + "rpc_url", + "explorer_url", + "evm_compat", + "evm_chain_id", + ) + search_fields = ("name", "name_slug", "rpc_url", "explorer_url", "evm_chain_id") + list_filter = ("evm_compat",) + ordering = ("name",) diff --git a/chains/apps.py b/chains/apps.py new file mode 100644 index 0000000..e01f14f --- /dev/null +++ b/chains/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ChainsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "chains" diff --git a/chains/migrations/0001_initial.py b/chains/migrations/0001_initial.py new file mode 100644 index 0000000..812d7c5 --- /dev/null +++ b/chains/migrations/0001_initial.py @@ -0,0 +1,60 @@ +# Generated by Django 5.0.4 on 2024-07-15 18:23 + +import django_extensions.db.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Chain", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(db_index=True, max_length=32, unique=True)), + ( + "name_slug", + django_extensions.db.fields.AutoSlugField( + blank=True, + editable=False, + max_length=32, + populate_from=("name",), + unique=True, + ), + ), + ("rpc_url", models.URLField()), + ("explorer_url", models.URLField()), + ("evm_compat", models.BooleanField()), + ( + "evm_chain_id", + models.IntegerField(blank=True, db_index=True, null=True), + ), + ], + options={ + "ordering": ("name",), + }, + ), + migrations.AddConstraint( + model_name="chain", + constraint=models.CheckConstraint( + check=models.Q( + models.Q(("evm_chain_id__isnull", False), ("evm_compat", True)), + models.Q(("evm_chain_id__isnull", True), ("evm_compat", False)), + _connector="OR", + ), + name="evm_chain_id_check", + ), + ), + ] diff --git a/chains/migrations/0002_add_near_chain.py b/chains/migrations/0002_add_near_chain.py new file mode 100644 index 0000000..73fec6f --- /dev/null +++ b/chains/migrations/0002_add_near_chain.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.4 on 2024-07-15 18:24 + +from django.db import migrations + + +def create_near_chain(apps, schema_editor): + Chain = apps.get_model("chains", "Chain") + # Create the "near" chain + near_chain, created = Chain.objects.get_or_create( + name="NEAR", defaults={"evm_compat": False} + ) + + # Set "near" chain as the default for all existing accounts + Account = apps.get_model("accounts", "Account") + Account.objects.update(chain=near_chain) + print("Updated all accounts to use NEAR chain") + + +class Migration(migrations.Migration): + + dependencies = [("chains", "0001_initial"), ("accounts", "0004_account_chain")] + + operations = [ + migrations.RunPython(create_near_chain), + ] diff --git a/chains/migrations/__init__.py b/chains/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chains/models.py b/chains/models.py new file mode 100644 index 0000000..0545a4c --- /dev/null +++ b/chains/models.py @@ -0,0 +1,48 @@ +from django.db import models +from django_extensions.db.fields import AutoSlugField + + +class Chain(models.Model): + ############################ + # BLOCKCHAIN MODEL FIELDS + ############################ + + # GENERAL + name = models.CharField( + max_length=32, unique=True, blank=False, null=False, db_index=True + ) + name_slug = AutoSlugField( + populate_from=("name",), + max_length=32, + unique=True, + blank=False, + null=False, + db_index=True, + ) + + # URLS + rpc_url = models.URLField() + explorer_url = models.URLField() + + # EVM + evm_compat = models.BooleanField(null=False, blank=False) + evm_chain_id = models.IntegerField(null=True, blank=True, db_index=True) + + ############################ + # META + ############################ + class Meta: + # default record ordering + ordering = ("name",) + + constraints = [ + # if an evm then the evm_chain_id id must be set otherwise should be null + models.CheckConstraint( + name="evm_chain_id_check", + check=models.Q(evm_compat=True, evm_chain_id__isnull=False) + | models.Q(evm_compat=False, evm_chain_id__isnull=True), + ), + ] + + def __str__(self): + return self.name diff --git a/chains/tests.py b/chains/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/chains/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/chains/views.py b/chains/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/chains/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/pyproject.toml b/pyproject.toml index fee60dd..d31b0bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ sentry-sdk = { extras = ["django"], version = "^1.45.0" } watchtower = "^3.1.0" django-cors-headers = "^4.3.1" drf-spectacular = "^0.27.2" +django-extensions = "^3.2.3" [tool.poetry.group.dev.dependencies] black = "^24.3.0" From fab5381c9570401a69da39c43c94ef4ca018ff2a Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:34:49 -0400 Subject: [PATCH 236/454] default to NEAR chain when creating new account --- accounts/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/accounts/models.py b/accounts/models.py index 35afcf4..65a869d 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -137,6 +137,9 @@ def fetch_near_social_profile_data(self, should_save=True): def save(self, *args, **kwargs): if self._state.adding: # If the account is being created (not updated) + if not self.chain_id: + # default to NEAR chain when none is provided + self.chain = Chain.objects.get(name="NEAR") self.fetch_near_social_profile_data( False # don't save yet as we want to avoid infinite loop ) From 8040b0b527a4ad624c6d01d64ecd0fc1b9f62a5a Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:48:17 -0400 Subject: [PATCH 237/454] add debug logging to handler.py for memory usage & timing --- indexer_app/handler.py | 90 ++++++++++++++++++++++++++++++++---------- indexer_app/logging.py | 12 +++++- pyproject.toml | 1 + 3 files changed, 82 insertions(+), 21 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index e65d0b1..ea93cb2 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -1,5 +1,6 @@ import base64 import json +import time from datetime import datetime from django.conf import settings @@ -7,21 +8,22 @@ from near_lake_framework import near_primitives from base.utils import convert_ns_to_utc -from pots.utils import match_pot_factory_pattern, match_pot_subaccount_pattern from nadabot.utils import match_nadabot_registry_pattern -from .logging import logger -from .utils import ( # handle_batch_donations, +from pots.utils import match_pot_factory_pattern, match_pot_subaccount_pattern + +from .logging import log_memory_usage, logger +from .utils import handle_add_nadabot_admin # handle_batch_donations, +from .utils import ( handle_add_stamp, handle_default_list_status_change, handle_list_admin_removal, handle_list_registration_update, handle_list_upvote, - handle_add_nadabot_admin, - handle_new_nadabot_registry, handle_new_donation, handle_new_group, handle_new_list, handle_new_list_registration, + handle_new_nadabot_registry, handle_new_pot, handle_new_pot_factory, handle_new_provider, @@ -29,17 +31,20 @@ handle_payout_challenge_response, handle_pot_application, handle_pot_application_status_change, + handle_pot_config_update, + handle_registry_blacklist_action, + handle_registry_unblacklist_action, handle_set_payouts, handle_social_profile_update, handle_transfer_payout, handle_update_default_human_threshold, - handle_registry_blacklist_action, - handle_registry_unblacklist_action, - handle_pot_config_update, ) async def handle_streamer_message(streamer_message: near_primitives.StreamerMessage): + start_time = time.time() + log_memory_usage("Start of handle_streamer_message") + block_timestamp = streamer_message.block.header.timestamp block_height = streamer_message.block.header.height now_datetime = datetime.fromtimestamp(block_timestamp / 1000000000) @@ -50,12 +55,19 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess logger.info( f"Block Height: {block_height}, Block Timestamp: {block_timestamp} ({formatted_date})" ) + logger.info( + f"Time after processing block info: {time.time() - start_time:.4f} seconds" + ) + log_memory_usage("After processing block info") # if block_height == 111867204: # with open("indexer_outcome2.json", "w") as file: # file.write(f"{streamer_message}") - for shard in streamer_message.shards: - for receipt_execution_outcome in shard.receipt_execution_outcomes: + for shard_index, shard in enumerate(streamer_message.shards): + shard_start_time = time.time() + for outcome_index, receipt_execution_outcome in enumerate( + shard.receipt_execution_outcomes + ): # we only want to proceed if the tx succeeded if ( "SuccessReceiptId" @@ -67,7 +79,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess receiver_id = receipt_execution_outcome.receipt.receiver_id if ( receiver_id != settings.NEAR_SOCIAL_CONTRACT_ADDRESS - and not receiver_id.endswith((settings.POTLOCK_TLA, settings.NADABOT_TLA)) + and not receiver_id.endswith( + (settings.POTLOCK_TLA, settings.NADABOT_TLA) + ) ): continue # 1. HANDLE LOGS @@ -75,6 +89,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess receipt = receipt_execution_outcome.receipt signer_id = receipt.receipt["Action"]["signer_id"] + log_processing_start = time.time() for log_index, log in enumerate( receipt_execution_outcome.execution_outcome.outcome.logs, start=1 ): @@ -85,20 +100,32 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess event_name = parsed_log.get("event") print("parsa parsa...", parsed_log) if event_name == "update_pot_config": - await handle_pot_config_update(parsed_log.get("data")[0], receiver_id) + await handle_pot_config_update( + parsed_log.get("data")[0], receiver_id + ) if event_name == "add_or_update_provider": - await handle_new_provider(parsed_log.get("data")[0], receiver_id, signer_id) + await handle_new_provider( + parsed_log.get("data")[0], receiver_id, signer_id + ) elif event_name == "add_stamp": - await handle_add_stamp(parsed_log.get("data")[0], receiver_id, signer_id) + await handle_add_stamp( + parsed_log.get("data")[0], receiver_id, signer_id + ) elif event_name == "update_default_human_threshold": - await handle_update_default_human_threshold(parsed_log.get("data")[0], receiver_id) + await handle_update_default_human_threshold( + parsed_log.get("data")[0], receiver_id + ) if event_name == "add_or_update_group": - await handle_new_group(parsed_log.get("data")[0], now_datetime) + await handle_new_group(parsed_log.get("data")[0], now_datetime) if event_name == "blacklist_account": - await handle_registry_blacklist_action(parsed_log.get("data")[0], receiver_id, now_datetime) + await handle_registry_blacklist_action( + parsed_log.get("data")[0], receiver_id, now_datetime + ) if event_name == "unblacklist_account": - await handle_registry_unblacklist_action(parsed_log.get("data")[0], receiver_id, now_datetime) + await handle_registry_unblacklist_action( + parsed_log.get("data")[0], receiver_id, now_datetime + ) except json.JSONDecodeError: logger.warning( f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" @@ -109,6 +136,11 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess # TODO: handle set_source_metadata logs for various contracts + logger.info( + f"Time to process logs for receipt {receipt_execution_outcome.receipt.receipt_id}: {time.time() - log_processing_start:.4f} seconds" + ) + log_memory_usage("After processing logs") + # 2. HANDLE METHOD CALLS # Skip if the tx failed # if ( @@ -120,6 +152,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess # # consider logging failures to logging service; for now, just skip # print("here we are...") # continue + method_call_processing_start = time.time() LISTS_CONTRACT = "lists." + settings.POTLOCK_TLA DONATE_CONTRACT = "donate." + settings.POTLOCK_TLA @@ -170,8 +203,12 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess await handle_new_pot_factory( args_dict, receiver_id, now_datetime ) - elif match_nadabot_registry_pattern(receiver_id): # matches registries in the pattern, version(v1).env(staging).nadabot.near - await handle_new_nadabot_registry(args_dict, receiver_id, now_datetime) + elif match_nadabot_registry_pattern( + receiver_id + ): # matches registries in the pattern, version(v1).env(staging).nadabot.near + await handle_new_nadabot_registry( + args_dict, receiver_id, now_datetime + ) elif match_pot_subaccount_pattern(receiver_id): logger.info( f"new pot deployment: {args_dict}, {action}" @@ -388,3 +425,16 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess logger.error(f"Error in indexer handler:\n{e}") # with open("indexer_error.txt", "a") as file: # file.write(f"{e}\n") + logger.info( + f"Time to process method calls for receipt {receipt_execution_outcome.receipt.receipt_id}: {time.time() - method_call_processing_start:.4f} seconds" + ) + log_memory_usage("After processing method calls") + logger.info( + f"Time to process shard {shard_index}: {time.time() - shard_start_time:.4f} seconds" + ) + log_memory_usage(f"After processing shard {shard_index}") + + logger.info( + f"Total time to process streamer message: {time.time() - start_time:.4f} seconds" + ) + log_memory_usage("End of handle_streamer_message") diff --git a/indexer_app/logging.py b/indexer_app/logging.py index 973684a..3627087 100644 --- a/indexer_app/logging.py +++ b/indexer_app/logging.py @@ -1,3 +1,13 @@ import logging -logger = logging.getLogger("indexer") \ No newline at end of file +import psutil + +logger = logging.getLogger("indexer") + + +def log_memory_usage(stage): + process = psutil.Process() + memory_info = process.memory_info() + logger.info( + f"{stage} - RSS: {memory_info.rss / 1024 / 1024:.2f} MB, VMS: {memory_info.vms / 1024 / 1024:.2f} MB" + ) diff --git a/pyproject.toml b/pyproject.toml index d31b0bb..e7bcb60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ watchtower = "^3.1.0" django-cors-headers = "^4.3.1" drf-spectacular = "^0.27.2" django-extensions = "^3.2.3" +psutil = "^6.0.0" [tool.poetry.group.dev.dependencies] black = "^24.3.0" From d6d54d71c8986bf6f8c1c00de1ce2cbec45f785b Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 17 Jul 2024 10:02:10 -0400 Subject: [PATCH 238/454] add additional debug logs for streamer processing time --- indexer_app/handler.py | 9 ++++++--- indexer_app/tasks.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index ea93cb2..4e95e59 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -1,3 +1,4 @@ +import asyncio import base64 import json import time @@ -48,9 +49,11 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess block_timestamp = streamer_message.block.header.timestamp block_height = streamer_message.block.header.height now_datetime = datetime.fromtimestamp(block_timestamp / 1000000000) - await cache.aset( - "block_height", block_height - ) # TODO: add custom timeout if it should be valid for longer than default (5 minutes) + # Fire and forget the cache update + asyncio.create_task(cache.aset("block_height", block_height)) + # await cache.aset( + # "block_height", block_height + # ) # TODO: add custom timeout if it should be valid for longer than default (5 minutes) formatted_date = convert_ns_to_utc(block_timestamp) logger.info( f"Block Height: {block_height}, Block Timestamp: {block_timestamp} ({formatted_date})" diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 0cd5b27..fc29b8f 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -1,5 +1,6 @@ import asyncio import logging +import time from pathlib import Path import requests @@ -47,16 +48,44 @@ async def indexer(from_block: int, to_block: int): while True: try: + # Log time before fetching a new block + fetch_start_time = time.time() # streamer_message is the current block streamer_message = await streamer_messages_queue.get() + fetch_end_time = time.time() + logger.info( + f"Time to fetch new block: {fetch_end_time - fetch_start_time:.4f} seconds" + ) block_count += 1 + + # Log time before caching block height + cache_start_time = time.time() await cache_block_height( "current_block_height", streamer_message.block.header.height, block_count, streamer_message.block.header.timestamp, ) # current block height + cache_end_time = time.time() + + logger.info( + f"Time to cache block height: {cache_end_time - cache_start_time:.4f} seconds" + ) + + # Log time before handling the streamer message + handle_start_time = time.time() await handle_streamer_message(streamer_message) + handle_end_time = time.time() + logger.info( + f"Time to handle streamer message: {handle_end_time - handle_start_time:.4f} seconds" + ) + + # Log total time for one iteration + iteration_end_time = time.time() + logger.info( + f"Total time for one iteration: {iteration_end_time - fetch_start_time:.4f} seconds" + ) + except Exception as e: logger.error(f"Error in streamer_messages_queue: {e}") From 80831d5892e286d07d82e681a5e3fea4a3574bb4 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 17 Jul 2024 10:11:41 -0400 Subject: [PATCH 239/454] remove BlockHeight db write in cache_block_height --- indexer_app/tasks.py | 6 +- indexer_app/utils.py | 247 ++++++++++++++++++++----------------------- 2 files changed, 121 insertions(+), 132 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index fc29b8f..51644b1 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -22,6 +22,8 @@ from .logging import logger from .utils import cache_block_height, get_block_height +CURRENT_BLOCK_HEIGHT_KEY = "current_block_height" + async def indexer(from_block: int, to_block: int): """ @@ -61,7 +63,7 @@ async def indexer(from_block: int, to_block: int): # Log time before caching block height cache_start_time = time.time() await cache_block_height( - "current_block_height", + CURRENT_BLOCK_HEIGHT_KEY, streamer_message.block.header.height, block_count, streamer_message.block.header.timestamp, @@ -98,7 +100,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height - start_block = get_block_height("current_block_height") + start_block = get_block_height(CURRENT_BLOCK_HEIGHT_KEY) # start_block = 119_568_113 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer(start_block - 1, None)) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 46aeed7..8413748 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -15,7 +15,7 @@ from donations.models import Donation from indexer_app.models import BlockHeight from lists.models import List, ListRegistration, ListUpvote -from nadabot.models import Group, NadabotRegistry, Provider, Stamp, BlackList +from nadabot.models import BlackList, Group, NadabotRegistry, Provider, Stamp from pots.models import ( Pot, PotApplication, @@ -52,9 +52,7 @@ async def handle_social_profile_update(args_dict, receiver_id, signer_id): async def handle_new_nadabot_registry( - data: dict, - receiverId: str, - created_at: datetime + data: dict, receiverId: str, created_at: datetime ): logger.info(f"nadabot registry init... {data}") @@ -66,7 +64,7 @@ async def handle_new_nadabot_registry( owner=owner, created_at=created_at, updated_at=created_at, - source_metadata=data.get('source_metadata') + source_metadata=data.get("source_metadata"), ) if data.get("admins"): @@ -78,9 +76,7 @@ async def handle_new_nadabot_registry( async def handle_registry_blacklist_action( - data: dict, - receiverId: str, - created_at: datetime + data: dict, receiverId: str, created_at: datetime ): logger.info(f"Registry blacklist action....... {data}") @@ -94,32 +90,29 @@ async def handle_registry_blacklist_action( "registry": registry, "account": account, "reason": data.get("reason"), - "date_blacklisted": created_at + "date_blacklisted": created_at, } ) await BlackList.objects.abulk_create( - objs = [BlackList(**data) for data in bulk_obj], ignore_conflicts=True + objs=[BlackList(**data) for data in bulk_obj], ignore_conflicts=True ) except Exception as e: logger.error(f"Error in adding acct to blacklist: {e}") async def handle_registry_unblacklist_action( - data: dict, - receiverId: str, - created_at: datetime + data: dict, receiverId: str, created_at: datetime ): logger.info(f"Registry remove blacklisted accts....... {data}") try: registry, _ = await Account.objects.aget_or_create(id=receiverId) - entries = BlackList.objects.filter(account__in=data["accounts"]) + entries = BlackList.objects.filter(account__in=data["accounts"]) await entries.adelete() except Exception as e: logger.error(f"Error in removing acct from blacklist: {e}") - async def handle_new_pot( data: dict, receiver_id: str, @@ -216,72 +209,71 @@ async def handle_new_pot( logger.error(f"Failed to handle new pot, Error: {e}") - async def handle_pot_config_update( log_data: dict, receiver_id: str, ): - try: - data = log_data - logger.info(f"asserting involved accts.... {receiver_id}") - if data.get("chef"): - chef, _ = await Account.objects.aget_or_create(id=data["chef"]) - owner, _ = await Account.objects.aget_or_create(id=data["owner"]) - logger.info(f"building updated config for pot {receiver_id}") - pot_config = { - "deployer": data["deployed_by"], - "source_metadata": data["source_metadata"], - "owner_id": data["owner"], - "chef_id": data.get("chef"), - "name": data["pot_name"], - "description": data["pot_description"], - "max_approved_applicants": data["max_projects"], - "base_currency": data["base_currency"], - "application_start": datetime.fromtimestamp( - data["application_start_ms"] / 1000 - ), - "application_end": datetime.fromtimestamp( - data["application_end_ms"] / 1000 - ), - "matching_round_start": datetime.fromtimestamp( - data["public_round_start_ms"] / 1000 - ), - "matching_round_end": datetime.fromtimestamp( - data["public_round_end_ms"] / 1000 - ), - "registry_provider": data["registry_provider"], - "min_matching_pool_donation_amount": data[ - "min_matching_pool_donation_amount" - ], - "sybil_wrapper_provider": data["sybil_wrapper_provider"], - "custom_sybil_checks": data.get("custom_sybil_checks"), - "custom_min_threshold_score": data.get("custom_min_threshold_score"), - "referral_fee_matching_pool_basis_points": data[ - "referral_fee_matching_pool_basis_points" - ], - "referral_fee_public_round_basis_points": data[ - "referral_fee_public_round_basis_points" - ], - "chef_fee_basis_points": data["chef_fee_basis_points"], - "matching_pool_balance": data["matching_pool_balance"], - "total_public_donations": data["total_public_donations"], - "public_donations_count": data["public_donations_count"], - "cooldown_period_ms": data["cooldown_end_ms"], - "all_paid_out": data["all_paid_out"], - "protocol_config_provider": data["protocol_config_provider"], - } + try: + data = log_data + logger.info(f"asserting involved accts.... {receiver_id}") + if data.get("chef"): + chef, _ = await Account.objects.aget_or_create(id=data["chef"]) + owner, _ = await Account.objects.aget_or_create(id=data["owner"]) + logger.info(f"building updated config for pot {receiver_id}") + pot_config = { + "deployer": data["deployed_by"], + "source_metadata": data["source_metadata"], + "owner_id": data["owner"], + "chef_id": data.get("chef"), + "name": data["pot_name"], + "description": data["pot_description"], + "max_approved_applicants": data["max_projects"], + "base_currency": data["base_currency"], + "application_start": datetime.fromtimestamp( + data["application_start_ms"] / 1000 + ), + "application_end": datetime.fromtimestamp( + data["application_end_ms"] / 1000 + ), + "matching_round_start": datetime.fromtimestamp( + data["public_round_start_ms"] / 1000 + ), + "matching_round_end": datetime.fromtimestamp( + data["public_round_end_ms"] / 1000 + ), + "registry_provider": data["registry_provider"], + "min_matching_pool_donation_amount": data[ + "min_matching_pool_donation_amount" + ], + "sybil_wrapper_provider": data["sybil_wrapper_provider"], + "custom_sybil_checks": data.get("custom_sybil_checks"), + "custom_min_threshold_score": data.get("custom_min_threshold_score"), + "referral_fee_matching_pool_basis_points": data[ + "referral_fee_matching_pool_basis_points" + ], + "referral_fee_public_round_basis_points": data[ + "referral_fee_public_round_basis_points" + ], + "chef_fee_basis_points": data["chef_fee_basis_points"], + "matching_pool_balance": data["matching_pool_balance"], + "total_public_donations": data["total_public_donations"], + "public_donations_count": data["public_donations_count"], + "cooldown_period_ms": data["cooldown_end_ms"], + "all_paid_out": data["all_paid_out"], + "protocol_config_provider": data["protocol_config_provider"], + } - pot, created = await Pot.objects.aupdate_or_create( - id=receiver_id, defaults=pot_config - ) + pot, created = await Pot.objects.aupdate_or_create( + id=receiver_id, defaults=pot_config + ) - if data.get("admins"): - for admin_id in data["admins"]: - admin, _ = await Account.objects.aget_or_create(id=admin_id) - pot.admins.aadd(admin) - # await Pot.objects.filter(id=receiver_id).aupdate(**pot_config) - except Exception as e: - logger.error(f"Failed to update Pot config, Error: {e}") + if data.get("admins"): + for admin_id in data["admins"]: + admin, _ = await Account.objects.aget_or_create(id=admin_id) + pot.admins.aadd(admin) + # await Pot.objects.filter(id=receiver_id).aupdate(**pot_config) + except Exception as e: + logger.error(f"Failed to update Pot config, Error: {e}") async def handle_new_pot_factory(data: dict, receiver_id: str, created_at: datetime): @@ -798,6 +790,8 @@ async def handle_add_nadabot_admin(data, receiverId): await obj.admins.aadd(user) except Exception as e: logger.error(f"Failed to add nadabot admin, Error: {e}") + + # # TODO: Need to abstract some actions. # async def handle_batch_donations( # receiver_id: str, @@ -1032,10 +1026,8 @@ async def handle_new_donation( # } # await Account.objects.filter(id=recipient.id).aupdate(**acctUpdate) -async def handle_update_default_human_threshold( - data: dict, - receiverId: str -): + +async def handle_update_default_human_threshold(data: dict, receiverId: str): logger.info(f"update threshold data... {data}") try: @@ -1048,11 +1040,7 @@ async def handle_update_default_human_threshold( logger.error(f"Failed to update default threshold, Error: {e}") -async def handle_new_provider( - data: dict, - receiverId: str, - signerId: str -): +async def handle_new_provider(data: dict, receiverId: str, signerId: str): logger.info(f"new provider data: {data}, {receiverId}") data = data["provider"] @@ -1070,38 +1058,38 @@ async def handle_new_provider( # had the same id emitted for them, the id `13`, so we have to catch it and manoeuvre aroubd it. # TODO: REMOVE when next version contract is deployed, as this issue would be fixed. if provider_id == 13: - provider_id = await cache.aget("last_id", 1) - await cache.aset("last_id", provider_id+1) + provider_id = await cache.aget("last_id", 1) + await cache.aset("last_id", provider_id + 1) provider = await Provider.objects.aupdate_or_create( - on_chain_id=provider_id, - contract=contract, - method_name=data["method_name"], - name=data["provider_name"], - description=data.get("description"), - status=data["status"], - admin_notes=data.get("admin_notes"), - default_weight=data["default_weight"], - gas=data.get("gas"), - tags=data.get("tags"), - icon_url=data.get("icon_url"), - external_url=data.get("external_url"), - submitted_by_id=data["submitted_by"], - submitted_at = datetime.fromtimestamp(data.get("submitted_at_ms") / 1000), - stamp_validity_ms = datetime.fromtimestamp(data.get("stamp_validity_ms") / 1000) if data.get("stamp_validity_ms") else None, - account_id_arg_name = data["account_id_arg_name"], - custom_args = data.get("custom_args"), - registry_id=receiverId + on_chain_id=provider_id, + contract=contract, + method_name=data["method_name"], + name=data["provider_name"], + description=data.get("description"), + status=data["status"], + admin_notes=data.get("admin_notes"), + default_weight=data["default_weight"], + gas=data.get("gas"), + tags=data.get("tags"), + icon_url=data.get("icon_url"), + external_url=data.get("external_url"), + submitted_by_id=data["submitted_by"], + submitted_at=datetime.fromtimestamp(data.get("submitted_at_ms") / 1000), + stamp_validity_ms=( + datetime.fromtimestamp(data.get("stamp_validity_ms") / 1000) + if data.get("stamp_validity_ms") + else None + ), + account_id_arg_name=data["account_id_arg_name"], + custom_args=data.get("custom_args"), + registry_id=receiverId, ) except Exception as e: logger.error(f"Failed to add new stamp provider: {e}") -async def handle_add_stamp( - data: dict, - receiverId: str, - signerId: str -): +async def handle_add_stamp(data: dict, receiverId: str, signerId: str): logger.info(f"new stamp data: {data}, {receiverId}") data = data["stamp"] @@ -1114,24 +1102,20 @@ async def handle_add_stamp( stamp = await Stamp.objects.aupdate_or_create( user=user, provider=provider, - verified_at = datetime.fromtimestamp(data["validated_at_ms"] / 1000) + verified_at=datetime.fromtimestamp(data["validated_at_ms"] / 1000), ) except Exception as e: logger.error(f"Failed to create stamp: {e}") - -async def handle_new_group( - data: dict, - created_at: datetime -): +async def handle_new_group(data: dict, created_at: datetime): logger.info(f"new group data: {data}") - group_data = data.get('group', {}) + group_data = data.get("group", {}) try: # group enums can have values, they are represented as a dict in the events from the indexer, and enum choices without values are presented as normal strings: # withValue: {'group': {'id': 5, 'name': 'Here we go again', 'providers': [8, 1, 4, 6], 'rule': {'IncreasingReturns': 10}}} # noValue: {"id":6,"name":"Lachlan test group","providers":[1,2],"rule":"Highest"} - rule = group_data['rule'] + rule = group_data["rule"] rule_key = rule rule_val = None if type(rule) == dict: @@ -1143,34 +1127,37 @@ async def handle_new_group( name=group_data["name"], created_at=created_at, updated_at=created_at, - rule_type = rule_key, - rule_val = rule_val + rule_type=rule_key, + rule_val=rule_val, ) logger.info(f"addding provider.... : {group_data['providers']}") if group_data.get("providers"): for provider_id in group_data["providers"]: - provider, _ = await Provider.objects.aget_or_create(on_chain_id=provider_id) + provider, _ = await Provider.objects.aget_or_create( + on_chain_id=provider_id + ) await group.providers.aadd(provider) except Exception as e: logger.error(f"Failed to create group, because: {e}") + async def cache_block_height( key: str, height: int, block_count: int, block_timestamp: int -) -> int: +): await cache.aset(key, height) # the cache os the default go to for the restart block, the db is a backup if the redis server crashes. # if (block_count % int(settings.BLOCK_SAVE_HEIGHT or 400)) == 0: # logger.info(f"saving daylight, {height}") - await BlockHeight.objects.aupdate_or_create( - id=1, - defaults={ - "block_height": height, - "block_timestamp": datetime.fromtimestamp(block_timestamp / 1000000000), - "updated_at": timezone.now(), - }, - ) # better than ovverriding model's save method to get a singleton? we need only one entry - return height + # await BlockHeight.objects.aupdate_or_create( + # id=1, + # defaults={ + # "block_height": height, + # "block_timestamp": datetime.fromtimestamp(block_timestamp / 1000000000), + # "updated_at": timezone.now(), + # }, + # ) # better than ovverriding model's save method to get a singleton? we need only one entry + # return height def get_block_height(key: str) -> int: From 0bea04f59ec4f53524dd17dc9275cc4878d078f9 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 17 Jul 2024 10:18:31 -0400 Subject: [PATCH 240/454] set cache key in asyncio task --- indexer_app/handler.py | 4 ++-- indexer_app/tasks.py | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 4e95e59..1fc264d 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -49,8 +49,8 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess block_timestamp = streamer_message.block.header.timestamp block_height = streamer_message.block.header.height now_datetime = datetime.fromtimestamp(block_timestamp / 1000000000) - # Fire and forget the cache update - asyncio.create_task(cache.aset("block_height", block_height)) + # # Fire and forget the cache update + # asyncio.create_task(cache.aset("block_height", block_height)) # await cache.aset( # "block_height", block_height # ) # TODO: add custom timeout if it should be valid for longer than default (5 minutes) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 51644b1..e82c10b 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -8,6 +8,7 @@ from celery import shared_task from celery.signals import task_revoked, worker_shutdown from django.conf import settings +from django.core.cache import cache from django.db import transaction from django.db.models import Count, DecimalField, Q, Sum, Value from django.db.models.functions import Cast, NullIf @@ -62,12 +63,18 @@ async def indexer(from_block: int, to_block: int): # Log time before caching block height cache_start_time = time.time() - await cache_block_height( - CURRENT_BLOCK_HEIGHT_KEY, - streamer_message.block.header.height, - block_count, - streamer_message.block.header.timestamp, - ) # current block height + # Fire and forget the cache update + asyncio.create_task( + cache.aset( + CURRENT_BLOCK_HEIGHT_KEY, streamer_message.block.header.height + ) + ) + # await cache_block_height( + # CURRENT_BLOCK_HEIGHT_KEY, + # streamer_message.block.header.height, + # block_count, + # streamer_message.block.header.timestamp, + # ) # current block height cache_end_time = time.time() logger.info( From b5f8cfe7c1340ea71e4a227be105535cf62bf083 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 17 Jul 2024 10:24:39 -0400 Subject: [PATCH 241/454] remove excess debug logs --- indexer_app/handler.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 1fc264d..e4e6184 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -58,10 +58,10 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess logger.info( f"Block Height: {block_height}, Block Timestamp: {block_timestamp} ({formatted_date})" ) - logger.info( - f"Time after processing block info: {time.time() - start_time:.4f} seconds" - ) - log_memory_usage("After processing block info") + # logger.info( + # f"Time after processing block info: {time.time() - start_time:.4f} seconds" + # ) + # log_memory_usage("After processing block info") # if block_height == 111867204: # with open("indexer_outcome2.json", "w") as file: # file.write(f"{streamer_message}") @@ -139,10 +139,10 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess # TODO: handle set_source_metadata logs for various contracts - logger.info( - f"Time to process logs for receipt {receipt_execution_outcome.receipt.receipt_id}: {time.time() - log_processing_start:.4f} seconds" - ) - log_memory_usage("After processing logs") + # logger.info( + # f"Time to process logs for receipt {receipt_execution_outcome.receipt.receipt_id}: {time.time() - log_processing_start:.4f} seconds" + # ) + # log_memory_usage("After processing logs") # 2. HANDLE METHOD CALLS # Skip if the tx failed @@ -428,16 +428,16 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess logger.error(f"Error in indexer handler:\n{e}") # with open("indexer_error.txt", "a") as file: # file.write(f"{e}\n") - logger.info( - f"Time to process method calls for receipt {receipt_execution_outcome.receipt.receipt_id}: {time.time() - method_call_processing_start:.4f} seconds" - ) - log_memory_usage("After processing method calls") - logger.info( - f"Time to process shard {shard_index}: {time.time() - shard_start_time:.4f} seconds" - ) - log_memory_usage(f"After processing shard {shard_index}") + # logger.info( + # f"Time to process method calls for receipt {receipt_execution_outcome.receipt.receipt_id}: {time.time() - method_call_processing_start:.4f} seconds" + # ) + # log_memory_usage("After processing method calls") + # logger.info( + # f"Time to process shard {shard_index}: {time.time() - shard_start_time:.4f} seconds" + # ) + # log_memory_usage(f"After processing shard {shard_index}") - logger.info( - f"Total time to process streamer message: {time.time() - start_time:.4f} seconds" - ) - log_memory_usage("End of handle_streamer_message") + # logger.info( + # f"Total time to process streamer message: {time.time() - start_time:.4f} seconds" + # ) + # log_memory_usage("End of handle_streamer_message") From 1da5fd29dadd7630fe3e07b9951ab4621fb980e4 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 17 Jul 2024 10:51:32 -0400 Subject: [PATCH 242/454] add BlockHeight record update back --- indexer_app/tasks.py | 18 +++++++++--------- indexer_app/utils.py | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index e82c10b..dd2175e 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -65,16 +65,16 @@ async def indexer(from_block: int, to_block: int): cache_start_time = time.time() # Fire and forget the cache update asyncio.create_task( - cache.aset( - CURRENT_BLOCK_HEIGHT_KEY, streamer_message.block.header.height - ) + cache_block_height( + CURRENT_BLOCK_HEIGHT_KEY, + streamer_message.block.header.height, + block_count, + streamer_message.block.header.timestamp, + ) # current block height + # cache.aset( + # CURRENT_BLOCK_HEIGHT_KEY, streamer_message.block.header.height + # ) ) - # await cache_block_height( - # CURRENT_BLOCK_HEIGHT_KEY, - # streamer_message.block.header.height, - # block_count, - # streamer_message.block.header.timestamp, - # ) # current block height cache_end_time = time.time() logger.info( diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 8413748..b31c989 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1149,14 +1149,14 @@ async def cache_block_height( # the cache os the default go to for the restart block, the db is a backup if the redis server crashes. # if (block_count % int(settings.BLOCK_SAVE_HEIGHT or 400)) == 0: # logger.info(f"saving daylight, {height}") - # await BlockHeight.objects.aupdate_or_create( - # id=1, - # defaults={ - # "block_height": height, - # "block_timestamp": datetime.fromtimestamp(block_timestamp / 1000000000), - # "updated_at": timezone.now(), - # }, - # ) # better than ovverriding model's save method to get a singleton? we need only one entry + await BlockHeight.objects.aupdate_or_create( + id=1, + defaults={ + "block_height": height, + "block_timestamp": datetime.fromtimestamp(block_timestamp / 1000000000), + "updated_at": timezone.now(), + }, + ) # better than ovverriding model's save method to get a singleton? we need only one entry # return height From 602fa993fa0966fadbb9beca43c1d7d422e0ac3c Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Wed, 17 Jul 2024 11:00:07 -0400 Subject: [PATCH 243/454] add debug log --- indexer_app/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index b31c989..f1ec520 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1145,6 +1145,7 @@ async def handle_new_group(data: dict, created_at: datetime): async def cache_block_height( key: str, height: int, block_count: int, block_timestamp: int ): + logger.info(f"caching block height: {height}") await cache.aset(key, height) # the cache os the default go to for the restart block, the db is a backup if the redis server crashes. # if (block_count % int(settings.BLOCK_SAVE_HEIGHT or 400)) == 0: From 1efa8150f552e545bf938656bb08f66349d48227 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Wed, 17 Jul 2024 20:33:29 +0100 Subject: [PATCH 244/454] create util function for common price fetching (#43) * create util function for common price fetching * resolve reviews * add chains app & Chain model (#41) * default to NEAR chain when creating new account * add debug logging to handler.py for memory usage & timing * add additional debug logs for streamer processing time * remove BlockHeight db write in cache_block_height * set cache key in asyncio task * remove excess debug logs * add BlockHeight record update back * add debug log * Revert "Merge branch 'dev' into common-price-fetch" This reverts commit 54d526ece477b0a49957c5f085de7090f2d05542, reversing changes made to 8b6c363c6e2c5fa9d5f7fe6f1c32ceb67a794a5d. * small fixes --------- Co-authored-by: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> --- donations/models.py | 114 +++++-------------- pots/models.py | 68 ++--------- tokens/migrations/0003_token_coingecko_id.py | 24 ++++ tokens/models.py | 76 +++++++++++++ 4 files changed, 141 insertions(+), 141 deletions(-) create mode 100644 tokens/migrations/0003_token_coingecko_id.py diff --git a/donations/models.py b/donations/models.py index bb756f9..d1da775 100644 --- a/donations/models.py +++ b/donations/models.py @@ -203,90 +203,36 @@ def fetch_usd_prices(self): existing_referrer_fee_usd = self.referrer_fee_usd existing_chef_fee_usd = self.chef_fee_usd # first, see if there is a TokenHistoricalPrice within 1 day (or HISTORICAL_PRICE_QUERY_HOURS) of self.donated_at - token = self.token - time_window = timedelta(hours=settings.HISTORICAL_PRICE_QUERY_HOURS or 24) - token_prices = TokenHistoricalPrice.objects.filter( - token=token, - timestamp__gte=self.donated_at - time_window, - timestamp__lte=self.donated_at + time_window, - ) - existing_token_price = token_prices.first() - total_amount = token.format_price(self.total_amount) - net_amount = token.format_price(self.net_amount) - protocol_amount = token.format_price(self.protocol_fee) - referrer_amount = ( - None if not self.referrer_fee else token.format_price(self.referrer_fee) - ) - chef_amount = None if not self.chef_fee else token.format_price(self.chef_fee) - # chef_amount = token.format_price(self.chef_fee or "0") - if existing_token_price: - try: - price_usd = existing_token_price.price_usd - self.total_amount_usd = total_amount * price_usd - self.net_amount_usd = net_amount * price_usd - self.protocol_fee_usd = protocol_amount * price_usd - self.referrer_fee_usd = ( - None if not referrer_amount else referrer_amount * price_usd - ) - self.chef_fee_usd = None if not chef_amount else chef_amount * price_usd - self.save() - logger.info( - "USD prices calculated and saved using existing TokenHistoricalPrice" - ) - except Exception as e: + + try: + token = self.token + price_usd = token.fetch_usd_prices_common(self.donated_at) + if not price_usd: logger.error( - f"Failed to calculate and save USD prices using existing TokenHistoricalPrice: {e}" - ) - # TODO: update totals for relevant accounts - else: - # no existing price within acceptable time period; fetch from coingecko - price_data = {} - try: - logger.info( - "No existing price within acceptable time period; fetching historical price..." + f"No USD price found for token {token.symbol} at {self.paid_at}" ) - endpoint = f"{settings.COINGECKO_URL}/coins/{self.token.id.id}/history?date={format_date(self.donated_at)}&localization=false" - if settings.COINGECKO_API_KEY: - endpoint += f"&x_cg_pro_api_key={settings.COINGECKO_API_KEY}" - logger.info(f"coingecko endpoint: {endpoint}") - response = requests.get(endpoint) - logger.info(f"coingecko response: {response}") - if response.status_code == 429: - logger.warning("Coingecko rate limit exceeded") - price_data = response.json() - except Exception as e: - logger.warning(f"Failed to fetch coingecko price data: {e}") - logger.info(f"coingecko price data: {price_data}") - price_usd = ( - price_data.get("market_data", {}).get("current_price", {}).get("usd") + return + total_amount = token.format_price(self.total_amount) + net_amount = token.format_price(self.net_amount) + protocol_amount = token.format_price(self.protocol_fee) + referrer_amount = ( + None if not self.referrer_fee else token.format_price(self.referrer_fee) ) - logger.info(f"unit price: {price_usd}") - if price_usd: - try: - # convert price_usd to decimal - price_usd = Decimal(price_usd) - self.total_amount_usd = total_amount * price_usd - self.net_amount_usd = net_amount * price_usd - self.protocol_fee_usd = protocol_amount * price_usd - self.referrer_fee_usd = ( - None if not referrer_amount else referrer_amount * price_usd - ) - self.chef_fee_usd = ( - None if not chef_amount else chef_amount * price_usd - ) - self.save() - except Exception as e: - logger.error( - f"Failed to calculate and save USD prices using fetched price: {e}" - ) - # TODO: update totals for relevant accounts - try: - TokenHistoricalPrice.objects.create( - token=token, - price_usd=price_usd, - timestamp=self.donated_at, - ) - except Exception as e: - logger.warning( - f"Error creating TokenHistoricalPrice: {e} token: {token} price_usd: {price_usd}" - ) + chef_amount = None if not self.chef_fee else token.format_price(self.chef_fee) + self.total_amount_usd = total_amount * price_usd + self.net_amount_usd = net_amount * price_usd + self.protocol_fee_usd = protocol_amount * price_usd + self.referrer_fee_usd = ( + None if not referrer_amount else referrer_amount * price_usd + ) + self.chef_fee_usd = None if not chef_amount else chef_amount * price_usd + self.save() + logger.info( + f"Saved USD prices for donation: {self.on_chain_id}" + ) + except Exception as e: + logger.error( + f"Failed to calculate and save USD prices: {e}" + ) + # chef_amount = token.format_price(self.chef_fee or "0") + # TODO: update totals for relevant accounts diff --git a/pots/models.py b/pots/models.py index 62051cf..ba326ae 100644 --- a/pots/models.py +++ b/pots/models.py @@ -450,65 +450,19 @@ class PotPayout(models.Model): ### Fetches USD prices for the Donation record and saves USD totals def fetch_usd_prices(self): # first, see if there is a TokenHistoricalPrice within 1 day (or HISTORICAL_PRICE_QUERY_HOURS) of self.paid_at - token = self.token - time_window = timedelta(hours=settings.HISTORICAL_PRICE_QUERY_HOURS or 24) - token_prices = TokenHistoricalPrice.objects.filter( - token=token, - timestamp__gte=self.paid_at - time_window, - timestamp__lte=self.paid_at + time_window, - ) - existing_token_price = token_prices.first() - amount = token.format_price(self.amount) - if existing_token_price: - try: - price_usd = existing_token_price.price_usd - self.amount_paid_usd = amount * price_usd - self.save() - except Exception as e: - logger.error( - f"Failed to calculate and save USD prices for payout using existing TokenHistoricalPrice: {e}" - ) - else: - # no existing price within acceptable time period; fetch from coingecko - try: + try: + token = self.token + price_usd = token.fetch_usd_prices_common(self.paid_at) + if not price_usd: logger.info( - "No existing price within acceptable time period; fetching historical price..." + f"No USD price found for token {self.token.symbol} at {self.paid_at}" ) - endpoint = f"{settings.COINGECKO_URL}/coins/{self.token.id.id}/history?date={format_date(self.paid_at)}&localization=false" - if settings.COINGECKO_API_KEY: - endpoint += f"&x_cg_pro_api_key={settings.COINGECKO_API_KEY}" - response = requests.get(endpoint) - logger.info(f"coingecko response: {response}") - if response.status_code == 429: - logger.warning("Coingecko rate limit exceeded") - price_data = response.json() - except Exception as e: - logger.warning(f"Failed to fetch coingecko price data: {e}") - logger.info(f"coingecko price data: {price_data}") - price_usd = ( - price_data.get("market_data", {}).get("current_price", {}).get("usd") - ) - logger.info(f"unit price: {price_usd}") - if price_usd: - try: - # convert price_usd to decimal - price_usd = Decimal(price_usd) - self.amount_paid_usd = amount * price_usd - self.save() - except Exception as e: - logger.error( - f"Failed to calculate and save USD prices using fetched price: {e}" - ) - try: - TokenHistoricalPrice.objects.create( - token=token, - price_usd=price_usd, - timestamp=self.paid_at, - ) - except Exception as e: - logger.warning( - f"Error creating TokenHistoricalPrice: {e} token: {token} price_usd: {price_usd}" - ) + return + self.amount_paid_usd = token.format_price(self.amount) * price_usd + self.save() + logger.info(f"Saved USD prices for pot payout for pot id: {self.pot.id}") + except Exception as e: + logger.error(f"Failed to calculate and save USD prices: {e}") class PotPayoutChallenge(models.Model): diff --git a/tokens/migrations/0003_token_coingecko_id.py b/tokens/migrations/0003_token_coingecko_id.py new file mode 100644 index 0000000..91793d5 --- /dev/null +++ b/tokens/migrations/0003_token_coingecko_id.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.6 on 2024-07-17 17:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("tokens", "0002_token_icon_token_name_token_symbol"), + ] + + operations = [ + migrations.AddField( + model_name="token", + name="coingecko_id", + field=models.CharField( + default="near", + help_text="Token id on coingecko.", + max_length=255, + verbose_name="coingecko_id", + ), + preserve_default=False, + ), + ] diff --git a/tokens/models.py b/tokens/models.py index 90a7a8d..e2cd906 100644 --- a/tokens/models.py +++ b/tokens/models.py @@ -1,11 +1,16 @@ +from datetime import timedelta from decimal import Decimal from os import name +import requests +from django.conf import settings from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ from accounts.models import Account +from base.logging import logger +from base.utils import format_date class Token(models.Model): @@ -37,6 +42,11 @@ class Token(models.Model): null=False, help_text=_("Token decimals."), ) + coingecko_id = models.CharField( + _("coingecko_id"), + max_length=255, + help_text=_("Token id on coingecko."), + ) def get_most_recent_price(self): return self.historical_prices.order_by("-timestamp").first() @@ -46,6 +56,72 @@ def format_price(self, amount_str: str): formatted_amount = Decimal(amount_str) / (Decimal("10") ** self.decimals) return formatted_amount + def fetch_usd_prices_common(self, timestamp): + + time_window = timedelta(hours=settings.HISTORICAL_PRICE_QUERY_HOURS or 24) + token_prices = TokenHistoricalPrice.objects.filter( + token=self, + timestamp__gte=timestamp - time_window, + timestamp__lte=timestamp + time_window, + ) + existing_token_price = token_prices.first() + if existing_token_price: + return existing_token_price.price_usd + + price_data = {} + if self.coingecko_id: + try: + logger.info( + "No existing price within acceptable time period; fetching historical pricefrom gecko..." + ) + endpoint = f"{settings.COINGECKO_URL}/coins/{self.coingecko_id}/history?date={format_date(timestamp)}&localization=false" + if settings.COINGECKO_API_KEY: + endpoint += f"&x_cg_pro_api_key={settings.COINGECKO_API_KEY}" + logger.info(f"coingecko endpoint: {endpoint}") + response = requests.get(endpoint) + logger.info(f"coingecko response: {response}") + if response.status_code == 429: + logger.warning("Coingecko rate limit exceeded") + price_data = response.json() + except Exception as e: + logger.warning(f"Failed to fetch coingecko price data: {e}") + return None + price_usd = ( + price_data.get("market_data", {}).get("current_price", {}).get("usd") + ) + if price_usd: + TokenHistoricalPrice.objects.create( + token=self, + timestamp=timestamp, + price_usd=price_usd, + ) + return Decimal(price_usd) + return None + + def save(self, *args, **kwargs): + try: + if self._state.adding: + endpoint = f"{settings.COINGECKO_URL}/coins/list?include_platform=true" + if settings.COINGECKO_API_KEY: + endpoint += f"&x_cg_pro_api_key={settings.COINGECKO_API_KEY}" + response = requests.get(endpoint) + logger.info(f"coingecko response: {response}") + if response.status_code == 429: + logger.warning("Coingecko rate limit exceeded") + price_data = response.json() + coin_data = list( + filter( + lambda x: x["symbol"] == kwargs["symbol"] + and x["platforms"].get("near-protocol"), + price_data, + ) + ) + if coin_data: + self.coingecko_id = coin_data[0]["id"] + except Exception as e: + logger.error(f"Failed to fetch token id from coingecko: {e}") + super().save(*args, **kwargs) + class TokenHistoricalPrice(models.Model): token = models.ForeignKey( From 8b6f6500dd8ed8c9187b15a38a0405e658c2ea0a Mon Sep 17 00:00:00 2001 From: Prometheus Date: Thu, 18 Jul 2024 14:24:32 +0100 Subject: [PATCH 245/454] handle zero net amount and migration script (#45) * handle zero net amount and migration script * remove unused import * small net_amount updates --------- Co-authored-by: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> --- .../0012_update_donations_net_amount.py | 27 +++++++++++++++++++ donations/models.py | 16 +++++------ .../management/commands/populatedata.py | 13 ++++++++- indexer_app/utils.py | 5 ++-- 4 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 donations/migrations/0012_update_donations_net_amount.py diff --git a/donations/migrations/0012_update_donations_net_amount.py b/donations/migrations/0012_update_donations_net_amount.py new file mode 100644 index 0000000..974d6ca --- /dev/null +++ b/donations/migrations/0012_update_donations_net_amount.py @@ -0,0 +1,27 @@ +# Add custom migration to donations app that uses run_python to update net_amount for all Donations where net_amount="0". + +from django.db import migrations + + +def update_donations_net_amount(apps, schema_editor): + Donation = apps.get_model("donations", "Donation") + for donation in Donation.objects.filter(net_amount="0"): + total_amount = int(donation.total_amount) + protocol_fee = int(donation.protocol_fee) + referrer_fee = int(donation.referrer_fee or 0) + chef_fee = int(donation.chef_fee or 0) + + net_amount = total_amount - protocol_fee - referrer_fee - chef_fee + donation.net_amount = net_amount + donation.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("donations", "0011_remove_donation_ft_alter_donation_token"), + ] + + operations = [ + migrations.RunPython(update_donations_net_amount), + ] diff --git a/donations/models.py b/donations/models.py index d1da775..21e8e1f 100644 --- a/donations/models.py +++ b/donations/models.py @@ -208,8 +208,8 @@ def fetch_usd_prices(self): token = self.token price_usd = token.fetch_usd_prices_common(self.donated_at) if not price_usd: - logger.error( - f"No USD price found for token {token.symbol} at {self.paid_at}" + logger.info( + f"No USD price found for token {token.name} ({token.id.id}) at {self.donated_at}" ) return total_amount = token.format_price(self.total_amount) @@ -218,7 +218,9 @@ def fetch_usd_prices(self): referrer_amount = ( None if not self.referrer_fee else token.format_price(self.referrer_fee) ) - chef_amount = None if not self.chef_fee else token.format_price(self.chef_fee) + chef_amount = ( + None if not self.chef_fee else token.format_price(self.chef_fee) + ) self.total_amount_usd = total_amount * price_usd self.net_amount_usd = net_amount * price_usd self.protocol_fee_usd = protocol_amount * price_usd @@ -227,12 +229,8 @@ def fetch_usd_prices(self): ) self.chef_fee_usd = None if not chef_amount else chef_amount * price_usd self.save() - logger.info( - f"Saved USD prices for donation: {self.on_chain_id}" - ) + logger.info(f"Saved USD prices for donation: {self.on_chain_id}") except Exception as e: - logger.error( - f"Failed to calculate and save USD prices: {e}" - ) + logger.error(f"Failed to calculate and save USD prices: {e}") # chef_amount = token.format_price(self.chef_fee or "0") # TODO: update totals for relevant accounts diff --git a/indexer_app/management/commands/populatedata.py b/indexer_app/management/commands/populatedata.py index f542081..7d816e7 100644 --- a/indexer_app/management/commands/populatedata.py +++ b/indexer_app/management/commands/populatedata.py @@ -414,6 +414,17 @@ def handle(self, *args, **options): if donation.get("chef_id"): chef, _ = Account.objects.get_or_create(id=donation["chef_id"]) + # net_amount is 0 for some donations, so calculate it + net_amount = donation["net_amount"] + if net_amount == "0": + total_amount = int(donation["total_amount"]) + protocol_fee = int(donation["protocol_fee"]) + referrer_fee = int(donation["referrer_fee"] or 0) + chef_fee = int(donation["chef_fee"] or 0) + net_amount = ( + total_amount - protocol_fee - referrer_fee - chef_fee + ) + # pot donations always NEAR ft_acct, _ = Account.objects.get_or_create(id="near") ft_token, _ = Token.objects.get_or_create(id=ft_acct) @@ -422,7 +433,7 @@ def handle(self, *args, **options): "total_amount": donation["total_amount"], "total_amount_usd": None, # USD amounts will be added later "net_amount_usd": None, - "net_amount": donation["net_amount"], + "net_amount": net_amount, "token": ft_token, "message": donation["message"], "donated_at": datetime.fromtimestamp( diff --git a/indexer_app/utils.py b/indexer_app/utils.py index f1ec520..552f1fe 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -826,15 +826,16 @@ async def handle_new_donation( logger.info(f"handle_new_donation args data: {data}, {receiver_id}") logger.info(f"donation data: {donation_data}") - if "net_amount" in donation_data: + if "net_amount" in donation_data and donation_data["net_amount"] != "0": net_amount = int(donation_data["net_amount"]) else: # direct donations don't have net_amount property, so have to calculate it here total_amount = int(donation_data["total_amount"]) protocol_fee = int(donation_data["protocol_fee"]) referrer_fee = int(donation_data["referrer_fee"] or 0) + chef_fee = int(donation_data.get("chef_fee") or 0) - net_amount = total_amount - protocol_fee - referrer_fee + net_amount = total_amount - protocol_fee - referrer_fee - chef_fee donated_at = datetime.fromtimestamp( (donation_data.get("donated_at") or donation_data.get("donated_at_ms")) / 1000 From da77c5014315fa3300428a1281463fa214d877f3 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 18 Jul 2024 09:30:09 -0400 Subject: [PATCH 246/454] add coingecko_id to token admin --- tokens/admin.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tokens/admin.py b/tokens/admin.py index 76aedc9..b949d2b 100644 --- a/tokens/admin.py +++ b/tokens/admin.py @@ -5,7 +5,15 @@ @admin.register(Token) class TokenAdmin(admin.ModelAdmin): - list_display = ("id", "name", "symbol", "icon", "decimals", "get_most_recent_price") + list_display = ( + "id", + "name", + "symbol", + "coingecko_id", + "icon", + "decimals", + "get_most_recent_price", + ) search_fields = ("id",) def get_most_recent_price(self, obj): From 167bc4d6e8406f903443c7ee4d204e3d0e3582bf Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 18 Jul 2024 09:39:02 -0400 Subject: [PATCH 247/454] make coingecko_id nullable --- .../0004_alter_token_coingecko_id.py | 23 +++++++++++++++++++ tokens/models.py | 1 + 2 files changed, 24 insertions(+) create mode 100644 tokens/migrations/0004_alter_token_coingecko_id.py diff --git a/tokens/migrations/0004_alter_token_coingecko_id.py b/tokens/migrations/0004_alter_token_coingecko_id.py new file mode 100644 index 0000000..d64b775 --- /dev/null +++ b/tokens/migrations/0004_alter_token_coingecko_id.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.4 on 2024-07-18 13:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("tokens", "0003_token_coingecko_id"), + ] + + operations = [ + migrations.AlterField( + model_name="token", + name="coingecko_id", + field=models.CharField( + help_text="Token id on coingecko.", + max_length=255, + null=True, + verbose_name="coingecko_id", + ), + ), + ] diff --git a/tokens/models.py b/tokens/models.py index e2cd906..9f560e9 100644 --- a/tokens/models.py +++ b/tokens/models.py @@ -45,6 +45,7 @@ class Token(models.Model): coingecko_id = models.CharField( _("coingecko_id"), max_length=255, + null=True, help_text=_("Token id on coingecko."), ) From 2b572409a87c422931f39221010f0f9498298d84 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 18 Jul 2024 09:46:43 -0400 Subject: [PATCH 248/454] add blank=True to nullable Token fields for admin --- ..._coingecko_id_alter_token_icon_and_more.py | 56 +++++++++++++++++++ tokens/models.py | 4 ++ 2 files changed, 60 insertions(+) create mode 100644 tokens/migrations/0005_alter_token_coingecko_id_alter_token_icon_and_more.py diff --git a/tokens/migrations/0005_alter_token_coingecko_id_alter_token_icon_and_more.py b/tokens/migrations/0005_alter_token_coingecko_id_alter_token_icon_and_more.py new file mode 100644 index 0000000..25d205a --- /dev/null +++ b/tokens/migrations/0005_alter_token_coingecko_id_alter_token_icon_and_more.py @@ -0,0 +1,56 @@ +# Generated by Django 5.0.4 on 2024-07-18 13:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("tokens", "0004_alter_token_coingecko_id"), + ] + + operations = [ + migrations.AlterField( + model_name="token", + name="coingecko_id", + field=models.CharField( + blank=True, + help_text="Token id on coingecko.", + max_length=255, + null=True, + verbose_name="coingecko_id", + ), + ), + migrations.AlterField( + model_name="token", + name="icon", + field=models.TextField( + blank=True, + help_text="Token icon (base64 data URL).", + null=True, + verbose_name="icon", + ), + ), + migrations.AlterField( + model_name="token", + name="name", + field=models.CharField( + blank=True, + help_text="Token name.", + max_length=255, + null=True, + verbose_name="name", + ), + ), + migrations.AlterField( + model_name="token", + name="symbol", + field=models.CharField( + blank=True, + help_text="Token symbol.", + max_length=255, + null=True, + verbose_name="symbol", + ), + ), + ] diff --git a/tokens/models.py b/tokens/models.py index 9f560e9..5ff50dc 100644 --- a/tokens/models.py +++ b/tokens/models.py @@ -24,17 +24,20 @@ class Token(models.Model): _("name"), max_length=255, null=True, + blank=True, help_text=_("Token name."), ) symbol = models.CharField( _("symbol"), max_length=255, null=True, + blank=True, help_text=_("Token symbol."), ) icon = models.TextField( _("icon"), null=True, + blank=True, help_text=_("Token icon (base64 data URL)."), ) decimals = models.PositiveIntegerField( @@ -46,6 +49,7 @@ class Token(models.Model): _("coingecko_id"), max_length=255, null=True, + blank=True, help_text=_("Token id on coingecko."), ) From b78e822716738cf8e1a70df104d8547dd782d803 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:36:24 -0400 Subject: [PATCH 249/454] fix payouts paid_at not being set --- indexer_app/utils.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 552f1fe..1df74d7 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -697,12 +697,21 @@ async def handle_transfer_payout( payout = { "recipient_id": data["project_id"], "amount": data["amount"], - "paid_at": data.get("paid_at", created_at), + "paid_at": data["paid_at"] or created_at, "tx_hash": receiptId, } await PotPayout.objects.filter(recipient_id=data["project_id"]).aupdate( **payout ) + # check if all_paid_out is now true + url = f"{settings.FASTNEAR_RPC_URL}/account/{receiver_id}/view/get_config" + response = requests.get(url) + if response.status_code != 200: + logger.error(f"Failed to get config for pot {receiver_id}: {response.text}") + else: + data = response.json() + pot = await Pot.objects.aget(id=receiver_id) + pot.all_paid_out = data["all_paid_out"] except Exception as e: logger.error(f"Failed to create payout data, Error: {e}") From 5fc8134302b46c1aab840ff0b79b6586e980ff01 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:42:14 -0400 Subject: [PATCH 250/454] save pot.all_paid_out --- indexer_app/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 1df74d7..9a5456e 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -712,6 +712,7 @@ async def handle_transfer_payout( data = response.json() pot = await Pot.objects.aget(id=receiver_id) pot.all_paid_out = data["all_paid_out"] + await pot.asave() except Exception as e: logger.error(f"Failed to create payout data, Error: {e}") From 317d311b8a27fed09a4aa1720caf4e0c7ee73f9f Mon Sep 17 00:00:00 2001 From: Prometheus Date: Mon, 22 Jul 2024 17:08:04 +0100 Subject: [PATCH 251/454] change account to id (#51) * change account to id * add donate contract --- accounts/api.py | 2 +- donations/admin.py | 4 ++-- donations/models.py | 2 +- .../management/commands/populatedata.py | 10 ++++---- indexer_app/tasks.py | 10 ++++---- indexer_app/utils.py | 24 ++++++++++--------- .../0002_rename_id_nadabotregistry_account.py | 18 ++++++++++++++ nadabot/models.py | 2 +- pots/admin.py | 14 +++++------ pots/api.py | 10 ++++---- ...ot_account_rename_id_potfactory_account.py | 23 ++++++++++++++++++ pots/models.py | 6 ++--- pots/serializers.py | 4 ++-- tokens/admin.py | 6 ++--- .../0006_rename_id_token_account.py | 18 ++++++++++++++ tokens/models.py | 2 +- 16 files changed, 108 insertions(+), 47 deletions(-) create mode 100644 nadabot/migrations/0002_rename_id_nadabotregistry_account.py create mode 100644 pots/migrations/0011_rename_id_pot_account_rename_id_potfactory_account.py create mode 100644 tokens/migrations/0006_rename_id_token_account.py diff --git a/accounts/api.py b/accounts/api.py index 3f2566a..f4f9f5f 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -199,7 +199,7 @@ def get(self, request: Request, *args, **kwargs): applicant=account, status=PotApplicationStatus.APPROVED ) pot_ids = applications.values_list("pot_id", flat=True) - pots = Pot.objects.filter(id__in=pot_ids) + pots = Pot.objects.filter(account__in=pot_ids) if request.query_params.get("status") == "live": pots = pots.filter( matching_round_start__lte=now, matching_round_end__gte=now diff --git a/donations/admin.py b/donations/admin.py index a9b1769..0103da2 100644 --- a/donations/admin.py +++ b/donations/admin.py @@ -21,7 +21,7 @@ class DonationAdmin(admin.ModelAdmin): "message", "donor__id", ) # Correct field name from 'donor__address' to 'donor__id' if 'id' is used in the model - list_filter = ("donated_at", "donor", "pot", "pot__id") + list_filter = ("donated_at", "donor", "pot", "pot__account") date_hierarchy = "donated_at" ordering = ("-donated_at",) @@ -43,7 +43,7 @@ def recipient_address(self, obj): return obj.recipient.id if obj.recipient else None def token_address(self, obj): - return obj.token.id + return obj.token.account def referrer_address(self, obj): return obj.referrer.id if obj.referrer else None diff --git a/donations/models.py b/donations/models.py index 21e8e1f..824be55 100644 --- a/donations/models.py +++ b/donations/models.py @@ -209,7 +209,7 @@ def fetch_usd_prices(self): price_usd = token.fetch_usd_prices_common(self.donated_at) if not price_usd: logger.info( - f"No USD price found for token {token.name} ({token.id.id}) at {self.donated_at}" + f"No USD price found for token {token.name} ({token.account.id}) at {self.donated_at}" ) return total_amount = token.format_price(self.total_amount) diff --git a/indexer_app/management/commands/populatedata.py b/indexer_app/management/commands/populatedata.py index 7d816e7..4d94246 100644 --- a/indexer_app/management/commands/populatedata.py +++ b/indexer_app/management/commands/populatedata.py @@ -158,7 +158,7 @@ def handle(self, *args, **options): if "decimals" in ft_metadata: token_defaults["decimals"] = ft_metadata["decimals"] ft_token, _ = Token.objects.update_or_create( - id=ft_acct, defaults=token_defaults + account=ft_acct, defaults=token_defaults ) if donation.get("referrer_id"): referrer, _ = Account.objects.get_or_create( @@ -260,7 +260,7 @@ def handle(self, *args, **options): if config.get("chef"): chef, _ = Account.objects.get_or_create(id=config["chef"]) pot_defaults = { - "pot_factory_id": POTFACTORY_ID, + "pot_factory_account": POTFACTORY_ID, "owner_id": config["owner"], "chef_id": config["chef"], "name": config["pot_name"], @@ -311,7 +311,7 @@ def handle(self, *args, **options): "protocol_config_provider": config["protocol_config_provider"], } pot, created = Pot.objects.update_or_create( - id=pot_acct, defaults=pot_defaults + account=pot_acct, defaults=pot_defaults ) print("pot created? ", created) print("adding admins") @@ -427,7 +427,7 @@ def handle(self, *args, **options): # pot donations always NEAR ft_acct, _ = Account.objects.get_or_create(id="near") - ft_token, _ = Token.objects.get_or_create(id=ft_acct) + ft_token, _ = Token.objects.get_or_create(account=ft_acct) donation_defaults = { "donor": donor, "total_amount": donation["total_amount"], @@ -483,7 +483,7 @@ def handle(self, *args, **options): recipient, _ = Account.objects.get_or_create( id=payout["project_id"] ) - near_token, _ = Token.objects.get_or_create(id=near_acct) + near_token, _ = Token.objects.get_or_create(account=near_acct) payout_defaults = { "pot": pot, "recipient": recipient, diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index dd2175e..c0853c1 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -200,7 +200,7 @@ def update_pot_statistics(): matching_pool_donations = Donation.objects.filter(pot=pot, matching_pool=True) public_donations = Donation.objects.filter(pot=pot, matching_pool=False) try: - print(f"Processing pot: {pot.id}") + print(f"Processing pot: {pot.account}") # total matching pool pot.total_matching_pool = sum( @@ -219,17 +219,17 @@ def update_pot_statistics(): jobs_logger.info(f"Total matching pool USD: {pot.total_matching_pool_usd}") # matching pool balance (get from contract) - url = f"{settings.FASTNEAR_RPC_URL}/account/{pot.id.id}/view/get_config" + url = f"{settings.FASTNEAR_RPC_URL}/account/{pot.account.id}/view/get_config" response = requests.get(url) if response.status_code != 200: jobs_logger.error( - f"Failed to get matching pool balance for pot {pot.id}: {response.text}" + f"Failed to get matching pool balance for pot {pot.account}: {response.text}" ) else: data = response.json() pot.matching_pool_balance = data["matching_pool_balance"] jobs_logger.info( - f"Matching pool balance for pot {pot.id}: {pot.matching_pool_balance}" + f"Matching pool balance for pot {pot.account}: {pot.matching_pool_balance}" ) # matching pool donations count @@ -273,7 +273,7 @@ def update_pot_statistics(): ] ) except Exception as e: - jobs_logger.error(f"Failed to update statistics for pot {pot.id}: {e}") + jobs_logger.error(f"Failed to update statistics for pot {pot.account}: {e}") @shared_task diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 9a5456e..7a3efea 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -60,7 +60,7 @@ async def handle_new_nadabot_registry( registry, _ = await Account.objects.aget_or_create(id=receiverId) owner, _ = await Account.objects.aget_or_create(id=data["owner"]) nadabot_registry, created = await NadabotRegistry.objects.aupdate_or_create( - id=registry, + account=registry, owner=owner, created_at=created_at, updated_at=created_at, @@ -140,7 +140,7 @@ async def handle_new_pot( # Create Pot object logger.info(f"creating pot with owner {owner_id}....") pot_defaults = { - "pot_factory_id": predecessorId, + "pot_factory_account": predecessorId, "deployer": signer, "deployed_at": created_at, "source_metadata": data["source_metadata"], @@ -186,7 +186,7 @@ async def handle_new_pot( "protocol_config_provider": data["protocol_config_provider"], } pot, created = await Pot.objects.aupdate_or_create( - id=receiver, defaults=pot_defaults + account=receiver, defaults=pot_defaults ) # Add admins to the Pot @@ -264,7 +264,7 @@ async def handle_pot_config_update( } pot, created = await Pot.objects.aupdate_or_create( - id=receiver_id, defaults=pot_config + account=receiver_id, defaults=pot_config ) if data.get("admins"): @@ -304,7 +304,7 @@ async def handle_new_pot_factory(data: dict, receiver_id: str, created_at: datet } # Create Factory object factory, factory_created = await PotFactory.objects.aupdate_or_create( - id=receiver, defaults=defaults + account=receiver, defaults=defaults ) # Add admins to the PotFactory @@ -663,10 +663,10 @@ async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): logger.info(f"set payout data: {data}, {receiver_id}") payouts = data.get("payouts", []) - pot = await Pot.objects.aget(id=receiver_id) + pot = await Pot.objects.aget(account=receiver_id) near_acct, _ = await Account.objects.aget_or_create(id="near") near_token, _ = await Token.objects.aget_or_create( - id=near_acct + account=near_acct ) # Pots only support native NEAR insertion_data = [] @@ -793,7 +793,7 @@ async def handle_list_admin_removal(data, receiver_id, signer_id, receiptId): async def handle_add_nadabot_admin(data, receiverId): logger.info(f"adding admin...: {data}, {receiverId}") try: - obj = await NadabotRegistry.objects.aget(id=receiverId) + obj = await NadabotRegistry.objects.aget(account=receiverId) for acct in data["account_ids"]: user, _ = await Account.objects.aget_or_create(id=acct) @@ -852,6 +852,8 @@ async def handle_new_donation( ) try: + # insert donate contract which is the receiver id(because of activity relationship mainly) + donate_contract, _ = await Account.objects.aget_or_create(id=receiver_id) # Upsert donor account donor, _ = await Account.objects.aget_or_create(id=donation_data["donor_id"]) recipient = None @@ -901,7 +903,7 @@ async def handle_new_donation( if "decimals" in ft_metadata: token_defaults["decimals"] = ft_metadata["decimals"] token, _ = await Token.objects.aupdate_or_create( - id=token_acct, defaults=token_defaults + account=token_acct, defaults=token_defaults ) except Exception as e: @@ -933,7 +935,7 @@ async def handle_new_donation( } if donation_type == "pot": - default_data["pot"] = await Pot.objects.aget(id=receiver_id) + default_data["pot"] = await Pot.objects.aget(account=receiver_id) logger.info(f"default donation data: {default_data}") @@ -1043,7 +1045,7 @@ async def handle_update_default_human_threshold(data: dict, receiverId: str): try: - reg = await NadabotRegistry.objects.filter(id=receiverId).aupdate( + reg = await NadabotRegistry.objects.filter(account=receiverId).aupdate( **{"default_human_threshold": data["default_human_threshold"]} ) logger.info("updated threshold..") diff --git a/nadabot/migrations/0002_rename_id_nadabotregistry_account.py b/nadabot/migrations/0002_rename_id_nadabotregistry_account.py new file mode 100644 index 0000000..eb5beda --- /dev/null +++ b/nadabot/migrations/0002_rename_id_nadabotregistry_account.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-07-19 22:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("nadabot", "0001_initial"), + ] + + operations = [ + migrations.RenameField( + model_name="nadabotregistry", + old_name="id", + new_name="account", + ), + ] diff --git a/nadabot/models.py b/nadabot/models.py index 971d9da..e0b6cdf 100644 --- a/nadabot/models.py +++ b/nadabot/models.py @@ -20,7 +20,7 @@ class RuleType(models.TextChoices): class NadabotRegistry(models.Model): - id = models.OneToOneField( + account = models.OneToOneField( Account, related_name="registry_id", on_delete=models.CASCADE, diff --git a/pots/admin.py b/pots/admin.py index 54159b4..100d23f 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -38,8 +38,8 @@ def __init__(self, *args, **kwargs): @admin.register(PotFactory) class PotFactoryAdmin(admin.ModelAdmin): form = PotFactoryForm - list_display = ("id", "owner", "deployed_at") - search_fields = ("id", "owner__id") + list_display = ("account", "owner", "deployed_at") + search_fields = ("account", "owner__id") def get_form(self, request, obj=None, **kwargs): form = super(PotFactoryAdmin, self).get_form(request, obj, **kwargs) @@ -79,8 +79,8 @@ def __init__(self, *args, **kwargs): @admin.register(Pot) class PotAdmin(admin.ModelAdmin): form = PotForm - list_display = ("id", "pot_factory", "deployer", "deployed_at", "name") - search_fields = ("id", "name", "deployer__id") + list_display = ("account", "pot_factory", "deployer", "deployed_at", "name") + search_fields = ("account", "name", "deployer__id") list_filter = ("deployed_at",) def get_form(self, request, obj=None, **kwargs): @@ -118,7 +118,7 @@ def __init__(self, *args, **kwargs): class PotApplicationAdmin(admin.ModelAdmin): form = PotApplicationAdminForm list_display = ("id", "pot", "applicant", "status", "submitted_at") - search_fields = ("pot__id", "applicant__id") + search_fields = ("pot__account", "applicant__id") list_filter = ("status", "submitted_at") autocomplete_fields = ["applicant"] @@ -188,7 +188,7 @@ def application_applicant_id(self, obj): @admin.register(PotPayout) class PotPayoutAdmin(admin.ModelAdmin): list_display = ("id", "pot", "recipient", "amount", "amount_paid_usd", "paid_at") - search_fields = ("pot__id", "recipient__id") + search_fields = ("pot__account", "recipient__id") list_filter = ("paid_at",) def has_add_permission(self, request): @@ -204,7 +204,7 @@ def has_delete_permission(self, request, obj=None): @admin.register(PotPayoutChallenge) class PotPayoutChallengeAdmin(admin.ModelAdmin): list_display = ("id", "challenger", "pot", "message", "created_at") - search_fields = ("challenger__id", "pot__id") + search_fields = ("challenger__id", "pot__account") list_filter = ("created_at",) def has_add_permission(self, request): diff --git a/pots/api.py b/pots/api.py index b7aa8b5..2db837e 100644 --- a/pots/api.py +++ b/pots/api.py @@ -95,7 +95,7 @@ class PotDetailAPI(APIView): def get(self, request: Request, *args, **kwargs): pot_id = kwargs.get("pot_id") try: - pot = Pot.objects.get(id=pot_id) + pot = Pot.objects.get(account=pot_id) except Pot.DoesNotExist: return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) serializer = PotSerializer(pot) @@ -129,7 +129,7 @@ class PotApplicationsAPI(APIView, PageNumberPagination): def get(self, request: Request, *args, **kwargs): pot_id = kwargs.get("pot_id") try: - pot = Pot.objects.get(id=pot_id) + pot = Pot.objects.get(account=pot_id) except Pot.DoesNotExist: return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) @@ -166,7 +166,7 @@ class PotDonationsAPI(APIView, PageNumberPagination): def get(self, request: Request, *args, **kwargs): pot_id = kwargs.get("pot_id") try: - pot = Pot.objects.get(id=pot_id) + pot = Pot.objects.get(account=pot_id) except Pot.DoesNotExist: return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) @@ -203,7 +203,7 @@ class PotSponsorsAPI(APIView, PageNumberPagination): def get(self, request: Request, *args, **kwargs): pot_id = kwargs.get("pot_id") try: - pot = Pot.objects.get(id=pot_id) + pot = Pot.objects.get(account=pot_id) except Pot.DoesNotExist: return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) @@ -245,7 +245,7 @@ class PotPayoutsAPI(APIView, PageNumberPagination): def get(self, request: Request, *args, **kwargs): pot_id = kwargs.get("pot_id") try: - pot = Pot.objects.get(id=pot_id) + pot = Pot.objects.get(account=pot_id) except Pot.DoesNotExist: return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) diff --git a/pots/migrations/0011_rename_id_pot_account_rename_id_potfactory_account.py b/pots/migrations/0011_rename_id_pot_account_rename_id_potfactory_account.py new file mode 100644 index 0000000..7ac40e8 --- /dev/null +++ b/pots/migrations/0011_rename_id_pot_account_rename_id_potfactory_account.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-07-19 22:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("pots", "0010_alter_potpayout_paid_at"), + ] + + operations = [ + migrations.RenameField( + model_name="pot", + old_name="id", + new_name="account", + ), + migrations.RenameField( + model_name="potfactory", + old_name="id", + new_name="account", + ), + ] diff --git a/pots/models.py b/pots/models.py index ba326ae..2cb8e82 100644 --- a/pots/models.py +++ b/pots/models.py @@ -13,7 +13,7 @@ class PotFactory(models.Model): - id = models.OneToOneField( + account = models.OneToOneField( Account, primary_key=True, related_name="pot_factory", @@ -70,7 +70,7 @@ class Meta: class Pot(models.Model): - id = models.OneToOneField( + account = models.OneToOneField( Account, primary_key=True, related_name="pot", @@ -460,7 +460,7 @@ def fetch_usd_prices(self): return self.amount_paid_usd = token.format_price(self.amount) * price_usd self.save() - logger.info(f"Saved USD prices for pot payout for pot id: {self.pot.id}") + logger.info(f"Saved USD prices for pot payout for pot id: {self.pot.account}") except Exception as e: logger.error(f"Failed to calculate and save USD prices: {e}") diff --git a/pots/serializers.py b/pots/serializers.py index 901affd..0ad1d3f 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -15,7 +15,7 @@ class PotSerializer(ModelSerializer): class Meta: model = Pot fields = [ - "id", + "account", "pot_factory", "deployer", "deployed_at", @@ -99,7 +99,7 @@ class Meta: EXAMPLE_POT_ID = "some-pot.v1.potfactory.potlock.near" SIMPLE_POT_EXAMPLE = { - "id": "some-pot.v1.potfactory.potlock.near", + "account": "some-pot.v1.potfactory.potlock.near", "deployed_at": "2024-02-16T17:45:03.600845Z", "source_metadata": { "link": "https://github.com/PotLock/core", diff --git a/tokens/admin.py b/tokens/admin.py index b949d2b..6caa086 100644 --- a/tokens/admin.py +++ b/tokens/admin.py @@ -6,7 +6,7 @@ @admin.register(Token) class TokenAdmin(admin.ModelAdmin): list_display = ( - "id", + "account", "name", "symbol", "coingecko_id", @@ -14,7 +14,7 @@ class TokenAdmin(admin.ModelAdmin): "decimals", "get_most_recent_price", ) - search_fields = ("id",) + search_fields = ("account",) def get_most_recent_price(self, obj): price = obj.get_most_recent_price() @@ -35,7 +35,7 @@ def get_most_recent_price(self, obj): @admin.register(TokenHistoricalPrice) class TokenHistoricalPriceAdmin(admin.ModelAdmin): list_display = ("token", "timestamp", "price_usd") - search_fields = ("token__id",) + search_fields = ("token__account",) list_filter = ("timestamp",) # def has_add_permission(self, request): diff --git a/tokens/migrations/0006_rename_id_token_account.py b/tokens/migrations/0006_rename_id_token_account.py new file mode 100644 index 0000000..a485351 --- /dev/null +++ b/tokens/migrations/0006_rename_id_token_account.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-07-19 22:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("tokens", "0005_alter_token_coingecko_id_alter_token_icon_and_more"), + ] + + operations = [ + migrations.RenameField( + model_name="token", + old_name="id", + new_name="account", + ), + ] diff --git a/tokens/models.py b/tokens/models.py index 5ff50dc..c32e742 100644 --- a/tokens/models.py +++ b/tokens/models.py @@ -14,7 +14,7 @@ class Token(models.Model): - id = models.OneToOneField( + account = models.OneToOneField( Account, on_delete=models.CASCADE, primary_key=True, From f7134080673eba36bd39884fd3c84e8bdd5f3bc9 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Tue, 23 Jul 2024 14:23:24 +0100 Subject: [PATCH 252/454] Whitelist deployer index (#54) * index admin add whitelist deployer functin * fix payout pot id --- indexer_app/handler.py | 6 +++++- indexer_app/utils.py | 14 +++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index e4e6184..d1aab45 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -13,7 +13,7 @@ from pots.utils import match_pot_factory_pattern, match_pot_subaccount_pattern from .logging import log_memory_usage, logger -from .utils import handle_add_nadabot_admin # handle_batch_donations, +from .utils import handle_add_factory_deployers, handle_add_nadabot_admin # handle_batch_donations, from .utils import ( handle_add_stamp, handle_default_list_status_change, @@ -422,6 +422,10 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess logger.info(f"adding admins.. {args_dict}") await handle_add_nadabot_admin(args_dict, receiver_id) break + case "admin_add_whitelisted_deployers": + logger.info(f"adding whitelisted deployers... {args_dict}") + await handle_add_factory_deployers(args_dict, receiver_id) + break # TODO: handle remove upvote except Exception as e: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 7a3efea..c940987 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -710,7 +710,7 @@ async def handle_transfer_payout( logger.error(f"Failed to get config for pot {receiver_id}: {response.text}") else: data = response.json() - pot = await Pot.objects.aget(id=receiver_id) + pot = await Pot.objects.aget(account=receiver_id) pot.all_paid_out = data["all_paid_out"] await pot.asave() except Exception as e: @@ -802,6 +802,18 @@ async def handle_add_nadabot_admin(data, receiverId): logger.error(f"Failed to add nadabot admin, Error: {e}") +async def handle_add_factory_deployers(data, receiverId): + logger.info(f"adding factory deployer...: {data}, {receiverId}") + try: + factory = await PotFactory.objects.aget(id=receiverId) + + for acct in data["whitelisted_deployers"]: + user, _ = await Account.objects.aget_or_create(id=acct) + await factory.whitelisted_deployers.aadd(user) + except Exception as e: + logger.error(f"Failed to add factory whitelisted deployers, Error: {e}") + + # # TODO: Need to abstract some actions. # async def handle_batch_donations( # receiver_id: str, From a3e861bd55f90ed900b03544bee28cd7052b937d Mon Sep 17 00:00:00 2001 From: Prometheus Date: Tue, 23 Jul 2024 18:28:04 +0100 Subject: [PATCH 253/454] api endpoint for pot factories (#55) * api endpoint for pot factories * fix response serializer --- api/urls.py | 4 +++ pots/api.py | 32 +++++++++++++++++++++++- pots/serializers.py | 60 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/api/urls.py b/api/urls.py index ea956f6..8026c40 100644 --- a/api/urls.py +++ b/api/urls.py @@ -27,6 +27,7 @@ PotApplicationsAPI, PotDetailAPI, PotDonationsAPI, + PotFactoriesAPI, PotPayoutsAPI, PotsListAPI, PotSponsorsAPI, @@ -117,6 +118,9 @@ path( "v1/pots//payouts", PotPayoutsAPI.as_view(), name="pots_payouts_api" ), + path( + "v1/potfactories", PotFactoriesAPI.as_view(), name="pot_factories_api" + ), # stats path("v1/stats", StatsAPI.as_view(), name="stats_api"), ] diff --git a/pots/api.py b/pots/api.py index 2db837e..8195376 100644 --- a/pots/api.py +++ b/pots/api.py @@ -26,16 +26,19 @@ PaginatedDonationsResponseSerializer, ) -from .models import Pot, PotApplication, PotApplicationStatus +from .models import Pot, PotApplication, PotApplicationStatus, PotFactory from .serializers import ( PAGINATED_PAYOUT_EXAMPLE, PAGINATED_POT_APPLICATION_EXAMPLE, PAGINATED_POT_EXAMPLE, + PAGINATED_POT_FACTORY_EXAMPLE, SIMPLE_POT_EXAMPLE, PaginatedPotApplicationsResponseSerializer, + PaginatedPotFactoriesResponseSerializer, PaginatedPotPayoutsResponseSerializer, PaginatedPotsResponseSerializer, PotApplicationSerializer, + PotFactorySerializer, PotPayoutSerializer, PotSerializer, ) @@ -68,6 +71,33 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) +class PotFactoriesAPI(APIView, PageNumberPagination): + + @extend_schema( + responses={ + 200: OpenApiResponse( + response=PaginatedPotFactoriesResponseSerializer, + description="Returns a paginated list of pot factories", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for pot factories", + value=PAGINATED_POT_FACTORY_EXAMPLE, + response_only=True, + ), + ], + ), + } + ) + @method_decorator(cache_page(60 * 5)) + def get(self, request: Request, *args, **kwargs): + pot_factories = PotFactory.objects.all() + results = self.paginate_queryset(pot_factories, request, view=self) + serializer = PotFactorySerializer(results, many=True) + return self.get_paginated_response(serializer.data) + + class PotDetailAPI(APIView): @extend_schema( diff --git a/pots/serializers.py b/pots/serializers.py index 0ad1d3f..5a35438 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -5,7 +5,7 @@ from base.serializers import ResultPagination from tokens.serializers import SIMPLE_TOKEN_EXAMPLE, TokenSerializer -from .models import Pot, PotApplication, PotPayout +from .models import Pot, PotApplication, PotFactory, PotPayout class PotSerializer(ModelSerializer): @@ -58,6 +58,29 @@ class Meta: chef = AccountSerializer() +class PotFactorySerializer(ModelSerializer): + + class Meta: + model = PotFactory + fields = [ + "account", + "owner", + "admins", + "whitelisted_deployers", + "source_metadata", + "deployed_at", + "protocol_fee_basis_points", + "require_whitelist", + "protocol_fee_recipient" + ] + + owner = AccountSerializer() + protocol_fee_recipient = AccountSerializer() + admins = AccountSerializer(many=True) + whitelisted_deployers = AccountSerializer(many=True) + + + class PotApplicationSerializer(ModelSerializer): class Meta: @@ -98,6 +121,10 @@ class Meta: EXAMPLE_POT_ID = "some-pot.v1.potfactory.potlock.near" +EXAMPLE_POT_FACTORY_ID = "v1.potfactory.potlock.near" + + + SIMPLE_POT_EXAMPLE = { "account": "some-pot.v1.potfactory.potlock.near", "deployed_at": "2024-02-16T17:45:03.600845Z", @@ -155,6 +182,37 @@ class PaginatedPotsResponseSerializer(serializers.Serializer): results = PotSerializer(many=True) +SIMPLE_POT_FACTORY_EXAMPLE = { + "account": "v1.potfactory.potlock.near", + "owner": SIMPLE_ACCOUNT_EXAMPLE, + "admins": [SIMPLE_ACCOUNT_EXAMPLE], + "whitelisted_deployers": [SIMPLE_ACCOUNT_EXAMPLE], + "source_metadata": { + "link": "https://github.com/PotLock/core", + "version": "1.0.0", + "commit_hash": "e6b108e9442920333b44eb1a4068b9b9ae551d79" + }, + "deployed_at": "2024-02-12T13:49:58.940854Z", + "protocol_fee_basis_points": 200, + "require_whitelist": False, + "protocol_fee_recipient": SIMPLE_ACCOUNT_EXAMPLE +} + +class PaginatedPotFactoriesResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = PotFactorySerializer(many=True) + + +PAGINATED_POT_FACTORY_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_POT_FACTORY_EXAMPLE], +} + + SIMPLE_POT_APPLICATION_EXAMPLE = { "id": 2, "message": "Hi, I'm a great project and I'd like to apply for this pot.", From 083213452686543e54eb55c4cd4a47e0be0a1296 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Tue, 23 Jul 2024 18:32:13 +0100 Subject: [PATCH 254/454] get cooldown end while setting oayout (#58) --- indexer_app/utils.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index c940987..fe7cd0e 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -683,6 +683,15 @@ async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): insertion_data.append(pot_payout) await PotPayout.objects.abulk_create(insertion_data, ignore_conflicts=True) + url = f"{settings.FASTNEAR_RPC_URL}/account/{receiver_id}/view/get_config" + response = requests.get(url) + if response.status_code != 200: + logger.error(f"Failed to get config for pot {receiver_id}: {response.text}") + else: + data = response.json() + pot = await Pot.objects.aget(account=receiver_id) + pot.cooldown_end =datetime.fromtimestamp(data["cooldown_end_ms"] / 1000) + await pot.asave() except Exception as e: logger.error(f"Failed to set payouts, Error: {e}") From f43324d7c1b08f697be361d26135509b6292160b Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:00:51 -0400 Subject: [PATCH 255/454] use default PageNumberPagination (#59) --- README.md | 2 +- accounts/api.py | 17 +++++++++++++---- api/pagination.py | 21 ++++++++++++++++----- base/api.py | 1 - base/serializers.py | 2 +- base/settings.py | 2 +- donations/api.py | 6 +++++- lists/api.py | 16 +++++++++++----- pots/api.py | 15 +++++++++++++-- pots/serializers.py | 17 ++++++++--------- 10 files changed, 69 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 7ab959d..e2f5204 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ Possible Error Codes: #### Pagination -Pagination available using `limit` and `page` as query param on endpoints that specify `paginated`. Default `limit` is 30. +Pagination available using `page` and `page_size` as query param on endpoints that specify `paginated`. Default `page_size` is 30. Endpoints that support pagination will return a success response containing the following: diff --git a/accounts/api.py b/accounts/api.py index f4f9f5f..8dd98d9 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -14,6 +14,7 @@ from rest_framework.response import Response from rest_framework.views import APIView +from api.pagination import pagination_parameters from base.logging import logger from donations.models import Donation from donations.serializers import ( @@ -41,7 +42,6 @@ AccountSerializer, PaginatedAccountsResponseSerializer, ) -from api.pagination import ResultPagination class DonorsAPI(APIView, PageNumberPagination): @@ -53,7 +53,8 @@ class DonorsAPI(APIView, PageNumberPagination): type=str, location=OpenApiParameter.QUERY, description="Sort by field, e.g., most_donated_usd", - ) + ), + *pagination_parameters, ], responses={ 200: OpenApiResponse( @@ -88,9 +89,12 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountsListAPI(APIView, ResultPagination): +class AccountsListAPI(APIView, PageNumberPagination): @extend_schema( + parameters=[ + *pagination_parameters, + ], responses={ 200: OpenApiResponse( response=PaginatedAccountsResponseSerializer, @@ -106,7 +110,7 @@ class AccountsListAPI(APIView, ResultPagination): ], ), 500: OpenApiResponse(description="Internal server error"), - } + }, ) @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): @@ -165,6 +169,7 @@ class AccountActivePotsAPI(APIView, PageNumberPagination): required=False, description="Filter by pot status", ), + *pagination_parameters, ], responses={ 200: OpenApiResponse( @@ -220,6 +225,7 @@ class AccountPotApplicationsAPI(APIView, PageNumberPagination): OpenApiParameter.QUERY, description="Filter pot applications by status", ), + *pagination_parameters, ], responses={ 200: OpenApiResponse( @@ -268,6 +274,7 @@ class AccountDonationsReceivedAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ OpenApiParameter("account_id", str, OpenApiParameter.PATH), + *pagination_parameters, ], responses={ 200: OpenApiResponse( @@ -308,6 +315,7 @@ class AccountDonationsSentAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ OpenApiParameter("account_id", str, OpenApiParameter.PATH), + *pagination_parameters, ], responses={ 200: OpenApiResponse( @@ -348,6 +356,7 @@ class AccountPayoutsReceivedAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ OpenApiParameter("account_id", str, OpenApiParameter.PATH), + *pagination_parameters, ], responses={ 200: OpenApiResponse( diff --git a/api/pagination.py b/api/pagination.py index 21f4d65..b949b6b 100644 --- a/api/pagination.py +++ b/api/pagination.py @@ -1,6 +1,17 @@ -from rest_framework.pagination import PageNumberPagination +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiParameter -class ResultPagination(PageNumberPagination): - page_size = 30 - page_size_query_param = 'limit' - max_page_size = 200 +pagination_parameters = [ + OpenApiParameter( + "page", + OpenApiTypes.INT, + OpenApiParameter.QUERY, + description="Page number for pagination", + ), + OpenApiParameter( + "page_size", + OpenApiTypes.INT, + OpenApiParameter.QUERY, + description="Number of results per page", + ), +] diff --git a/base/api.py b/base/api.py index 602f810..a5540ef 100644 --- a/base/api.py +++ b/base/api.py @@ -9,7 +9,6 @@ extend_schema, ) from rest_framework import serializers -from rest_framework.pagination import PageNumberPagination from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView diff --git a/base/serializers.py b/base/serializers.py index a27aa7e..0437351 100644 --- a/base/serializers.py +++ b/base/serializers.py @@ -1,7 +1,7 @@ from rest_framework import serializers -class ResultPagination(serializers.DecimalField): +class TwoDecimalPlacesField(serializers.DecimalField): def to_representation(self, value): if value is None: return value diff --git a/base/settings.py b/base/settings.py index 3919a7e..a2ed79b 100644 --- a/base/settings.py +++ b/base/settings.py @@ -111,7 +111,7 @@ DEFAULT_PAGE_SIZE = 30 REST_FRAMEWORK = { - "DEFAULT_PAGINATION_CLASS": "api.pagination.ResultPagination", + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", "PAGE_SIZE": DEFAULT_PAGE_SIZE, "DEFAULT_THROTTLE_CLASSES": [ # "rest_framework.throttling.UserRateThrottle", diff --git a/donations/api.py b/donations/api.py index 74f8d55..0e8f522 100644 --- a/donations/api.py +++ b/donations/api.py @@ -13,6 +13,7 @@ from rest_framework.response import Response from rest_framework.views import APIView +from api.pagination import pagination_parameters from base.logging import logger from .serializers import DonationContractConfigSerializer @@ -23,6 +24,9 @@ class DonationContractConfigAPI(APIView, PageNumberPagination): @extend_schema( + parameters=[ + *pagination_parameters, + ], responses={ 200: OpenApiResponse( response=DonationContractConfigSerializer, @@ -43,7 +47,7 @@ class DonationContractConfigAPI(APIView, PageNumberPagination): ], ), 500: OpenApiResponse(description="Internal server error"), - } + }, ) @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): diff --git a/lists/api.py b/lists/api.py index 4cfa60a..6b0d6cb 100644 --- a/lists/api.py +++ b/lists/api.py @@ -15,6 +15,8 @@ from rest_framework.response import Response from rest_framework.views import APIView +from api.pagination import pagination_parameters + from .models import List, ListRegistrationStatus from .serializers import ( PAGINATED_LIST_EXAMPLE, @@ -31,6 +33,9 @@ class ListsListAPI(APIView, PageNumberPagination): @extend_schema( + parameters=[ + *pagination_parameters, + ], responses={ 200: OpenApiResponse( response=PaginatedListsResponseSerializer, @@ -46,7 +51,7 @@ class ListsListAPI(APIView, PageNumberPagination): ], ), 500: OpenApiResponse(description="Internal server error"), - } + }, ) @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): @@ -110,6 +115,7 @@ class ListRegistrationsAPI(APIView, PageNumberPagination): OpenApiParameter.QUERY, description="Filter registrations by category", ), + *pagination_parameters, ], responses={ 200: OpenApiResponse( @@ -149,10 +155,10 @@ def get(self, request: Request, *args, **kwargs): ) registrations = registrations.filter(status=status_param) if category_param: - category_regex_pattern = rf'\[.*?"{category_param}".*?\]' - registrations = registrations.filter( - registrant__near_social_profile_data__plCategories__iregex = category_regex_pattern - ) + category_regex_pattern = rf'\[.*?"{category_param}".*?\]' + registrations = registrations.filter( + registrant__near_social_profile_data__plCategories__iregex=category_regex_pattern + ) results = self.paginate_queryset(registrations, request, view=self) serializer = ListRegistrationSerializer(results, many=True) return self.get_paginated_response(serializer.data) diff --git a/pots/api.py b/pots/api.py index 8195376..8ee267b 100644 --- a/pots/api.py +++ b/pots/api.py @@ -19,6 +19,7 @@ AccountSerializer, PaginatedAccountsResponseSerializer, ) +from api.pagination import pagination_parameters from donations.models import Donation from donations.serializers import ( PAGINATED_DONATION_EXAMPLE, @@ -47,6 +48,9 @@ class PotsListAPI(APIView, PageNumberPagination): @extend_schema( + parameters=[ + *pagination_parameters, + ], responses={ 200: OpenApiResponse( response=PaginatedPotsResponseSerializer, @@ -61,7 +65,7 @@ class PotsListAPI(APIView, PageNumberPagination): ), ], ), - } + }, ) @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): @@ -74,6 +78,9 @@ def get(self, request: Request, *args, **kwargs): class PotFactoriesAPI(APIView, PageNumberPagination): @extend_schema( + parameters=[ + *pagination_parameters, + ], responses={ 200: OpenApiResponse( response=PaginatedPotFactoriesResponseSerializer, @@ -88,7 +95,7 @@ class PotFactoriesAPI(APIView, PageNumberPagination): ), ], ), - } + }, ) @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): @@ -137,6 +144,7 @@ class PotApplicationsAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ OpenApiParameter("pot_id", str, OpenApiParameter.PATH), + *pagination_parameters, ], responses={ 200: OpenApiResponse( @@ -174,6 +182,7 @@ class PotDonationsAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ OpenApiParameter("pot_id", str, OpenApiParameter.PATH), + *pagination_parameters, ], responses={ 200: OpenApiResponse( @@ -211,6 +220,7 @@ class PotSponsorsAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ OpenApiParameter("pot_id", str, OpenApiParameter.PATH), + *pagination_parameters, ], responses={ 200: OpenApiResponse( @@ -253,6 +263,7 @@ class PotPayoutsAPI(APIView, PageNumberPagination): @extend_schema( parameters=[ OpenApiParameter("pot_id", str, OpenApiParameter.PATH), + *pagination_parameters, ], responses={ 200: OpenApiResponse( diff --git a/pots/serializers.py b/pots/serializers.py index 5a35438..3f3c722 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -2,15 +2,15 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer -from base.serializers import ResultPagination +from base.serializers import TwoDecimalPlacesField from tokens.serializers import SIMPLE_TOKEN_EXAMPLE, TokenSerializer from .models import Pot, PotApplication, PotFactory, PotPayout class PotSerializer(ModelSerializer): - total_matching_pool_usd = ResultPagination(max_digits=20, decimal_places=2) - total_public_donations_usd = ResultPagination(max_digits=20, decimal_places=2) + total_matching_pool_usd = TwoDecimalPlacesField(max_digits=20, decimal_places=2) + total_public_donations_usd = TwoDecimalPlacesField(max_digits=20, decimal_places=2) class Meta: model = Pot @@ -71,7 +71,7 @@ class Meta: "deployed_at", "protocol_fee_basis_points", "require_whitelist", - "protocol_fee_recipient" + "protocol_fee_recipient", ] owner = AccountSerializer() @@ -80,7 +80,6 @@ class Meta: whitelisted_deployers = AccountSerializer(many=True) - class PotApplicationSerializer(ModelSerializer): class Meta: @@ -124,7 +123,6 @@ class Meta: EXAMPLE_POT_FACTORY_ID = "v1.potfactory.potlock.near" - SIMPLE_POT_EXAMPLE = { "account": "some-pot.v1.potfactory.potlock.near", "deployed_at": "2024-02-16T17:45:03.600845Z", @@ -188,16 +186,17 @@ class PaginatedPotsResponseSerializer(serializers.Serializer): "admins": [SIMPLE_ACCOUNT_EXAMPLE], "whitelisted_deployers": [SIMPLE_ACCOUNT_EXAMPLE], "source_metadata": { - "link": "https://github.com/PotLock/core", + "link": "https://github.com/PotLock/core", "version": "1.0.0", - "commit_hash": "e6b108e9442920333b44eb1a4068b9b9ae551d79" + "commit_hash": "e6b108e9442920333b44eb1a4068b9b9ae551d79", }, "deployed_at": "2024-02-12T13:49:58.940854Z", "protocol_fee_basis_points": 200, "require_whitelist": False, - "protocol_fee_recipient": SIMPLE_ACCOUNT_EXAMPLE + "protocol_fee_recipient": SIMPLE_ACCOUNT_EXAMPLE, } + class PaginatedPotFactoriesResponseSerializer(serializers.Serializer): count = serializers.IntegerField() next = serializers.CharField(allow_null=True) From ec9bb25cbdf9b9bed745ff1849a680a68cb4da67 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Thu, 25 Jul 2024 16:44:46 +0100 Subject: [PATCH 256/454] factory update configs (#60) * method for factory update config * index config update functions * use pot config update method for indexing * clear deployers before adding --- indexer_app/handler.py | 22 ++++++++++++++++++---- indexer_app/utils.py | 13 +++++++++++-- pots/models.py | 27 +++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index d1aab45..9a1c206 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -13,7 +13,7 @@ from pots.utils import match_pot_factory_pattern, match_pot_subaccount_pattern from .logging import log_memory_usage, logger -from .utils import handle_add_factory_deployers, handle_add_nadabot_admin # handle_batch_donations, +from .utils import handle_add_nadabot_admin, handle_set_factory_configs # handle_batch_donations, from .utils import ( handle_add_stamp, handle_default_list_status_change, @@ -420,11 +420,25 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess break case "owner_add_admins": logger.info(f"adding admins.. {args_dict}") + if not match_nadabot_registry_pattern(receiver_id): + break await handle_add_nadabot_admin(args_dict, receiver_id) break - case "admin_add_whitelisted_deployers": - logger.info(f"adding whitelisted deployers... {args_dict}") - await handle_add_factory_deployers(args_dict, receiver_id) + case ( + "admin_set_require_whitelist" + | "admin_add_whitelisted_deployers" + | "admin_set_protocol_config" + | "admin_set_protocol_fee_recipient_account" + | "admin_set_protocol_fee_basis_points" + | "owner_set_admins" + | "owner_clear_admins" + | "owner_add_admins" + | "owner_remove_admins" + ): + if not match_pot_factory_pattern(receiver_id): + break + logger.info(f"updating configs.. {args_dict}") + await handle_set_factory_configs(args_dict, receiver_id) break # TODO: handle remove upvote diff --git a/indexer_app/utils.py b/indexer_app/utils.py index fe7cd0e..7a2a143 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -2,6 +2,7 @@ import json from datetime import datetime from math import log +from asgiref.sync import sync_to_async import requests from django.conf import settings @@ -814,14 +815,22 @@ async def handle_add_nadabot_admin(data, receiverId): async def handle_add_factory_deployers(data, receiverId): logger.info(f"adding factory deployer...: {data}, {receiverId}") try: - factory = await PotFactory.objects.aget(id=receiverId) - + factory = await PotFactory.objects.aget(account=receiverId) for acct in data["whitelisted_deployers"]: user, _ = await Account.objects.aget_or_create(id=acct) await factory.whitelisted_deployers.aadd(user) except Exception as e: logger.error(f"Failed to add factory whitelisted deployers, Error: {e}") + +async def handle_set_factory_configs(data, receiverId): + logger.info(f"setting factory configs...: {data}, {receiverId}") + try: + factory = await PotFactory.objects.aget(account=receiverId) + config_update = sync_to_async(factory.update_configs) + await config_update() + except Exception as e: + logger.error(f"Failed to update factory configs, Error: {e}") # # TODO: Need to abstract some actions. # async def handle_batch_donations( diff --git a/pots/models.py b/pots/models.py index 2cb8e82..44c71ef 100644 --- a/pots/models.py +++ b/pots/models.py @@ -67,6 +67,33 @@ class PotFactory(models.Model): class Meta: verbose_name_plural = "Pot Factories" + + + def update_configs(self): + try: + + url = f"{settings.FASTNEAR_RPC_URL}/account/{self.account.id}/view/get_config" + response = requests.get(url) + if response.status_code != 200: + logger.error(f"Failed to get config for pot {self.account}: {response.text}") + else: + config = response.json() + print(config) + self.protocol_fee_basis_points = config.get("protocol_fee_basis_points") + acct, created = Account.objects.get_or_create(id=config.get("protocol_fee_recipient_account")) + self.protocol_fee_recipient = acct + self.require_whitelist = config.get("require_whitelist") + self.owner, created = Account.objects.get_or_create(id=config.get("owner")) + self.admins.clear() + for admin_id in config.get("admins"): + self.admins.add(Account.objects.get_or_create(id=admin_id)[0]) + self.whitelisted_deployers.clear() + for deployer_id in config.get("whitelisted_deployers"): + self.whitelisted_deployers.add(Account.objects.get_or_create(id=deployer_id)[0]) + self.save() + except Exception as e: + logger.error(f"Failed to update factory config, Error: {e}") + class Pot(models.Model): From 8c2b2496c1509be4f675d8bb0f10fdfbc54577f9 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:55:56 -0400 Subject: [PATCH 257/454] remove caching of blockheight (just save in db) (#61) --- indexer_app/tasks.py | 30 ++++++++++++------------------ indexer_app/utils.py | 28 ++++++++++------------------ 2 files changed, 22 insertions(+), 36 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index c0853c1..dfeed8a 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -8,7 +8,6 @@ from celery import shared_task from celery.signals import task_revoked, worker_shutdown from django.conf import settings -from django.core.cache import cache from django.db import transaction from django.db.models import Count, DecimalField, Q, Sum, Value from django.db.models.functions import Cast, NullIf @@ -21,7 +20,7 @@ from pots.models import Pot, PotPayout from .logging import logger -from .utils import cache_block_height, get_block_height +from .utils import get_block_height, save_block_height CURRENT_BLOCK_HEIGHT_KEY = "current_block_height" @@ -62,23 +61,16 @@ async def indexer(from_block: int, to_block: int): block_count += 1 # Log time before caching block height - cache_start_time = time.time() - # Fire and forget the cache update - asyncio.create_task( - cache_block_height( - CURRENT_BLOCK_HEIGHT_KEY, - streamer_message.block.header.height, - block_count, - streamer_message.block.header.timestamp, - ) # current block height - # cache.aset( - # CURRENT_BLOCK_HEIGHT_KEY, streamer_message.block.header.height - # ) + save_start_time = time.time() + # Update current block height + await save_block_height( + streamer_message.block.header.height, + streamer_message.block.header.timestamp, ) - cache_end_time = time.time() + save_end_time = time.time() logger.info( - f"Time to cache block height: {cache_end_time - cache_start_time:.4f} seconds" + f"Time to save block height: {save_end_time - save_start_time:.4f} seconds" ) # Log time before handling the streamer message @@ -107,7 +99,7 @@ def listen_to_near_events(): try: # Update below with desired network & block height - start_block = get_block_height(CURRENT_BLOCK_HEIGHT_KEY) + start_block = get_block_height() # start_block = 119_568_113 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer(start_block - 1, None)) @@ -219,7 +211,9 @@ def update_pot_statistics(): jobs_logger.info(f"Total matching pool USD: {pot.total_matching_pool_usd}") # matching pool balance (get from contract) - url = f"{settings.FASTNEAR_RPC_URL}/account/{pot.account.id}/view/get_config" + url = ( + f"{settings.FASTNEAR_RPC_URL}/account/{pot.account.id}/view/get_config" + ) response = requests.get(url) if response.status_code != 200: jobs_logger.error( diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 7a2a143..20f8a21 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -2,9 +2,9 @@ import json from datetime import datetime from math import log -from asgiref.sync import sync_to_async import requests +from asgiref.sync import sync_to_async from django.conf import settings from django.core.cache import cache from django.forms.models import model_to_dict @@ -691,7 +691,7 @@ async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): else: data = response.json() pot = await Pot.objects.aget(account=receiver_id) - pot.cooldown_end =datetime.fromtimestamp(data["cooldown_end_ms"] / 1000) + pot.cooldown_end = datetime.fromtimestamp(data["cooldown_end_ms"] / 1000) await pot.asave() except Exception as e: logger.error(f"Failed to set payouts, Error: {e}") @@ -822,7 +822,7 @@ async def handle_add_factory_deployers(data, receiverId): except Exception as e: logger.error(f"Failed to add factory whitelisted deployers, Error: {e}") - + async def handle_set_factory_configs(data, receiverId): logger.info(f"setting factory configs...: {data}, {receiverId}") try: @@ -832,6 +832,7 @@ async def handle_set_factory_configs(data, receiverId): except Exception as e: logger.error(f"Failed to update factory configs, Error: {e}") + # # TODO: Need to abstract some actions. # async def handle_batch_donations( # receiver_id: str, @@ -1185,18 +1186,11 @@ async def handle_new_group(data: dict, created_at: datetime): logger.error(f"Failed to create group, because: {e}") -async def cache_block_height( - key: str, height: int, block_count: int, block_timestamp: int -): - logger.info(f"caching block height: {height}") - await cache.aset(key, height) - # the cache os the default go to for the restart block, the db is a backup if the redis server crashes. - # if (block_count % int(settings.BLOCK_SAVE_HEIGHT or 400)) == 0: - # logger.info(f"saving daylight, {height}") +async def save_block_height(block_height: int, block_timestamp: int): await BlockHeight.objects.aupdate_or_create( id=1, defaults={ - "block_height": height, + "block_height": block_height, "block_timestamp": datetime.fromtimestamp(block_timestamp / 1000000000), "updated_at": timezone.now(), }, @@ -1204,9 +1198,7 @@ async def cache_block_height( # return height -def get_block_height(key: str) -> int: - block_height = cache.get(key) - if not block_height: - record = BlockHeight.objects.filter(id=1).first() - block_height = 104_922_190 if not record else record.block_height - return block_height +def get_block_height() -> int: + record = BlockHeight.objects.filter(id=1).first() + if record: + return record.block_height From 9514e49276d78a2e0b44f95b327a54d2935d0492 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:05:53 -0400 Subject: [PATCH 258/454] add asyncio handling of blockheight saving (#62) * remove caching of blockheight (just save in db) * add asyncio task scheudling back in for saving blockheight --- indexer_app/tasks.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index dfeed8a..2d06c48 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -63,9 +63,11 @@ async def indexer(from_block: int, to_block: int): # Log time before caching block height save_start_time = time.time() # Update current block height - await save_block_height( - streamer_message.block.header.height, - streamer_message.block.header.timestamp, + asyncio.create_task( + save_block_height( + streamer_message.block.header.height, + streamer_message.block.header.timestamp, + ) ) save_end_time = time.time() From d73321da6b5886831054bcbe2fbe570fcb355228 Mon Sep 17 00:00:00 2001 From: Lachlan Glen <54282009+lachlanglen@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:18:25 -0400 Subject: [PATCH 259/454] add block_timestamp to BlockHeightAdmin --- indexer_app/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/admin.py b/indexer_app/admin.py index e94e67a..422dd86 100644 --- a/indexer_app/admin.py +++ b/indexer_app/admin.py @@ -5,7 +5,7 @@ @admin.register(BlockHeight) class BlockHeightAdmin(admin.ModelAdmin): - list_display = ("id", "block_height", "updated_at") + list_display = ("id", "block_height", "block_timestamp", "updated_at") ordering = ("-updated_at",) def has_add_permission(self, request): From aacaf29c4d1598198c1e81d5339adfcd9d48c8e8 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Thu, 25 Jul 2024 18:34:29 +0100 Subject: [PATCH 260/454] add balnk field to nullable fields (#63) --- ..._alter_account_near_social_profile_data.py | 23 ++ accounts/models.py | 1 + ...ty_action_result_alter_activity_tx_hash.py | 34 +++ activities/models.py | 2 + ...n_chef_alter_donation_chef_fee_and_more.py | 169 ++++++++++++ donations/models.py | 13 + .../0003_alter_blockheight_block_timestamp.py | 23 ++ indexer_app/models.py | 1 + ...age_url_alter_list_description_and_more.py | 67 +++++ lists/models.py | 5 + ...rule_type_alter_group_rule_val_and_more.py | 51 ++++ nadabot/models.py | 3 + ...t_base_currency_alter_pot_chef_and_more.py | 261 ++++++++++++++++++ pots/models.py | 23 ++ 14 files changed, 676 insertions(+) create mode 100644 accounts/migrations/0006_alter_account_near_social_profile_data.py create mode 100644 activities/migrations/0005_alter_activity_action_result_alter_activity_tx_hash.py create mode 100644 donations/migrations/0013_alter_donation_chef_alter_donation_chef_fee_and_more.py create mode 100644 indexer_app/migrations/0003_alter_blockheight_block_timestamp.py create mode 100644 lists/migrations/0007_alter_list_cover_image_url_alter_list_description_and_more.py create mode 100644 nadabot/migrations/0003_alter_group_rule_type_alter_group_rule_val_and_more.py create mode 100644 pots/migrations/0012_alter_pot_base_currency_alter_pot_chef_and_more.py diff --git a/accounts/migrations/0006_alter_account_near_social_profile_data.py b/accounts/migrations/0006_alter_account_near_social_profile_data.py new file mode 100644 index 0000000..45dc3ea --- /dev/null +++ b/accounts/migrations/0006_alter_account_near_social_profile_data.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-07-25 17:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0005_alter_account_chain"), + ] + + operations = [ + migrations.AlterField( + model_name="account", + name="near_social_profile_data", + field=models.JSONField( + blank=True, + help_text="NEAR social data contained under 'profile' key.", + null=True, + verbose_name="NEAR social profile data", + ), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 65a869d..a6175b0 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -57,6 +57,7 @@ class Account(models.Model): near_social_profile_data = models.JSONField( _("NEAR social profile data"), null=True, + blank=True, help_text=_("NEAR social data contained under 'profile' key."), ) diff --git a/activities/migrations/0005_alter_activity_action_result_alter_activity_tx_hash.py b/activities/migrations/0005_alter_activity_action_result_alter_activity_tx_hash.py new file mode 100644 index 0000000..039c2a5 --- /dev/null +++ b/activities/migrations/0005_alter_activity_action_result_alter_activity_tx_hash.py @@ -0,0 +1,34 @@ +# Generated by Django 5.0.6 on 2024-07-25 17:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("activities", "0004_alter_activity_tx_hash"), + ] + + operations = [ + migrations.AlterField( + model_name="activity", + name="action_result", + field=models.JSONField( + blank=True, + help_text="Activity action result.", + null=True, + verbose_name="action result", + ), + ), + migrations.AlterField( + model_name="activity", + name="tx_hash", + field=models.CharField( + blank=True, + help_text="Transaction hash.", + max_length=64, + null=True, + verbose_name="transaction hash", + ), + ), + ] diff --git a/activities/models.py b/activities/models.py index 3eefb5b..274a0e3 100644 --- a/activities/models.py +++ b/activities/models.py @@ -48,12 +48,14 @@ class Activity(models.Model): action_result = models.JSONField( _("action result"), null=True, + blank=True, help_text=_("Activity action result."), ) tx_hash = models.CharField( _("transaction hash"), max_length=64, null=True, + blank=True, help_text=_("Transaction hash."), ) type = models.CharField( diff --git a/donations/migrations/0013_alter_donation_chef_alter_donation_chef_fee_and_more.py b/donations/migrations/0013_alter_donation_chef_alter_donation_chef_fee_and_more.py new file mode 100644 index 0000000..0b4314d --- /dev/null +++ b/donations/migrations/0013_alter_donation_chef_alter_donation_chef_fee_and_more.py @@ -0,0 +1,169 @@ +# Generated by Django 5.0.6 on 2024-07-25 17:16 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ("donations", "0012_update_donations_net_amount"), + ("pots", "0012_alter_pot_base_currency_alter_pot_chef_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="donation", + name="chef", + field=models.ForeignKey( + blank=True, + help_text="Donation chef.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="chef_donations", + to="accounts.account", + ), + ), + migrations.AlterField( + model_name="donation", + name="chef_fee", + field=models.CharField( + blank=True, + help_text="Chef fee.", + max_length=64, + null=True, + verbose_name="chef fee", + ), + ), + migrations.AlterField( + model_name="donation", + name="chef_fee_usd", + field=models.DecimalField( + blank=True, + decimal_places=2, + help_text="Chef fee in USD.", + max_digits=20, + null=True, + verbose_name="chef fee in USD", + ), + ), + migrations.AlterField( + model_name="donation", + name="message", + field=models.TextField( + blank=True, + help_text="Donation message.", + max_length=1024, + null=True, + verbose_name="message", + ), + ), + migrations.AlterField( + model_name="donation", + name="net_amount_usd", + field=models.DecimalField( + blank=True, + decimal_places=2, + help_text="Net amount in USD.", + max_digits=20, + null=True, + verbose_name="net amount in USD", + ), + ), + migrations.AlterField( + model_name="donation", + name="pot", + field=models.ForeignKey( + blank=True, + help_text="Donation pot.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="donations", + to="pots.pot", + ), + ), + migrations.AlterField( + model_name="donation", + name="protocol_fee_usd", + field=models.DecimalField( + blank=True, + decimal_places=2, + help_text="Protocol fee in USD.", + max_digits=20, + null=True, + verbose_name="protocol fee in USD", + ), + ), + migrations.AlterField( + model_name="donation", + name="recipient", + field=models.ForeignKey( + blank=True, + help_text="Donation recipient.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="received_donations", + to="accounts.account", + ), + ), + migrations.AlterField( + model_name="donation", + name="referrer", + field=models.ForeignKey( + blank=True, + help_text="Donation referrer.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="referral_donations", + to="accounts.account", + ), + ), + migrations.AlterField( + model_name="donation", + name="referrer_fee", + field=models.CharField( + blank=True, + help_text="Referrer fee.", + max_length=64, + null=True, + verbose_name="referrer fee", + ), + ), + migrations.AlterField( + model_name="donation", + name="referrer_fee_usd", + field=models.DecimalField( + blank=True, + decimal_places=2, + help_text="Referrer fee in USD.", + max_digits=20, + null=True, + verbose_name="referrer fee in USD", + ), + ), + migrations.AlterField( + model_name="donation", + name="total_amount_usd", + field=models.DecimalField( + blank=True, + db_index=True, + decimal_places=2, + help_text="Total amount in USD.", + max_digits=20, + null=True, + verbose_name="total amount in USD", + ), + ), + migrations.AlterField( + model_name="donation", + name="tx_hash", + field=models.CharField( + blank=True, + help_text="Transaction hash.", + max_length=64, + null=True, + verbose_name="transaction hash", + ), + ), + ] diff --git a/donations/models.py b/donations/models.py index 824be55..b05c431 100644 --- a/donations/models.py +++ b/donations/models.py @@ -47,6 +47,7 @@ class Donation(models.Model): max_digits=20, decimal_places=2, null=True, + blank=True, help_text=_("Total amount in USD."), db_index=True, ) @@ -61,6 +62,7 @@ class Donation(models.Model): max_digits=20, decimal_places=2, null=True, + blank=True, help_text=_("Net amount in USD."), ) # ft = models.ForeignKey( @@ -83,6 +85,7 @@ class Donation(models.Model): on_delete=models.CASCADE, related_name="donations", null=True, + blank=True, help_text=_("Donation pot."), db_index=True, ) @@ -96,6 +99,7 @@ class Donation(models.Model): _("message"), max_length=1024, null=True, + blank=True, help_text=_("Donation message."), ) donated_at = models.DateTimeField( @@ -109,6 +113,7 @@ class Donation(models.Model): on_delete=models.CASCADE, related_name="received_donations", null=True, + blank=True, help_text=_("Donation recipient."), db_index=True, ) @@ -123,6 +128,7 @@ class Donation(models.Model): max_digits=20, decimal_places=2, null=True, + blank=True, help_text=_("Protocol fee in USD."), ) referrer = models.ForeignKey( @@ -130,12 +136,14 @@ class Donation(models.Model): on_delete=models.CASCADE, related_name="referral_donations", null=True, + blank=True, help_text=_("Donation referrer."), ) referrer_fee = models.CharField( _("referrer fee"), max_length=64, null=True, + blank=True, help_text=_("Referrer fee."), ) referrer_fee_usd = models.DecimalField( @@ -143,6 +151,7 @@ class Donation(models.Model): max_digits=20, decimal_places=2, null=True, + blank=True, help_text=_("Referrer fee in USD."), ) chef = models.ForeignKey( @@ -150,12 +159,14 @@ class Donation(models.Model): on_delete=models.CASCADE, related_name="chef_donations", null=True, + blank=True, help_text=_("Donation chef."), ) chef_fee = models.CharField( _("chef fee"), max_length=64, null=True, + blank=True, help_text=_("Chef fee."), ) chef_fee_usd = models.DecimalField( @@ -163,12 +174,14 @@ class Donation(models.Model): max_digits=20, decimal_places=2, null=True, + blank=True, help_text=_("Chef fee in USD."), ) tx_hash = models.CharField( _("transaction hash"), max_length=64, null=True, + blank=True, help_text=_("Transaction hash."), ) diff --git a/indexer_app/migrations/0003_alter_blockheight_block_timestamp.py b/indexer_app/migrations/0003_alter_blockheight_block_timestamp.py new file mode 100644 index 0000000..d48b410 --- /dev/null +++ b/indexer_app/migrations/0003_alter_blockheight_block_timestamp.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-07-25 17:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("indexer_app", "0002_blockheight_block_timestamp"), + ] + + operations = [ + migrations.AlterField( + model_name="blockheight", + name="block_timestamp", + field=models.DateTimeField( + blank=True, + help_text="date equivalent of the block height.", + null=True, + verbose_name="block timestamp", + ), + ), + ] diff --git a/indexer_app/models.py b/indexer_app/models.py index ace12ec..933bfe9 100644 --- a/indexer_app/models.py +++ b/indexer_app/models.py @@ -16,6 +16,7 @@ class BlockHeight(models.Model): _("block timestamp"), help_text=_("date equivalent of the block height."), null=True, + blank=True, ) updated_at = models.DateTimeField( _("updated at"), diff --git a/lists/migrations/0007_alter_list_cover_image_url_alter_list_description_and_more.py b/lists/migrations/0007_alter_list_cover_image_url_alter_list_description_and_more.py new file mode 100644 index 0000000..66ef357 --- /dev/null +++ b/lists/migrations/0007_alter_list_cover_image_url_alter_list_description_and_more.py @@ -0,0 +1,67 @@ +# Generated by Django 5.0.6 on 2024-07-25 17:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("lists", "0006_alter_listregistration_unique_together"), + ] + + operations = [ + migrations.AlterField( + model_name="list", + name="cover_image_url", + field=models.URLField( + blank=True, + help_text="Cover image url.", + null=True, + verbose_name="cover image url", + ), + ), + migrations.AlterField( + model_name="list", + name="description", + field=models.TextField( + blank=True, + help_text="List description.", + max_length=256, + null=True, + verbose_name="description", + ), + ), + migrations.AlterField( + model_name="listregistration", + name="admin_notes", + field=models.TextField( + blank=True, + help_text="Admin notes.", + max_length=1024, + null=True, + verbose_name="admin notes", + ), + ), + migrations.AlterField( + model_name="listregistration", + name="registrant_notes", + field=models.TextField( + blank=True, + help_text="Registrant notes.", + max_length=1024, + null=True, + verbose_name="registrant notes", + ), + ), + migrations.AlterField( + model_name="listregistration", + name="tx_hash", + field=models.CharField( + blank=True, + help_text="Transaction hash.", + max_length=64, + null=True, + verbose_name="transaction hash", + ), + ), + ] diff --git a/lists/models.py b/lists/models.py index 953b1fe..9754f93 100644 --- a/lists/models.py +++ b/lists/models.py @@ -46,11 +46,13 @@ class List(models.Model): _("description"), max_length=256, null=True, + blank=True, help_text=_("List description."), ) cover_image_url = models.URLField( _("cover image url"), null=True, + blank=True, help_text=_("Cover image url."), ) admin_only_registrations = models.BooleanField( @@ -161,18 +163,21 @@ class ListRegistration(models.Model): _("registrant notes"), max_length=1024, null=True, + blank=True, help_text=_("Registrant notes."), ) admin_notes = models.TextField( _("admin notes"), max_length=1024, null=True, + blank=True, help_text=_("Admin notes."), ) tx_hash = models.CharField( _("transaction hash"), max_length=64, null=True, + blank=True, help_text=_("Transaction hash."), ) diff --git a/nadabot/migrations/0003_alter_group_rule_type_alter_group_rule_val_and_more.py b/nadabot/migrations/0003_alter_group_rule_type_alter_group_rule_val_and_more.py new file mode 100644 index 0000000..ea2de00 --- /dev/null +++ b/nadabot/migrations/0003_alter_group_rule_type_alter_group_rule_val_and_more.py @@ -0,0 +1,51 @@ +# Generated by Django 5.0.6 on 2024-07-25 17:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("nadabot", "0002_rename_id_nadabotregistry_account"), + ] + + operations = [ + migrations.AlterField( + model_name="group", + name="rule_type", + field=models.CharField( + blank=True, + choices=[ + ("Highest", "Highest"), + ("Lowest", "Lowest"), + ("Sum", "Sum"), + ("Diminishing_returns", "Diminishing Returns"), + ("Increasing_returns", "Increasing Returns"), + ], + help_text="The rule this group uses.", + max_length=100, + null=True, + verbose_name="rule type", + ), + ), + migrations.AlterField( + model_name="group", + name="rule_val", + field=models.PositiveIntegerField( + blank=True, + help_text="An optional value that the group's rule choices might carry.", + null=True, + verbose_name="rule value", + ), + ), + migrations.AlterField( + model_name="provider", + name="custom_args", + field=models.CharField( + blank=True, + help_text="Custom args as Base64VecU8.", + null=True, + verbose_name="custom args", + ), + ), + ] diff --git a/nadabot/models.py b/nadabot/models.py index e0b6cdf..ee21794 100644 --- a/nadabot/models.py +++ b/nadabot/models.py @@ -207,6 +207,7 @@ class Provider(models.Model): custom_args = models.CharField( _("custom args"), null=True, + blank=True, help_text=_("Custom args as Base64VecU8.") ) registry = models.ForeignKey( @@ -254,11 +255,13 @@ class Group(models.Model): choices=RuleType, max_length=100, null=True, + blank=True, help_text=_("The rule this group uses.") ) rule_val = models.PositiveIntegerField( _("rule value"), null=True, + blank=True, help_text=_("An optional value that the group's rule choices might carry."), ) providers = models.ManyToManyField( diff --git a/pots/migrations/0012_alter_pot_base_currency_alter_pot_chef_and_more.py b/pots/migrations/0012_alter_pot_base_currency_alter_pot_chef_and_more.py new file mode 100644 index 0000000..ab2e610 --- /dev/null +++ b/pots/migrations/0012_alter_pot_base_currency_alter_pot_chef_and_more.py @@ -0,0 +1,261 @@ +# Generated by Django 5.0.6 on 2024-07-25 17:16 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ("pots", "0011_rename_id_pot_account_rename_id_potfactory_account"), + ] + + operations = [ + migrations.AlterField( + model_name="pot", + name="base_currency", + field=models.CharField( + blank=True, + help_text="Base currency.", + max_length=64, + null=True, + verbose_name="base currency", + ), + ), + migrations.AlterField( + model_name="pot", + name="chef", + field=models.ForeignKey( + blank=True, + help_text="Pot chef.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="chef_pots", + to="accounts.account", + ), + ), + migrations.AlterField( + model_name="pot", + name="cooldown_end", + field=models.DateTimeField( + blank=True, + help_text="Pot cooldown end date.", + null=True, + verbose_name="cooldown end", + ), + ), + migrations.AlterField( + model_name="pot", + name="cooldown_period_ms", + field=models.PositiveIntegerField( + blank=True, + help_text="Pot cooldown period in ms.", + null=True, + verbose_name="cooldown period in ms", + ), + ), + migrations.AlterField( + model_name="pot", + name="custom_min_threshold_score", + field=models.PositiveIntegerField( + blank=True, + help_text="Custom min threshold score.", + null=True, + verbose_name="custom min threshold score", + ), + ), + migrations.AlterField( + model_name="pot", + name="custom_sybil_checks", + field=models.CharField( + blank=True, + help_text="Custom sybil checks.", + null=True, + verbose_name="custom sybil checks", + ), + ), + migrations.AlterField( + model_name="pot", + name="protocol_config_provider", + field=models.CharField( + blank=True, + help_text="Protocol config provider.", + null=True, + verbose_name="protocol config provider", + ), + ), + migrations.AlterField( + model_name="pot", + name="registry_provider", + field=models.CharField( + blank=True, + help_text="Registry provider.", + null=True, + verbose_name="registry provider", + ), + ), + migrations.AlterField( + model_name="pot", + name="sybil_wrapper_provider", + field=models.CharField( + blank=True, + help_text="Sybil wrapper provider.", + null=True, + verbose_name="sybil wrapper provider", + ), + ), + migrations.AlterField( + model_name="pot", + name="total_matching_pool_usd", + field=models.DecimalField( + blank=True, + decimal_places=2, + help_text="Total matching pool in USD.", + max_digits=20, + null=True, + verbose_name="total matching pool in USD", + ), + ), + migrations.AlterField( + model_name="pot", + name="total_public_donations_usd", + field=models.DecimalField( + blank=True, + decimal_places=2, + help_text="Total public donations in USD.", + max_digits=20, + null=True, + verbose_name="total public donations in USD", + ), + ), + migrations.AlterField( + model_name="potapplication", + name="message", + field=models.TextField( + blank=True, + help_text="Application message.", + max_length=1024, + null=True, + verbose_name="message", + ), + ), + migrations.AlterField( + model_name="potapplication", + name="tx_hash", + field=models.CharField( + blank=True, + help_text="Transaction hash.", + null=True, + verbose_name="transaction hash", + ), + ), + migrations.AlterField( + model_name="potapplication", + name="updated_at", + field=models.DateTimeField( + blank=True, + help_text="Application last update date.", + null=True, + verbose_name="updated at", + ), + ), + migrations.AlterField( + model_name="potapplicationreview", + name="notes", + field=models.TextField( + blank=True, + help_text="Review notes.", + max_length=1024, + null=True, + verbose_name="notes", + ), + ), + migrations.AlterField( + model_name="potapplicationreview", + name="tx_hash", + field=models.CharField( + blank=True, + help_text="Transaction hash.", + null=True, + verbose_name="transaction hash", + ), + ), + migrations.AlterField( + model_name="potfactory", + name="source_metadata", + field=models.JSONField( + blank=True, + help_text="Pot factory source metadata.", + null=True, + verbose_name="source metadata", + ), + ), + migrations.AlterField( + model_name="potpayout", + name="amount_paid_usd", + field=models.DecimalField( + blank=True, + decimal_places=2, + help_text="Payout amount in USD.", + max_digits=20, + null=True, + verbose_name="amount paid in USD", + ), + ), + migrations.AlterField( + model_name="potpayout", + name="paid_at", + field=models.DateTimeField( + blank=True, + db_index=True, + help_text="Payout date.", + null=True, + verbose_name="paid at", + ), + ), + migrations.AlterField( + model_name="potpayout", + name="tx_hash", + field=models.CharField( + blank=True, + help_text="Transaction hash.", + null=True, + verbose_name="transaction hash", + ), + ), + migrations.AlterField( + model_name="potpayoutchallengeadminresponse", + name="challenger", + field=models.ForeignKey( + blank=True, + help_text="challenger being responded to.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="payout_admin_responses", + to="accounts.account", + ), + ), + migrations.AlterField( + model_name="potpayoutchallengeadminresponse", + name="pot", + field=models.ForeignKey( + blank=True, + help_text="Pot being challenged.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="payout_responses", + to="pots.pot", + ), + ), + migrations.AlterField( + model_name="potpayoutchallengeadminresponse", + name="tx_hash", + field=models.CharField( + blank=True, + help_text="Transaction hash.", + null=True, + verbose_name="transaction hash", + ), + ), + ] diff --git a/pots/models.py b/pots/models.py index 44c71ef..fe57a21 100644 --- a/pots/models.py +++ b/pots/models.py @@ -45,6 +45,7 @@ class PotFactory(models.Model): source_metadata = models.JSONField( _("source metadata"), null=True, + blank=True, help_text=_("Pot factory source metadata."), ) protocol_fee_basis_points = models.PositiveIntegerField( @@ -147,6 +148,7 @@ class Pot(models.Model): on_delete=models.CASCADE, related_name="chef_pots", null=True, + blank=True, help_text=_("Pot chef."), ) name = models.TextField( @@ -168,6 +170,7 @@ class Pot(models.Model): _("base currency"), max_length=64, null=True, + blank=True, help_text=_("Base currency."), ) application_start = models.DateTimeField( @@ -193,6 +196,7 @@ class Pot(models.Model): registry_provider = models.CharField( _("registry provider"), null=True, + blank=True, help_text=_("Registry provider."), ) min_matching_pool_donation_amount = models.CharField( @@ -203,16 +207,19 @@ class Pot(models.Model): sybil_wrapper_provider = models.CharField( _("sybil wrapper provider"), null=True, + blank=True, help_text=_("Sybil wrapper provider."), ) custom_sybil_checks = models.CharField( _("custom sybil checks"), null=True, + blank=True, help_text=_("Custom sybil checks."), ) custom_min_threshold_score = models.PositiveIntegerField( _("custom min threshold score"), null=True, + blank=True, help_text=_("Custom min threshold score."), ) referral_fee_matching_pool_basis_points = models.PositiveIntegerField( @@ -240,6 +247,7 @@ class Pot(models.Model): max_digits=20, decimal_places=2, null=True, + blank=True, help_text=_("Total matching pool in USD."), ) matching_pool_balance = models.CharField( @@ -262,6 +270,7 @@ class Pot(models.Model): max_digits=20, decimal_places=2, null=True, + blank=True, help_text=_("Total public donations in USD."), ) public_donations_count = models.PositiveIntegerField( @@ -272,11 +281,13 @@ class Pot(models.Model): cooldown_end = models.DateTimeField( _("cooldown end"), null=True, + blank=True, help_text=_("Pot cooldown end date."), ) cooldown_period_ms = models.PositiveIntegerField( _("cooldown period in ms"), null=True, + blank=True, help_text=_("Pot cooldown period in ms."), ) all_paid_out = models.BooleanField( @@ -287,6 +298,7 @@ class Pot(models.Model): protocol_config_provider = models.CharField( _("protocol config provider"), null=True, + blank=True, help_text=_("Protocol config provider."), ) @@ -336,6 +348,7 @@ class PotApplication(models.Model): _("message"), max_length=1024, null=True, + blank=True, help_text=_("Application message."), ) status = models.CharField( @@ -354,11 +367,13 @@ class PotApplication(models.Model): updated_at = models.DateTimeField( _("updated at"), null=True, + blank=True, help_text=_("Application last update date."), ) tx_hash = models.CharField( _("transaction hash"), null=True, + blank=True, help_text=_("Transaction hash."), ) @@ -395,6 +410,7 @@ class PotApplicationReview(models.Model): _("notes"), max_length=1024, null=True, + blank=True, help_text=_("Review notes."), ) status = models.CharField( @@ -412,6 +428,7 @@ class PotApplicationReview(models.Model): tx_hash = models.CharField( _("transaction hash"), null=True, + blank=True, help_text=_("Transaction hash."), ) @@ -453,6 +470,7 @@ class PotPayout(models.Model): max_digits=20, decimal_places=2, null=True, + blank=True, help_text=_("Payout amount in USD."), ) token = models.ForeignKey( @@ -465,12 +483,14 @@ class PotPayout(models.Model): paid_at = models.DateTimeField( _("paid at"), null=True, + blank=True, help_text=_("Payout date."), db_index=True, ) tx_hash = models.CharField( _("transaction hash"), null=True, + blank=True, help_text=_("Transaction hash."), ) @@ -541,6 +561,7 @@ class PotPayoutChallengeAdminResponse(models.Model): on_delete=models.CASCADE, related_name="payout_admin_responses", null=True, + blank=True, help_text=_("challenger being responded to."), ) @@ -549,6 +570,7 @@ class PotPayoutChallengeAdminResponse(models.Model): on_delete=models.CASCADE, related_name="payout_responses", null=True, + blank=True, help_text=_("Pot being challenged."), ) @@ -578,6 +600,7 @@ class PotPayoutChallengeAdminResponse(models.Model): tx_hash = models.CharField( _("transaction hash"), null=True, + blank=True, help_text=_("Transaction hash."), ) From b7894828533aca683a6a1d3f434b17695dd77c6d Mon Sep 17 00:00:00 2001 From: Prometheus Date: Sat, 27 Jul 2024 23:59:04 +0100 Subject: [PATCH 261/454] customize pagination size (#65) * customize pagination size * remove, max page size * Update readme * inherit custom pagination for all endpoint --- accounts/api.py | 17 +++++++++-------- api/pagination.py | 7 +++++++ donations/api.py | 5 +++-- lists/api.py | 6 +++--- pots/api.py | 14 +++++++------- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/accounts/api.py b/accounts/api.py index 8dd98d9..0d600fd 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -9,12 +9,13 @@ OpenApiTypes, extend_schema, ) -from rest_framework.pagination import PageNumberPagination + from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView from api.pagination import pagination_parameters +from api.pagination import CustomSizePageNumberPagination from base.logging import logger from donations.models import Donation from donations.serializers import ( @@ -44,7 +45,7 @@ ) -class DonorsAPI(APIView, PageNumberPagination): +class DonorsAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ @@ -89,7 +90,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountsListAPI(APIView, PageNumberPagination): +class AccountsListAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ @@ -157,7 +158,7 @@ def get(self, request: Request, *args, **kwargs): return Response(serializer.data) -class AccountActivePotsAPI(APIView, PageNumberPagination): +class AccountActivePotsAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ @@ -214,7 +215,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountPotApplicationsAPI(APIView, PageNumberPagination): +class AccountPotApplicationsAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ @@ -269,7 +270,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountDonationsReceivedAPI(APIView, PageNumberPagination): +class AccountDonationsReceivedAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ @@ -310,7 +311,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountDonationsSentAPI(APIView, PageNumberPagination): +class AccountDonationsSentAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ @@ -351,7 +352,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class AccountPayoutsReceivedAPI(APIView, PageNumberPagination): +class AccountPayoutsReceivedAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ diff --git a/api/pagination.py b/api/pagination.py index b949b6b..95e6edb 100644 --- a/api/pagination.py +++ b/api/pagination.py @@ -1,5 +1,12 @@ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter +from rest_framework.pagination import PageNumberPagination + + + +# ovveeride PageNumberPagination to add page_size_query_param alias +class CustomSizePageNumberPagination(PageNumberPagination): + page_size_query_param = 'page_size' pagination_parameters = [ OpenApiParameter( diff --git a/donations/api.py b/donations/api.py index 0e8f522..3f89cbd 100644 --- a/donations/api.py +++ b/donations/api.py @@ -8,12 +8,13 @@ OpenApiResponse, extend_schema, ) -from rest_framework.pagination import PageNumberPagination + from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView from api.pagination import pagination_parameters +from api.pagination import CustomSizePageNumberPagination from base.logging import logger from .serializers import DonationContractConfigSerializer @@ -21,7 +22,7 @@ DONATE_CONTRACT = "donate." + settings.POTLOCK_TLA -class DonationContractConfigAPI(APIView, PageNumberPagination): +class DonationContractConfigAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ diff --git a/lists/api.py b/lists/api.py index 6b0d6cb..be366df 100644 --- a/lists/api.py +++ b/lists/api.py @@ -10,12 +10,12 @@ OpenApiResponse, extend_schema, ) -from rest_framework.pagination import PageNumberPagination from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView from api.pagination import pagination_parameters +from api.pagination import CustomSizePageNumberPagination from .models import List, ListRegistrationStatus from .serializers import ( @@ -30,7 +30,7 @@ ) -class ListsListAPI(APIView, PageNumberPagination): +class ListsListAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ @@ -98,7 +98,7 @@ def get(self, request: Request, *args, **kwargs): return Response(serializer.data) -class ListRegistrationsAPI(APIView, PageNumberPagination): +class ListRegistrationsAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ diff --git a/pots/api.py b/pots/api.py index 8ee267b..4a3a474 100644 --- a/pots/api.py +++ b/pots/api.py @@ -8,7 +8,6 @@ OpenApiResponse, extend_schema, ) -from rest_framework.pagination import PageNumberPagination from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -20,6 +19,7 @@ PaginatedAccountsResponseSerializer, ) from api.pagination import pagination_parameters +from api.pagination import CustomSizePageNumberPagination from donations.models import Donation from donations.serializers import ( PAGINATED_DONATION_EXAMPLE, @@ -45,7 +45,7 @@ ) -class PotsListAPI(APIView, PageNumberPagination): +class PotsListAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ @@ -75,7 +75,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class PotFactoriesAPI(APIView, PageNumberPagination): +class PotFactoriesAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ @@ -139,7 +139,7 @@ def get(self, request: Request, *args, **kwargs): return Response(serializer.data) -class PotApplicationsAPI(APIView, PageNumberPagination): +class PotApplicationsAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ @@ -177,7 +177,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class PotDonationsAPI(APIView, PageNumberPagination): +class PotDonationsAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ @@ -215,7 +215,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class PotSponsorsAPI(APIView, PageNumberPagination): +class PotSponsorsAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ @@ -258,7 +258,7 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class PotPayoutsAPI(APIView, PageNumberPagination): +class PotPayoutsAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ From f5dc1f7f3a495b92854c61e3973318feee006eab Mon Sep 17 00:00:00 2001 From: Prometheus Date: Mon, 29 Jul 2024 14:15:15 +0100 Subject: [PATCH 262/454] use id to assign response admin (#66) * use id to assign response admin * fix admin payout search --- indexer_app/utils.py | 4 ++-- pots/admin.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 20f8a21..6f1862e 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -731,7 +731,7 @@ async def handle_payout_challenge( data: dict, receiver_id: str, signer_id: str, receiptId: str, created_at: datetime ): try: - + acct, _ = await Account.objects.aget_or_create(id=signer_id) logger.info(f"challenging payout..: {data}, {receiver_id}") payoutChallenge = { "created_at": created_at, @@ -762,7 +762,7 @@ async def handle_payout_challenge_response( try: logger.info(f"responding to payout challenge..: {data}, {receiver_id}") response_defaults = { - "admin": signer_id, + "admin_id": signer_id, "message": data.get("notes"), "resolved": data.get("resolve_challenge"), "tx_hash": receiptId, diff --git a/pots/admin.py b/pots/admin.py index 100d23f..ba9c0c1 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -220,7 +220,7 @@ def has_delete_permission(self, request, obj=None): @admin.register(PotPayoutChallengeAdminResponse) class PotPayoutChallengeAdminResponseAdmin(admin.ModelAdmin): list_display = ("id", "pot", "admin", "message", "created_at", "resolved") - search_fields = ("admin__id", "challenge__id") + search_fields = ("admin__id", "challenger__id") list_filter = ("created_at", "resolved") def has_add_permission(self, request): From cf5ced4eb45de871c2f18f1a1357c037fe57f3de Mon Sep 17 00:00:00 2001 From: Prometheus Date: Mon, 29 Jul 2024 14:23:48 +0100 Subject: [PATCH 263/454] pot config update (#64) * pot config update * use model methid for pot config update --- indexer_app/utils.py | 126 +++++++++++++++++++++++-------------------- pots/models.py | 39 +++++++++++++- 2 files changed, 105 insertions(+), 60 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 6f1862e..4200112 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -134,6 +134,15 @@ async def handle_new_pot( signer, _ = await Account.objects.aget_or_create(id=signer_id) receiver, _ = await Account.objects.aget_or_create(id=receiver_id) + # check if pot exists + pot = await Pot.objects.filter(account=receiver).afirst() + if pot: + logger.info("Pot already exists, update using api call") + pot_config_update = sync_to_async(pot.update_configs) + await pot_config_update() + return + + logger.info("upsert chef") if data.get("chef"): chef, _ = await Account.objects.aget_or_create(id=data["chef"]) @@ -186,8 +195,8 @@ async def handle_new_pot( "all_paid_out": False, "protocol_config_provider": data["protocol_config_provider"], } - pot, created = await Pot.objects.aupdate_or_create( - account=receiver, defaults=pot_defaults + pot = await Pot.objects.acreate( + account=receiver, **pot_defaults ) # Add admins to the Pot @@ -215,63 +224,62 @@ async def handle_pot_config_update( receiver_id: str, ): try: - data = log_data - logger.info(f"asserting involved accts.... {receiver_id}") - if data.get("chef"): - chef, _ = await Account.objects.aget_or_create(id=data["chef"]) - owner, _ = await Account.objects.aget_or_create(id=data["owner"]) - logger.info(f"building updated config for pot {receiver_id}") - pot_config = { - "deployer": data["deployed_by"], - "source_metadata": data["source_metadata"], - "owner_id": data["owner"], - "chef_id": data.get("chef"), - "name": data["pot_name"], - "description": data["pot_description"], - "max_approved_applicants": data["max_projects"], - "base_currency": data["base_currency"], - "application_start": datetime.fromtimestamp( - data["application_start_ms"] / 1000 - ), - "application_end": datetime.fromtimestamp( - data["application_end_ms"] / 1000 - ), - "matching_round_start": datetime.fromtimestamp( - data["public_round_start_ms"] / 1000 - ), - "matching_round_end": datetime.fromtimestamp( - data["public_round_end_ms"] / 1000 - ), - "registry_provider": data["registry_provider"], - "min_matching_pool_donation_amount": data[ - "min_matching_pool_donation_amount" - ], - "sybil_wrapper_provider": data["sybil_wrapper_provider"], - "custom_sybil_checks": data.get("custom_sybil_checks"), - "custom_min_threshold_score": data.get("custom_min_threshold_score"), - "referral_fee_matching_pool_basis_points": data[ - "referral_fee_matching_pool_basis_points" - ], - "referral_fee_public_round_basis_points": data[ - "referral_fee_public_round_basis_points" - ], - "chef_fee_basis_points": data["chef_fee_basis_points"], - "matching_pool_balance": data["matching_pool_balance"], - "total_public_donations": data["total_public_donations"], - "public_donations_count": data["public_donations_count"], - "cooldown_period_ms": data["cooldown_end_ms"], - "all_paid_out": data["all_paid_out"], - "protocol_config_provider": data["protocol_config_provider"], - } - - pot, created = await Pot.objects.aupdate_or_create( - account=receiver_id, defaults=pot_config - ) - - if data.get("admins"): - for admin_id in data["admins"]: - admin, _ = await Account.objects.aget_or_create(id=admin_id) - pot.admins.aadd(admin) + pot = await Pot.objects.filter(account=receiver_id).afirst() + if pot: + logger.info("Pot already exists, updating using api call") + pot_config_update = sync_to_async(pot.update_configs) + await pot_config_update() + # pot_config = { + # "deployer": data["deployed_by"], + # "source_metadata": data["source_metadata"], + # "owner_id": data["owner"], + # "chef_id": data.get("chef"), + # "name": data["pot_name"], + # "description": data["pot_description"], + # "max_approved_applicants": data["max_projects"], + # "base_currency": data["base_currency"], + # "application_start": datetime.fromtimestamp( + # data["application_start_ms"] / 1000 + # ), + # "application_end": datetime.fromtimestamp( + # data["application_end_ms"] / 1000 + # ), + # "matching_round_start": datetime.fromtimestamp( + # data["public_round_start_ms"] / 1000 + # ), + # "matching_round_end": datetime.fromtimestamp( + # data["public_round_end_ms"] / 1000 + # ), + # "registry_provider": data["registry_provider"], + # "min_matching_pool_donation_amount": data[ + # "min_matching_pool_donation_amount" + # ], + # "sybil_wrapper_provider": data["sybil_wrapper_provider"], + # "custom_sybil_checks": data.get("custom_sybil_checks"), + # "custom_min_threshold_score": data.get("custom_min_threshold_score"), + # "referral_fee_matching_pool_basis_points": data[ + # "referral_fee_matching_pool_basis_points" + # ], + # "referral_fee_public_round_basis_points": data[ + # "referral_fee_public_round_basis_points" + # ], + # "chef_fee_basis_points": data["chef_fee_basis_points"], + # "matching_pool_balance": data["matching_pool_balance"], + # "total_public_donations": data["total_public_donations"], + # "public_donations_count": data["public_donations_count"], + # "cooldown_period_ms": data["cooldown_end_ms"], + # "all_paid_out": data["all_paid_out"], + # "protocol_config_provider": data["protocol_config_provider"], + # } + + # pot, created = await Pot.objects.aupdate_or_create( + # account=receiver_id, defaults=pot_config + # ) + + # if data.get("admins"): + # for admin_id in data["admins"]: + # admin, _ = await Account.objects.aget_or_create(id=admin_id) + # pot.admins.aadd(admin) # await Pot.objects.filter(id=receiver_id).aupdate(**pot_config) except Exception as e: logger.error(f"Failed to update Pot config, Error: {e}") diff --git a/pots/models.py b/pots/models.py index fe57a21..0276fbc 100644 --- a/pots/models.py +++ b/pots/models.py @@ -1,4 +1,4 @@ -from datetime import timedelta +from datetime import datetime, timedelta from decimal import Decimal import requests @@ -313,6 +313,43 @@ class Meta: name="idx_matching_period", ), ] + + def update_configs(self): + try: + url = f"{settings.FASTNEAR_RPC_URL}/account/{self.account.id}/view/get_config" + response = requests.get(url) + if response.status_code != 200: + logger.error(f"Failed to get config for pot {self.account}: {response.text}") + else: + config = response.json() + print(config) + self.owner, created = Account.objects.get_or_create(id=config.get("owner")) + self.admins.clear() + for admin_id in config.get("admins", []): + self.admins.add(Account.objects.get_or_create(id=admin_id)[0]) + self.chef, created = Account.objects.get_or_create(id=config.get("chef")) + self.name = config.get("pot_name") + self.description = config.get("pot_description") + self.max_approved_applicants = config.get("max_projects") + self.base_currency = config.get("base_currency") + self.application_start = datetime.fromtimestamp(config.get("application_start_ms") / 1000) + self.application_end = datetime.fromtimestamp(config.get("application_end_ms") / 1000) + self.matching_round_start = datetime.fromtimestamp(config.get("public_round_start_ms") / 1000) + self.matching_round_end = datetime.fromtimestamp(config.get("public_round_end_ms") / 1000) + self.registry_provider = config.get("registry_provider") + self.min_matching_pool_donation_amount = config.get("min_matching_pool_donation_amount") + self.sybil_wrapper_provider = config.get("sybil_wrapper_provider") + self.custom_sybil_checks = config.get("custom_sybil_checks") + self.custom_min_threshold_score = config.get("custom_min_threshold_score") + self.referral_fee_matching_pool_basis_points = config.get("referral_fee_matching_pool_basis_points") + self.referral_fee_public_round_basis_points = config.get("referral_fee_public_round_basis_points") + self.chef_fee_basis_points = config.get("chef_fee_basis_points") + self.cooldown_end = datetime.fromtimestamp(config.get("cooldown_end_ms") / 1000) + self.all_paid_out = config.get("all_paid_out") + self.protocol_config_provider = config.get("protocol_config_provider") + self.save() + except Exception as e: + logger.error(f"Failed to update pot config, Error: {e}") class PotApplicationStatus(models.TextChoices): From 8e4a8b047329f9ddec5b4b0f8c4629dff7e8374b Mon Sep 17 00:00:00 2001 From: Prometheus Date: Mon, 29 Jul 2024 23:34:02 +0100 Subject: [PATCH 264/454] get all list registrations for an account (#68) * get all list registrations for an account * update readme * remove category filter --- README.md | 4 ++++ accounts/api.py | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ api/urls.py | 6 ++++++ 3 files changed, 60 insertions(+) diff --git a/README.md b/README.md index e2f5204..a3aa27d 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,10 @@ enum PotApplicationStatus { } ``` +#### ✅ Get registrations to lists by account: `GET /accounts/{ACCOUNT_ID}/list-registrations` (paginated) + +Can specify status to filter by using `status` query param if desired, e.g. `status=Approved` + ### `List` endpoints #### ✅ Get all lists: `GET /lists` (paginated) diff --git a/accounts/api.py b/accounts/api.py index 0d600fd..907fb33 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -23,6 +23,8 @@ DonationSerializer, PaginatedDonationsResponseSerializer, ) +from lists.models import ListRegistration, ListRegistrationStatus +from lists.serializers import PAGINATED_LIST_REGISTRATION_EXAMPLE, ListRegistrationSerializer, PaginatedListRegistrationsResponseSerializer from pots.models import Pot, PotApplication, PotApplicationStatus, PotPayout from pots.serializers import ( PAGINATED_PAYOUT_EXAMPLE, @@ -391,3 +393,51 @@ def get(self, request: Request, *args, **kwargs): results = self.paginate_queryset(payouts, request, view=self) serializer = PotPayoutSerializer(results, many=True) return self.get_paginated_response(serializer.data) + + +class AccountListRegistrationsAPI(APIView, CustomSizePageNumberPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("account_id", str, OpenApiParameter.PATH), + *pagination_parameters, + ], + responses={ + 200: OpenApiResponse( + response=PaginatedListRegistrationsResponseSerializer, + description="Returns List registrations for the account provided", + examples=[ + OpenApiExample( + "example-1", + summary="Simple registration example", + description="Example response for list registrations", + value=PAGINATED_LIST_REGISTRATION_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Account not found"), + 500: OpenApiResponse(description="Internal server error"), + }, + ) + @method_decorator(cache_page(60 * 5)) + def get(self, request: Request, *args, **kwargs): + account_id = kwargs.get("account_id") + try: + account = Account.objects.get(id=account_id) + except Account.DoesNotExist: + return Response( + {"message": f"Account with ID {account_id} not found."}, status=404 + ) + + registrations = ListRegistration.objects.filter(registrant=account) + status_param = request.query_params.get("status") + if status_param: + if status_param not in ListRegistrationStatus.values: + return Response( + {"message": f"Invalid status value: {status_param}"}, status=400 + ) + registrations = registrations.filter(status=status_param) + results = self.paginate_queryset(registrations, request, view=self) + serializer = ListRegistrationSerializer(results, many=True) + return self.get_paginated_response(serializer.data) diff --git a/api/urls.py b/api/urls.py index 8026c40..fd0c4d8 100644 --- a/api/urls.py +++ b/api/urls.py @@ -10,6 +10,7 @@ AccountDetailAPI, AccountDonationsReceivedAPI, AccountDonationsSentAPI, + AccountListRegistrationsAPI, AccountPayoutsReceivedAPI, AccountPotApplicationsAPI, AccountsListAPI, @@ -76,6 +77,11 @@ AccountPayoutsReceivedAPI.as_view(), name="accounts_api_by_id_payouts_received", ), + path( + "v1/accounts//list-registrations", + AccountListRegistrationsAPI.as_view(), + name="accounts_api_by_id_registrations", + ), # donate contract config path( "v1/donate_contract_config", From fdc3079bb0866d2820868a6169a955adf40bbcec Mon Sep 17 00:00:00 2001 From: Prometheus Date: Mon, 29 Jul 2024 23:35:15 +0100 Subject: [PATCH 265/454] Fix payout challenge indexing (#67) * use select_related for queries * remove comment on cache * add txhah to challenge model --- accounts/api.py | 4 ++-- lists/api.py | 4 ++-- .../0013_potpayoutchallenge_tx_hash.py | 23 +++++++++++++++++++ pots/models.py | 6 +++++ 4 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 pots/migrations/0013_potpayoutchallenge_tx_hash.py diff --git a/accounts/api.py b/accounts/api.py index 907fb33..f97eb1e 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -307,7 +307,7 @@ def get(self, request: Request, *args, **kwargs): {"message": f"Account with ID {account_id} not found."}, status=404 ) - donations = Donation.objects.filter(recipient=account) + donations = Donation.objects.prefetch_related('donor', 'pot', 'recipient', 'referrer', 'chef', 'token').filter(recipient=account) results = self.paginate_queryset(donations, request, view=self) serializer = DonationSerializer(results, many=True) return self.get_paginated_response(serializer.data) @@ -348,7 +348,7 @@ def get(self, request: Request, *args, **kwargs): {"message": f"Account with ID {account_id} not found."}, status=404 ) - donations = Donation.objects.filter(donor=account) + donations = Donation.objects.select_related('donor', 'pot', 'recipient', 'referrer', 'chef', 'token').prefetch_related('pot__admins').filter(donor=account) #TODO: this takes more time than just doing a prefetch_related for the fields. results = self.paginate_queryset(donations, request, view=self) serializer = DonationSerializer(results, many=True) return self.get_paginated_response(serializer.data) diff --git a/lists/api.py b/lists/api.py index be366df..cc73e13 100644 --- a/lists/api.py +++ b/lists/api.py @@ -139,13 +139,13 @@ class ListRegistrationsAPI(APIView, CustomSizePageNumberPagination): def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") try: - list_obj = List.objects.get(id=list_id) + list_obj = List.objects.prefetch_related('registrations').get(id=list_id) except List.DoesNotExist: return Response( {"message": f"List with ID {list_id} not found."}, status=404 ) - registrations = list_obj.registrations.all() + registrations = list_obj.registrations.select_related().all() status_param = request.query_params.get("status") category_param = request.query_params.get("category") if status_param: diff --git a/pots/migrations/0013_potpayoutchallenge_tx_hash.py b/pots/migrations/0013_potpayoutchallenge_tx_hash.py new file mode 100644 index 0000000..e0d6b13 --- /dev/null +++ b/pots/migrations/0013_potpayoutchallenge_tx_hash.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-07-29 21:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("pots", "0012_alter_pot_base_currency_alter_pot_chef_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="potpayoutchallenge", + name="tx_hash", + field=models.CharField( + blank=True, + help_text="Transaction hash.", + null=True, + verbose_name="transaction hash", + ), + ), + ] diff --git a/pots/models.py b/pots/models.py index 0276fbc..87dca53 100644 --- a/pots/models.py +++ b/pots/models.py @@ -580,6 +580,12 @@ class PotPayoutChallenge(models.Model): null=False, help_text=_("Challenge message."), ) + tx_hash = models.CharField( + _("transaction hash"), + null=True, + blank=True, + help_text=_("Transaction hash."), + ) class Meta: verbose_name_plural = "Payout Challenges" From d46d9a8889c7ef831edb7295d3424a00d6ddc0ca Mon Sep 17 00:00:00 2001 From: Prometheus Date: Tue, 30 Jul 2024 00:43:01 +0100 Subject: [PATCH 266/454] fix cooldown update in pot method (#69) --- pots/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pots/models.py b/pots/models.py index 87dca53..720fccb 100644 --- a/pots/models.py +++ b/pots/models.py @@ -344,7 +344,8 @@ def update_configs(self): self.referral_fee_matching_pool_basis_points = config.get("referral_fee_matching_pool_basis_points") self.referral_fee_public_round_basis_points = config.get("referral_fee_public_round_basis_points") self.chef_fee_basis_points = config.get("chef_fee_basis_points") - self.cooldown_end = datetime.fromtimestamp(config.get("cooldown_end_ms") / 1000) + if config.get("cooldown_end_ms"): + self.cooldown_end = datetime.fromtimestamp(config.get("cooldown_end_ms") / 1000) self.all_paid_out = config.get("all_paid_out") self.protocol_config_provider = config.get("protocol_config_provider") self.save() From 8f00c7de97a5f9831e3cc582f305e6d5cd914f84 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Wed, 31 Jul 2024 15:55:19 +0100 Subject: [PATCH 267/454] replace unserializable timestamp (#72) --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 4200112..dbbee8d 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -758,7 +758,7 @@ async def handle_payout_challenge( } activity, activity_created = await Activity.objects.aupdate_or_create( - action_result=payoutChallenge, type="Challenge_Payout", defaults=defaults + action_result=data, type="Challenge_Payout", defaults=defaults ) except Exception as e: logger.error(f"Failed to create payoutchallenge, Error: {e}") From f8a42ee8e165e265efe52365523ba234e4bed17a Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 7 Aug 2024 23:46:04 +0100 Subject: [PATCH 268/454] fix handling new list and acct creation --- .../management/commands/populatedata.py | 42 ++++----- indexer_app/utils.py | 90 +++++++++---------- pots/models.py | 14 +-- 3 files changed, 73 insertions(+), 73 deletions(-) diff --git a/indexer_app/management/commands/populatedata.py b/indexer_app/management/commands/populatedata.py index 4d94246..f10f646 100644 --- a/indexer_app/management/commands/populatedata.py +++ b/indexer_app/management/commands/populatedata.py @@ -38,7 +38,7 @@ def handle(self, *args, **options): print(f"adding {len(lists)} direct lists") for l in lists: list_id = l["id"] - owner, _ = Account.objects.get_or_create(id=l["owner"]) + owner, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=l["owner"]) list_defaults = { "on_chain_id": list_id, "owner": owner, @@ -56,7 +56,7 @@ def handle(self, *args, **options): print("created list: ", list.on_chain_id, created) # add admins for admin in l["admins"]: - admin_obj, _ = Account.objects.get_or_create(id=admin) + admin_obj, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=admin) list.admins.add(admin_obj) # get registrations in pages page = 0 @@ -79,12 +79,12 @@ def handle(self, *args, **options): print("registrations: ", registrations) print(f"adding {len(registrations)} registrations") for reg in registrations: - registrant, _ = Account.objects.get_or_create( + registrant, _ = Account.objects.get_or_create(defaults={"chain_id":1}, id=reg["registrant_id"] ) registrar = registrant if "registered_by" in reg: - registrar, _ = Account.objects.get_or_create( + registrar, _ = Account.objects.get_or_create(defaults={"chain_id":1}, id=reg["registered_by"] ) registration_defaults = { @@ -129,13 +129,13 @@ def handle(self, *args, **options): donations = donations.json() print(f"adding {len(donations)} direct donations") for donation in donations: - donor, _ = Account.objects.get_or_create(id=donation["donor_id"]) + donor, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=donation["donor_id"]) referrer = None - recipient, _ = Account.objects.get_or_create( + recipient, _ = Account.objects.get_or_create(defaults={"chain_id":1}, id=donation["recipient_id"] ) ft_id = donation["ft_id"] - ft_acct, _ = Account.objects.get_or_create(id=ft_id) + ft_acct, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=ft_id) token_defaults = { "decimals": 24, } @@ -161,7 +161,7 @@ def handle(self, *args, **options): account=ft_acct, defaults=token_defaults ) if donation.get("referrer_id"): - referrer, _ = Account.objects.get_or_create( + referrer, _ = Account.objects.get_or_create(defaults={"chain_id":1}, id=donation["referrer_id"] ) @@ -216,7 +216,7 @@ def handle(self, *args, **options): # pot factory POTFACTORY_ID = "v1.potfactory.potlock.near" # pots - near_acct, _ = Account.objects.get_or_create(id="near") + near_acct, _ = Account.objects.get_or_create(defaults={"chain_id":1},id="near") url = f"{settings.FASTNEAR_RPC_URL}/account/{POTFACTORY_ID}/view/get_pots" response = requests.get(url) if response.status_code != 200: @@ -255,10 +255,10 @@ def handle(self, *args, **options): source_metadata = response.json() print("source metadata; ", source_metadata) # add or update pot - pot_acct, _ = Account.objects.get_or_create(id=pot_id) - owner, _ = Account.objects.get_or_create(id=config["owner"]) + pot_acct, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=pot_id) + owner, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=config["owner"]) if config.get("chef"): - chef, _ = Account.objects.get_or_create(id=config["chef"]) + chef, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=config["chef"]) pot_defaults = { "pot_factory_account": POTFACTORY_ID, "owner_id": config["owner"], @@ -316,7 +316,7 @@ def handle(self, *args, **options): print("pot created? ", created) print("adding admins") for admin in config["admins"]: - admin_obj, _ = Account.objects.get_or_create(id=admin) + admin_obj, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=admin) pot.admins.add(admin_obj) # add activity activity_defaults = { @@ -346,7 +346,7 @@ def handle(self, *args, **options): applications = applications.json() print(f"adding {len(applications)} applications") for appl in applications: - applicant, _ = Account.objects.get_or_create(id=appl["project_id"]) + applicant, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=appl["project_id"]) application_defaults = { "applicant": applicant, "pot": pot, @@ -399,20 +399,20 @@ def handle(self, *args, **options): donations = donations.json() print(f"adding {len(donations)} donations") for donation in donations: - donor, _ = Account.objects.get_or_create(id=donation["donor_id"]) + donor, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=donation["donor_id"]) project = None referrer = None chef = None if donation.get("project_id"): - project, _ = Account.objects.get_or_create( + project, _ = Account.objects.get_or_create(defaults={"chain_id":1}, id=donation["project_id"] ) if donation.get("referrer_id"): - referrer, _ = Account.objects.get_or_create( + referrer, _ = Account.objects.get_or_create(defaults={"chain_id":1}, id=donation["referrer_id"] ) if donation.get("chef_id"): - chef, _ = Account.objects.get_or_create(id=donation["chef_id"]) + chef, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=donation["chef_id"]) # net_amount is 0 for some donations, so calculate it net_amount = donation["net_amount"] @@ -426,7 +426,7 @@ def handle(self, *args, **options): ) # pot donations always NEAR - ft_acct, _ = Account.objects.get_or_create(id="near") + ft_acct, _ = Account.objects.get_or_create(defaults={"chain_id":1},id="near") ft_token, _ = Token.objects.get_or_create(account=ft_acct) donation_defaults = { "donor": donor, @@ -480,7 +480,7 @@ def handle(self, *args, **options): if "paid_at" not in payout else datetime.fromtimestamp(payout["paid_at"] / 1000) ) - recipient, _ = Account.objects.get_or_create( + recipient, _ = Account.objects.get_or_create(defaults={"chain_id":1}, id=payout["project_id"] ) near_token, _ = Token.objects.get_or_create(account=near_acct) @@ -512,7 +512,7 @@ def handle(self, *args, **options): challenges = challenges.json() print(f"adding {len(challenges)} challenges") for c in challenges: - challenger, _ = Account.objects.get_or_create(id=c["challenger_id"]) + challenger, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=c["challenger_id"]) challenge_defaults = { "challenger": challenger, "pot": pot, diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 3d3b01a..f476b6e 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -58,8 +58,8 @@ async def handle_new_nadabot_registry( logger.info(f"nadabot registry init... {data}") try: - registry, _ = await Account.objects.aget_or_create(id=receiverId) - owner, _ = await Account.objects.aget_or_create(id=data["owner"]) + registry, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=receiverId) + owner, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=data["owner"]) nadabot_registry, created = await NadabotRegistry.objects.aupdate_or_create( account=registry, owner=owner, @@ -70,7 +70,7 @@ async def handle_new_nadabot_registry( if data.get("admins"): for admin_id in data["admins"]: - admin, _ = await Account.objects.aget_or_create(id=admin_id) + admin, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=admin_id) await nadabot_registry.admins.aadd(admin) except Exception as e: logger.error(f"Error in registry initiialization: {e}") @@ -82,10 +82,10 @@ async def handle_registry_blacklist_action( logger.info(f"Registry blacklist action....... {data}") try: - registry, _ = await Account.objects.aget_or_create(id=receiverId) + registry, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=receiverId) bulk_obj = [] for acct in data["accounts"]: - account, _ = await Account.objects.aget_or_create(id=acct) + account, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=acct) bulk_obj.append( { "registry": registry, @@ -107,7 +107,7 @@ async def handle_registry_unblacklist_action( logger.info(f"Registry remove blacklisted accts....... {data}") try: - registry, _ = await Account.objects.aget_or_create(id=receiverId) + registry, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=receiverId) entries = BlackList.objects.filter(account__in=data["accounts"]) await entries.adelete() except Exception as e: @@ -130,9 +130,9 @@ async def handle_new_pot( owner_id = ( data.get("owner") or signer_id ) # owner is optional; if not provided, owner will be transaction signer (this logic is implemented by Pot contract's "new" method) - owner, _ = await Account.objects.aget_or_create(id=owner_id) - signer, _ = await Account.objects.aget_or_create(id=signer_id) - receiver, _ = await Account.objects.aget_or_create(id=receiver_id) + owner, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=owner_id) + signer, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=signer_id) + receiver, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=receiver_id) # check if pot exists pot = await Pot.objects.filter(account=receiver).afirst() @@ -144,7 +144,7 @@ async def handle_new_pot( logger.info("upsert chef") if data.get("chef"): - chef, _ = await Account.objects.aget_or_create(id=data["chef"]) + chef, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=data["chef"]) # Create Pot object logger.info(f"creating pot with owner {owner_id}....") @@ -199,7 +199,7 @@ async def handle_new_pot( # Add admins to the Pot if data.get("admins"): for admin_id in data["admins"]: - admin, _ = await Account.objects.aget_or_create(id=admin_id) + admin, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=admin_id) pot.admins.aadd(admin) defaults = { @@ -275,7 +275,7 @@ async def handle_pot_config_update( # if data.get("admins"): # for admin_id in data["admins"]: - # admin, _ = await Account.objects.aget_or_create(id=admin_id) + # admin, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=admin_id) # pot.admins.aadd(admin) # await Pot.objects.filter(id=receiver_id).aupdate(**pot_config) except Exception as e: @@ -288,14 +288,14 @@ async def handle_new_pot_factory(data: dict, receiver_id: str, created_at: datet logger.info("upserting accounts...") # Upsert accounts - owner, _ = await Account.objects.aget_or_create( + owner, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, id=data["owner"], ) - protocol_fee_recipient_account, _ = await Account.objects.aget_or_create( + protocol_fee_recipient_account, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, id=data["protocol_fee_recipient_account"], ) - receiver, _ = await Account.objects.aget_or_create( + receiver, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, id=receiver_id, ) @@ -316,7 +316,7 @@ async def handle_new_pot_factory(data: dict, receiver_id: str, created_at: datet # Add admins to the PotFactory if data.get("admins"): for admin_id in data["admins"]: - admin, _ = await Account.objects.aget_or_create( + admin, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, id=admin_id, ) await factory.admins.aadd(admin) @@ -324,7 +324,7 @@ async def handle_new_pot_factory(data: dict, receiver_id: str, created_at: datet # Add whitelisted deployers to the PotFactory if data.get("whitelisted_deployers"): for deployer_id in data["whitelisted_deployers"]: - deployer, _ = await Account.objects.aget_or_create(id=deployer_id) + deployer, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=deployer_id) await factory.whitelisted_deployers.aadd(deployer) except Exception as e: logger.error(f"Failed to handle new pot Factory, Error: {e}") @@ -342,10 +342,18 @@ async def handle_new_list( ) # TODO: RECEIVE AS A FUNCTION ARGUMENT ) + logger.info("upserting involveed accts...") + + await Account.objects.aget_or_create(defaults={"chain_id":1},id=data["owner"]) + + await Account.objects.aget_or_create(defaults={"chain_id":1},id=signer_id) + + await Account.objects.aget_or_create(defaults={"chain_id":1},id=receiver_id) + logger.info(f"creating list..... {data}") listObject = await List.objects.acreate( - id=data["id"], + on_chain_id=data["id"], owner_id=data["owner"], default_registration_status=data["default_registration_status"], name=data["name"], @@ -356,17 +364,9 @@ async def handle_new_list( updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), ) - logger.info("upserting involveed accts...") - - await Account.objects.aget_or_create(id=data["owner"]) - - await Account.objects.aget_or_create(id=signer_id) - - await Account.objects.aget_or_create(id=receiver_id) - if data.get("admins"): for admin_id in data["admins"]: - admin_object, _ = await Account.objects.aget_or_create( + admin_object, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, id=admin_id, ) await listObject.admins.aadd(admin_object) @@ -418,7 +418,7 @@ async def handle_new_list_registration( await Account.objects.abulk_create( objs=[Account(**data) for data in project_list], ignore_conflicts=True ) - await Account.objects.aget_or_create(id=signer_id) + await Account.objects.aget_or_create(defaults={"chain_id":1},id=signer_id) logger.info("Upserted accounts/registrants(signer)") except Exception as e: logger.error(f"Encountered error trying to get create acct: {e}") @@ -492,12 +492,12 @@ async def handle_pot_application( logger.info(f"new pot application data: {data}, {appl_data}") # Update or create the account - project, _ = await Account.objects.aget_or_create( + project, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, id=appl_data["project_id"], ) # TODO: wouldn't this be the same as the project_id? should inspect - signer, _ = await Account.objects.aget_or_create( + signer, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, id=signer_id, ) @@ -635,7 +635,7 @@ async def handle_list_upvote( logger.info(f"upvote list: {data}, {receiver_id}") - acct, _ = await Account.objects.aget_or_create( + acct, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, id=signer_id, ) @@ -670,7 +670,7 @@ async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): logger.info(f"set payout data: {data}, {receiver_id}") payouts = data.get("payouts", []) pot = await Pot.objects.aget(account=receiver_id) - near_acct, _ = await Account.objects.aget_or_create(id="near") + near_acct, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id="near") near_token, _ = await Token.objects.aget_or_create( account=near_acct ) # Pots only support native NEAR @@ -736,7 +736,7 @@ async def handle_payout_challenge( data: dict, receiver_id: str, signer_id: str, receiptId: str, created_at: datetime ): try: - acct, _ = await Account.objects.aget_or_create(id=signer_id) + acct, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=signer_id) logger.info(f"challenging payout..: {data}, {receiver_id}") payoutChallenge = { "created_at": created_at, @@ -811,7 +811,7 @@ async def handle_add_nadabot_admin(data, receiverId): obj = await NadabotRegistry.objects.aget(account=receiverId) for acct in data["account_ids"]: - user, _ = await Account.objects.aget_or_create(id=acct) + user, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=acct) await obj.admins.aadd(user) except Exception as e: logger.error(f"Failed to add nadabot admin, Error: {e}") @@ -822,7 +822,7 @@ async def handle_add_factory_deployers(data, receiverId): try: factory = await PotFactory.objects.aget(account=receiverId) for acct in data["whitelisted_deployers"]: - user, _ = await Account.objects.aget_or_create(id=acct) + user, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=acct) await factory.whitelisted_deployers.aadd(user) except Exception as e: logger.error(f"Failed to add factory whitelisted deployers, Error: {e}") @@ -889,33 +889,33 @@ async def handle_new_donation( try: # insert donate contract which is the receiver id(because of activity relationship mainly) - donate_contract, _ = await Account.objects.aget_or_create(id=receiver_id) + donate_contract, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=receiver_id) # Upsert donor account - donor, _ = await Account.objects.aget_or_create(id=donation_data["donor_id"]) + donor, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=donation_data["donor_id"]) recipient = None referrer = None chef = None if donation_data.get("recipient_id"): # direct donations have recipient_id - recipient, _ = await Account.objects.aget_or_create( + recipient, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, id=donation_data["recipient_id"] ) if donation_data.get("project_id"): # pot donations have project_id - recipient, _ = await Account.objects.aget_or_create( + recipient, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, id=donation_data["project_id"] ) if donation_data.get("referrer_id"): - referrer, _ = await Account.objects.aget_or_create( + referrer, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, id=donation_data["referrer_id"] ) if donation_data.get("chef_id"): - chef, _ = await Account.objects.aget_or_create(id=donation_data["chef_id"]) + chef, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=donation_data["chef_id"]) # Upsert token account ft_id = donation_data.get("ft_id") or "near" - token_acct, token_acct_created = await Account.objects.aget_or_create(id=ft_id) + token_acct, token_acct_created = await Account.objects.aget_or_create(defaults={"chain_id":1},id=ft_id) token_defaults = { "decimals": 24, } @@ -1098,8 +1098,8 @@ async def handle_new_provider(data: dict, receiverId: str, signerId: str): ) try: - submitter, _ = await Account.objects.aget_or_create(id=data["submitted_by"]) - contract, _ = await Account.objects.aget_or_create(id=data["contract_id"]) + submitter, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=data["submitted_by"]) + contract, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=data["contract_id"]) provider_id = data["id"] @@ -1144,7 +1144,7 @@ async def handle_add_stamp(data: dict, receiverId: str, signerId: str): logger.info(f"upserting accounts involved, {data['user_id']}") - user, _ = await Account.objects.aget_or_create(id=data["user_id"]) + user, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=data["user_id"]) provider, _ = await Provider.objects.aget_or_create(on_chain_id=data["provider_id"]) try: diff --git a/pots/models.py b/pots/models.py index 26dc8af..e692e6f 100644 --- a/pots/models.py +++ b/pots/models.py @@ -84,21 +84,21 @@ def update_configs(self): config = response.json() print(config) self.protocol_fee_basis_points = config.get("protocol_fee_basis_points") - acct, created = Account.objects.get_or_create( + acct, created = Account.objects.get_or_create(defaults={"chain_id":1}, id=config.get("protocol_fee_recipient_account") ) self.protocol_fee_recipient = acct self.require_whitelist = config.get("require_whitelist") - self.owner, created = Account.objects.get_or_create( + self.owner, created = Account.objects.get_or_create(defaults={"chain_id":1}, id=config.get("owner") ) self.admins.clear() for admin_id in config.get("admins"): - self.admins.add(Account.objects.get_or_create(id=admin_id)[0]) + self.admins.add(Account.objects.get_or_create(defaults={"chain_id":1},id=admin_id)[0]) self.whitelisted_deployers.clear() for deployer_id in config.get("whitelisted_deployers"): self.whitelisted_deployers.add( - Account.objects.get_or_create(id=deployer_id)[0] + Account.objects.get_or_create(defaults={"chain_id":1},id=deployer_id)[0] ) self.save() except Exception as e: @@ -335,13 +335,13 @@ def update_configs(self): else: config = response.json() print(config) - self.owner, created = Account.objects.get_or_create( + self.owner, created = Account.objects.get_or_create(defaults={"chain_id":1}, id=config.get("owner") ) self.admins.clear() for admin_id in config.get("admins", []): - self.admins.add(Account.objects.get_or_create(id=admin_id)[0]) - self.chef, created = Account.objects.get_or_create( + self.admins.add(Account.objects.get_or_create(defaults={"chain_id":1},id=admin_id)[0]) + self.chef, created = Account.objects.get_or_create(defaults={"chain_id":1}, id=config.get("chef") ) self.name = config.get("pot_name") From 8c4e4e757a6968390d75568815dd3918626b065d Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 8 Aug 2024 11:56:37 +0100 Subject: [PATCH 269/454] fix creating projects --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index f476b6e..f51144e 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -397,7 +397,7 @@ async def handle_new_list_registration( insert_data = [] for dt in reg_data: logger.info(f"dt: {dt}") - project_list.append({"id": dt["registrant_id"]}) + project_list.append({"chain_id":1, "id": dt["registrant_id"]}) insert_data.append( { "id": dt["id"], From a0ee8e10f4798724d3a71ef40d6218fb0eea5e8a Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 8 Aug 2024 12:53:19 +0100 Subject: [PATCH 270/454] fix project creation activity --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index f51144e..8b9d4b6 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -435,7 +435,7 @@ async def handle_new_list_registration( defaults = { "signer_id": signer_id, "receiver_id": receiver_id, - "timestamp": datetime.fromtimestamp(insert_data[0]["submitted_at"] / 1000), + "timestamp": insert_data[0]["submitted_at"], "tx_hash": receipt.receipt_id, } From 985d1ef9cd822064e42fd0ef9d975ed75b4963c2 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 22 Aug 2024 15:15:28 +0100 Subject: [PATCH 271/454] add preview link cors regex --- base/settings.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/base/settings.py b/base/settings.py index a2ed79b..40978e4 100644 --- a/base/settings.py +++ b/base/settings.py @@ -173,6 +173,10 @@ "https://dev.local", ] +CORS_ALLOWED_ORIGIN_REGEXES = [ + "^https:\/\/potlock-next-[\w-]+-potlock\.vercel\.app\/?$" +] + # REDIS / CACHE CONFIGS REDIS_SCHEMA = ( From 568e8ef98accf98a9863fcf5e790862542d9c6a5 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 27 Aug 2024 03:16:13 +0100 Subject: [PATCH 272/454] models for grant picks indexing on stellar --- accounts/models.py | 70 ++++++++++++ pots/models.py | 271 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 336 insertions(+), 5 deletions(-) diff --git a/accounts/models.py b/accounts/models.py index a6175b0..4cd0288 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -145,3 +145,73 @@ def save(self, *args, **kwargs): False # don't save yet as we want to avoid infinite loop ) super().save(*args, **kwargs) + + +class ProjectStatus(models.TextChoices): + NEW = "NEW", _("New") + APPROVED = "APPROVED", _("Approved") + REJECTED = "REJECTED", _("Rejected") + COMPLETED = "COMPLETED", _("Completed") + + +class ProjectContact(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=255) + value = models.CharField(max_length=255) + +class ProjectContract(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=255) + contract_address = models.CharField(max_length=255) + +class ProjectRepository(models.Model): + id = models.AutoField(primary_key=True) + label = models.CharField(max_length=255) + url = models.URLField(max_length=200) + + +class ProjectFundingHistory(models.Model): + id = models.AutoField(primary_key=True) + source = models.CharField(max_length=255) + amount = models.DecimalField(max_digits=20, decimal_places=2) + denomination = models.CharField(max_length=255) + description = models.TextField() + timestamp = models.DateTimeField(auto_now_add=True) + + +class Project(models.Model): + id = models.BigAutoField(primary_key=True) + image_url = models.URLField(max_length=200) + video_url = models.URLField(max_length=200) + name = models.CharField(max_length=255) + overview = models.TextField() + owner = models.ForeignKey(Account, related_name='owned_projects', on_delete=models.CASCADE) + payout_address = models.ForeignKey(Account, related_name='payout_projects', on_delete=models.CASCADE) + contacts = models.ManyToManyField( + ProjectContact, + related_name="contact_lists", + help_text=_("project contacts."), + ) + contracts = models.ManyToManyField( + ProjectContract, + related_name="contract_list", + help_text=_("project contracts."), + ) + team_members = models.ManyToManyField( + Account, + related_name="team_members", + help_text=_("Project Team Member"), + ) + repositories = models.ManyToManyField( + ProjectRepository, + related_name="repository_list", + help_text=_("project repositories."), + ) + status = models.CharField(max_length=50, choices=ProjectStatus.choices) + submited_ms = models.BigIntegerField() + updated_ms = models.BigIntegerField(null=True, blank=True) + admins = models.ManyToManyField( + Account, + related_name="admin_projects", + help_text=_("Project Admin"), + ) \ No newline at end of file diff --git a/pots/models.py b/pots/models.py index e692e6f..4c69b86 100644 --- a/pots/models.py +++ b/pots/models.py @@ -5,8 +5,9 @@ from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ +from django.core.exceptions import ValidationError -from accounts.models import Account +from accounts.models import Account, Project, ProjectContact from base.logging import logger from base.utils import format_date from tokens.models import Token, TokenHistoricalPrice @@ -392,6 +393,8 @@ class PotApplicationStatus(models.TextChoices): APPROVED = "Approved", "Approved" REJECTED = "Rejected", "Rejected" INREVIEW = "InReview", "InReview" + BLACKLISTED = "Blacklisted", "Blacklisted" + class PotApplication(models.Model): @@ -404,16 +407,26 @@ class PotApplication(models.Model): Pot, on_delete=models.CASCADE, related_name="applications", - null=False, + null=True, + blank=True, help_text=_("Pot applied to."), db_index=True, ) + round = models.ForeignKey( + "Round", + on_delete=models.CASCADE, + related_name="applications", + null=True, + blank=True, + help_text=_("Round applied to."), + db_index=True, + ) applicant = models.ForeignKey( Account, on_delete=models.CASCADE, related_name="pot_applications", null=False, - help_text=_("Account that applied to the pot."), + help_text=_("Account that applied to the pot or round."), db_index=True, ) message = models.TextField( @@ -451,11 +464,30 @@ class PotApplication(models.Model): class Meta: verbose_name_plural = "Pot Applications" + constraints = [ + models.CheckConstraint( + check=( + models.Q(pot__isnull=False) & models.Q(round__isnull=True) | + models.Q(pot__isnull=True) & models.Q(round__isnull=False) + ), + name="pot_or_round" + ) + ] + + def clean(self): + if self.pot is None and self.round is None: + raise ValidationError(_("Either pot or round must be specified.")) + if self.pot is not None and self.round is not None: + raise ValidationError(_("Only one of pot or round can be specified.")) - unique_together = (("pot", "applicant"),) + def save(self, *args, **kwargs): + self.clean() + super().save(*args, **kwargs) def __str__(self): - return f"{self.applicant.id} - {self.pot}" + if self.pot: + return f"{self.applicant.id} - {self.pot}" + return f"{self.applicant.id} - {self.round}" class PotApplicationReview(models.Model): @@ -693,3 +725,232 @@ class Meta: verbose_name_plural = "Payout Challenge Responses" unique_together = (("challenger", "pot", "created_at"),) + + +class Round(models.Model): + contract = models.OneToOneField( + Account, + primary_key=True, + related_name="round", + on_delete=models.CASCADE, + help_text=_("Round account ID."), + ) + deployed_at = models.DateTimeField( + _("deployed at"), + null=False, + help_text=_("Round deployment date."), + db_index=True, + ) + owner = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="owned_rounds", + null=False, + help_text=_("Round owner."), + ) + admins = models.ManyToManyField( + Account, + related_name="admin_rounds", + help_text=_("Round admins."), + ) + name = models.TextField( + _("name"), + null=False, + help_text=_("Round name."), + ) + description = models.TextField( + _("description"), + null=False, + help_text=_("Round description."), + ) + contacts = models.ManyToManyField( + ProjectContact, + related_name="round_contacts", + help_text=_("Round contacts."), + ) + expected_amount = models.CharField( + _("expected amount"), + null=False, + help_text=_("Expected amount."), + ) + + base_currency = models.CharField( + _("base currency"), + max_length=64, + null=True, + blank=True, + help_text=_("Base currency."), + ) + application_start = models.DateTimeField( + _("application start"), + null=False, + help_text=_("Round application start date."), + ) + application_end = models.DateTimeField( + _("application end"), + null=False, + help_text=_("Round application end date."), + ) + voting_start = models.DateTimeField( + _("voting start"), + null=False, + help_text=_("Round voting start date."), + ) + voting_end = models.DateTimeField( + _("voting end"), + null=False, + help_text=_("Round voting end date."), + ) + use_whitelist = models.BooleanField( + _("use whitelist"), + null=False, + help_text=_("Use whitelist."), + ) + use_vault = models.BooleanField( + _("use vault"), + null=False, + help_text=_("Use vault."), + ) + num_picks_per_voter = models.PositiveIntegerField( + _("num picks per voter"), + null=True, + blank=True, + help_text=_("Number of picks per voter."), + ) + max_participants = models.PositiveIntegerField( + _("max participants"), + null=True, + blank=True, + help_text=_("Max participants."), + ) + allow_applications = models.BooleanField( + _("allow applications"), + null=False, + help_text=_("Allow applications."), + ) + is_video_required = models.BooleanField( + _("is video required"), + null=False, + help_text=_("Is video required."), + ) + cooldown_end = models.DateTimeField( + _("cooldown end"), + null=True, + blank=True, + help_text=_("Round cooldown end date."), + ) + cooldown_period_ms = models.PositiveIntegerField( + _("cooldown period in ms"), + null=True, + blank=True, + help_text=_("Round cooldown period in ms."), + ) + compliance_req_desc = models.TextField( + _("compliance req desc"), + null=False, + help_text=_("Compliance req desc."), + ) + compliance_period_ms = models.PositiveIntegerField( + _("compliance period in ms"), + null=True, + blank=True, + help_text=_("Compliance period in ms."), + ) + compliance_end = models.DateTimeField( + _("compliance end"), + null=True, + blank=True, + help_text=_("Compliance end date."), + ) + allow_remaining_dist = models.BooleanField( + _("allow remaining dist"), + null=False, + help_text=_("Allow remaining dist."), + ) + remaining_dist_address = models.CharField( + _("remaining dist address"), + max_length=255, + null=False, + help_text=_("Remaining dist address."), + ) + referrer_fee_basis_points = models.PositiveIntegerField( + _("referrer fee basis points"), + null=True, + blank=True, + help_text=_("Referrer fee basis points."), + ) + + +class RoundDeposit(models.Model): + id = models.AutoField( + _("deposit id"), + primary_key=True, + help_text=_("Deposit id."), + ) + round = models.ForeignKey( + Round, + on_delete=models.CASCADE, + related_name="deposits", + null=False, + help_text=_("Round that this deposit is for."), + db_index=True, + ) + depositor = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="round_deposits", + null=False, + help_text=_("Deposit maker."), + db_index=True, + ) + amount = models.CharField( + _("amount"), + null=False, + help_text=_("Deposit amount."), + ) + amount_in_usd = models.DecimalField( + _("amount deposited in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Deposit amount in USD."), + ) + token = models.ForeignKey( + Token, + on_delete=models.CASCADE, + related_name="round_deposits", + null=True, + help_text=_("Deposit token."), + ) + deposit_at = models.DateTimeField( + _("deposit at"), + null=True, + blank=True, + help_text=_("Deposit date."), + db_index=True, + ) + tx_hash = models.CharField( + _("transaction hash"), + null=True, + blank=True, + help_text=_("Transaction hash."), + ) + +class Vote(models.Model): + round = models.ForeignKey(Round, on_delete=models.CASCADE, related_name='votes') + voter = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='votes') + voted_at = models.DateTimeField() + + class Meta: + unique_together = ('round', 'voter', 'voted_at') + + + +class VotePair(models.Model): + vote = models.ForeignKey(Vote, on_delete=models.CASCADE, related_name='pairs') + pair_id = models.PositiveIntegerField() + project = models.ForeignKey('PotApplication', on_delete=models.CASCADE, related_name='vote_pairs') + + class Meta: + unique_together = ('vote', 'pair_id') \ No newline at end of file From a3556fce185bf6269e37a73ff0357ae2f40abdc1 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 28 Aug 2024 17:56:53 +0100 Subject: [PATCH 273/454] fix testnet pot deployment, add cors allow --- base/settings.py | 32 +++++++++++++------ .../management/commands/populatedata.py | 2 +- indexer_app/utils.py | 4 +-- pots/utils.py | 2 +- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/base/settings.py b/base/settings.py index 40978e4..36fecc4 100644 --- a/base/settings.py +++ b/base/settings.py @@ -163,15 +163,29 @@ WSGI_APPLICATION = "base.wsgi.application" -CORS_ALLOWED_ORIGINS = [ - "http://localhost:3000", - "https://alpha.potlock.io", - "https://alpha.potlock.org", - "https://alpha.potlock.xyz", - "https://alpha.potlock.app", # regex matching might not be advisable. - "http://dev.local", - "https://dev.local", -] + +if ENVIRONMENT == "testnet": + CORS_ALLOWED_ORIGINS = [ + "http://localhost:3000", + "https://test.potlock.org", + "https://test.potlock.xyz", + "https://test.potlock.io", + "https://test.potlock.app", + "https://testnet.potlock.org", + "https://testnet.potlock.xyz", + "https://testnet.potlock.app", + "https://testnet.potlock.io" + ] +else: + CORS_ALLOWED_ORIGINS = [ + "http://localhost:3000", + "https://alpha.potlock.io", + "https://alpha.potlock.org", + "https://alpha.potlock.xyz", + "https://alpha.potlock.app", # regex matching might not be advisable. + "http://dev.local", + "https://dev.local", + ] CORS_ALLOWED_ORIGIN_REGEXES = [ "^https:\/\/potlock-next-[\w-]+-potlock\.vercel\.app\/?$" diff --git a/indexer_app/management/commands/populatedata.py b/indexer_app/management/commands/populatedata.py index f10f646..e69422d 100644 --- a/indexer_app/management/commands/populatedata.py +++ b/indexer_app/management/commands/populatedata.py @@ -260,7 +260,7 @@ def handle(self, *args, **options): if config.get("chef"): chef, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=config["chef"]) pot_defaults = { - "pot_factory_account": POTFACTORY_ID, + "pot_factory_id": POTFACTORY_ID, "owner_id": config["owner"], "chef_id": config["chef"], "name": config["pot_name"], diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 8b9d4b6..ad5fd26 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -149,7 +149,7 @@ async def handle_new_pot( # Create Pot object logger.info(f"creating pot with owner {owner_id}....") pot_defaults = { - "pot_factory_account": predecessorId, + "pot_factory_id": predecessorId, "deployer": signer, "deployed_at": created_at, "source_metadata": data["source_metadata"], @@ -174,7 +174,7 @@ async def handle_new_pot( "registry_provider": data["registry_provider"], "min_matching_pool_donation_amount": data[ "min_matching_pool_donation_amount" - ], + ] or "0", "sybil_wrapper_provider": data["sybil_wrapper_provider"], "custom_sybil_checks": data.get("custom_sybil_checks"), "custom_min_threshold_score": data.get("custom_min_threshold_score"), diff --git a/pots/utils.py b/pots/utils.py index ef041d2..3591b6d 100644 --- a/pots/utils.py +++ b/pots/utils.py @@ -3,7 +3,7 @@ from django.conf import settings BASE_PATTERN = ( - r"potlock\.testnet" + r"v\d+\.potfactory\.potlock\.testnet" if settings.ENVIRONMENT == "testnet" else r"v\d+\.potfactory\.potlock\.near" ) From fa92f2bca0440fa08c14dd34883f6b2c37b57436 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 5 Sep 2024 22:17:43 +0100 Subject: [PATCH 274/454] fix: update cors origins --- base/settings.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index 36fecc4..6a58ec5 100644 --- a/base/settings.py +++ b/base/settings.py @@ -182,9 +182,17 @@ "https://alpha.potlock.io", "https://alpha.potlock.org", "https://alpha.potlock.xyz", - "https://alpha.potlock.app", # regex matching might not be advisable. + "https://alpha.potlock.app", "http://dev.local", "https://dev.local", + "https://potlock.org", + "https://potlock.io", + "https://potlock.app", + "https://potlock.xyz", + "https://landing.potlock.org", + "https://landing.potlock.io", + "https://landing.potlock.app", + "https://landing.potlock.xyz" ] CORS_ALLOWED_ORIGIN_REGEXES = [ From f9398a96b70ef0f860bf35a2fdf0e2e634009683 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Tue, 10 Sep 2024 03:39:37 +0100 Subject: [PATCH 275/454] fix config and pot --- indexer_app/utils.py | 2 +- pots/models.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index ad5fd26..120c424 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -200,7 +200,7 @@ async def handle_new_pot( if data.get("admins"): for admin_id in data["admins"]: admin, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=admin_id) - pot.admins.aadd(admin) + await pot.admins.aadd(admin) defaults = { "signer_id": signer_id, diff --git a/pots/models.py b/pots/models.py index e692e6f..0121d48 100644 --- a/pots/models.py +++ b/pots/models.py @@ -341,9 +341,10 @@ def update_configs(self): self.admins.clear() for admin_id in config.get("admins", []): self.admins.add(Account.objects.get_or_create(defaults={"chain_id":1},id=admin_id)[0]) - self.chef, created = Account.objects.get_or_create(defaults={"chain_id":1}, - id=config.get("chef") - ) + if config.get("chef"): + self.chef, created = Account.objects.get_or_create(defaults={"chain_id":1}, + id=config.get("chef") + ) self.name = config.get("pot_name") self.description = config.get("pot_description") self.max_approved_applicants = config.get("max_projects") From d874fdecaba6f9e9af2927ae69bb250d73ff2c1c Mon Sep 17 00:00:00 2001 From: Prometheus Date: Mon, 16 Sep 2024 20:59:53 +0100 Subject: [PATCH 276/454] whitelist bos local --- base/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/settings.py b/base/settings.py index 6a58ec5..84f694b 100644 --- a/base/settings.py +++ b/base/settings.py @@ -167,6 +167,7 @@ if ENVIRONMENT == "testnet": CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", + "http://127.0.0.1:8080", "https://test.potlock.org", "https://test.potlock.xyz", "https://test.potlock.io", @@ -179,6 +180,7 @@ else: CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", + "http://127.0.0.1:8080", "https://alpha.potlock.io", "https://alpha.potlock.org", "https://alpha.potlock.xyz", From d221a345602a7765f1bda73b94960303116268c7 Mon Sep 17 00:00:00 2001 From: Prometheus Date: Wed, 18 Sep 2024 12:12:33 +0100 Subject: [PATCH 277/454] add bos to cors --- base/settings.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index 84f694b..79c12fe 100644 --- a/base/settings.py +++ b/base/settings.py @@ -194,7 +194,15 @@ "https://landing.potlock.org", "https://landing.potlock.io", "https://landing.potlock.app", - "https://landing.potlock.xyz" + "https://landing.potlock.xyz", + "https://bos.potlock.org", + "https://app.potlock.org", + "https://bos.potlock.xyz", + "https://app.potlock.xyz", + "https://bos.potlock.io", + "https://app.potlock.io", + "https://bos.potlock.app", + "https://app.potlock.app" ] CORS_ALLOWED_ORIGIN_REGEXES = [ From de527675f725d0e4c10e86f0811cfc26c9b2cd8c Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 19 Sep 2024 00:21:36 +0100 Subject: [PATCH 278/454] feat: stellar indexing for grantpicks... --- accounts/models.py | 70 -- base/celery.py | 12 + base/settings.py | 4 +- grantpicks/__init__.py | 0 grantpicks/admin.py | 3 + grantpicks/api.py | 0 grantpicks/apps.py | 6 + grantpicks/migrations/0001_initial.py | 668 ++++++++++++++++++ grantpicks/migrations/__init__.py | 0 grantpicks/models.py | 426 +++++++++++ grantpicks/serializers.py | 0 grantpicks/tests.py | 3 + grantpicks/views.py | 3 + indexer_app/handler.py | 3 + .../management/commands/populatedata.py | 2 +- indexer_app/tasks.py | 131 +++- indexer_app/utils.py | 345 ++++++++- ...potapplication_unique_together_and_more.py | 109 +++ pots/models.py | 243 +------ pyproject.toml | 1 + 20 files changed, 1717 insertions(+), 312 deletions(-) create mode 100644 grantpicks/__init__.py create mode 100644 grantpicks/admin.py create mode 100644 grantpicks/api.py create mode 100644 grantpicks/apps.py create mode 100644 grantpicks/migrations/0001_initial.py create mode 100644 grantpicks/migrations/__init__.py create mode 100644 grantpicks/models.py create mode 100644 grantpicks/serializers.py create mode 100644 grantpicks/tests.py create mode 100644 grantpicks/views.py create mode 100644 pots/migrations/0014_alter_potapplication_unique_together_and_more.py diff --git a/accounts/models.py b/accounts/models.py index 4cd0288..a6175b0 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -145,73 +145,3 @@ def save(self, *args, **kwargs): False # don't save yet as we want to avoid infinite loop ) super().save(*args, **kwargs) - - -class ProjectStatus(models.TextChoices): - NEW = "NEW", _("New") - APPROVED = "APPROVED", _("Approved") - REJECTED = "REJECTED", _("Rejected") - COMPLETED = "COMPLETED", _("Completed") - - -class ProjectContact(models.Model): - id = models.AutoField(primary_key=True) - name = models.CharField(max_length=255) - value = models.CharField(max_length=255) - -class ProjectContract(models.Model): - id = models.AutoField(primary_key=True) - name = models.CharField(max_length=255) - contract_address = models.CharField(max_length=255) - -class ProjectRepository(models.Model): - id = models.AutoField(primary_key=True) - label = models.CharField(max_length=255) - url = models.URLField(max_length=200) - - -class ProjectFundingHistory(models.Model): - id = models.AutoField(primary_key=True) - source = models.CharField(max_length=255) - amount = models.DecimalField(max_digits=20, decimal_places=2) - denomination = models.CharField(max_length=255) - description = models.TextField() - timestamp = models.DateTimeField(auto_now_add=True) - - -class Project(models.Model): - id = models.BigAutoField(primary_key=True) - image_url = models.URLField(max_length=200) - video_url = models.URLField(max_length=200) - name = models.CharField(max_length=255) - overview = models.TextField() - owner = models.ForeignKey(Account, related_name='owned_projects', on_delete=models.CASCADE) - payout_address = models.ForeignKey(Account, related_name='payout_projects', on_delete=models.CASCADE) - contacts = models.ManyToManyField( - ProjectContact, - related_name="contact_lists", - help_text=_("project contacts."), - ) - contracts = models.ManyToManyField( - ProjectContract, - related_name="contract_list", - help_text=_("project contracts."), - ) - team_members = models.ManyToManyField( - Account, - related_name="team_members", - help_text=_("Project Team Member"), - ) - repositories = models.ManyToManyField( - ProjectRepository, - related_name="repository_list", - help_text=_("project repositories."), - ) - status = models.CharField(max_length=50, choices=ProjectStatus.choices) - submited_ms = models.BigIntegerField() - updated_ms = models.BigIntegerField(null=True, blank=True) - admins = models.ManyToManyField( - Account, - related_name="admin_projects", - help_text=_("Project Admin"), - ) \ No newline at end of file diff --git a/base/celery.py b/base/celery.py index a08ab54..9ba059d 100644 --- a/base/celery.py +++ b/base/celery.py @@ -40,12 +40,24 @@ "schedule": crontab(minute="*/5"), # Executes every 5 minutes "options": {"queue": "beat_tasks"}, }, + "fetch_stellar_events_every_minute": { + "task": "indexer_app.tasks.stellar_event_indexer", + "schedule": crontab(minute="*/1"), # Executes every 1 minutes + "options": {"queue": "beat_tasks"}, + }, + "process_stellar_event_every_minute": { + "task": "indexer_app.tasks.process_stellar_events", + "schedule": crontab(minute="*/1"), # Executes every 1 minutes + "options": {"queue": "beat_tasks"}, + }, } app.conf.task_routes = { "indexer_app.tasks.update_account_statistics": {"queue": "beat_tasks"}, "indexer_app.tasks.fetch_usd_prices": {"queue": "beat_tasks"}, "indexer_app.tasks.update_pot_statistics": {"queue": "beat_tasks"}, + "indexer_app.tasks.stellar_event_indexer": {"queue": "beat_tasks"}, + "indexer_app.tasks.process_stellar_events": {"queue": "beat_tasks"}, } SPOT_INDEXER_QUEUE_NAME = "spot_indexing" diff --git a/base/settings.py b/base/settings.py index 40978e4..63e3600 100644 --- a/base/settings.py +++ b/base/settings.py @@ -60,7 +60,8 @@ POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else "potlock.near" NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" - +STELLAR_CONTRACT_ID = "CBP5F3C5SPPJRNRJSOOQQKUGX2CE2G6BRV34MOEBHNFGBRQ5ZSXNGWAB" if ENVIRONMENT == "testnet" else "CBP5F3C5SPPJRNRJSOOQQKUGX2CE2G6BRV34MOEBHNFGBRQ5ZSXNGWAB" +STELLAR_PROJECTS_REGISTRY_CONTRACT = "CBYUL5Y4GTIZAST3W2LVYKFTYNJA3EUG3MN4W2TTFONV6VXEACMRLBEP" NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) @@ -106,6 +107,7 @@ "tokens", "nadabot", "chains", + "grantpicks" ] DEFAULT_PAGE_SIZE = 30 diff --git a/grantpicks/__init__.py b/grantpicks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/grantpicks/admin.py b/grantpicks/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/grantpicks/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/grantpicks/api.py b/grantpicks/api.py new file mode 100644 index 0000000..e69de29 diff --git a/grantpicks/apps.py b/grantpicks/apps.py new file mode 100644 index 0000000..cb16bc9 --- /dev/null +++ b/grantpicks/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GrantpicksConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "grantpicks" diff --git a/grantpicks/migrations/0001_initial.py b/grantpicks/migrations/0001_initial.py new file mode 100644 index 0000000..b99c686 --- /dev/null +++ b/grantpicks/migrations/0001_initial.py @@ -0,0 +1,668 @@ +# Generated by Django 5.0.6 on 2024-09-18 23:05 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ("tokens", "0006_rename_id_token_account"), + ] + + operations = [ + migrations.CreateModel( + name="ProjectContact", + fields=[ + ("id", models.AutoField(primary_key=True, serialize=False)), + ("name", models.CharField(max_length=255)), + ("value", models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name="ProjectContract", + fields=[ + ("id", models.AutoField(primary_key=True, serialize=False)), + ("name", models.CharField(max_length=255)), + ("contract_address", models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name="ProjectFundingHistory", + fields=[ + ("id", models.AutoField(primary_key=True, serialize=False)), + ("source", models.CharField(max_length=255)), + ("amount", models.DecimalField(decimal_places=2, max_digits=20)), + ("denomination", models.CharField(max_length=255)), + ("description", models.TextField()), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.CreateModel( + name="ProjectRepository", + fields=[ + ("id", models.AutoField(primary_key=True, serialize=False)), + ("label", models.CharField(max_length=255)), + ("url", models.URLField()), + ], + ), + migrations.CreateModel( + name="StellarEvent", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("ledger_sequence", models.BigIntegerField()), + ("event_type", models.CharField(max_length=100)), + ("contract_id", models.CharField(max_length=56, null=True)), + ("data", models.JSONField()), + ("ingested_at", models.DateTimeField()), + ( + "transaction_hash", + models.CharField( + blank=True, + help_text="Transaction hash.", + max_length=64, + null=True, + verbose_name="transaction hash", + ), + ), + ("processed", models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name="Project", + fields=[ + ("id", models.AutoField(primary_key=True, serialize=False)), + ( + "on_chain_id", + models.IntegerField( + help_text="Project id in contract", + unique=True, + verbose_name="contract project id", + ), + ), + ("image_url", models.URLField()), + ("video_url", models.URLField()), + ("name", models.CharField(max_length=255)), + ("overview", models.TextField()), + ( + "status", + models.CharField( + choices=[ + ("NEW", "New"), + ("APPROVED", "Approved"), + ("REJECTED", "Rejected"), + ("COMPLETED", "Completed"), + ], + max_length=50, + ), + ), + ("submited_ms", models.BigIntegerField()), + ("updated_ms", models.BigIntegerField(blank=True, null=True)), + ( + "admins", + models.ManyToManyField( + help_text="Project Admin", + related_name="admin_projects", + to="accounts.account", + ), + ), + ( + "owner", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="owned_projects", + to="accounts.account", + ), + ), + ( + "payout_address", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="payout_projects", + to="accounts.account", + ), + ), + ( + "team_members", + models.ManyToManyField( + help_text="Project Team Member", + related_name="team_members", + to="accounts.account", + ), + ), + ( + "contacts", + models.ManyToManyField( + help_text="project contacts.", + related_name="contact_lists", + to="grantpicks.projectcontact", + ), + ), + ( + "contracts", + models.ManyToManyField( + help_text="project contracts.", + related_name="contract_list", + to="grantpicks.projectcontract", + ), + ), + ( + "repositories", + models.ManyToManyField( + help_text="project repositories.", + related_name="repository_list", + to="grantpicks.projectrepository", + ), + ), + ], + ), + migrations.CreateModel( + name="Round", + fields=[ + ( + "id", + models.AutoField( + help_text="Round ID in DB (does not necessarily correspond to on-chain ID).", + primary_key=True, + serialize=False, + verbose_name="round id", + ), + ), + ( + "on_chain_id", + models.IntegerField( + help_text="Round ID in contract", + unique=True, + verbose_name="contract round ID", + ), + ), + ( + "deployed_at", + models.DateTimeField( + db_index=True, + help_text="Round deployment date.", + verbose_name="deployed at", + ), + ), + ( + "name", + models.TextField(help_text="Round name.", verbose_name="name"), + ), + ( + "description", + models.TextField( + help_text="Round description.", verbose_name="description" + ), + ), + ( + "expected_amount", + models.CharField( + help_text="Expected amount.", verbose_name="expected amount" + ), + ), + ( + "base_currency", + models.CharField( + blank=True, + help_text="Base currency.", + max_length=64, + null=True, + verbose_name="base currency", + ), + ), + ( + "application_start", + models.DateTimeField( + help_text="Round application start date.", + null=True, + verbose_name="application start", + ), + ), + ( + "application_end", + models.DateTimeField( + help_text="Round application end date.", + null=True, + verbose_name="application end", + ), + ), + ( + "voting_start", + models.DateTimeField( + help_text="Round voting start date.", + verbose_name="voting start", + ), + ), + ( + "voting_end", + models.DateTimeField( + help_text="Round voting end date.", verbose_name="voting end" + ), + ), + ( + "use_whitelist", + models.BooleanField( + help_text="Use whitelist.", verbose_name="use whitelist" + ), + ), + ( + "use_vault", + models.BooleanField( + help_text="Use vault.", verbose_name="use vault" + ), + ), + ( + "num_picks_per_voter", + models.PositiveIntegerField( + blank=True, + help_text="Number of picks per voter.", + null=True, + verbose_name="num picks per voter", + ), + ), + ( + "max_participants", + models.PositiveIntegerField( + blank=True, + help_text="Max participants.", + null=True, + verbose_name="max participants", + ), + ), + ( + "allow_applications", + models.BooleanField( + help_text="Allow applications.", + verbose_name="allow applications", + ), + ), + ( + "is_video_required", + models.BooleanField( + help_text="Is video required.", verbose_name="is video required" + ), + ), + ( + "cooldown_end", + models.DateTimeField( + blank=True, + help_text="Round cooldown end date.", + null=True, + verbose_name="cooldown end", + ), + ), + ( + "cooldown_period_ms", + models.PositiveIntegerField( + blank=True, + help_text="Round cooldown period in ms.", + null=True, + verbose_name="cooldown period in ms", + ), + ), + ( + "compliance_req_desc", + models.TextField( + help_text="Compliance req desc.", + verbose_name="compliance req desc", + ), + ), + ( + "compliance_period_ms", + models.PositiveIntegerField( + blank=True, + help_text="Compliance period in ms.", + null=True, + verbose_name="compliance period in ms", + ), + ), + ( + "compliance_end", + models.DateTimeField( + blank=True, + help_text="Compliance end date.", + null=True, + verbose_name="compliance end", + ), + ), + ( + "allow_remaining_dist", + models.BooleanField( + help_text="Allow remaining dist.", + verbose_name="allow remaining dist", + ), + ), + ( + "remaining_dist_memo", + models.CharField( + blank=True, + help_text="Remaining dist memo.", + max_length=255, + null=True, + verbose_name="remaining dist memo", + ), + ), + ( + "remaining_dist_at_ms", + models.BigIntegerField( + blank=True, + help_text="Remaining dist at ms.", + null=True, + verbose_name="remaining dist at ms", + ), + ), + ( + "current_vault_balance", + models.CharField( + blank=True, + help_text="Current vault balance.", + max_length=255, + null=True, + verbose_name="current vault balance", + ), + ), + ( + "current_vault_balance_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Current vault balance in USD.", + max_digits=20, + null=True, + verbose_name="current vault balance in USD", + ), + ), + ( + "referrer_fee_basis_points", + models.PositiveIntegerField( + blank=True, + help_text="Referrer fee basis points.", + null=True, + verbose_name="referrer fee basis points", + ), + ), + ( + "vault_total_deposits", + models.CharField( + help_text="Vault total deposits.", + null=True, + verbose_name="vault total deposits", + ), + ), + ( + "vault_total_deposits_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Vault total deposits in USD.", + max_digits=20, + null=True, + verbose_name="vault total deposits in USD", + ), + ), + ( + "round_complete", + models.DateTimeField( + blank=True, + help_text="Round complete date.", + null=True, + verbose_name="round complete", + ), + ), + ( + "admins", + models.ManyToManyField( + help_text="Round admins.", + related_name="admin_rounds", + to="accounts.account", + ), + ), + ( + "approved_projects", + models.ManyToManyField( + help_text="Projects Approved for round.", + related_name="rounds_approved_in", + to="grantpicks.project", + ), + ), + ( + "contacts", + models.ManyToManyField( + help_text="Round contacts.", + related_name="round_contacts", + to="grantpicks.projectcontact", + ), + ), + ( + "factory_contract", + models.ForeignKey( + help_text="Round factory account ID.", + on_delete=django.db.models.deletion.CASCADE, + related_name="spawned_rounds", + to="accounts.account", + ), + ), + ( + "owner", + models.ForeignKey( + help_text="Round owner.", + on_delete=django.db.models.deletion.CASCADE, + related_name="owned_rounds", + to="accounts.account", + ), + ), + ( + "remaining_dist_address", + models.ForeignKey( + blank=True, + help_text="Remaining dist address.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="rounds_with_remaining_dist", + to="accounts.account", + ), + ), + ( + "remaining_dist_by", + models.ForeignKey( + blank=True, + help_text="Account responsible for remaining distribution.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="remaining_dist_rounds", + to="accounts.account", + ), + ), + ], + ), + migrations.CreateModel( + name="RoundDeposit", + fields=[ + ( + "id", + models.AutoField( + help_text="Deposit id.", + primary_key=True, + serialize=False, + verbose_name="deposit id", + ), + ), + ( + "on_chain_id", + models.IntegerField( + help_text="Deposit ID in contract", + unique=True, + verbose_name="contract deposit ID", + ), + ), + ( + "amount", + models.CharField( + help_text="Deposit amount.", verbose_name="amount" + ), + ), + ( + "amount_in_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Deposit amount in USD.", + max_digits=20, + null=True, + verbose_name="amount deposited in USD", + ), + ), + ( + "protocol_fee", + models.CharField( + help_text="protocol fee amount.", + null=True, + verbose_name="protocol_fee", + ), + ), + ( + "referrer_fee", + models.CharField( + help_text="referrer fee amount.", + null=True, + verbose_name="referrer_fee", + ), + ), + ( + "deposit_at", + models.DateTimeField( + blank=True, + db_index=True, + help_text="Deposit date.", + null=True, + verbose_name="deposit at", + ), + ), + ( + "tx_hash", + models.CharField( + blank=True, + help_text="Transaction hash.", + null=True, + verbose_name="transaction hash", + ), + ), + ( + "depositor", + models.ForeignKey( + help_text="Deposit maker.", + on_delete=django.db.models.deletion.CASCADE, + related_name="round_deposits", + to="accounts.account", + ), + ), + ( + "round", + models.ForeignKey( + help_text="Round that this deposit is for.", + on_delete=django.db.models.deletion.CASCADE, + related_name="deposits", + to="grantpicks.round", + ), + ), + ( + "token", + models.ForeignKey( + help_text="Deposit token.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="round_deposits", + to="tokens.token", + ), + ), + ], + ), + migrations.CreateModel( + name="Vote", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "tx_hash", + models.CharField( + blank=True, + help_text="Transaction hash.", + null=True, + verbose_name="transaction hash", + ), + ), + ("voted_at", models.DateTimeField()), + ( + "round", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="votes", + to="grantpicks.round", + ), + ), + ( + "voter", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="votes", + to="accounts.account", + ), + ), + ], + options={ + "unique_together": {("round", "voter", "voted_at")}, + }, + ), + migrations.CreateModel( + name="VotePair", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("pair_id", models.PositiveIntegerField()), + ( + "project", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="vote_pairs", + to="grantpicks.project", + ), + ), + ( + "vote", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="pairs", + to="grantpicks.vote", + ), + ), + ], + options={ + "unique_together": {("vote", "pair_id")}, + }, + ), + ] diff --git a/grantpicks/migrations/__init__.py b/grantpicks/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/grantpicks/models.py b/grantpicks/models.py new file mode 100644 index 0000000..5223448 --- /dev/null +++ b/grantpicks/models.py @@ -0,0 +1,426 @@ +from django.db import models + +from django.utils.translation import gettext_lazy as _ + +from accounts.models import Account +from pots.models import PotApplication +from tokens.models import Token + +# Create your models here. + + +class ProjectStatus(models.TextChoices): + NEW = "NEW", _("New") + APPROVED = "APPROVED", _("Approved") + REJECTED = "REJECTED", _("Rejected") + COMPLETED = "COMPLETED", _("Completed") + + +class ProjectContact(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=255) + value = models.CharField(max_length=255) + +class ProjectContract(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=255) + contract_address = models.CharField(max_length=255) + +class ProjectRepository(models.Model): + id = models.AutoField(primary_key=True) + label = models.CharField(max_length=255) + url = models.URLField(max_length=200) + + +class ProjectFundingHistory(models.Model): + id = models.AutoField(primary_key=True) + source = models.CharField(max_length=255) + amount = models.DecimalField(max_digits=20, decimal_places=2) + denomination = models.CharField(max_length=255) + description = models.TextField() + timestamp = models.DateTimeField(auto_now_add=True) + + +class Project(models.Model): + id = models.AutoField(primary_key=True) + on_chain_id =models.IntegerField( + _("contract project id"), + null=False, + unique=True, + help_text=_("Project id in contract"), + ) + image_url = models.URLField(max_length=200) + video_url = models.URLField(max_length=200) + name = models.CharField(max_length=255) + overview = models.TextField() + owner = models.ForeignKey(Account, related_name='owned_projects', on_delete=models.CASCADE) + payout_address = models.ForeignKey(Account, related_name='payout_projects', on_delete=models.CASCADE) + contacts = models.ManyToManyField( + ProjectContact, + related_name="contact_lists", + help_text=_("project contacts."), + ) + contracts = models.ManyToManyField( + ProjectContract, + related_name="contract_list", + help_text=_("project contracts."), + ) + team_members = models.ManyToManyField( + Account, + related_name="team_members", + help_text=_("Project Team Member"), + ) + repositories = models.ManyToManyField( + ProjectRepository, + related_name="repository_list", + help_text=_("project repositories."), + ) + status = models.CharField(max_length=50, choices=ProjectStatus.choices) + submited_ms = models.BigIntegerField() + updated_ms = models.BigIntegerField(null=True, blank=True) + admins = models.ManyToManyField( + Account, + related_name="admin_projects", + help_text=_("Project Admin"), + ) + + + + +class Round(models.Model): + id = models.AutoField( + _("round id"), + primary_key=True, + help_text=_("Round ID in DB (does not necessarily correspond to on-chain ID)."), + ) + on_chain_id = models.IntegerField( + _("contract round ID"), + null=False, + unique=True, + help_text=_("Round ID in contract"), + ) + factory_contract = models.ForeignKey( + Account, + related_name="spawned_rounds", + on_delete=models.CASCADE, + help_text=_("Round factory account ID."), + ) + deployed_at = models.DateTimeField( + _("deployed at"), + null=False, + help_text=_("Round deployment date."), + db_index=True, + ) + owner = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="owned_rounds", + null=False, + help_text=_("Round owner."), + ) + admins = models.ManyToManyField( + Account, + related_name="admin_rounds", + help_text=_("Round admins."), + ) + name = models.TextField( + _("name"), + null=False, + help_text=_("Round name."), + ) + description = models.TextField( + _("description"), + null=False, + help_text=_("Round description."), + ) + contacts = models.ManyToManyField( + ProjectContact, + related_name="round_contacts", + help_text=_("Round contacts."), + ) + expected_amount = models.CharField( + _("expected amount"), + null=False, + help_text=_("Expected amount."), + ) + + base_currency = models.CharField( + _("base currency"), + max_length=64, + null=True, + blank=True, + help_text=_("Base currency."), + ) + application_start = models.DateTimeField( + _("application start"), + null=True, + help_text=_("Round application start date."), + ) + application_end = models.DateTimeField( + _("application end"), + null=True, + help_text=_("Round application end date."), + ) + voting_start = models.DateTimeField( + _("voting start"), + null=False, + help_text=_("Round voting start date."), + ) + voting_end = models.DateTimeField( + _("voting end"), + null=False, + help_text=_("Round voting end date."), + ) + use_whitelist = models.BooleanField( + _("use whitelist"), + null=False, + help_text=_("Use whitelist."), + ) + use_vault = models.BooleanField( + _("use vault"), + null=False, + help_text=_("Use vault."), + ) + num_picks_per_voter = models.PositiveIntegerField( + _("num picks per voter"), + null=True, + blank=True, + help_text=_("Number of picks per voter."), + ) + max_participants = models.PositiveIntegerField( + _("max participants"), + null=True, + blank=True, + help_text=_("Max participants."), + ) + approved_projects = models.ManyToManyField( + Project, + related_name="rounds_approved_in", + help_text=_("Projects Approved for round."), + ) + allow_applications = models.BooleanField( + _("allow applications"), + null=False, + help_text=_("Allow applications."), + ) + is_video_required = models.BooleanField( + _("is video required"), + null=False, + help_text=_("Is video required."), + ) + cooldown_end = models.DateTimeField( + _("cooldown end"), + null=True, + blank=True, + help_text=_("Round cooldown end date."), + ) + cooldown_period_ms = models.PositiveIntegerField( + _("cooldown period in ms"), + null=True, + blank=True, + help_text=_("Round cooldown period in ms."), + ) + compliance_req_desc = models.TextField( + _("compliance req desc"), + null=False, + help_text=_("Compliance req desc."), + ) + compliance_period_ms = models.PositiveIntegerField( + _("compliance period in ms"), + null=True, + blank=True, + help_text=_("Compliance period in ms."), + ) + compliance_end = models.DateTimeField( + _("compliance end"), + null=True, + blank=True, + help_text=_("Compliance end date."), + ) + allow_remaining_dist = models.BooleanField( + _("allow remaining dist"), + null=False, + help_text=_("Allow remaining dist."), + ) + remaining_dist_address = models.ForeignKey( + Account, + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="rounds_with_remaining_dist", + help_text=_("Remaining dist address."), + ) + remaining_dist_memo = models.CharField( + _("remaining dist memo"), + max_length=255, + null=True, + blank=True, + help_text=_("Remaining dist memo."), + ) + remaining_dist_at_ms = models.BigIntegerField( + _("remaining dist at ms"), + null=True, + blank=True, + help_text=_("Remaining dist at ms."), + ) + current_vault_balance = models.CharField( + _("current vault balance"), + max_length=255, + null=True, + blank=True, + help_text=_("Current vault balance."), + ) + current_vault_balance_usd = models.DecimalField( + _("current vault balance in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Current vault balance in USD."), + ) + remaining_dist_by = models.ForeignKey( + Account, + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="remaining_dist_rounds", + help_text=_("Account responsible for remaining distribution."), + ) + referrer_fee_basis_points = models.PositiveIntegerField( + _("referrer fee basis points"), + null=True, + blank=True, + help_text=_("Referrer fee basis points."), + ) + vault_total_deposits = models.CharField( + _("vault total deposits"), + null=True, + help_text=_("Vault total deposits."), + ) + vault_total_deposits_usd = models.DecimalField( + _("vault total deposits in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Vault total deposits in USD."), + ) + round_complete = models.DateTimeField( + _("round complete"), + null=True, + blank=True, + help_text=_("Round complete date."), + ) + + +class RoundDeposit(models.Model): + id = models.AutoField( + _("deposit id"), + primary_key=True, + help_text=_("Deposit id."), + ) + on_chain_id = models.IntegerField( + _("contract deposit ID"), + null=False, + unique=True, + help_text=_("Deposit ID in contract"), + ) + round = models.ForeignKey( + Round, + on_delete=models.CASCADE, + related_name="deposits", + null=False, + help_text=_("Round that this deposit is for."), + db_index=True, + ) + depositor = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="round_deposits", + null=False, + help_text=_("Deposit maker."), + db_index=True, + ) + amount = models.CharField( + _("amount"), + null=False, + help_text=_("Deposit amount."), + ) + amount_in_usd = models.DecimalField( + _("amount deposited in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Deposit amount in USD."), + ) + protocol_fee = models.CharField( + _("protocol_fee"), + null=True, + help_text=_("protocol fee amount."), + ) + referrer_fee = models.CharField( + _("referrer_fee"), + null=True, + help_text=_("referrer fee amount."), + ) + token = models.ForeignKey( + Token, + on_delete=models.CASCADE, + related_name="round_deposits", + null=True, + help_text=_("Deposit token."), + ) + deposit_at = models.DateTimeField( + _("deposit at"), + null=True, + blank=True, + help_text=_("Deposit date."), + db_index=True, + ) + tx_hash = models.CharField( + _("transaction hash"), + null=True, + blank=True, + help_text=_("Transaction hash."), + ) + +class Vote(models.Model): + round = models.ForeignKey(Round, on_delete=models.CASCADE, related_name='votes') + voter = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='votes') + tx_hash = models.CharField( + _("transaction hash"), + null=True, + blank=True, + help_text=_("Transaction hash."), + ) + voted_at = models.DateTimeField() + + class Meta: + unique_together = ('round', 'voter', 'voted_at') + + + +class VotePair(models.Model): + vote = models.ForeignKey(Vote, on_delete=models.CASCADE, related_name='pairs') + pair_id = models.PositiveIntegerField() + project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='vote_pairs') + + class Meta: + unique_together = ('vote', 'pair_id') + + +class StellarEvent(models.Model): + ledger_sequence = models.BigIntegerField() + event_type = models.CharField(max_length=100) + contract_id = models.CharField(max_length=56, null=True) + data = models.JSONField() + ingested_at = models.DateTimeField() + transaction_hash = models.CharField( + _("transaction hash"), + max_length=64, + null=True, + blank=True, + help_text=_("Transaction hash."), + ) + processed = models.BooleanField(default=False) \ No newline at end of file diff --git a/grantpicks/serializers.py b/grantpicks/serializers.py new file mode 100644 index 0000000..e69de29 diff --git a/grantpicks/tests.py b/grantpicks/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/grantpicks/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/grantpicks/views.py b/grantpicks/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/grantpicks/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/indexer_app/handler.py b/indexer_app/handler.py index de6c67b..45a6a43 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -7,8 +7,11 @@ from django.conf import settings from django.core.cache import cache from near_lake_framework import near_primitives +from stellar_sdk.soroban_rpc import GetEventsResponse + from base.utils import convert_ns_to_utc +from grantpicks.models import StellarEvent from nadabot.utils import match_nadabot_registry_pattern from pots.utils import match_pot_factory_pattern, match_pot_subaccount_pattern diff --git a/indexer_app/management/commands/populatedata.py b/indexer_app/management/commands/populatedata.py index f10f646..e69422d 100644 --- a/indexer_app/management/commands/populatedata.py +++ b/indexer_app/management/commands/populatedata.py @@ -260,7 +260,7 @@ def handle(self, *args, **options): if config.get("chef"): chef, _ = Account.objects.get_or_create(defaults={"chain_id":1},id=config["chef"]) pot_defaults = { - "pot_factory_account": POTFACTORY_ID, + "pot_factory_id": POTFACTORY_ID, "owner_id": config["owner"], "chef_id": config["chef"], "name": config["pot_name"], diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 2d06c48..54e6cc0 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -1,4 +1,6 @@ import asyncio +from datetime import datetime +import json import logging import time from pathlib import Path @@ -8,19 +10,22 @@ from celery import shared_task from celery.signals import task_revoked, worker_shutdown from django.conf import settings -from django.db import transaction from django.db.models import Count, DecimalField, Q, Sum, Value from django.db.models.functions import Cast, NullIf from near_lake_framework import LakeConfig, streamer +import stellar_sdk +from stellar_sdk.soroban_server import EventFilter, EventFilterType +from stellar_sdk import Address, stellar_xdr, scval from accounts.models import Account from base.celery import SPOT_INDEXER_QUEUE_NAME from donations.models import Donation +from grantpicks.models import Round, RoundDeposit, StellarEvent, ProjectContact from indexer_app.handler import handle_streamer_message -from pots.models import Pot, PotPayout +from pots.models import Pot, PotApplication, PotApplicationStatus, PotPayout from .logging import logger -from .utils import get_block_height, save_block_height +from .utils import create_or_update_round, create_round_application, get_block_height, get_ledger_sequence, process_application_to_round, process_project_event, process_rounds_deposit_event, process_vote_event, save_block_height, update_application, update_approved_projects, update_ledger_sequence CURRENT_BLOCK_HEIGHT_KEY = "current_block_height" @@ -101,8 +106,8 @@ def listen_to_near_events(): try: # Update below with desired network & block height - start_block = get_block_height() - # start_block = 119_568_113 + # start_block = get_block_height() + start_block = 112682360 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer(start_block - 1, None)) except WorkerLostError: @@ -152,6 +157,7 @@ def spot_index_near_events(start_block): @shared_task def fetch_usd_prices(): + print("In here now........") donations = Donation.objects.filter( Q(total_amount_usd__isnull=True) | Q(net_amount_usd__isnull=True) @@ -326,6 +332,121 @@ def update_account_statistics(): ) jobs_logger.info(f"Account stats for {accounts.count()} accounts updated.") +def address_to_string(obj): + if isinstance(obj, Address): + return obj.address + raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable") + +@shared_task +def stellar_event_indexer(): + server = stellar_sdk.SorobanServer( + "https://soroban-testnet.stellar.org" + if settings.ENVIRONMENT == "testnet" or settings.ENVIRONMENT == "local" + else "https://horizon.stellar.org" + ) + contract_ids = [settings.STELLAR_CONTRACT_ID, settings.STELLAR_PROJECTS_REGISTRY_CONTRACT] + start_sequence = get_ledger_sequence() + # start_sequence = 12169 + if not start_sequence: + start_sequence = 12169 + jobs_logger.info(f"Ingesting Stellar events from ledger {start_sequence}... contracts: {contract_ids}") + try: + # Fetch events for the current sequence + events = server.get_events( + start_ledger=start_sequence, + filters=[ + EventFilter( + event_type=EventFilterType.CONTRACT, + contract_ids=contract_ids + ) + ] + ) + stellar_events = [] + for event in events.events: + event_name = stellar_sdk.scval.to_native(event.topic[0]) + event_value = event.value + if event.value is not None: + event_value = stellar_sdk.scval.to_native(event.value) + event_value = json.loads(json.dumps(event_value, default=address_to_string)) + print("event value:. ", event_value) + stellar_events.append(StellarEvent( + ledger_sequence=event.ledger, + event_type=event_name, + contract_id=event.contract_id, + ingested_at=event.ledger_close_at, + transaction_hash=event.transaction_hash, + data=event_value + )) + + StellarEvent.objects.bulk_create( + objs=stellar_events, + ignore_conflicts=True + ) + update_ledger_sequence(events.latest_ledger, event.ledger_close_at) + jobs_logger.info(f"Ingested {len(stellar_events)} Stellar events from ledger {start_sequence} to {events.latest_ledger}...") + + except Exception as e: + jobs_logger.error(f"Error processing ledger {start_sequence}: {e}") + + + +@shared_task +def process_stellar_events(): + unprocessed_events = StellarEvent.objects.filter(processed=False).order_by('id') + jobs_logger.info(f"Processing {unprocessed_events.count()} unprocessed Stellar events...") + + for event in unprocessed_events: + try: + event_data = event.data + event_name = event.event_type + + if event_name == 'c_project': + process_project_event(event_data) + event.processed = True + + elif event_name == 'c_round' or event_name == 'u_round': + create_or_update_round(event_data, event.contract_id, event.ingested_at) + # Mark event as processed + event.processed = True + + elif event_name == 'apply_to_round': + process_application_to_round(event_data, event.transaction_hash) + # Mark event as processed + event.processed = True + + elif event_name == 'c_app': + create_round_application(event_data, event.transaction_hash) + event.processed = True + + + elif event_name == 'u_app': # application review and aproval + jobs_logger.info(f"eventulating data for event, {event_data}") + update_application(event_data, event.transaction_hash) + event.processed = True + + elif event_name == 'u_ap': + jobs_logger.info(f"eventulating data for approved project event, {event_data}") + update_approved_projects(event_data) + event.processed = True + + elif event_name == 'c_depo': + process_rounds_deposit_event(event_data, event.transaction_hash) + # Mark event as processed + event.processed = True + + elif event_name == 'c_vote': + process_vote_event(event_data, event.transaction_hash) + # Mark event as processed + event.processed = True + event.save() + + except Exception as e: + jobs_logger.error(f"Error processing Stellar event { event_name, event.id}: {e}") + + jobs_logger.info(f"Finished processing Stellar events.") + + + @task_revoked.connect def on_task_revoked(request, terminated, signum, expired, **kwargs): diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 8b9d4b6..45bdbd6 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -7,13 +7,14 @@ from asgiref.sync import sync_to_async from django.conf import settings from django.core.cache import cache -from django.forms.models import model_to_dict +from django.db import transaction from django.utils import timezone from near_lake_framework.near_primitives import ExecutionOutcome, Receipt from accounts.models import Account from activities.models import Activity from donations.models import Donation +from grantpicks.models import Project, ProjectContact, ProjectContract, ProjectFundingHistory, ProjectRepository, ProjectStatus, Round, RoundDeposit, Vote, VotePair from indexer_app.models import BlockHeight from lists.models import List, ListRegistration, ListUpvote from nadabot.models import BlackList, Group, NadabotRegistry, Provider, Stamp @@ -21,6 +22,7 @@ Pot, PotApplication, PotApplicationReview, + PotApplicationStatus, PotFactory, PotPayout, PotPayoutChallenge, @@ -149,7 +151,7 @@ async def handle_new_pot( # Create Pot object logger.info(f"creating pot with owner {owner_id}....") pot_defaults = { - "pot_factory_account": predecessorId, + "pot_factory_id": predecessorId, "deployer": signer, "deployed_at": created_at, "source_metadata": data["source_metadata"], @@ -579,7 +581,7 @@ async def handle_pot_application_status_change( } await PotApplicationReview.objects.aupdate_or_create( - application_id=appl.id, + application=appl, reviewer_id=signer_id, reviewed_at=updated_at, defaults=defaults, @@ -1207,3 +1209,340 @@ def get_block_height() -> int: record = BlockHeight.objects.filter(id=1).first() if record: return record.block_height + + + +def update_ledger_sequence(sequence, timestamp: datetime): + BlockHeight.objects.update_or_create( + id=2, + defaults={ + "block_height": sequence, + "block_timestamp": timestamp, + "updated_at": timezone.now(), + }, + ) + +def get_ledger_sequence() -> int: + record = BlockHeight.objects.filter(id=2).first() + if record: + return record.block_height + + +def update_approved_projects(event_data): + round_id, project_ids = event_data[0], event_data[1] + + with transaction.atomic(): + try: + round_obj = Round.objects.get(on_chain_id=round_id) + for ids in project_ids: + project = Project.objects.get(id=ids) + round_obj.approved_projects.add(project) + + except Exception as e: + logger.error(f"Error updating application for Round {round_id}: {e}") + +def update_application(event_data, txhash): + round_id, application_data, reviewer_id = event_data[0], event_data[1], event_data[2] + + with transaction.atomic(): + try: + round_obj = Round.objects.get(on_chain_id=round_id) + applicant = Account.objects.get(id=application_data['applicant_id']) + + reviewer = Account.objects.get(id=reviewer_id) + + status = PotApplicationStatus[application_data['status'][0].upper()] + submitted_at = datetime.fromtimestamp(application_data['submited_ms'] / 1000) + updated_at = datetime.fromtimestamp(application_data['updated_ms'] / 1000) + + defaults = { + "notes": application_data.get("review_notes"), + "status": application_data['status'][0], + "tx_hash": txhash, + } + + appl = PotApplication.objects.filter( + applicant_id=application_data["applicant_id"] + ).first() + + PotApplicationReview.objects.update_or_create( + application_id=appl.id, + reviewer=reviewer, + reviewed_at=updated_at, + defaults=defaults, + ) + + # Update the PotApplication object + PotApplication.objects.filter(applicant_id=application_data["applicant_id"]).update( + **{"status": application_data["status"], "updated_at": updated_at} + ) + + except Exception as e: + logger.error(f"Error updating application for Round {round_id}: {e}") + + + +def process_vote_event(event_data, tx_hash): + try: + with transaction.atomic(): + + round_id, vote_data = event_data[0], event_data[1] + + round_obj = Round.objects.get(on_chain_id=round_id) + voter, _ = Account.objects.get_or_create(id=vote_data['voter']) + voted_at = datetime.fromtimestamp(vote_data['voted_ms'] / 1000) + + # Create or update the Vote + vote, created = Vote.objects.update_or_create( + round=round_obj, + voter=voter, + defaults={ + 'tx_hash': tx_hash, + 'voted_at': voted_at + } + ) + + # Process vote pairs + for pick in vote_data['picks']: + pair_id = pick['pair_id'] + project_id = pick['project_id'] + + # Assuming project_id corresponds to PotApplication id + project_voted = Project.objects.get(id=project_id) + + VotePair.objects.update_or_create( + vote=vote, + pair_id=pair_id, + defaults={'project': project_voted} + ) + + logger.info(f"Processed vote for Round: {round_id}, Voter: {voter.id}") + return vote + except Exception as e: + logger.error(f"Error processing vote for Round: {str(e)}") + + +def process_project_event(event_data): + try: + # Extract project data + project_data = event_data + + # Create or get the owner Account + owner, _ = Account.objects.get_or_create(id=project_data['owner']) + + # Create or get the payout Account + payout_address, _ = Account.objects.get_or_create(id=project_data['payout_address']) + + # Create the Project + project, created = Project.objects.update_or_create( + on_chain_id=project_data['id'], + defaults={ + 'image_url': project_data['image_url'], + 'video_url': project_data['video_url'], + 'name': project_data['name'], + 'overview': project_data['overview'], + 'owner': owner, + 'payout_address': payout_address, + 'status': ProjectStatus("NEW").name, + 'submited_ms': project_data['submited_ms'], + 'updated_ms': project_data['updated_ms'], + } + ) + + for contact_data in project_data['contacts']: + contact, _ = ProjectContact.objects.get_or_create( + name=contact_data['name'], + value=contact_data['value'] + ) + project.contacts.add(contact) + + for contract_data in project_data['contracts']: + contract, _ = ProjectContract.objects.get_or_create( + name=contract_data['name'], + contract_address=contract_data['contract_address'] + ) + project.contracts.add(contract) + + # Create and associate ProjectRepositories + for repo_data in project_data['repositories']: + repo, _ = ProjectRepository.objects.get_or_create( + label=repo_data['label'], + url=repo_data['url'] + ) + project.repositories.add(repo) + + for funding_data in project_data['funding_histories']: + ProjectFundingHistory.objects.create( + source=funding_data['source'], + amount=funding_data['amount'], + denomination=funding_data['denomiation'], # Note: There's a typo in the event data + description=funding_data['description'], + timestamp=timezone.datetime.fromtimestamp(funding_data['funded_ms'] / 1000) + ) + + # Associate admins + for admin_address in project_data['admins']: + admin, _ = Account.objects.get_or_create(id=admin_address) + project.admins.add(admin) + + # Associate team members + for team_member_data in project_data['team_members']: + team_member, _ = Account.objects.get_or_create(id=team_member_data['value']) + project.team_members.add(team_member) + + if created: + logger.info(f"Created new Project: {project.id}") + else: + logger.info(f"Updated existing Project: {project.id}") + + except Exception as e: + logger.error(f"Error processing project event: {str(e)}") + + + +def create_or_update_round(event_data, contract_id, timestamp): + try: + # Create Round + round_id = event_data.get('id') + owner_address = event_data.get('owner') + owner, _ = Account.objects.get_or_create(id=owner_address) + factory_contract, _ = Account.objects.get_or_create(id=contract_id) + remaining_dist_address = event_data.get('remaining_dist_address') + remaining_dist_address_obj, _ = Account.objects.get_or_create(id=remaining_dist_address) + + round_obj, created = Round.objects.update_or_create( + on_chain_id=round_id, + defaults={ + 'owner': owner, + 'factory_contract': factory_contract, + 'name': event_data.get('name'), + 'description': event_data.get('description'), + 'expected_amount': event_data.get('expected_amount'), + 'application_start': datetime.fromtimestamp(event_data.get('application_start_ms') / 1000) if event_data.get('application_start_ms') else None, + 'application_end': datetime.fromtimestamp(event_data.get('application_end_ms') / 1000) if event_data.get('application_end_ms') else None, + 'voting_start': datetime.fromtimestamp(event_data.get('voting_start_ms') / 1000), + 'voting_end': datetime.fromtimestamp(event_data.get('voting_end_ms') / 1000), + 'use_whitelist': event_data.get('use_whitelist'), + 'use_vault': event_data.get('use_vault'), + 'num_picks_per_voter': event_data.get('num_picks_per_voter'), + 'max_participants': event_data.get('max_participants'), + 'allow_applications': event_data.get('allow_applications'), + 'allow_remaining_dist': event_data.get('allow_remaining_dist'), + 'compliance_end': datetime.fromtimestamp(event_data.get('compliance_end_ms') / 1000) if event_data.get('compliance_end_ms') else None, + 'compliance_period_ms': event_data.get('compliance_period_ms'), + 'compliance_req_desc': event_data.get('compliance_req_desc'), + 'cooldown_end': datetime.fromtimestamp(event_data.get('cooldown_end_ms') / 1000) if event_data.get('cooldown_end_ms') else None, + 'cooldown_period_ms': event_data.get('cooldown_period_ms'), + 'is_video_required': event_data.get('is_video_required'), + 'referrer_fee_basis_points': event_data.get('referrer_fee_basis_points'), + 'remaining_dist_address_id': remaining_dist_address, + 'remaining_dist_at_ms': datetime.fromtimestamp(event_data.get('remaining_dist_at_ms') / 1000) if event_data.get('remaining_dist_at_ms') else None, + 'remaining_dist_by_id': event_data.get('remaining_dist_by'), + 'remaining_dist_memo': event_data.get('remaining_dist_memo'), + 'round_complete': datetime.fromtimestamp(event_data.get('round_complete_ms') / 1000) if event_data.get('round_complete_ms') else None, + 'vault_total_deposits': event_data.get('vault_total_deposits'), + 'current_vault_balance': event_data.get('current_vault_balance'), + 'deployed_at': timestamp + } + ) + + # Create contacts for the round + for contact in event_data.get('contacts', []): + contact_obj, created = ProjectContact.objects.update_or_create( + name=contact['name'], + value=contact['value'] + ) + round_obj.contacts.add(contact_obj) + + logger.info(f"Created/Updated Round: {round_id}") + except Exception as e: + logger.error(f"Error processing rounds event: {str(e)}") + + +def process_application_to_round(event_data, tx_hash): + try: + # Process application to Round + round_id, application_data = event_data[0], event_data[1] + applicant_id = application_data.get('applicant_id') + status = PotApplicationStatus[application_data['status'][0].upper()] + submitted_at = datetime.fromtimestamp(application_data['submited_ms'] / 1000) + updated_at = ( + datetime.fromtimestamp(application_data['updated_ms'] / 1000) + if application_data['updated_ms'] else None + ) + + round_obj = Round.objects.get(on_chain_id=round_id) + applicant, _ = Account.objects.get_or_create(id=applicant_id) + + pot_application, created = PotApplication.objects.update_or_create( + round=round_obj, + applicant=applicant, + defaults={ + 'message': application_data['applicant_note'], + 'status': status, + 'submitted_at': submitted_at, + 'updated_at': updated_at, + 'tx_hash': tx_hash + } + ) + logger.info(f"Processed application for Round: {round_id}, Applicant: {applicant_id}") + except Exception as e: + logger.error(f"Error processing rounds applications event: {str(e)}") + + + +def create_round_application(event_data, tx_hash): + try: + + round_id, application_data = event_data[0], event_data[1] + applicant, _ = Account.objects.get_or_create(id=application_data["applicant_id"]) + round_obj = Round.objects.get(on_chain_id=round_id) + logger.info(f"Creating application for round: {round_id}") + + appl_defaults = { + "message": application_data["applicant_note"], + "submitted_at": datetime.fromtimestamp(application_data["submited_ms"] / 1000), + "status": application_data["status"][0], + "tx_hash": tx_hash, + } + + PotApplication.objects.update_or_create( + applicant=applicant, + round=round_obj, + project_id=application_data["project_id"], + defaults=appl_defaults, + ) + except Exception as e: + logger.error(f"Error creating applications to rounds: {str(e)}") + + +def process_rounds_deposit_event(event_data, tx_hash): + try: + + # Process deposit event + round_id, deposit_data = event_data + round_obj = Round.objects.get(on_chain_id=round_id) + amount = deposit_data["total_amount"] + depositor, _ = Account.objects.get_or_create(id=deposit_data["depositor_id"]) + + # Create or update a RoundDeposit object + deposit, created = RoundDeposit.objects.update_or_create( + round=round_obj, + on_chain_id=deposit_data["deposit_id"], + depositor=depositor, + defaults={ + 'amount': amount, + 'protocol_fee': deposit_data["protocol_fee"], + 'referrer_fee': deposit_data["referrer_fee"], + 'tx_hash': tx_hash, + 'deposit_at': datetime.fromtimestamp(deposit_data["deposited_at"] / 1000), + } + ) + + round_obj.vault_total_deposits = str(int(round_obj.vault_total_deposits or 0) + amount) + round_obj.current_vault_balance = str(int(round_obj.current_vault_balance or 0) + deposit_data["net_amount"]) + round_obj.save() + + logger.info(f"Processed deposit for Round: {round_id}, Depositor: {depositor.id}, Amount: {amount}") + except Exception as e: + logger.error(f"Error creating applications to rounds: {str(e)}") diff --git a/pots/migrations/0014_alter_potapplication_unique_together_and_more.py b/pots/migrations/0014_alter_potapplication_unique_together_and_more.py new file mode 100644 index 0000000..1ad3e5f --- /dev/null +++ b/pots/migrations/0014_alter_potapplication_unique_together_and_more.py @@ -0,0 +1,109 @@ +# Generated by Django 5.0.6 on 2024-09-18 23:05 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ("grantpicks", "0001_initial"), + ("pots", "0013_potpayoutchallenge_tx_hash"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="potapplication", + unique_together=set(), + ), + migrations.AddField( + model_name="potapplication", + name="project", + field=models.ForeignKey( + blank=True, + help_text="Project that applied.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="applications", + to="grantpicks.project", + ), + ), + migrations.AddField( + model_name="potapplication", + name="round", + field=models.ForeignKey( + blank=True, + help_text="Round applied to.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="applications", + to="grantpicks.round", + ), + ), + migrations.AlterField( + model_name="potapplication", + name="applicant", + field=models.ForeignKey( + help_text="Account that applied to the pot or round.", + on_delete=django.db.models.deletion.CASCADE, + related_name="pot_applications", + to="accounts.account", + ), + ), + migrations.AlterField( + model_name="potapplication", + name="pot", + field=models.ForeignKey( + blank=True, + help_text="Pot applied to.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="applications", + to="pots.pot", + ), + ), + migrations.AlterField( + model_name="potapplication", + name="status", + field=models.CharField( + choices=[ + ("Pending", "Pending"), + ("Approved", "Approved"), + ("Rejected", "Rejected"), + ("InReview", "InReview"), + ("Blacklisted", "Blacklisted"), + ], + help_text="Application status.", + max_length=32, + verbose_name="status", + ), + ), + migrations.AlterField( + model_name="potapplicationreview", + name="status", + field=models.CharField( + choices=[ + ("Pending", "Pending"), + ("Approved", "Approved"), + ("Rejected", "Rejected"), + ("InReview", "InReview"), + ("Blacklisted", "Blacklisted"), + ], + help_text="Application status for this review.", + max_length=32, + verbose_name="status", + ), + ), + migrations.AddConstraint( + model_name="potapplication", + constraint=models.CheckConstraint( + check=models.Q( + models.Q(("pot__isnull", False), ("round__isnull", True)), + models.Q(("pot__isnull", True), ("round__isnull", False)), + _connector="OR", + ), + name="pot_or_round", + ), + ), + ] diff --git a/pots/models.py b/pots/models.py index 4c69b86..9729d5e 100644 --- a/pots/models.py +++ b/pots/models.py @@ -7,10 +7,10 @@ from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError -from accounts.models import Account, Project, ProjectContact +from accounts.models import Account from base.logging import logger from base.utils import format_date -from tokens.models import Token, TokenHistoricalPrice +from tokens.models import Token class PotFactory(models.Model): @@ -413,7 +413,7 @@ class PotApplication(models.Model): db_index=True, ) round = models.ForeignKey( - "Round", + "grantpicks.Round", on_delete=models.CASCADE, related_name="applications", null=True, @@ -421,6 +421,14 @@ class PotApplication(models.Model): help_text=_("Round applied to."), db_index=True, ) + project = models.ForeignKey( + "grantpicks.Project", + on_delete=models.CASCADE, + related_name="applications", + null=True, + blank=True, + help_text=_("Project that applied."), + ) applicant = models.ForeignKey( Account, on_delete=models.CASCADE, @@ -725,232 +733,3 @@ class Meta: verbose_name_plural = "Payout Challenge Responses" unique_together = (("challenger", "pot", "created_at"),) - - -class Round(models.Model): - contract = models.OneToOneField( - Account, - primary_key=True, - related_name="round", - on_delete=models.CASCADE, - help_text=_("Round account ID."), - ) - deployed_at = models.DateTimeField( - _("deployed at"), - null=False, - help_text=_("Round deployment date."), - db_index=True, - ) - owner = models.ForeignKey( - Account, - on_delete=models.CASCADE, - related_name="owned_rounds", - null=False, - help_text=_("Round owner."), - ) - admins = models.ManyToManyField( - Account, - related_name="admin_rounds", - help_text=_("Round admins."), - ) - name = models.TextField( - _("name"), - null=False, - help_text=_("Round name."), - ) - description = models.TextField( - _("description"), - null=False, - help_text=_("Round description."), - ) - contacts = models.ManyToManyField( - ProjectContact, - related_name="round_contacts", - help_text=_("Round contacts."), - ) - expected_amount = models.CharField( - _("expected amount"), - null=False, - help_text=_("Expected amount."), - ) - - base_currency = models.CharField( - _("base currency"), - max_length=64, - null=True, - blank=True, - help_text=_("Base currency."), - ) - application_start = models.DateTimeField( - _("application start"), - null=False, - help_text=_("Round application start date."), - ) - application_end = models.DateTimeField( - _("application end"), - null=False, - help_text=_("Round application end date."), - ) - voting_start = models.DateTimeField( - _("voting start"), - null=False, - help_text=_("Round voting start date."), - ) - voting_end = models.DateTimeField( - _("voting end"), - null=False, - help_text=_("Round voting end date."), - ) - use_whitelist = models.BooleanField( - _("use whitelist"), - null=False, - help_text=_("Use whitelist."), - ) - use_vault = models.BooleanField( - _("use vault"), - null=False, - help_text=_("Use vault."), - ) - num_picks_per_voter = models.PositiveIntegerField( - _("num picks per voter"), - null=True, - blank=True, - help_text=_("Number of picks per voter."), - ) - max_participants = models.PositiveIntegerField( - _("max participants"), - null=True, - blank=True, - help_text=_("Max participants."), - ) - allow_applications = models.BooleanField( - _("allow applications"), - null=False, - help_text=_("Allow applications."), - ) - is_video_required = models.BooleanField( - _("is video required"), - null=False, - help_text=_("Is video required."), - ) - cooldown_end = models.DateTimeField( - _("cooldown end"), - null=True, - blank=True, - help_text=_("Round cooldown end date."), - ) - cooldown_period_ms = models.PositiveIntegerField( - _("cooldown period in ms"), - null=True, - blank=True, - help_text=_("Round cooldown period in ms."), - ) - compliance_req_desc = models.TextField( - _("compliance req desc"), - null=False, - help_text=_("Compliance req desc."), - ) - compliance_period_ms = models.PositiveIntegerField( - _("compliance period in ms"), - null=True, - blank=True, - help_text=_("Compliance period in ms."), - ) - compliance_end = models.DateTimeField( - _("compliance end"), - null=True, - blank=True, - help_text=_("Compliance end date."), - ) - allow_remaining_dist = models.BooleanField( - _("allow remaining dist"), - null=False, - help_text=_("Allow remaining dist."), - ) - remaining_dist_address = models.CharField( - _("remaining dist address"), - max_length=255, - null=False, - help_text=_("Remaining dist address."), - ) - referrer_fee_basis_points = models.PositiveIntegerField( - _("referrer fee basis points"), - null=True, - blank=True, - help_text=_("Referrer fee basis points."), - ) - - -class RoundDeposit(models.Model): - id = models.AutoField( - _("deposit id"), - primary_key=True, - help_text=_("Deposit id."), - ) - round = models.ForeignKey( - Round, - on_delete=models.CASCADE, - related_name="deposits", - null=False, - help_text=_("Round that this deposit is for."), - db_index=True, - ) - depositor = models.ForeignKey( - Account, - on_delete=models.CASCADE, - related_name="round_deposits", - null=False, - help_text=_("Deposit maker."), - db_index=True, - ) - amount = models.CharField( - _("amount"), - null=False, - help_text=_("Deposit amount."), - ) - amount_in_usd = models.DecimalField( - _("amount deposited in USD"), - max_digits=20, - decimal_places=2, - null=True, - blank=True, - help_text=_("Deposit amount in USD."), - ) - token = models.ForeignKey( - Token, - on_delete=models.CASCADE, - related_name="round_deposits", - null=True, - help_text=_("Deposit token."), - ) - deposit_at = models.DateTimeField( - _("deposit at"), - null=True, - blank=True, - help_text=_("Deposit date."), - db_index=True, - ) - tx_hash = models.CharField( - _("transaction hash"), - null=True, - blank=True, - help_text=_("Transaction hash."), - ) - -class Vote(models.Model): - round = models.ForeignKey(Round, on_delete=models.CASCADE, related_name='votes') - voter = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='votes') - voted_at = models.DateTimeField() - - class Meta: - unique_together = ('round', 'voter', 'voted_at') - - - -class VotePair(models.Model): - vote = models.ForeignKey(Vote, on_delete=models.CASCADE, related_name='pairs') - pair_id = models.PositiveIntegerField() - project = models.ForeignKey('PotApplication', on_delete=models.CASCADE, related_name='vote_pairs') - - class Meta: - unique_together = ('vote', 'pair_id') \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index e7bcb60..f1a82fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ django-cors-headers = "^4.3.1" drf-spectacular = "^0.27.2" django-extensions = "^3.2.3" psutil = "^6.0.0" +stellar-sdk = "^11.0.0" [tool.poetry.group.dev.dependencies] black = "^24.3.0" From a61d17525230878405adbf7f5cdfd2973b41c042 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 19 Sep 2024 00:36:34 +0100 Subject: [PATCH 279/454] fix: comment hardcoded start block --- indexer_app/tasks.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 54e6cc0..9a4b477 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -106,8 +106,8 @@ def listen_to_near_events(): try: # Update below with desired network & block height - # start_block = get_block_height() - start_block = 112682360 + start_block = get_block_height() + # start_block = 112682360 logger.info(f"what's the start block, pray tell? {start_block-1}") loop.run_until_complete(indexer(start_block - 1, None)) except WorkerLostError: @@ -157,7 +157,6 @@ def spot_index_near_events(start_block): @shared_task def fetch_usd_prices(): - print("In here now........") donations = Donation.objects.filter( Q(total_amount_usd__isnull=True) | Q(net_amount_usd__isnull=True) From d1401acd1683d37282bf5a6810e6d44ac66a54a7 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 19 Sep 2024 05:46:00 +0100 Subject: [PATCH 280/454] feat: add api endpoint for rounds, projects and votes --- api/urls.py | 8 + grantpicks/api.py | 223 ++++++++++++++++++++++++++++ grantpicks/serializers.py | 301 ++++++++++++++++++++++++++++++++++++++ indexer_app/tasks.py | 13 +- 4 files changed, 539 insertions(+), 6 deletions(-) diff --git a/api/urls.py b/api/urls.py index fd0c4d8..2e4acc8 100644 --- a/api/urls.py +++ b/api/urls.py @@ -18,6 +18,7 @@ ) from base.api import StatsAPI from donations.api import DonationContractConfigAPI +from grantpicks.api import ProjectListAPI, ProjectRoundVotesAPI, RoundDetailAPI, RoundsListAPI from lists.api import ( ListDetailAPI, ListRandomRegistrationAPI, @@ -129,4 +130,11 @@ ), # stats path("v1/stats", StatsAPI.as_view(), name="stats_api"), + + # grantpicks + path("v1/rounds", RoundsListAPI.as_view(), name="rounds_api"), + path("v1/round//", RoundDetailAPI.as_view(), name="rounds_api_by_id"), + path("v1/round///votes", ProjectRoundVotesAPI.as_view(), name="project_round_votes_api_by_id"), + path("v1/projects", ProjectListAPI.as_view(), name="projects_api"), + ] diff --git a/grantpicks/api.py b/grantpicks/api.py index e69de29..f1334d7 100644 --- a/grantpicks/api.py +++ b/grantpicks/api.py @@ -0,0 +1,223 @@ +from django.db.models import Q +from django.utils import timezone +from django.utils.decorators import method_decorator +from django.views.decorators.cache import cache_page +from drf_spectacular.utils import ( + OpenApiExample, + OpenApiParameter, + OpenApiResponse, + extend_schema, +) +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.views import APIView + +from accounts.models import Account +from accounts.serializers import ( + PAGINATED_ACCOUNT_EXAMPLE, + AccountSerializer, + PaginatedAccountsResponseSerializer, +) +from api.pagination import pagination_parameters +from api.pagination import CustomSizePageNumberPagination +from donations.models import Donation +from donations.serializers import ( + PAGINATED_DONATION_EXAMPLE, + DonationSerializer, + PaginatedDonationsResponseSerializer, +) + +from .models import Project, Round +from .serializers import ( + PAGINATED_PROJECT_EXAMPLE, + PAGINATED_ROUND_APPLICATION_EXAMPLE, + PAGINATED_ROUND_EXAMPLE, + PAGINATED_VOTE_PAIR_EXAMPLE, + SIMPLE_ROUND_EXAMPLE, + PaginatedProjectsResponseSerializer, + PaginatedRoundApplicationsResponseSerializer, + PaginatedRoundsResponseSerializer, + PaginatedVotePairResponseSerializer, + ProjectSerializer, + RoundApplicationSerializer, + RoundSerializer, + VotePairSerializer, + VoteSerializer, +) + + +class RoundsListAPI(APIView, CustomSizePageNumberPagination): + + @extend_schema( + parameters=[ + *pagination_parameters, + ], + responses={ + 200: OpenApiResponse( + response=PaginatedRoundsResponseSerializer, + description="Returns a paginated list of rounds", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for rounds", + value=PAGINATED_ROUND_EXAMPLE, + response_only=True, + ), + ], + ), + }, + ) + @method_decorator(cache_page(60 * 1)) + def get(self, request: Request, *args, **kwargs): + rounds = Round.objects.all() + results = self.paginate_queryset(rounds, request, view=self) + serializer = RoundSerializer(results, many=True) + return self.get_paginated_response(serializer.data) + +class RoundDetailAPI(APIView): + + @extend_schema( + parameters=[ + OpenApiParameter("round_id", str, OpenApiParameter.PATH), + ], + responses={ + 200: OpenApiResponse( + response=RoundSerializer, + description="Returns rounds details", + examples=[ + OpenApiExample( + "example-1", + summary="Simple round example", + description="Example response for round detail", + value=SIMPLE_ROUND_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Round not found"), + }, + ) + @method_decorator(cache_page(60 * 1)) + def get(self, request: Request, *args, **kwargs): + round_id = kwargs.get("round_id") + try: + round = Round.objects.get(id=round_id) + except Round.DoesNotExist: + return Response({"message": f"Round with ID {round_id} not found."}, status=404) + serializer = RoundSerializer(round) + return Response(serializer.data) + + +class RoundApplicationsAPI(APIView, CustomSizePageNumberPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("round_id", str, OpenApiParameter.PATH), + *pagination_parameters, + ], + responses={ + 200: OpenApiResponse( + response=PaginatedRoundApplicationsResponseSerializer, + description="Returns applications for the round", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for round applications", + value=PAGINATED_ROUND_APPLICATION_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Round not found"), + }, + ) + @method_decorator(cache_page(60 * 1)) + def get(self, request: Request, *args, **kwargs): + round_id = kwargs.get("round_id") + try: + round = Round.objects.get(id=round_id) + except Round.DoesNotExist: + return Response({"message": f"Round with ID {round_id} not found."}, status=404) + + applications = round.applications.all() + results = self.paginate_queryset(applications, request, view=self) + serializer = RoundApplicationSerializer(results, many=True) + return self.get_paginated_response(serializer.data) + + +class ProjectRoundVotesAPI(APIView, CustomSizePageNumberPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("round_id", str, OpenApiParameter.PATH), + OpenApiParameter("project_id", str, OpenApiParameter.PATH), # Added project_id parameter + *pagination_parameters, + ], + responses={ + 200: OpenApiResponse( + response=PaginatedVotePairResponseSerializer, # Update to use the appropriate serializer + description="Returns votes for a project in the round", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for votes", + value=PAGINATED_VOTE_PAIR_EXAMPLE, # Update to use the appropriate example + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Round or project not found"), + }, + ) + @method_decorator(cache_page(60 * 1)) + def get(self, request: Request, *args, **kwargs): + round_id = kwargs.get("round_id") + project_id = kwargs.get("project_id") # Get project_id from kwargs + try: + round_obj = Round.objects.get(id=round_id) + # project = Project.objects.get(id=project_id) # comment out now, might use later if decide to return vote pairs instead + except Round.DoesNotExist: + return Response({"message": f"Round with ID {round_id} not found."}, status=404) + + # Retrieve votes for the specified project in the round + + votes = round_obj.votes.filter(pairs__project_id=project_id) # Adjust the filter as needed + # vote_pairs = project.vote_pairs.all() + results = self.paginate_queryset(votes, request, view=self) + serializer = VoteSerializer(results, many=True) # Use the appropriate serializer for votes + return self.get_paginated_response(serializer.data) + + + + +class ProjectListAPI(APIView, CustomSizePageNumberPagination): + + @extend_schema( + parameters=[ + *pagination_parameters, + ], + responses={ + 200: OpenApiResponse( + response=PaginatedProjectsResponseSerializer, + description="Returns a paginated list of projects", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for projects", + value=PAGINATED_PROJECT_EXAMPLE, + response_only=True, + ), + ], + ), + }, + ) + @method_decorator(cache_page(60 * 5)) + def get(self, request: Request, *args, **kwargs): + projects = Project.objects.all() + results = self.paginate_queryset(projects, request, view=self) + serializer = ProjectSerializer(results, many=True) + return self.get_paginated_response(serializer.data) \ No newline at end of file diff --git a/grantpicks/serializers.py b/grantpicks/serializers.py index e69de29..2353aac 100644 --- a/grantpicks/serializers.py +++ b/grantpicks/serializers.py @@ -0,0 +1,301 @@ +from rest_framework import serializers +from rest_framework.serializers import ModelSerializer + +from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer +from base.serializers import TwoDecimalPlacesField + +from .models import Project, ProjectContact, ProjectContract, ProjectRepository, Round, Vote, VotePair +from pots.models import PotApplication + + + + +class ProjectContactSerializer(serializers.ModelSerializer): + class Meta: + model = ProjectContact + fields = ['id', 'name', 'value'] + +class ProjectContractSerializer(serializers.ModelSerializer): + class Meta: + model = ProjectContract + fields = ['id', 'name', 'contract_address'] + +class ProjectRepositorySerializer(serializers.ModelSerializer): + class Meta: + model = ProjectRepository + fields = ['id', 'label', 'url'] + +class ProjectSerializer(serializers.ModelSerializer): + contacts = ProjectContactSerializer(many=True, required=False) + contracts = ProjectContractSerializer(many=True, required=False) + repositories = ProjectRepositorySerializer(many=True, required=False) + owner = AccountSerializer() + payout_address = AccountSerializer() + + class Meta: + model = Project + fields = [ + 'id', + 'on_chain_id', + 'image_url', + 'video_url', + 'name', + 'overview', + 'owner', + 'payout_address', + 'contacts', + 'contracts', + 'team_members', + 'repositories', + 'status', + 'submited_ms', + 'updated_ms', + 'admins', + ] + +class RoundSerializer(ModelSerializer): + current_vault_balance_usd = TwoDecimalPlacesField(max_digits=20, decimal_places=2) + vault_total_deposits_usd = TwoDecimalPlacesField(max_digits=20, decimal_places=2) + + class Meta: + model = Round + fields = [ + "id", # Include the primary key + "on_chain_id", + "factory_contract", + "deployed_at", + "owner", + "admins", + "name", + "description", + "contacts", + "expected_amount", + "base_currency", + "application_start", + "application_end", + "voting_start", + "voting_end", + "use_whitelist", + "use_vault", + "num_picks_per_voter", + "max_participants", + "approved_projects", + "allow_applications", + "is_video_required", + "cooldown_end", + "cooldown_period_ms", + "compliance_req_desc", + "compliance_period_ms", + "compliance_end", + "allow_remaining_dist", + "remaining_dist_address", + "remaining_dist_memo", + "remaining_dist_at_ms", + "current_vault_balance", + "current_vault_balance_usd", + "remaining_dist_by", + "referrer_fee_basis_points", + "vault_total_deposits", + "vault_total_deposits_usd", + "round_complete", + ] + owner = AccountSerializer() + remaining_dist_address = AccountSerializer() + admins = AccountSerializer(many=True) + remaining_dist_by = AccountSerializer() + + +class RoundApplicationSerializer(ModelSerializer): + + class Meta: + model = PotApplication + fields = [ + "id", + "round", + "applicant", + "project", + "message", + "status", + "submitted_at", + "updated_at", + "tx_hash", + ] + + round = RoundSerializer() + applicant = AccountSerializer() + project = ProjectSerializer() + + +SIMPLE_PROJECT_EXAMPLE = { + "id": 1, + "on_chain_id": 123, + "image_url": "https://example.com/image.png", + "video_url": "https://example.com/video.mp4", + "name": "My Project", + "overview": "This project aims to do something impactful.", + "owner": "GD4I4FXMIKKKVSGVCGNILRFFHDQHITMDW545SCLGEOKGBN6W44AV6367", + "payout_address": "GD4I4FXMIKKKVSGVCGNILRFFHDQHITMDW545SCLGEOKGBN6W44AV6367", + "contacts": [], + "contracts": [], + "team_members": [], + "repositories": [], + "status": "NEW", + "submited_ms": 1726657637439, + "updated_ms": None, + "admins": [], +} + +SIMPLE_ROUND_EXAMPLE = { + "id": 1, + "name": "InteractGrant TO Apply V1", + "owner": "GD4I4FXMIKKKVSGVCGNILRFFHDQHITMDW545SCLGEOKGBN6W44AV6367", + "contacts": [], + "use_vault": True, + "description": "This is a test round", + "use_whitelist": False, + "voting_start": "2024-06-25T00:00:00Z", + "voting_end": "2024-06-30T00:00:00Z", + "cooldown_end": None, + "expected_amount": 100000000000, + "max_participants": 10, + "compliance_end": None, + "is_video_required": False, + "remaining_dist_by": "GD4I4FXMIKKKVSGVCGNILRFFHDQHITMDW545SCLGEOKGBN6W44AV6367", + "round_complete": None, + "allow_applications": True, + "application_start": "2024-06-20T00:00:00Z", + "application_end": "2024-06-25T00:00:00Z", + "cooldown_period_ms": None, + "compliance_req_desc": "This is a compliance requirement", + "num_picks_per_voter": 2, + "remaining_dist_memo": "", + "allow_remaining_dist": False, + "remaining_dist_address": "GD4I4FXMIKKKVSGVCGNILRFFHDQHITMDW545SCLGEOKGBN6W44AV6367", + "vault_total_deposits": 0, + "current_vault_balance": 0, + "referrer_fee_basis_points": 0, +} + +PAGINATED_ROUND_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_ROUND_EXAMPLE], +} + +PAGINATED_PROJECT_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_PROJECT_EXAMPLE], +} + + + +class PaginatedRoundsResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = RoundSerializer(many=True) + + +class PaginatedProjectsResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = ProjectSerializer(many=True) + + + + +SIMPLE_ROUND_APPLICATION_EXAMPLE = { + "id": 2, + "message": "Hi, I'm a great project and I'd like to apply for this pot.", + "status": "Pending", + "submitted_at": "2024-06-05T18:06:45.519Z", + "updated_at": "2024-06-05T18:06:45.519Z", + "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", + "round": SIMPLE_ROUND_EXAMPLE, + "applicant": SIMPLE_ACCOUNT_EXAMPLE, +} + +PAGINATED_ROUND_APPLICATION_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_ROUND_APPLICATION_EXAMPLE], +} + + +class PaginatedRoundApplicationsResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = RoundApplicationSerializer(many=True) + + + +class VoteSerializer(serializers.ModelSerializer): + round = serializers.PrimaryKeyRelatedField(queryset=Round.objects.all()) + voter = AccountSerializer() + + class Meta: + model = Vote + fields = [ + 'id', + 'round', + 'voter', + 'tx_hash', + 'voted_at', + ] + + + +class PaginatedVotesResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = VoteSerializer(many=True) + + + +class VotePairSerializer(serializers.ModelSerializer): + vote = VoteSerializer() + project = ProjectSerializer() + + class Meta: + model = VotePair + fields = [ + 'vote', + 'pair_id', + 'project', + ] + +class PaginatedVotePairResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = VotePairSerializer(many=True) + + +SIMPLE_VOTE_EXAMPLE = { + "id": 1, + "round": 1, + "voter": SIMPLE_ACCOUNT_EXAMPLE, + "tx_hash": "0x123456789abcdef", + "voted_at": "2024-06-25T00:00:00Z", +} + + +SIMPLE_VOTE_PAIR_EXAMPLE = { + "vote": SIMPLE_VOTE_EXAMPLE, + "pair_id": 1, + "project": SIMPLE_PROJECT_EXAMPLE +} + +PAGINATED_VOTE_PAIR_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_VOTE_PAIR_EXAMPLE], +} \ No newline at end of file diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 9a4b477..94143c6 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -377,12 +377,13 @@ def stellar_event_indexer(): data=event_value )) - StellarEvent.objects.bulk_create( - objs=stellar_events, - ignore_conflicts=True - ) - update_ledger_sequence(events.latest_ledger, event.ledger_close_at) - jobs_logger.info(f"Ingested {len(stellar_events)} Stellar events from ledger {start_sequence} to {events.latest_ledger}...") + if len(stellar_events) > 1: + StellarEvent.objects.bulk_create( + objs=stellar_events, + ignore_conflicts=True + ) + update_ledger_sequence(events.latest_ledger, event.ledger_close_at) + jobs_logger.info(f"Ingested {len(stellar_events)} Stellar events from ledger {start_sequence} to {events.latest_ledger}...") except Exception as e: jobs_logger.error(f"Error processing ledger {start_sequence}: {e}") From 8b14697e5521a08e5e724d829bd69ee313e2f938 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 19 Sep 2024 09:07:09 +0100 Subject: [PATCH 281/454] fix: stellar event ingesting --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 94143c6..ac000f8 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -377,7 +377,7 @@ def stellar_event_indexer(): data=event_value )) - if len(stellar_events) > 1: + if len(stellar_events) > 0: StellarEvent.objects.bulk_create( objs=stellar_events, ignore_conflicts=True From a09cda3bce07bd6c1f4058122072e19bf440d669 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 20 Sep 2024 11:53:56 +0100 Subject: [PATCH 282/454] add filtering and sorting to rounds list --- api/urls.py | 7 ++++++- grantpicks/api.py | 51 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/api/urls.py b/api/urls.py index 2e4acc8..50b32d5 100644 --- a/api/urls.py +++ b/api/urls.py @@ -18,7 +18,7 @@ ) from base.api import StatsAPI from donations.api import DonationContractConfigAPI -from grantpicks.api import ProjectListAPI, ProjectRoundVotesAPI, RoundDetailAPI, RoundsListAPI +from grantpicks.api import ProjectListAPI, ProjectRoundVotesAPI, RoundApplicationsAPI, RoundDetailAPI, RoundsListAPI from lists.api import ( ListDetailAPI, ListRandomRegistrationAPI, @@ -136,5 +136,10 @@ path("v1/round//", RoundDetailAPI.as_view(), name="rounds_api_by_id"), path("v1/round///votes", ProjectRoundVotesAPI.as_view(), name="project_round_votes_api_by_id"), path("v1/projects", ProjectListAPI.as_view(), name="projects_api"), + path( + "v1/rounds//applications", + RoundApplicationsAPI.as_view(), + name="rounds_applications_api", + ), ] diff --git a/grantpicks/api.py b/grantpicks/api.py index f1334d7..40923e3 100644 --- a/grantpicks/api.py +++ b/grantpicks/api.py @@ -27,7 +27,7 @@ PaginatedDonationsResponseSerializer, ) -from .models import Project, Round +from .models import Project, ProjectStatus, Round from .serializers import ( PAGINATED_PROJECT_EXAMPLE, PAGINATED_ROUND_APPLICATION_EXAMPLE, @@ -50,6 +50,12 @@ class RoundsListAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ + OpenApiParameter( + name="sort", + type=str, + location=OpenApiParameter.QUERY, + description="Sort by field, e.g., deployed_at, vault_total_deposits", + ), *pagination_parameters, ], responses={ @@ -66,11 +72,21 @@ class RoundsListAPI(APIView, CustomSizePageNumberPagination): ), ], ), + 500: OpenApiResponse(description="Internal server error"), }, ) @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): rounds = Round.objects.all() + sort = request.query_params.get("sort", None) + if sort == "deployed_at": + rounds = rounds.order_by( + "-deployed_at" + ) + if sort == "vault_total_deposits": + rounds = rounds.order_by( + "-vault_total_deposits" + ) results = self.paginate_queryset(rounds, request, view=self) serializer = RoundSerializer(results, many=True) return self.get_paginated_response(serializer.data) @@ -88,7 +104,7 @@ class RoundDetailAPI(APIView): examples=[ OpenApiExample( "example-1", - summary="Simple round example", + summary="Simple Round example", description="Example response for round detail", value=SIMPLE_ROUND_EXAMPLE, response_only=True, @@ -197,6 +213,18 @@ class ProjectListAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ + OpenApiParameter( + "status", + str, + OpenApiParameter.QUERY, + description="Filter projects by status", + ), + OpenApiParameter( + "owner", + str, + OpenApiParameter.QUERY, + description="Filter projects by owner id", + ), *pagination_parameters, ], responses={ @@ -213,11 +241,30 @@ class ProjectListAPI(APIView, CustomSizePageNumberPagination): ), ], ), + 400: OpenApiResponse(description="Invalid status value"), + 404: OpenApiResponse(description="owner not found"), + 500: OpenApiResponse(description="Internal server error"), }, ) @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): projects = Project.objects.all() + status_param = request.query_params.get("status") + if status_param: + if status_param not in ProjectStatus.values: + return Response( + {"message": f"Invalid status value: {status_param}"}, status=400 + ) + projects = projects.filter(status=status_param) + project_owner = request.query_params.get("owner") + if project_owner: + try: + account = Account.objects.get(id=project_owner) + except Account.DoesNotExist: + return Response( + {"message": f"Account with ID {project_owner} not found."}, status=404 + ) + projects = projects.filter(owner=account) results = self.paginate_queryset(projects, request, view=self) serializer = ProjectSerializer(results, many=True) return self.get_paginated_response(serializer.data) \ No newline at end of file From 6418d572d3af68920c60c91f22ec6c9eea716a4b Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sat, 21 Sep 2024 21:09:42 +0100 Subject: [PATCH 283/454] update ledger always --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index ac000f8..9d8dfda 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -382,8 +382,8 @@ def stellar_event_indexer(): objs=stellar_events, ignore_conflicts=True ) - update_ledger_sequence(events.latest_ledger, event.ledger_close_at) jobs_logger.info(f"Ingested {len(stellar_events)} Stellar events from ledger {start_sequence} to {events.latest_ledger}...") + update_ledger_sequence(events.latest_ledger, event.ledger_close_at) except Exception as e: jobs_logger.error(f"Error processing ledger {start_sequence}: {e}") From 44ff64eb51c2b01b303b7857c0853648f635a322 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sat, 21 Sep 2024 21:59:29 +0100 Subject: [PATCH 284/454] use ledger timestamp --- indexer_app/tasks.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 9d8dfda..fcf28d7 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -361,6 +361,7 @@ def stellar_event_indexer(): ] ) stellar_events = [] + ledger_timestamp = datetime.now() for event in events.events: event_name = stellar_sdk.scval.to_native(event.topic[0]) event_value = event.value @@ -382,8 +383,9 @@ def stellar_event_indexer(): objs=stellar_events, ignore_conflicts=True ) + ledger_timestamp = event.ledger_close_at jobs_logger.info(f"Ingested {len(stellar_events)} Stellar events from ledger {start_sequence} to {events.latest_ledger}...") - update_ledger_sequence(events.latest_ledger, event.ledger_close_at) + update_ledger_sequence(events.latest_ledger, ledger_timestamp) except Exception as e: jobs_logger.error(f"Error processing ledger {start_sequence}: {e}") From 0dc2907b1f7527b533fcc6644f1f82516e4909d8 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 26 Sep 2024 03:54:14 +0100 Subject: [PATCH 285/454] list improvement and stellar token price --- grantpicks/models.py | 26 ++++ indexer_app/handler.py | 16 +- indexer_app/tasks.py | 44 +++--- indexer_app/utils.py | 144 +++++++++++++++--- lists/serializers.py | 10 +- .../0015_alter_potpayout_options_and_more.py | 133 ++++++++++++++++ pots/models.py | 87 ++++++++++- tokens/models.py | 4 +- 8 files changed, 407 insertions(+), 57 deletions(-) create mode 100644 pots/migrations/0015_alter_potpayout_options_and_more.py diff --git a/grantpicks/models.py b/grantpicks/models.py index 5223448..26f717b 100644 --- a/grantpicks/models.py +++ b/grantpicks/models.py @@ -2,6 +2,10 @@ from django.utils.translation import gettext_lazy as _ +from datetime import datetime + +from base.logging import logger + from accounts.models import Account from pots.models import PotApplication from tokens.models import Token @@ -313,6 +317,28 @@ class Round(models.Model): ) + + + def update_vault_usd_equivalent(self): + # first, see if there is a TokenHistoricalPrice within 1 day (or HISTORICAL_PRICE_QUERY_HOURS) of self.paid_at + try: + token = Token.objects.get(account_id="stellar") + price_usd = token.fetch_usd_prices_common(datetime.now()) + if not price_usd: + logger.info( + f"No USD price found for token {token.symbol} at {datetime.now()}" + ) + return + self.vault_total_deposits_usd = token.format_price(self.vault_total_deposits) * price_usd + self.current_vault_balance_usd = token.format_price(self.current_vault_balance) * price_usd + self.save() + logger.info( + f"Saved USD prices for round vault for round id: {self.id}" + ) + except Exception as e: + logger.error(f"Failed to calculate and stellar vault USD prices: {e}") + + class RoundDeposit(models.Model): id = models.AutoField( _("deposit id"), diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 45a6a43..32b7066 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -20,12 +20,14 @@ handle_add_nadabot_admin, # handle_batch_donations, handle_add_stamp, handle_default_list_status_change, + handle_delete_list, handle_list_admin_removal, handle_list_registration_update, handle_list_upvote, handle_new_donation, handle_new_group, handle_new_list, + handle_new_list_and_reg, handle_new_list_registration, handle_new_nadabot_registry, handle_new_pot, @@ -133,6 +135,10 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess await handle_registry_unblacklist_action( parsed_log.get("data")[0], receiver_id, now_datetime ) + if event_name == "delete_list": + await handle_delete_list( + parsed_log.get("data")[0] + ) except json.JSONDecodeError: logger.warning( f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" @@ -411,7 +417,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess logger.info(f"creating list... {args_dict}, {action}") if receiver_id != LISTS_CONTRACT: break - await handle_new_list(signer_id, receiver_id, status_obj) + await handle_new_list(signer_id, receiver_id, status_obj, None) break case "upvote": @@ -419,7 +425,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess if receiver_id != LISTS_CONTRACT: break await handle_list_upvote( - args_dict, receiver_id, signer_id, receipt.receipt_id + args_dict, receiver_id, signer_id, receipt.receipt_id, now_datetime ) break case "owner_add_admins": @@ -444,6 +450,12 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess logger.info(f"updating configs.. {args_dict}") await handle_set_factory_configs(args_dict, receiver_id) break + case "create_list_with_registrations": + logger.info(f"creating list... {args_dict}, {action}") + if receiver_id != LISTS_CONTRACT: + break + await handle_new_list_and_reg(signer_id, receiver_id, status_obj, receipt) + break # TODO: handle remove upvote except Exception as e: diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index fcf28d7..6671590 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -25,7 +25,7 @@ from pots.models import Pot, PotApplication, PotApplicationStatus, PotPayout from .logging import logger -from .utils import create_or_update_round, create_round_application, get_block_height, get_ledger_sequence, process_application_to_round, process_project_event, process_rounds_deposit_event, process_vote_event, save_block_height, update_application, update_approved_projects, update_ledger_sequence +from .utils import create_or_update_round, create_round_application, create_round_payout, get_block_height, get_ledger_sequence, process_application_to_round, process_project_event, process_rounds_deposit_event, process_vote_event, save_block_height, update_application, update_approved_projects, update_ledger_sequence, update_round_payout CURRENT_BLOCK_HEIGHT_KEY = "current_block_height" @@ -403,43 +403,41 @@ def process_stellar_events(): event_name = event.event_type if event_name == 'c_project': - process_project_event(event_data) - event.processed = True + event.processed = process_project_event(event_data) elif event_name == 'c_round' or event_name == 'u_round': - create_or_update_round(event_data, event.contract_id, event.ingested_at) + # Mark event as processed - event.processed = True + event.processed = create_or_update_round(event_data, event.contract_id, event.ingested_at) elif event_name == 'apply_to_round': - process_application_to_round(event_data, event.transaction_hash) + # Mark event as processed - event.processed = True + event.processed = process_application_to_round(event_data, event.transaction_hash) elif event_name == 'c_app': - create_round_application(event_data, event.transaction_hash) - event.processed = True + + event.processed = create_round_application(event_data, event.transaction_hash) - elif event_name == 'u_app': # application review and aproval - jobs_logger.info(f"eventulating data for event, {event_data}") - update_application(event_data, event.transaction_hash) - event.processed = True + elif event_name == 'u_app': # application review and aproval + event.processed = update_application(event_data, event.transaction_hash) - elif event_name == 'u_ap': - jobs_logger.info(f"eventulating data for approved project event, {event_data}") - update_approved_projects(event_data) - event.processed = True + elif event_name == 'u_ap': + event.processed = update_approved_projects(event_data) elif event_name == 'c_depo': - process_rounds_deposit_event(event_data, event.transaction_hash) - # Mark event as processed - event.processed = True + + event.processed = process_rounds_deposit_event(event_data, event.transaction_hash) elif event_name == 'c_vote': - process_vote_event(event_data, event.transaction_hash) - # Mark event as processed - event.processed = True + + event.processed = process_vote_event(event_data, event.transaction_hash) + elif event_name == "c_pay": + event.processed = create_round_payout(event_data, event.transaction_hash) + elif event_name == "u_pay": + + event.processed = update_round_payout(event_data, event.transaction_hash) event.save() except Exception as e: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 159a733..b732426 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -332,17 +332,30 @@ async def handle_new_pot_factory(data: dict, receiver_id: str, created_at: datet logger.error(f"Failed to handle new pot Factory, Error: {e}") +async def handle_new_list_and_reg(signer_id: str, receiver_id: str, status_obj: ExecutionOutcome, receipt: Receipt): + create_data, reg_data = json.loads( + base64.b64decode(status_obj.status.get("SuccessValue")).decode( + "utf-8" + ) # TODO: RECEIVE AS A FUNCTION ARGUMENT + ) + await handle_new_list(signer_id, receiver_id, None, create_data) + if reg_data: + await handle_new_list_registration(reg_data, receiver_id, signer_id, receipt, None) + pass + + async def handle_new_list( - signer_id: str, receiver_id: str, status_obj: ExecutionOutcome + signer_id: str, receiver_id: str, status_obj: ExecutionOutcome | None, data: dict | None ): # receipt = block.receipts().filter(receiptId=receiptId)[0] try: - data = json.loads( - base64.b64decode(status_obj.status.get("SuccessValue")).decode( - "utf-8" - ) # TODO: RECEIVE AS A FUNCTION ARGUMENT - ) + if not data: + data = json.loads( + base64.b64decode(status_obj.status.get("SuccessValue")).decode( + "utf-8" + ) # TODO: RECEIVE AS A FUNCTION ARGUMENT + ) logger.info("upserting involveed accts...") @@ -376,12 +389,21 @@ async def handle_new_list( logger.error(f"Failed to handle new list, Error: {e}") +async def handle_delete_list( + data: dict +): + try: + logger.info(f"deleting list..... {data}") + lst = await List.objects.filter(on_chain_id=data["list_id"]).adelete() + except Exception as e: + logger.error(f"Failed to delete, Error: {e}") + async def handle_new_list_registration( data: dict, receiver_id: str, signer_id: str, receipt: Receipt, - status_obj: ExecutionOutcome, + status_obj: ExecutionOutcome | None, ): logger.info(f"new Project data: {data}, {receiver_id}") @@ -389,11 +411,14 @@ async def handle_new_list_registration( if receipt is None: return - reg_data = json.loads( - base64.b64decode(status_obj.status["SuccessValue"]).decode( - "utf-8" - ) # TODO: RECEIVE AS A FUNCTION ARGUMENT - ) + if status_obj: + reg_data = json.loads( + base64.b64decode(status_obj.status["SuccessValue"]).decode( + "utf-8" + ) # TODO: RECEIVE AS A FUNCTION ARGUMENT + ) + else: + reg_data = data # Prepare data for insertion project_list = [] insert_data = [] @@ -560,9 +585,7 @@ async def handle_pot_application_status_change( ) # Retrieve the PotApplication object - appl = await PotApplication.objects.filter( - applicant_id=data["project_id"] - ).afirst() + appl = await PotApplication.objects.select_related('round', 'pot', 'project', 'applicant').filter(applicant_id=data["project_id"], pot_id=receiver_id).afirst() if not appl: logger.error( @@ -588,7 +611,7 @@ async def handle_pot_application_status_change( ) # Update the PotApplication object - await PotApplication.objects.filter(applicant_id=data["project_id"]).aupdate( + await PotApplication.objects.select_related('round', 'pot', 'project', 'applicant').filter(applicant_id=data["project_id"], pot_id=receiver_id).aupdate( **{"status": update_data["status"], "updated_at": updated_at} ) @@ -631,7 +654,7 @@ async def handle_default_list_status_change( async def handle_list_upvote( - data: dict, receiver_id: str, signer_id: str, receiptId: str + data: dict, receiver_id: str, signer_id: str, receiptId: str, created_at: datetime ): try: @@ -641,11 +664,15 @@ async def handle_list_upvote( id=signer_id, ) - created_at = datetime.now() + + up_default = { + "created_at": created_at + } await ListUpvote.objects.aupdate_or_create( list_id=data.get("list_id") or receiver_id, account_id=signer_id, + defaults=up_default ) defaults = { @@ -791,7 +818,7 @@ async def handle_list_admin_removal(data, receiver_id, signer_id, receiptId): list_obj = await List.objects.aget(id=data["list_id"]) for acct in data["admins"]: - list_obj.admins.remove({"admins_id": acct}) # maybe check + await list_obj.admins.aremove({"admins_id": acct}) # maybe check activity = { "signer_id": signer_id, @@ -840,6 +867,8 @@ async def handle_set_factory_configs(data, receiverId): logger.error(f"Failed to update factory configs, Error: {e}") + + # # TODO: Need to abstract some actions. # async def handle_batch_donations( # receiver_id: str, @@ -1237,9 +1266,11 @@ def update_approved_projects(event_data): for ids in project_ids: project = Project.objects.get(id=ids) round_obj.approved_projects.add(project) + return True except Exception as e: logger.error(f"Error updating application for Round {round_id}: {e}") + return False def update_application(event_data, txhash): round_id, application_data, reviewer_id = event_data[0], event_data[1], event_data[2] @@ -1277,8 +1308,11 @@ def update_application(event_data, txhash): **{"status": application_data["status"], "updated_at": updated_at} ) + return True + except Exception as e: logger.error(f"Error updating application for Round {round_id}: {e}") + return False @@ -1317,9 +1351,10 @@ def process_vote_event(event_data, tx_hash): ) logger.info(f"Processed vote for Round: {round_id}, Voter: {voter.id}") - return vote + return True except Exception as e: logger.error(f"Error processing vote for Round: {str(e)}") + return False def process_project_event(event_data): @@ -1394,9 +1429,12 @@ def process_project_event(event_data): logger.info(f"Created new Project: {project.id}") else: logger.info(f"Updated existing Project: {project.id}") + + return True except Exception as e: logger.error(f"Error processing project event: {str(e)}") + return False @@ -1455,8 +1493,10 @@ def create_or_update_round(event_data, contract_id, timestamp): round_obj.contacts.add(contact_obj) logger.info(f"Created/Updated Round: {round_id}") + return True except Exception as e: logger.error(f"Error processing rounds event: {str(e)}") + return False def process_application_to_round(event_data, tx_hash): @@ -1486,8 +1526,10 @@ def process_application_to_round(event_data, tx_hash): } ) logger.info(f"Processed application for Round: {round_id}, Applicant: {applicant_id}") + return True except Exception as e: logger.error(f"Error processing rounds applications event: {str(e)}") + return False @@ -1512,8 +1554,11 @@ def create_round_application(event_data, tx_hash): project_id=application_data["project_id"], defaults=appl_defaults, ) + + return True except Exception as e: logger.error(f"Error creating applications to rounds: {str(e)}") + return False def process_rounds_deposit_event(event_data, tx_hash): @@ -1542,7 +1587,64 @@ def process_rounds_deposit_event(event_data, tx_hash): round_obj.vault_total_deposits = str(int(round_obj.vault_total_deposits or 0) + amount) round_obj.current_vault_balance = str(int(round_obj.current_vault_balance or 0) + deposit_data["net_amount"]) round_obj.save() + round_obj.update_vault_usd_equivalent() logger.info(f"Processed deposit for Round: {round_id}, Depositor: {depositor.id}, Amount: {amount}") + return True except Exception as e: - logger.error(f"Error creating applications to rounds: {str(e)}") + logger.error(f"Error processing deposits to rounds: {str(e)}") + return False + + + +def create_round_payout(event_data, tx_hash): + try: + + round_id, payout_data = event_data + amount = payout_data["amount"] + recipient_id = payout_data["recipient_id"] + memo = payout_data.get("memo") + + stellar_token_acct, _ = Account.objects.get_or_create(defaults={"chain_id":2},id="stellar") + stellar_token, _ = Token.objects.get_or_create( + account=stellar_token_acct + ) + + payout = PotPayout( + round_id=round_id, + on_chain_id=payout_data["id"], + amount=amount, + recipient_id=recipient_id, + memo=memo, + token=stellar_token, + paid_at=None, + tx_hash=tx_hash, + ) + payout.save() + logger.info(f"Created payout for round {round_id} to {recipient_id} for amount {amount}.") + return True + except Exception as e: + logger.error(f"Error creating round payout: {str(e)}") + return False + + +def update_round_payout(event_data, tx_hash): + try: + _, payout_data = event_data + payout_on_chain_id = payout_data["id"] + amount = payout_data["amount"] + paid_at_ms = payout_data.get("paid_at_ms") + recipient_id, _ = Account.objects.get_or_create(id=payout_data["recipient_id"]) + memo = payout_data.get("memo") + payout = PotPayout.objects.get(on_chain_id=payout_on_chain_id) + payout.amount = amount + payout.paid_at = datetime.fromtimestamp(paid_at_ms / 1000) + payout.recipient = recipient_id + payout.tx_hash = tx_hash + payout.memo = memo + payout.save() + logger.info(f"Updated payout {payout_on_chain_id} for recipient {recipient_id} with amount {amount}.") + return True + except Exception as e: + logger.error(f"Error updating Payout. {str(e)}") + return False \ No newline at end of file diff --git a/lists/serializers.py b/lists/serializers.py index b9a495a..162b28d 100644 --- a/lists/serializers.py +++ b/lists/serializers.py @@ -3,7 +3,13 @@ from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer -from .models import List, ListRegistration +from .models import List, ListRegistration, ListUpvote + + +class ListUpvoteSerializer(ModelSerializer): + class Meta: + model = ListUpvote + fields = '__all__' class ListSerializer(ModelSerializer): @@ -16,6 +22,7 @@ class Meta: "admins", "name", "description", + "upvotes", "cover_image_url", "admin_only_registrations", "default_registration_status", @@ -25,6 +32,7 @@ class Meta: owner = AccountSerializer() admins = AccountSerializer(many=True) + upvotes = ListUpvoteSerializer(many=True) # def get_owner(self, obj): # return AccountSerializer(obj.owner).data diff --git a/pots/migrations/0015_alter_potpayout_options_and_more.py b/pots/migrations/0015_alter_potpayout_options_and_more.py new file mode 100644 index 0000000..6bb3546 --- /dev/null +++ b/pots/migrations/0015_alter_potpayout_options_and_more.py @@ -0,0 +1,133 @@ +# Generated by Django 5.0.6 on 2024-09-26 00:10 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ("grantpicks", "0001_initial"), + ("pots", "0014_alter_potapplication_unique_together_and_more"), + ("tokens", "0006_rename_id_token_account"), + ] + + operations = [ + migrations.AlterModelOptions( + name="potpayout", + options={"verbose_name_plural": "Pot Payouts"}, + ), + migrations.AlterUniqueTogether( + name="potpayoutchallenge", + unique_together=set(), + ), + migrations.AddField( + model_name="potpayout", + name="memo", + field=models.CharField( + blank=True, + help_text="Round payout memo", + null=True, + verbose_name="payout memo", + ), + ), + migrations.AddField( + model_name="potpayout", + name="on_chain_id", + field=models.IntegerField( + blank=True, + db_index=True, + help_text="Payout id in contract", + null=True, + unique=True, + verbose_name="contract payout id", + ), + ), + migrations.AddField( + model_name="potpayout", + name="round", + field=models.ForeignKey( + blank=True, + help_text="Round that this payout is for.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="payouts", + to="grantpicks.round", + ), + ), + migrations.AddField( + model_name="potpayoutchallenge", + name="round", + field=models.ForeignKey( + blank=True, + help_text="Round that this payout challenge is for.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="payouts_challenges", + to="grantpicks.round", + ), + ), + migrations.AddField( + model_name="potpayoutchallengeadminresponse", + name="round", + field=models.ForeignKey( + blank=True, + help_text="Round that this payout challenge response is for.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="payouts_challenge_responses", + to="grantpicks.round", + ), + ), + migrations.AlterField( + model_name="potpayout", + name="pot", + field=models.ForeignKey( + help_text="Pot that this payout is for.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="payouts", + to="pots.pot", + ), + ), + migrations.AlterField( + model_name="potpayoutchallenge", + name="pot", + field=models.ForeignKey( + help_text="Pot challenged.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="challenges", + to="pots.pot", + ), + ), + migrations.AddConstraint( + model_name="potpayout", + constraint=models.CheckConstraint( + check=models.Q( + models.Q(("pot__isnull", False), ("round__isnull", True)), + models.Q(("pot__isnull", True), ("round__isnull", False)), + _connector="OR", + ), + name="payout_pot_or_round", + ), + ), + migrations.AddConstraint( + model_name="potpayoutchallenge", + constraint=models.UniqueConstraint( + fields=("challenger", "pot"), name="unique_challenger_pot" + ), + ), + migrations.AddConstraint( + model_name="potpayoutchallenge", + constraint=models.CheckConstraint( + check=models.Q( + models.Q(("pot__isnull", False), ("round__isnull", True)), + models.Q(("pot__isnull", True), ("round__isnull", False)), + _connector="OR", + ), + name="payout_challenge_pot_or_round", + ), + ), + ] diff --git a/pots/models.py b/pots/models.py index ac8fd64..009c54b 100644 --- a/pots/models.py +++ b/pots/models.py @@ -557,14 +557,37 @@ class PotPayout(models.Model): primary_key=True, help_text=_("Payout id."), ) + on_chain_id =models.IntegerField( + _("contract payout id"), + null=True, + blank=True, + unique=True, + help_text=_("Payout id in contract"), + db_index=True + ) pot = models.ForeignKey( Pot, on_delete=models.CASCADE, related_name="payouts", - null=False, + null=True, help_text=_("Pot that this payout is for."), db_index=True, ) + round = models.ForeignKey( + "grantpicks.Round", + on_delete=models.CASCADE, + related_name="payouts", + null=True, + blank=True, + help_text=_("Round that this payout is for."), + db_index=True, + ) + memo = models.CharField( + _("payout memo"), + null=True, + blank=True, + help_text=_("Round payout memo"), + ) recipient = models.ForeignKey( Account, on_delete=models.CASCADE, @@ -607,6 +630,28 @@ class PotPayout(models.Model): help_text=_("Transaction hash."), ) + class Meta: + verbose_name_plural = "Pot Payouts" + constraints = [ + models.CheckConstraint( + check=( + models.Q(pot__isnull=False) & models.Q(round__isnull=True) | + models.Q(pot__isnull=True) & models.Q(round__isnull=False) + ), + name="payout_pot_or_round" + ) + ] + + def clean(self): + if self.pot is None and self.round is None: + raise ValidationError(_("Either pot or round must be specified.")) + if self.pot is not None and self.round is not None: + raise ValidationError(_("Only one of pot or round can be specified.")) + + def save(self, *args, **kwargs): + self.clean() + super().save(*args, **kwargs) + ### Fetches USD prices for the Donation record and saves USD totals def fetch_usd_prices(self): # first, see if there is a TokenHistoricalPrice within 1 day (or HISTORICAL_PRICE_QUERY_HOURS) of self.paid_at @@ -644,9 +689,18 @@ class PotPayoutChallenge(models.Model): Pot, on_delete=models.CASCADE, related_name="challenges", - null=False, + null=True, help_text=_("Pot challenged."), ) + round = models.ForeignKey( + "grantpicks.Round", + on_delete=models.CASCADE, + related_name="payouts_challenges", + null=True, + blank=True, + help_text=_("Round that this payout challenge is for."), + db_index=True, + ) created_at = models.DateTimeField( _("created at"), null=False, @@ -668,12 +722,19 @@ class PotPayoutChallenge(models.Model): class Meta: verbose_name_plural = "Payout Challenges" - unique_together = (("challenger", "pot"),) - - class Meta: - verbose_name_plural = "Payout Challenges" - - unique_together = (("challenger", "pot"),) + constraints = [ + models.UniqueConstraint( + fields=['challenger', 'pot'], + name='unique_challenger_pot' + ), + models.CheckConstraint( + check=( + models.Q(pot__isnull=False) & models.Q(round__isnull=True) | + models.Q(pot__isnull=True) & models.Q(round__isnull=False) + ), + name="payout_challenge_pot_or_round" + ) + ] class PotPayoutChallengeAdminResponse(models.Model): @@ -700,6 +761,16 @@ class PotPayoutChallengeAdminResponse(models.Model): help_text=_("Pot being challenged."), ) + round = models.ForeignKey( + "grantpicks.Round", + on_delete=models.CASCADE, + related_name="payouts_challenge_responses", + null=True, + blank=True, + help_text=_("Round that this payout challenge response is for."), + db_index=True, + ) + admin = models.ForeignKey( Account, on_delete=models.CASCADE, diff --git a/tokens/models.py b/tokens/models.py index c32e742..4af6712 100644 --- a/tokens/models.py +++ b/tokens/models.py @@ -116,8 +116,8 @@ def save(self, *args, **kwargs): price_data = response.json() coin_data = list( filter( - lambda x: x["symbol"] == kwargs["symbol"] - and x["platforms"].get("near-protocol"), + lambda x: x["symbol"] == self.symbol + and x["name"] == "Stellar", price_data, ) ) From f3f43467dbdcd510d4fda1ad1dd175207dbf745b Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 26 Sep 2024 09:14:47 +0100 Subject: [PATCH 286/454] fix log error --- pots/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pots/models.py b/pots/models.py index 009c54b..e7254fc 100644 --- a/pots/models.py +++ b/pots/models.py @@ -666,7 +666,7 @@ def fetch_usd_prices(self): self.amount_paid_usd = token.format_price(self.amount) * price_usd self.save() logger.info( - f"Saved USD prices for pot payout for pot id: {self.pot.account}" + f"Saved USD prices for pot payout for pot id: {self.pot.account if self.pot else self.round}" ) except Exception as e: logger.error(f"Failed to calculate and save USD prices: {e}") From 7447f70fda1bd0553df28691c1e2ee4b0feace32 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 26 Sep 2024 13:37:14 +0100 Subject: [PATCH 287/454] add contact serializer to rounds --- grantpicks/serializers.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/grantpicks/serializers.py b/grantpicks/serializers.py index 2353aac..f28cc81 100644 --- a/grantpicks/serializers.py +++ b/grantpicks/serializers.py @@ -53,6 +53,7 @@ class Meta: 'admins', ] + class RoundSerializer(ModelSerializer): current_vault_balance_usd = TwoDecimalPlacesField(max_digits=20, decimal_places=2) vault_total_deposits_usd = TwoDecimalPlacesField(max_digits=20, decimal_places=2) @@ -103,6 +104,7 @@ class Meta: remaining_dist_address = AccountSerializer() admins = AccountSerializer(many=True) remaining_dist_by = AccountSerializer() + contacts = ProjectContactSerializer(many=True, required=False) class RoundApplicationSerializer(ModelSerializer): @@ -135,7 +137,13 @@ class Meta: "overview": "This project aims to do something impactful.", "owner": "GD4I4FXMIKKKVSGVCGNILRFFHDQHITMDW545SCLGEOKGBN6W44AV6367", "payout_address": "GD4I4FXMIKKKVSGVCGNILRFFHDQHITMDW545SCLGEOKGBN6W44AV6367", - "contacts": [], + "contacts": [ + { + "id": 2, + "name": "Email", + "value": "dvorak@gmail.com" + } + ], "contracts": [], "team_members": [], "repositories": [], @@ -149,7 +157,13 @@ class Meta: "id": 1, "name": "InteractGrant TO Apply V1", "owner": "GD4I4FXMIKKKVSGVCGNILRFFHDQHITMDW545SCLGEOKGBN6W44AV6367", - "contacts": [], + "contacts": [ + { + "id": 2, + "name": "Email", + "value": "dvorak@gmail.com" + } + ], "use_vault": True, "description": "This is a test round", "use_whitelist": False, From 7518252dccc8a9e5cd13d58bfd059a43205afd9d Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 3 Oct 2024 02:36:28 +0100 Subject: [PATCH 288/454] add project stats, whitelist grantpicks --- accounts/api.py | 47 +++++++++++++++- api/urls.py | 10 +++- base/settings.py | 20 ++++++- grantpicks/api.py | 114 +++++++++++++++++++++++++++++++++++++- grantpicks/serializers.py | 19 ++++++- indexer_app/handler.py | 6 +- pots/utils.py | 5 ++ 7 files changed, 207 insertions(+), 14 deletions(-) diff --git a/accounts/api.py b/accounts/api.py index f97eb1e..2255a4f 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -23,8 +23,8 @@ DonationSerializer, PaginatedDonationsResponseSerializer, ) -from lists.models import ListRegistration, ListRegistrationStatus -from lists.serializers import PAGINATED_LIST_REGISTRATION_EXAMPLE, ListRegistrationSerializer, PaginatedListRegistrationsResponseSerializer +from lists.models import List, ListRegistration, ListRegistrationStatus +from lists.serializers import PAGINATED_LIST_EXAMPLE, PAGINATED_LIST_REGISTRATION_EXAMPLE, ListRegistrationSerializer, ListSerializer, PaginatedListRegistrationsResponseSerializer, PaginatedListsResponseSerializer from pots.models import Pot, PotApplication, PotApplicationStatus, PotPayout from pots.serializers import ( PAGINATED_PAYOUT_EXAMPLE, @@ -441,3 +441,46 @@ def get(self, request: Request, *args, **kwargs): results = self.paginate_queryset(registrations, request, view=self) serializer = ListRegistrationSerializer(results, many=True) return self.get_paginated_response(serializer.data) + + + +class AccountUpvotedListsAPI(APIView, CustomSizePageNumberPagination): + + @extend_schema( + parameters=[ + OpenApiParameter("account_id", str, OpenApiParameter.PATH), + *pagination_parameters, + ], + responses={ + 200: OpenApiResponse( + response=PaginatedListsResponseSerializer, + description="Returns a paginated list of user upvoted lists", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for lists", + value=PAGINATED_LIST_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Account not found"), + 500: OpenApiResponse(description="Internal server error"), + }, + ) + @method_decorator(cache_page(60 * 5)) + def get(self, request: Request, *args, **kwargs): + account_id = kwargs.get("account_id") + try: + account = Account.objects.get(id=account_id) + except Account.DoesNotExist: + return Response( + {"message": f"Account with ID {account_id} not found."}, status=404 + ) + upvoted_lists = List.objects.filter(upvotes__account=account) + results = self.paginate_queryset(upvoted_lists, request, view=self) + serializer = ListSerializer(results, many=True) + return self.get_paginated_response(serializer.data) + + diff --git a/api/urls.py b/api/urls.py index 50b32d5..2c40737 100644 --- a/api/urls.py +++ b/api/urls.py @@ -13,12 +13,13 @@ AccountListRegistrationsAPI, AccountPayoutsReceivedAPI, AccountPotApplicationsAPI, + AccountUpvotedListsAPI, AccountsListAPI, DonorsAPI, ) from base.api import StatsAPI from donations.api import DonationContractConfigAPI -from grantpicks.api import ProjectListAPI, ProjectRoundVotesAPI, RoundApplicationsAPI, RoundDetailAPI, RoundsListAPI +from grantpicks.api import AccountProjectListAPI, ProjectListAPI, ProjectRoundVotesAPI, ProjectStatsAPI, RoundApplicationsAPI, RoundDetailAPI, RoundsListAPI from lists.api import ( ListDetailAPI, ListRandomRegistrationAPI, @@ -83,6 +84,11 @@ AccountListRegistrationsAPI.as_view(), name="accounts_api_by_id_registrations", ), + path( + "v1/accounts//upvoted-lists", + AccountUpvotedListsAPI.as_view(), + name="accounts_api_upvoted_lists", + ), # donate contract config path( "v1/donate_contract_config", @@ -141,5 +147,7 @@ RoundApplicationsAPI.as_view(), name="rounds_applications_api", ), + path("v1//projects", AccountProjectListAPI.as_view(), name="user_projects_api"), + path("v1///project-stats", ProjectStatsAPI.as_view(), name="projects_api"), ] diff --git a/base/settings.py b/base/settings.py index 8729356..3f318b5 100644 --- a/base/settings.py +++ b/base/settings.py @@ -58,14 +58,27 @@ REDIS_PORT = os.environ.get("PL_REDIS_PORT", 6379) SENTRY_DSN = os.environ.get("PL_SENTRY_DSN") -POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else "potlock.near" -NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" +# POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else "potlock.near" +POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "staging" else "potlock.near") +# NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" +NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "staging" else "nadabot.near") STELLAR_CONTRACT_ID = "CBP5F3C5SPPJRNRJSOOQQKUGX2CE2G6BRV34MOEBHNFGBRQ5ZSXNGWAB" if ENVIRONMENT == "testnet" else "CBP5F3C5SPPJRNRJSOOQQKUGX2CE2G6BRV34MOEBHNFGBRQ5ZSXNGWAB" STELLAR_PROJECTS_REGISTRY_CONTRACT = "CBYUL5Y4GTIZAST3W2LVYKFTYNJA3EUG3MN4W2TTFONV6VXEACMRLBEP" NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) +# TODO: split settigns file by enviroment +if ENVIRONMENT == "testnet": + POTLOCK_PATTERN = r'\.potlock\.testnet$' + NADABOT_PATTERN = r'\.nadabot\.testnet$' +elif ENVIRONMENT == "staging": + POTLOCK_PATTERN = r'\.staging\.potlock\.near$' + NADABOT_PATTERN = r'\.staging\.nadabot\.near$' +else: # mainnet/prod + POTLOCK_PATTERN = r'(? Date: Thu, 3 Oct 2024 05:51:46 +0100 Subject: [PATCH 289/454] endpoint for user upvoted lists, add pairs to vote --- grantpicks/api.py | 5 ++--- grantpicks/serializers.py | 27 ++++++++++++++------------- indexer_app/utils.py | 4 +++- lists/api.py | 17 +++++++++++++++++ 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/grantpicks/api.py b/grantpicks/api.py index 2be0ec5..d4c301d 100644 --- a/grantpicks/api.py +++ b/grantpicks/api.py @@ -39,11 +39,10 @@ PaginatedProjectsResponseSerializer, PaginatedRoundApplicationsResponseSerializer, PaginatedRoundsResponseSerializer, - PaginatedVotePairResponseSerializer, + PaginatedVotesResponseSerializer, ProjectSerializer, RoundApplicationSerializer, RoundSerializer, - VotePairSerializer, VoteSerializer, ) @@ -175,7 +174,7 @@ class ProjectRoundVotesAPI(APIView, CustomSizePageNumberPagination): ], responses={ 200: OpenApiResponse( - response=PaginatedVotePairResponseSerializer, # Update to use the appropriate serializer + response=PaginatedVotesResponseSerializer, # Update to use the appropriate serializer description="Returns votes for a project in the round", examples=[ OpenApiExample( diff --git a/grantpicks/serializers.py b/grantpicks/serializers.py index 9569288..5c3c90e 100644 --- a/grantpicks/serializers.py +++ b/grantpicks/serializers.py @@ -266,9 +266,22 @@ class PaginatedRoundApplicationsResponseSerializer(serializers.Serializer): +class VotePairSerializer(serializers.ModelSerializer): + project = ProjectSerializer() + + class Meta: + model = VotePair + fields = [ + 'pair_id', + 'project', + ] + + + class VoteSerializer(serializers.ModelSerializer): round = serializers.PrimaryKeyRelatedField(queryset=Round.objects.all()) voter = AccountSerializer() + pairs = VotePairSerializer(many=True) class Meta: model = Vote @@ -276,6 +289,7 @@ class Meta: 'id', 'round', 'voter', + 'pairs', 'tx_hash', 'voted_at', ] @@ -289,19 +303,6 @@ class PaginatedVotesResponseSerializer(serializers.Serializer): results = VoteSerializer(many=True) - -class VotePairSerializer(serializers.ModelSerializer): - vote = VoteSerializer() - project = ProjectSerializer() - - class Meta: - model = VotePair - fields = [ - 'vote', - 'pair_id', - 'project', - ] - class PaginatedVotePairResponseSerializer(serializers.Serializer): count = serializers.IntegerField() next = serializers.CharField(allow_null=True) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index b732426..0716ad9 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -669,8 +669,10 @@ async def handle_list_upvote( "created_at": created_at } + list_obj = List.objects.get(on_chain_id=data.get("list_id")) + await ListUpvote.objects.aupdate_or_create( - list_id=data.get("list_id") or receiver_id, + list=list_obj, account_id=signer_id, defaults=up_default ) diff --git a/lists/api.py b/lists/api.py index cc73e13..8c87118 100644 --- a/lists/api.py +++ b/lists/api.py @@ -14,6 +14,7 @@ from rest_framework.response import Response from rest_framework.views import APIView +from accounts.models import Account from api.pagination import pagination_parameters from api.pagination import CustomSizePageNumberPagination @@ -34,6 +35,12 @@ class ListsListAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ + OpenApiParameter( + "account", + str, + OpenApiParameter.QUERY, + description="Filter lists by account", + ), *pagination_parameters, ], responses={ @@ -50,12 +57,22 @@ class ListsListAPI(APIView, CustomSizePageNumberPagination): ), ], ), + 404: OpenApiResponse(description="Account not found"), 500: OpenApiResponse(description="Internal server error"), }, ) @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): lists = List.objects.all() + account_id = request.query_params.get("account") + if account_id: + try: + account = Account.objects.get(id=account_id) + lists = lists.filter(owner=account) + except Account.DoesNotExist: + return Response( + {"message": f"Account with ID {account_id} not found."}, status=404 + ) results = self.paginate_queryset(lists, request, view=self) serializer = ListSerializer(results, many=True) return self.get_paginated_response(serializer.data) From 40b60b865ab0abbaa7dfe0891df7408bf291c0cf Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 3 Oct 2024 19:53:40 +0100 Subject: [PATCH 290/454] remove upvote --- indexer_app/handler.py | 9 +++++++++ indexer_app/utils.py | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 98d8628..dad3401 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -40,6 +40,7 @@ handle_pot_config_update, handle_registry_blacklist_action, handle_registry_unblacklist_action, + handle_remove_upvote, handle_set_factory_configs, handle_set_payouts, handle_social_profile_update, @@ -426,6 +427,14 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess args_dict, receiver_id, signer_id, receipt.receipt_id, now_datetime ) break + case "remove_upvote": + logger.info(f"removing upvote... {args_dict}") + if receiver_id != LISTS_CONTRACT: + break + await handle_remove_upvote( + args_dict, receiver_id, signer_id + ) + break case "owner_add_admins": logger.info(f"adding admins.. {args_dict}") if not match_nadabot_registry_pattern(receiver_id): diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 0716ad9..bc34468 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -669,7 +669,7 @@ async def handle_list_upvote( "created_at": created_at } - list_obj = List.objects.get(on_chain_id=data.get("list_id")) + list_obj = await List.objects.aget(on_chain_id=data.get("list_id")) await ListUpvote.objects.aupdate_or_create( list=list_obj, @@ -695,6 +695,23 @@ async def handle_list_upvote( logger.error(f"Failed to upvote list, Error: {e}") + +async def handle_remove_upvote( + data: dict, receiver_id: str, signer_id: str +): + try: + + logger.info(f"remove upvote from list: {data}, {receiver_id}") + list_obj = await List.objects.aget(on_chain_id=data.get("list_id")) + await ListUpvote.objects.filter(list=list_obj, account_id=signer_id).adelete() + + logger.info( + f"Upvote removed successfully" + ) + except Exception as e: + logger.error(f"Failed to remove upvote from list, Error: {e}") + + async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): try: From 3a4a31dba51067399728203fce3d989b8558a37c Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 7 Oct 2024 11:15:10 +0100 Subject: [PATCH 291/454] remove caching from lists --- accounts/api.py | 1 - lists/api.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/accounts/api.py b/accounts/api.py index 2255a4f..8d2bb8b 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -469,7 +469,6 @@ class AccountUpvotedListsAPI(APIView, CustomSizePageNumberPagination): 500: OpenApiResponse(description="Internal server error"), }, ) - @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): account_id = kwargs.get("account_id") try: diff --git a/lists/api.py b/lists/api.py index 8c87118..84daa01 100644 --- a/lists/api.py +++ b/lists/api.py @@ -61,7 +61,7 @@ class ListsListAPI(APIView, CustomSizePageNumberPagination): 500: OpenApiResponse(description="Internal server error"), }, ) - @method_decorator(cache_page(60 * 5)) + @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): lists = List.objects.all() account_id = request.query_params.get("account") @@ -152,7 +152,7 @@ class ListRegistrationsAPI(APIView, CustomSizePageNumberPagination): 500: OpenApiResponse(description="Internal server error"), }, ) - @method_decorator(cache_page(60 * 5)) + @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") try: From 5e3f19a0ef1c0880b2bf56f735ec906b722002b6 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 7 Oct 2024 13:56:20 +0100 Subject: [PATCH 292/454] add reg count to list details --- indexer_app/utils.py | 3 ++- lists/serializers.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index bc34468..e8abc95 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -422,6 +422,7 @@ async def handle_new_list_registration( # Prepare data for insertion project_list = [] insert_data = [] + parent_list = await List.objects.aget(on_chain_id=reg_data[0]["list_id"]) for dt in reg_data: logger.info(f"dt: {dt}") project_list.append({"chain_id":1, "id": dt["registrant_id"]}) @@ -429,7 +430,7 @@ async def handle_new_list_registration( { "id": dt["id"], "registrant_id": dt["registrant_id"], - "list_id": dt["list_id"], + "list_id": parent_list.id, "status": dt["status"], "submitted_at": datetime.fromtimestamp(dt["submitted_ms"] / 1000), "updated_at": datetime.fromtimestamp(dt["updated_ms"] / 1000), diff --git a/lists/serializers.py b/lists/serializers.py index 162b28d..486abc6 100644 --- a/lists/serializers.py +++ b/lists/serializers.py @@ -23,6 +23,7 @@ class Meta: "name", "description", "upvotes", + "registrations_count", "cover_image_url", "admin_only_registrations", "default_registration_status", @@ -33,6 +34,10 @@ class Meta: owner = AccountSerializer() admins = AccountSerializer(many=True) upvotes = ListUpvoteSerializer(many=True) + registrations_count = SerializerMethodField() + + def get_registrations_count(self, obj): + return obj.registrations.count() # def get_owner(self, obj): # return AccountSerializer(obj.owner).data From f01709e2f21061d77a2b405a7aef4103f56c67b3 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 8 Oct 2024 18:45:48 +0100 Subject: [PATCH 293/454] use onchain id for lists route --- indexer_app/handler.py | 11 +++++++++-- indexer_app/utils.py | 10 ++++++++++ lists/api.py | 12 ++++++------ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index dad3401..c67eb27 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -22,6 +22,7 @@ handle_default_list_status_change, handle_delete_list, handle_list_admin_removal, + handle_list_owner_change, handle_list_registration_update, handle_list_upvote, handle_new_donation, @@ -96,6 +97,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess log_data = [] receipt = receipt_execution_outcome.receipt signer_id = receipt.receipt["Action"]["signer_id"] + LISTS_CONTRACT = "lists." + settings.POTLOCK_TLA log_processing_start = time.time() for log_index, log in enumerate( @@ -138,6 +140,12 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess await handle_delete_list( parsed_log.get("data")[0] ) + if event_name == "owner_transfer": + if receiver_id != LISTS_CONTRACT: + continue + await handle_list_owner_change( + parsed_log.get("data")[0] + ) except json.JSONDecodeError: logger.warning( f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" @@ -164,8 +172,7 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess # # consider logging failures to logging service; for now, just skip # print("here we are...") # continue - method_call_processing_start = time.time() - LISTS_CONTRACT = "lists." + settings.POTLOCK_TLA + # method_call_processing_start = time.time() DONATE_CONTRACT = "donate." + settings.POTLOCK_TLA for index, action in enumerate( diff --git a/indexer_app/utils.py b/indexer_app/utils.py index e8abc95..358d49b 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -854,6 +854,16 @@ async def handle_list_admin_removal(data, receiver_id, signer_id, receiptId): logger.error(f"Failed to remove list admin, Error: {e}") +async def handle_list_owner_change(data): + try: + logger.info(f"changing owner... ...: {data}") + await List.objects.filter(id=data["list_id"]).aupdate(**{ + "owner": data["new_owner_id"] + }) + + except Exception as e: + logger.error(f"Failed to change list owner, Error: {e}") + async def handle_add_nadabot_admin(data, receiverId): logger.info(f"adding admin...: {data}, {receiverId}") try: diff --git a/lists/api.py b/lists/api.py index 84daa01..12dfc07 100644 --- a/lists/api.py +++ b/lists/api.py @@ -106,10 +106,10 @@ class ListDetailAPI(APIView): def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") try: - list_obj = List.objects.get(id=list_id) + list_obj = List.objects.get(on_chain_id=list_id) except List.DoesNotExist: return Response( - {"message": f"List with ID {list_id} not found."}, status=404 + {"message": f"List with onchain ID {list_id} not found."}, status=404 ) serializer = ListSerializer(list_obj) return Response(serializer.data) @@ -156,10 +156,10 @@ class ListRegistrationsAPI(APIView, CustomSizePageNumberPagination): def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") try: - list_obj = List.objects.prefetch_related('registrations').get(id=list_id) + list_obj = List.objects.prefetch_related('registrations').get(on_chain_id=list_id) except List.DoesNotExist: return Response( - {"message": f"List with ID {list_id} not found."}, status=404 + {"message": f"List with on chain ID {list_id} not found."}, status=404 ) registrations = list_obj.registrations.select_related().all() @@ -214,10 +214,10 @@ class ListRandomRegistrationAPI(APIView): def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") try: - list_obj = List.objects.get(id=list_id) + list_obj = List.objects.get(on_chain_id=list_id) except List.DoesNotExist: return Response( - {"message": f"List with ID {list_id} not found."}, status=404 + {"message": f"List on chain ID {list_id} not found."}, status=404 ) registrations = list_obj.registrations.all() From 9abb1723e266e934c0123cf051ad5ac32bf22228 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 9 Oct 2024 05:55:03 +0100 Subject: [PATCH 294/454] filter appllication by status --- pots/api.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pots/api.py b/pots/api.py index 4a3a474..11b8283 100644 --- a/pots/api.py +++ b/pots/api.py @@ -144,6 +144,13 @@ class PotApplicationsAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ OpenApiParameter("pot_id", str, OpenApiParameter.PATH), + OpenApiParameter( + "status", + str, + OpenApiParameter.QUERY, + required=False, + description="Filter by application status", + ), *pagination_parameters, ], responses={ @@ -172,6 +179,13 @@ def get(self, request: Request, *args, **kwargs): return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) applications = pot.applications.all() + status_param = request.query_params.get("status") + if status_param: + if status_param not in PotApplicationStatus.values: + return Response( + {"message": f"Invalid status value: {status_param}"}, status=400 + ) + applications = applications.filter(status=status_param) results = self.paginate_queryset(applications, request, view=self) serializer = PotApplicationSerializer(results, many=True) return self.get_paginated_response(serializer.data) From 8856e60f5e89eeaca2acceec834f7b3bb89b13bc Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 10 Oct 2024 01:27:55 +0100 Subject: [PATCH 295/454] update admins from event and update cors --- base/settings.py | 2 ++ indexer_app/handler.py | 33 +++++++++++++++++++++------------ indexer_app/utils.py | 26 ++++++++++++++++++++++---- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/base/settings.py b/base/settings.py index 3f318b5..03e3dc3 100644 --- a/base/settings.py +++ b/base/settings.py @@ -183,6 +183,8 @@ CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", "http://127.0.0.1:8080", + "http://dev.local", + "https://dev.local", "https://test.potlock.org", "https://test.potlock.xyz", "https://test.potlock.io", diff --git a/indexer_app/handler.py b/indexer_app/handler.py index c67eb27..3b773ec 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -21,8 +21,9 @@ handle_add_stamp, handle_default_list_status_change, handle_delete_list, - handle_list_admin_removal, + handle_list_admin_ops, handle_list_owner_change, + handle_list_registration_removal, handle_list_registration_update, handle_list_upvote, handle_new_donation, @@ -146,6 +147,12 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess await handle_list_owner_change( parsed_log.get("data")[0] ) + if event_name == "update_admins": + if receiver_id != LISTS_CONTRACT: + continue + await handle_list_admin_ops( + parsed_log.get("data")[0], receiver_id, signer_id, receipt.receipt_id + ) except json.JSONDecodeError: logger.warning( f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" @@ -345,6 +352,19 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess ) break + case ( + "unregister" + ): # TODO: listen for create_registration event instead of method call + logger.info( + f"registrations revoke incoming: {args_dict}, {action}" + ) + if receiver_id != LISTS_CONTRACT: + break + await handle_list_registration_removal( + args_dict, receiver_id + ) + break + case "chef_set_application_status": logger.info( f"application status change incoming: {args_dict}" @@ -408,17 +428,6 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess ) break - case ( - "owner_remove_admins" - ): # TODO: use update_admins event instead of method call to handle all cases - logger.info(f"attempting to remove admins....: {args_dict}") - if receiver_id != LISTS_CONTRACT: - break - await handle_list_admin_removal( - args_dict, receiver_id, signer_id, receipt.receipt_id - ) - break - case "create_list": logger.info(f"creating list... {args_dict}, {action}") if receiver_id != LISTS_CONTRACT: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 358d49b..5a5a04e 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -474,6 +474,19 @@ async def handle_new_list_registration( logger.error(f"Encountered error trying to insert activity: {e}") +async def handle_list_registration_removal( + data: dict, + receiver_id: str, +): + logger.info(f"list reg removal: {data}, {receiver_id}") + + try: + list_obj = await List.objects.aget(on_chain_id=data["list_id"]) + await list_obj.registrations.filter(id=data["registration_id"]).adelete() + + except Exception as e: + logger.error(f"Encountered error trying to remove reg: {e}") + async def handle_list_registration_update( data: dict, receiver_id: str, status_obj: ExecutionOutcome ): @@ -831,14 +844,19 @@ async def handle_payout_challenge_response( logger.error(f"Failed to handle admin challeneg response, Error: {e}") -async def handle_list_admin_removal(data, receiver_id, signer_id, receiptId): +async def handle_list_admin_ops(data, receiver_id, signer_id, receiptId): try: - logger.info(f"removing admin...: {data}, {receiver_id}") + logger.info(f"updating admin...: {data}, {receiver_id}") list_obj = await List.objects.aget(id=data["list_id"]) for acct in data["admins"]: - await list_obj.admins.aremove({"admins_id": acct}) # maybe check + acct_obj = await Account.objects.aget(id=acct) + if not await list_obj.admins.acontains(acct_obj): + await list_obj.admins.aadd(acct_obj) + for admin in list_obj.admins.all(): + if not admin.id in data: + await list_obj.admins.aremove(admin) activity = { "signer_id": signer_id, @@ -848,7 +866,7 @@ async def handle_list_admin_removal(data, receiver_id, signer_id, receiptId): } activity, activity_created = await Activity.objects.aupdate_or_create( - type="Remove_List_Admin", defaults=activity + type="List_Admin_Ops", defaults=activity ) except Exception as e: logger.error(f"Failed to remove list admin, Error: {e}") From 16ae0a370ae9231a55d46bac271369a46ad0af51 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 10 Oct 2024 14:49:26 +0100 Subject: [PATCH 296/454] fix update list --- indexer_app/handler.py | 14 ++++++++++++++ indexer_app/utils.py | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 3b773ec..18b7eb8 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -25,6 +25,7 @@ handle_list_owner_change, handle_list_registration_removal, handle_list_registration_update, + handle_list_update, handle_list_upvote, handle_new_donation, handle_new_group, @@ -153,6 +154,12 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess await handle_list_admin_ops( parsed_log.get("data")[0], receiver_id, signer_id, receipt.receipt_id ) + if event_name == "update_lis": + if receiver_id != LISTS_CONTRACT: + continue + await handle_list_update( + parsed_log.get("data")[0], receiver_id, signer_id, receipt.receipt_id + ) except json.JSONDecodeError: logger.warning( f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" @@ -435,6 +442,13 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess await handle_new_list(signer_id, receiver_id, status_obj, None) break + case "update_list": + logger.info(f"updating list... {args_dict}, {action}") + if receiver_id != LISTS_CONTRACT: + break + await handle_list_update(signer_id, receiver_id, status_obj, None) + break + case "upvote": logger.info(f"up voting... {args_dict}") if receiver_id != LISTS_CONTRACT: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 5a5a04e..b82687d 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -389,6 +389,43 @@ async def handle_new_list( logger.error(f"Failed to handle new list, Error: {e}") + +async def handle_list_update( + signer_id: str, receiver_id: str, status_obj: ExecutionOutcome | None, data: dict | None +): + try: + + if not data: + data = json.loads( + base64.b64decode(status_obj.status.get("SuccessValue")).decode( + "utf-8" + ) # TODO: RECEIVE AS A FUNCTION ARGUMENT + ) + + + logger.info(f"updating list..... {data}") + + listObject = await List.objects.filter(on_chain_id=data["id"]).aupdate( + owner_id=data["owner"], + default_registration_status=data["default_registration_status"], + name=data["name"], + description=data["description"], + cover_image_url=data["cover_image_url"], + admin_only_registrations=data["admin_only_registrations"], + created_at=datetime.fromtimestamp(data["created_at"] / 1000), + updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), + ) + + # if data.get("admins"): + # for admin_id in data["admins"]: + # admin_object, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, + # id=admin_id, + # ) + # await listObject.admins.aadd(admin_object) + except Exception as e: + logger.error(f"Failed to handle list update, Error: {e}") + + async def handle_delete_list( data: dict ): @@ -848,7 +885,7 @@ async def handle_list_admin_ops(data, receiver_id, signer_id, receiptId): try: logger.info(f"updating admin...: {data}, {receiver_id}") - list_obj = await List.objects.aget(id=data["list_id"]) + list_obj = await List.objects.aget(on_chain_id=data["list_id"]) for acct in data["admins"]: acct_obj = await Account.objects.aget(id=acct) From 87c8282a0610f2fe34f563eb0d497781e12e4c18 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 10 Oct 2024 22:40:47 +0100 Subject: [PATCH 297/454] fix async iteration --- indexer_app/utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index b82687d..46123e8 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -888,11 +888,12 @@ async def handle_list_admin_ops(data, receiver_id, signer_id, receiptId): list_obj = await List.objects.aget(on_chain_id=data["list_id"]) for acct in data["admins"]: - acct_obj = await Account.objects.aget(id=acct) - if not await list_obj.admins.acontains(acct_obj): - await list_obj.admins.aadd(acct_obj) - for admin in list_obj.admins.all(): - if not admin.id in data: + admin, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=acct) + contains = await list_obj.admins.acontains(admin) + if not contains: + await list_obj.admins.aadd(admin) + async for admin in list_obj.admins.all(): + if not admin.id in data["admins"]: await list_obj.admins.aremove(admin) activity = { From cbabcb0bd03e6f38aad757a41d17851645b0b4f3 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 21 Oct 2024 11:18:47 +0100 Subject: [PATCH 298/454] prepare prod scripts --- .github/workflows/deploy-prod.yml | 51 +++++++++++++++++++++++++++++++ base/settings.py | 13 ++++---- indexer_app/tasks.py | 2 +- indexer_app/utils.py | 1 + scripts/after_install.sh | 26 +++++++++++----- 5 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/deploy-prod.yml diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml new file mode 100644 index 0000000..3dfae34 --- /dev/null +++ b/.github/workflows/deploy-prod.yml @@ -0,0 +1,51 @@ +name: Prod deploy to EC2 on Push + +on: + push: + branches: [prod] + +env: + AWS_REGION: "us-east-1" + +# Permission can be added at job level or workflow level +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout +jobs: + DeployToCodeDeploy: + runs-on: ubuntu-latest + steps: + - name: Git clone the repository + uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1.7.0 + with: + role-to-assume: arn:aws:iam::471112976510:role/GitHubAction-AssumeRoleWithAction + role-session-name: GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ env.AWS_REGION }} + + - name: Generate appspec.yml for prod + run: cp appspec.yml appspec.yml + + - name: Set environment variables + id: vars + run: | + echo "DATETIME=$(date +'%Y-%m-%d_%H-%M-%S')" >> $GITHUB_ENV + echo "FILENAME=django-indexer-${DATETIME}.zip" >> $GITHUB_ENV + echo "S3_BUCKET=django-indexer" >> $GITHUB_ENV + + - name: Create zip of repository + run: zip -r "${{ env.FILENAME }}" . + + - name: Upload repository to S3 + run: aws s3 cp "${{ env.FILENAME }}" "s3://${{ env.S3_BUCKET }}/" + + - name: Create CodeDeploy Deployment + id: deploy + run: | + aws deploy create-deployment \ + --application-name django-indexer \ + --deployment-group-name django-indexer-prod \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --s3-location bucket=${{ env.S3_BUCKET }},bundleType=zip,key=${{ env.FILENAME }} diff --git a/base/settings.py b/base/settings.py index 03e3dc3..67e62b4 100644 --- a/base/settings.py +++ b/base/settings.py @@ -28,13 +28,14 @@ # SECURITY WARNING: keep the secret key used in production secret! # TODO: update before prod release -SECRET_KEY = "django-insecure-=r_v_es6w6rxv42^#kc2hca6p%=fe_*cog_5!t%19zea!enlju" +SECRET_KEY = os.environ.get("PL_DJANGO_SECRET_KEY", "django-insecure-=r_v_es6w6rxv42^#kc2hca6p%=fe_*cog_5!t%19zea!enlju") ALLOWED_HOSTS = [ "ec2-100-27-57-47.compute-1.amazonaws.com", "127.0.0.1", "dev.potlock.io", "test-dev.potlock.io", + "api.potlock.io" # "alpha.potlock.io", ] @@ -59,11 +60,11 @@ SENTRY_DSN = os.environ.get("PL_SENTRY_DSN") # POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else "potlock.near" -POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "staging" else "potlock.near") +POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") # NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" -NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "staging" else "nadabot.near") -STELLAR_CONTRACT_ID = "CBP5F3C5SPPJRNRJSOOQQKUGX2CE2G6BRV34MOEBHNFGBRQ5ZSXNGWAB" if ENVIRONMENT == "testnet" else "CBP5F3C5SPPJRNRJSOOQQKUGX2CE2G6BRV34MOEBHNFGBRQ5ZSXNGWAB" -STELLAR_PROJECTS_REGISTRY_CONTRACT = "CBYUL5Y4GTIZAST3W2LVYKFTYNJA3EUG3MN4W2TTFONV6VXEACMRLBEP" +NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") +STELLAR_CONTRACT_ID = "CCVVNTUD6CFPKZ2C4JAZIQGCAK2S6D6KPP5IELGHTHHJPYV2B62GPJTK" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_PROJECTS_REGISTRY_CONTRACT = "CAUINLSA42RCTY35UFGOM2NLKMSRM6FW6NP7AR4GTUZHUQZZWB6CRBSJ" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) @@ -72,7 +73,7 @@ if ENVIRONMENT == "testnet": POTLOCK_PATTERN = r'\.potlock\.testnet$' NADABOT_PATTERN = r'\.nadabot\.testnet$' -elif ENVIRONMENT == "staging": +elif ENVIRONMENT == "dev": POTLOCK_PATTERN = r'\.staging\.potlock\.near$' NADABOT_PATTERN = r'\.staging\.nadabot\.near$' else: # mainnet/prod diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 6671590..0fc479e 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -347,7 +347,7 @@ def stellar_event_indexer(): start_sequence = get_ledger_sequence() # start_sequence = 12169 if not start_sequence: - start_sequence = 12169 + start_sequence = 552451 jobs_logger.info(f"Ingesting Stellar events from ledger {start_sequence}... contracts: {contract_ids}") try: # Fetch events for the current sequence diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 46123e8..8f72a30 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1324,6 +1324,7 @@ def get_block_height() -> int: record = BlockHeight.objects.filter(id=1).first() if record: return record.block_height + return 130852956 diff --git a/scripts/after_install.sh b/scripts/after_install.sh index 152ad3a..c9baee7 100644 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -50,18 +50,30 @@ poetry run python manage.py showmigrations >> "$LOG_FILE" 2>&1 # Logging full o PENDING_MIGRATIONS=$(poetry run python manage.py showmigrations | grep "\[ \]" | wc -l) # Count unapplied migrations if [ "$PENDING_MIGRATIONS" -gt 0 ]; then - echo "Migrations found; stopping services..." >> "$LOG_FILE" - sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat + # echo "Migrations found; stopping services..." >> "$LOG_FILE" + # sudo systemctl stop gunicorn celery-indexer-worker celery-beat-worker celery-beat echo 'Applying migrations...' >> "$LOG_FILE" poetry run python manage.py migrate >> "$LOG_FILE" 2>&1 - echo 'Starting services...' >> "$LOG_FILE" - sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat + # echo 'Starting services...' >> "$LOG_FILE" + # sudo systemctl start gunicorn celery-indexer-worker celery-beat-worker celery-beat else - echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" - poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 - sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat + echo 'No migrations found.' >> "$LOG_FILE" + # echo 'No migrations found. Running collectstatic and restarting services...' >> "$LOG_FILE" + # poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 + # sudo systemctl restart gunicorn celery-indexer-worker celery-beat-worker celery-beat fi +# Collect static +echo 'Running collectstatic...' >> "$LOG_FILE" +poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 + +# Gracefully reload Gunicorn to apply the changes without downtime +echo 'Reloading Gunicorn...' >> "$LOG_FILE" +sudo systemctl kill --signal=HUP gunicorn + +echo 'Restarting services...' >> "$LOG_FILE" +sudo systemctl restart celery-indexer-worker celery-beat-worker celery-beat + echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" \ No newline at end of file From 441b2eaaf2437c4b9ed52613a19b17bb2fc55136 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 21 Oct 2024 12:08:52 +0100 Subject: [PATCH 299/454] remove file copy flow --- .github/workflows/deploy-prod.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 3dfae34..93b2ded 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -25,8 +25,8 @@ jobs: role-session-name: GitHub_to_AWS_via_FederatedOIDC aws-region: ${{ env.AWS_REGION }} - - name: Generate appspec.yml for prod - run: cp appspec.yml appspec.yml + # - name: Generate appspec.yml for prod + # run: cp appspec.yml appspec.yml - name: Set environment variables id: vars From ffdb806f9a0016124f356d1c8a6af3e5c33c764c Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 21 Oct 2024 12:28:00 +0100 Subject: [PATCH 300/454] point to correct s3 --- .github/workflows/deploy-prod.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 93b2ded..dd551b9 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -32,8 +32,8 @@ jobs: id: vars run: | echo "DATETIME=$(date +'%Y-%m-%d_%H-%M-%S')" >> $GITHUB_ENV - echo "FILENAME=django-indexer-${DATETIME}.zip" >> $GITHUB_ENV - echo "S3_BUCKET=django-indexer" >> $GITHUB_ENV + echo "FILENAME=django-indexer-prod-${DATETIME}.zip" >> $GITHUB_ENV + echo "S3_BUCKET=django-indexer-prod" >> $GITHUB_ENV - name: Create zip of repository run: zip -r "${{ env.FILENAME }}" . From 37eedb432213c35e42ebfb05a4e5cb814f47d49b Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 21 Oct 2024 18:35:45 +0100 Subject: [PATCH 301/454] update allowed host --- base/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/base/settings.py b/base/settings.py index 67e62b4..2bc0628 100644 --- a/base/settings.py +++ b/base/settings.py @@ -32,6 +32,7 @@ ALLOWED_HOSTS = [ "ec2-100-27-57-47.compute-1.amazonaws.com", + "ec2-44-208-38-31.compute-1.amazonaws.com", "127.0.0.1", "dev.potlock.io", "test-dev.potlock.io", From 3a63572cfe87bc3b758d9392e9397f1f60cf0946 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 23 Oct 2024 04:02:48 +0100 Subject: [PATCH 302/454] configure sample rate for sentry --- base/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index 2bc0628..4013be7 100644 --- a/base/settings.py +++ b/base/settings.py @@ -379,7 +379,7 @@ # Set profiles_sample_rate to 1.0 to profile 100% # of sampled transactions. # We recommend adjusting this value in production. - profiles_sample_rate=1.0, + profiles_sample_rate=1.0 if ENVIRONMENT in ["testnet", "dev"] else 0.05, ) From ab258d389b94162c0e066faf7d1eeef845911845 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 23 Oct 2024 11:14:51 +0100 Subject: [PATCH 303/454] return from job if no contract --- indexer_app/tasks.py | 2 ++ indexer_app/utils.py | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 0fc479e..7c3abb0 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -344,6 +344,8 @@ def stellar_event_indexer(): else "https://horizon.stellar.org" ) contract_ids = [settings.STELLAR_CONTRACT_ID, settings.STELLAR_PROJECTS_REGISTRY_CONTRACT] + if contract_ids == ['', '']: + return start_sequence = get_ledger_sequence() # start_sequence = 12169 if not start_sequence: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 8f72a30..46123e8 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1324,7 +1324,6 @@ def get_block_height() -> int: record = BlockHeight.objects.filter(id=1).first() if record: return record.block_height - return 130852956 From 251c52e9255f89c3ec1a0d128df3411ff2b847b3 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 5 Nov 2024 03:24:09 +0100 Subject: [PATCH 304/454] index near grantpicks --- base/settings.py | 4 +- chains/migrations/0003_add_stellar_chain.py | 23 +++ ..._round_chain_rounddeposit_memo_and_more.py | 45 +++++ .../migrations/0003_alter_votepair_project.py | 24 +++ grantpicks/models.py | 29 ++- indexer_app/handler.py | 48 ++++- indexer_app/tasks.py | 2 +- indexer_app/utils.py | 170 +++++++++++------- .../0016_alter_potapplication_project.py | 27 +++ pots/models.py | 2 +- 10 files changed, 304 insertions(+), 70 deletions(-) create mode 100644 chains/migrations/0003_add_stellar_chain.py create mode 100644 grantpicks/migrations/0002_round_chain_rounddeposit_memo_and_more.py create mode 100644 grantpicks/migrations/0003_alter_votepair_project.py create mode 100644 pots/migrations/0016_alter_potapplication_project.py diff --git a/base/settings.py b/base/settings.py index 4013be7..84d5d3c 100644 --- a/base/settings.py +++ b/base/settings.py @@ -64,8 +64,8 @@ POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") # NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") -STELLAR_CONTRACT_ID = "CCVVNTUD6CFPKZ2C4JAZIQGCAK2S6D6KPP5IELGHTHHJPYV2B62GPJTK" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") -STELLAR_PROJECTS_REGISTRY_CONTRACT = "CAUINLSA42RCTY35UFGOM2NLKMSRM6FW6NP7AR4GTUZHUQZZWB6CRBSJ" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_CONTRACT_ID = "CBMCMOS3KX2R3AFOG3SZFPOFSX2ASMIC3TWF5FTIQTQSLZ6G3RUBQUM5" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_PROJECTS_REGISTRY_CONTRACT = "CDOR6NBGSIE6ARJZHRAJAEDN2NG4QS67LSQRMJETVX6GHEJELLVPXWJQ" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) diff --git a/chains/migrations/0003_add_stellar_chain.py b/chains/migrations/0003_add_stellar_chain.py new file mode 100644 index 0000000..000deb6 --- /dev/null +++ b/chains/migrations/0003_add_stellar_chain.py @@ -0,0 +1,23 @@ +from django.db import migrations + + +def create_stellar_chain(apps, schema_editor): + Chain = apps.get_model("chains", "Chain") + # Create the "stellar" chain + stellar_chain, created = Chain.objects.get_or_create( + name="stellar", defaults={"evm_compat": False} + ) + + # Set "stellar" chain as the default for all existing accounts + Round = apps.get_model("grantpicks", "Round") + Round.objects.update(chain=stellar_chain) + print("Updated all Rounds to use stellar chain") + + +class Migration(migrations.Migration): + + dependencies = [("chains", "0001_initial"), ("chains", "0002_add_near_chain"), ("grantpicks", "0001_initial"), ("grantpicks", "0002_round_chain_rounddeposit_memo_and_more")] + + operations = [ + migrations.RunPython(create_stellar_chain), + ] diff --git a/grantpicks/migrations/0002_round_chain_rounddeposit_memo_and_more.py b/grantpicks/migrations/0002_round_chain_rounddeposit_memo_and_more.py new file mode 100644 index 0000000..cf08d12 --- /dev/null +++ b/grantpicks/migrations/0002_round_chain_rounddeposit_memo_and_more.py @@ -0,0 +1,45 @@ +# Generated by Django 5.0.6 on 2024-11-04 09:56 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ("chains", "0002_add_near_chain"), + ("grantpicks", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="round", + name="chain", + field=models.ForeignKey( + blank=True, + help_text="Blockchain this round was created on.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="rounds", + related_query_name="round", + to="chains.chain", + ), + ), + migrations.AddField( + model_name="rounddeposit", + name="memo", + field=models.TextField( + blank=True, help_text="Deposit memo.", null=True, verbose_name="memo" + ), + ), + migrations.AlterField( + model_name="round", + name="approved_projects", + field=models.ManyToManyField( + help_text="Projects Approved for round.", + related_name="rounds_approved_in", + to="accounts.account", + ), + ), + ] diff --git a/grantpicks/migrations/0003_alter_votepair_project.py b/grantpicks/migrations/0003_alter_votepair_project.py new file mode 100644 index 0000000..2044c04 --- /dev/null +++ b/grantpicks/migrations/0003_alter_votepair_project.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.6 on 2024-11-04 16:41 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ("grantpicks", "0002_round_chain_rounddeposit_memo_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="votepair", + name="project", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="vote_pairs", + to="accounts.account", + ), + ), + ] diff --git a/grantpicks/models.py b/grantpicks/models.py index 26f717b..9fdd073 100644 --- a/grantpicks/models.py +++ b/grantpicks/models.py @@ -7,6 +7,7 @@ from base.logging import logger from accounts.models import Account +from chains.models import Chain from pots.models import PotApplication from tokens.models import Token @@ -103,6 +104,15 @@ class Round(models.Model): unique=True, help_text=_("Round ID in contract"), ) + chain = models.ForeignKey( + Chain, + null=True, + blank=True, + on_delete=models.CASCADE, + related_name="rounds", + related_query_name="round", + help_text=_("Blockchain this round was created on."), + ) factory_contract = models.ForeignKey( Account, related_name="spawned_rounds", @@ -198,7 +208,7 @@ class Round(models.Model): help_text=_("Max participants."), ) approved_projects = models.ManyToManyField( - Project, + Account, related_name="rounds_approved_in", help_text=_("Projects Approved for round."), ) @@ -337,6 +347,15 @@ def update_vault_usd_equivalent(self): ) except Exception as e: logger.error(f"Failed to calculate and stellar vault USD prices: {e}") + + + def save(self, *args, **kwargs): + if self._state.adding: # If the account is being created (not updated) + if not self.chain_id: + # default to stellar chain when none is provided + self.chain = Chain.objects.get(name="stellar") + super().save(*args, **kwargs) + class RoundDeposit(models.Model): @@ -404,6 +423,12 @@ class RoundDeposit(models.Model): help_text=_("Deposit date."), db_index=True, ) + memo = models.TextField( + _("memo"), + null=True, + blank=True, + help_text=_("Deposit memo."), + ) tx_hash = models.CharField( _("transaction hash"), null=True, @@ -430,7 +455,7 @@ class Meta: class VotePair(models.Model): vote = models.ForeignKey(Vote, on_delete=models.CASCADE, related_name='pairs') pair_id = models.PositiveIntegerField() - project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='vote_pairs') + project = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='vote_pairs') class Meta: unique_together = ('vote', 'pair_id') diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 18b7eb8..69b67b9 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -3,6 +3,8 @@ import json import time from datetime import datetime +from asgiref.sync import sync_to_async + from django.conf import settings from django.core.cache import cache @@ -17,6 +19,9 @@ from .logging import log_memory_usage, logger from .utils import ( + create_or_update_round, + create_round_application, + create_round_payout, handle_add_nadabot_admin, # handle_batch_donations, handle_add_stamp, handle_default_list_status_change, @@ -49,6 +54,10 @@ handle_social_profile_update, handle_transfer_payout, handle_update_default_human_threshold, + process_rounds_deposit_event, + process_vote_event, + update_application, + update_round_payout, ) @@ -154,12 +163,47 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess await handle_list_admin_ops( parsed_log.get("data")[0], receiver_id, signer_id, receipt.receipt_id ) - if event_name == "update_lis": + if event_name == "update_list": if receiver_id != LISTS_CONTRACT: continue await handle_list_update( - parsed_log.get("data")[0], receiver_id, signer_id, receipt.receipt_id + receiver_id, signer_id, None, parsed_log.get("data")[0], + ) + if event_name == "create_round" or event_name == "round_updated": + create_or_update_round_async = sync_to_async(create_or_update_round) + await create_or_update_round_async( + parsed_log.get("data")[0], receiver_id, now_datetime, 1 + ) + if event_name == "deposit": + round_depo = sync_to_async(process_rounds_deposit_event) + await round_depo(parsed_log.get("data")[0], receipt.receipt_id, 1) + if event_name == "application_created": + create_round_application_async = sync_to_async(create_round_application) + await create_round_application_async( + parsed_log.get("data")[0], receipt.receipt_id, 1 + ) + if event_name == "application_updated": + update_application_async = sync_to_async(update_application) + await update_application_async( + parsed_log.get("data")[0], receipt.receipt_id, signer_id, 1 + ) + if event_name == "vote": + process_vote_event_async = sync_to_async(process_vote_event) + await process_vote_event_async( + parsed_log.get("data")[0], receipt.receipt_id, 1 + ) + if event_name == "payouts_set": + create_round_payout_aync = sync_to_async(create_round_payout) + await create_round_payout_aync( + parsed_log.get("data")[0], receipt.receipt_id, 1 + ) + if event_name == "payout_processed": + update_round_payout_aync = sync_to_async(update_round_payout) + await update_round_payout_aync( + parsed_log.get("data")[0], receipt.receipt_id, 1 ) + if event_name == "payouts_challenge_created": + pass except json.JSONDecodeError: logger.warning( f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 7c3abb0..074fd4b 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -349,7 +349,7 @@ def stellar_event_indexer(): start_sequence = get_ledger_sequence() # start_sequence = 12169 if not start_sequence: - start_sequence = 552451 + start_sequence = 774951 jobs_logger.info(f"Ingesting Stellar events from ledger {start_sequence}... contracts: {contract_ids}") try: # Fetch events for the current sequence diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 46123e8..32fcfc7 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -13,6 +13,7 @@ from accounts.models import Account from activities.models import Activity +from chains.models import Chain from donations.models import Donation from grantpicks.models import Project, ProjectContact, ProjectContract, ProjectFundingHistory, ProjectRepository, ProjectStatus, Round, RoundDeposit, Vote, VotePair from indexer_app.models import BlockHeight @@ -1324,6 +1325,7 @@ def get_block_height() -> int: record = BlockHeight.objects.filter(id=1).first() if record: return record.block_height + return 178243042 @@ -1343,43 +1345,47 @@ def get_ledger_sequence() -> int: return record.block_height -def update_approved_projects(event_data): +def update_approved_projects(event_data, chain_id=2): round_id, project_ids = event_data[0], event_data[1] with transaction.atomic(): try: - round_obj = Round.objects.get(on_chain_id=round_id) + round_obj = Round.objects.get(on_chain_id=round_id, chain_id=chain_id) for ids in project_ids: project = Project.objects.get(id=ids) - round_obj.approved_projects.add(project) + round_obj.approved_projects.add(project.owner) return True except Exception as e: logger.error(f"Error updating application for Round {round_id}: {e}") return False -def update_application(event_data, txhash): - round_id, application_data, reviewer_id = event_data[0], event_data[1], event_data[2] +def update_application(event_data, txhash, reviewer_id=None, chain_id=2): + if type(event_data) == list: + round_id, application_data, reviewer_id = event_data[0], event_data[1], event_data[2] + else: + event_data = event_data['application'] + round_id, application_data, reviewer_id = event_data["round_id"], event_data, reviewer_id with transaction.atomic(): try: - round_obj = Round.objects.get(on_chain_id=round_id) - applicant = Account.objects.get(id=application_data['applicant_id']) + round_obj = Round.objects.get(on_chain_id=round_id, chain_id=chain_id) + applicant = Account.objects.get(id=application_data['applicant_id'], chain_id=chain_id) - reviewer = Account.objects.get(id=reviewer_id) + reviewer = Account.objects.get(id=reviewer_id, chain_id=chain_id) - status = PotApplicationStatus[application_data['status'][0].upper()] + status = PotApplicationStatus[application_data['status'].upper()] submitted_at = datetime.fromtimestamp(application_data['submited_ms'] / 1000) updated_at = datetime.fromtimestamp(application_data['updated_ms'] / 1000) defaults = { - "notes": application_data.get("review_notes"), - "status": application_data['status'][0], + "notes": application_data.get("review_note"), + "status": status, "tx_hash": txhash, } appl = PotApplication.objects.filter( - applicant_id=application_data["applicant_id"] + applicant=applicant ).first() PotApplicationReview.objects.update_or_create( @@ -1390,8 +1396,8 @@ def update_application(event_data, txhash): ) # Update the PotApplication object - PotApplication.objects.filter(applicant_id=application_data["applicant_id"]).update( - **{"status": application_data["status"], "updated_at": updated_at} + PotApplication.objects.filter(applicant=applicant, round=round_obj).update( + **{"status": status, "updated_at": updated_at} ) return True @@ -1402,14 +1408,17 @@ def update_application(event_data, txhash): -def process_vote_event(event_data, tx_hash): +def process_vote_event(event_data, tx_hash, chain_id=2): try: with transaction.atomic(): - - round_id, vote_data = event_data[0], event_data[1] + if type(event_data) == list: + round_id, vote_data = event_data[0], event_data[1] + else: + event_data = event_data['vote'] + round_id, vote_data = event_data.get("round_id", 2), event_data - round_obj = Round.objects.get(on_chain_id=round_id) - voter, _ = Account.objects.get_or_create(id=vote_data['voter']) + round_obj = Round.objects.get(on_chain_id=round_id, chain_id=chain_id) + voter, _ = Account.objects.get_or_create(id=vote_data['voter'], chain_id=chain_id) voted_at = datetime.fromtimestamp(vote_data['voted_ms'] / 1000) # Create or update the Vote @@ -1422,37 +1431,45 @@ def process_vote_event(event_data, tx_hash): } ) + + # Process vote pairs for pick in vote_data['picks']: - pair_id = pick['pair_id'] - project_id = pick['project_id'] - + if chain_id == 1: + pair_id = pick['pair_id'] + project_id = pick['voted_project'] + else: + pair_id = pick['pair_id'] + project_id = Project.objects.get(id=pick['project_id']).owner.id + # Assuming project_id corresponds to PotApplication id - project_voted = Project.objects.get(id=project_id) + VotePair.objects.update_or_create( vote=vote, pair_id=pair_id, - defaults={'project': project_voted} + defaults={'project_id': project_id} ) - logger.info(f"Processed vote for Round: {round_id}, Voter: {voter.id}") + logger.info(f"Processed vote for Round: {round_id}, Voter: {voter.id}, Project: {project_id}") return True except Exception as e: logger.error(f"Error processing vote for Round: {str(e)}") return False -def process_project_event(event_data): +def process_project_event(event_data, chain_id=2): try: # Extract project data project_data = event_data + logger.info(f"process_project_event: {project_data}, {chain_id}") + # Create or get the owner Account - owner, _ = Account.objects.get_or_create(id=project_data['owner']) + owner, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=project_data['owner']) # Create or get the payout Account - payout_address, _ = Account.objects.get_or_create(id=project_data['payout_address']) + payout_address, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=project_data['payout_address']) # Create the Project project, created = Project.objects.update_or_create( @@ -1496,7 +1513,7 @@ def process_project_event(event_data): ProjectFundingHistory.objects.create( source=funding_data['source'], amount=funding_data['amount'], - denomination=funding_data['denomiation'], # Note: There's a typo in the event data + denomination=funding_data['denomination'], # Note: There's a typo in the event data description=funding_data['description'], timestamp=timezone.datetime.fromtimestamp(funding_data['funded_ms'] / 1000) ) @@ -1524,21 +1541,38 @@ def process_project_event(event_data): -def create_or_update_round(event_data, contract_id, timestamp): +def create_or_update_round(event_data, contract_id, timestamp, chain_id=2): try: + logger.info(f"create_or_update_round: {event_data}, {contract_id}, {chain_id}") # Create Round + # event_data = event_data.get('round_detail') round_id = event_data.get('id') owner_address = event_data.get('owner') - owner, _ = Account.objects.get_or_create(id=owner_address) - factory_contract, _ = Account.objects.get_or_create(id=contract_id) - remaining_dist_address = event_data.get('remaining_dist_address') - remaining_dist_address_obj, _ = Account.objects.get_or_create(id=remaining_dist_address) + owner, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=owner_address) + factory_contract, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=contract_id) + remaining_dist_address = event_data.get('remaining_dist_address', event_data.get('remaining_funds_redistribution_recipient')) + if remaining_dist_address: + remaining_dist_address_obj, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=remaining_dist_address) + + remaining_dist_by = event_data.get('remaining_dist_by', event_data.get('remaining_funds_redistributed_by')) + if remaining_dist_by: + remaining_dist_by_obj, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=remaining_dist_by) + + near_round_cp = event_data.get('round_complete') + if near_round_cp: + round_time_stamp = timestamp + elif event_data.get('round_complete_ms'): + round_time_stamp = datetime.fromtimestamp(event_data.get('round_complete_ms') / 1000) + else: + round_time_stamp = None round_obj, created = Round.objects.update_or_create( on_chain_id=round_id, + chain=Chain.objects.get(id=chain_id), defaults={ 'owner': owner, 'factory_contract': factory_contract, + 'chain_id': chain_id, 'name': event_data.get('name'), 'description': event_data.get('description'), 'expected_amount': event_data.get('expected_amount'), @@ -1547,23 +1581,23 @@ def create_or_update_round(event_data, contract_id, timestamp): 'voting_start': datetime.fromtimestamp(event_data.get('voting_start_ms') / 1000), 'voting_end': datetime.fromtimestamp(event_data.get('voting_end_ms') / 1000), 'use_whitelist': event_data.get('use_whitelist'), - 'use_vault': event_data.get('use_vault'), + 'use_vault': event_data.get('use_vault', False), 'num_picks_per_voter': event_data.get('num_picks_per_voter'), 'max_participants': event_data.get('max_participants'), 'allow_applications': event_data.get('allow_applications'), - 'allow_remaining_dist': event_data.get('allow_remaining_dist'), + 'allow_remaining_dist': event_data.get('allow_remaining_dist', event_data.get('allow_remaining_funds_redistribution')), 'compliance_end': datetime.fromtimestamp(event_data.get('compliance_end_ms') / 1000) if event_data.get('compliance_end_ms') else None, 'compliance_period_ms': event_data.get('compliance_period_ms'), - 'compliance_req_desc': event_data.get('compliance_req_desc'), + 'compliance_req_desc': event_data.get('compliance_req_desc', event_data.get('compliance_requirement_description')), 'cooldown_end': datetime.fromtimestamp(event_data.get('cooldown_end_ms') / 1000) if event_data.get('cooldown_end_ms') else None, 'cooldown_period_ms': event_data.get('cooldown_period_ms'), - 'is_video_required': event_data.get('is_video_required'), + 'is_video_required': event_data.get('is_video_required') or event_data.get('application_requires_video', False) , 'referrer_fee_basis_points': event_data.get('referrer_fee_basis_points'), 'remaining_dist_address_id': remaining_dist_address, 'remaining_dist_at_ms': datetime.fromtimestamp(event_data.get('remaining_dist_at_ms') / 1000) if event_data.get('remaining_dist_at_ms') else None, - 'remaining_dist_by_id': event_data.get('remaining_dist_by'), - 'remaining_dist_memo': event_data.get('remaining_dist_memo'), - 'round_complete': datetime.fromtimestamp(event_data.get('round_complete_ms') / 1000) if event_data.get('round_complete_ms') else None, + 'remaining_dist_by_id': remaining_dist_by, + 'remaining_dist_memo': event_data.get('remaining_dist_memo', event_data.get('remaining_funds_redistribution_memo')), + 'round_complete': round_time_stamp, 'vault_total_deposits': event_data.get('vault_total_deposits'), 'current_vault_balance': event_data.get('current_vault_balance'), 'deployed_at': timestamp @@ -1590,7 +1624,7 @@ def process_application_to_round(event_data, tx_hash): # Process application to Round round_id, application_data = event_data[0], event_data[1] applicant_id = application_data.get('applicant_id') - status = PotApplicationStatus[application_data['status'][0].upper()] + status = PotApplicationStatus[application_data['status'].upper()] submitted_at = datetime.fromtimestamp(application_data['submited_ms'] / 1000) updated_at = ( datetime.fromtimestamp(application_data['updated_ms'] / 1000) @@ -1619,25 +1653,31 @@ def process_application_to_round(event_data, tx_hash): -def create_round_application(event_data, tx_hash): +def create_round_application(event_data, tx_hash, chain_id=2): try: - - round_id, application_data = event_data[0], event_data[1] - applicant, _ = Account.objects.get_or_create(id=application_data["applicant_id"]) - round_obj = Round.objects.get(on_chain_id=round_id) + logger.info(f"create_round_application: {event_data}, {tx_hash}, {chain_id}") + if type(event_data) == list: + round_id, application_data = event_data[0], event_data[1] + else: + event_data = event_data['application'] + round_id, application_data = event_data["round_id"], event_data + applicant, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=application_data["applicant_id"]) + round_obj = Round.objects.get(on_chain_id=round_id, chain_id=chain_id) + status = PotApplicationStatus[application_data['status'].upper()] logger.info(f"Creating application for round: {round_id}") appl_defaults = { "message": application_data["applicant_note"], "submitted_at": datetime.fromtimestamp(application_data["submited_ms"] / 1000), - "status": application_data["status"][0], + "status": status, "tx_hash": tx_hash, } + PotApplication.objects.update_or_create( applicant=applicant, round=round_obj, - project_id=application_data["project_id"], + project=applicant, defaults=appl_defaults, ) @@ -1647,31 +1687,36 @@ def create_round_application(event_data, tx_hash): return False -def process_rounds_deposit_event(event_data, tx_hash): +def process_rounds_deposit_event(event_data, tx_hash, chain_id=2): try: - + logger.info(f"process_rounds_deposit_event: {event_data}, {tx_hash}, {chain_id}") # Process deposit event - round_id, deposit_data = event_data - round_obj = Round.objects.get(on_chain_id=round_id) + if type(event_data) == list: + round_id, deposit_data = event_data + else: + event_data = event_data['deposit'] + round_id, deposit_data = event_data["round_id"], event_data + round_obj = Round.objects.get(on_chain_id=round_id, chain_id=chain_id) amount = deposit_data["total_amount"] - depositor, _ = Account.objects.get_or_create(id=deposit_data["depositor_id"]) + depositor, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=deposit_data["depositor_id"]) # Create or update a RoundDeposit object deposit, created = RoundDeposit.objects.update_or_create( round=round_obj, - on_chain_id=deposit_data["deposit_id"], + on_chain_id=deposit_data.get("deposit_id", deposit_data.get("id")), depositor=depositor, defaults={ 'amount': amount, 'protocol_fee': deposit_data["protocol_fee"], 'referrer_fee': deposit_data["referrer_fee"], + 'memo': deposit_data["memo"], 'tx_hash': tx_hash, 'deposit_at': datetime.fromtimestamp(deposit_data["deposited_at"] / 1000), } ) - round_obj.vault_total_deposits = str(int(round_obj.vault_total_deposits or 0) + amount) - round_obj.current_vault_balance = str(int(round_obj.current_vault_balance or 0) + deposit_data["net_amount"]) + round_obj.vault_total_deposits = str(int(round_obj.vault_total_deposits or 0) + int(amount)) + round_obj.current_vault_balance = str(int(round_obj.current_vault_balance or 0) + int(deposit_data["net_amount"])) round_obj.save() round_obj.update_vault_usd_equivalent() @@ -1683,15 +1728,15 @@ def process_rounds_deposit_event(event_data, tx_hash): -def create_round_payout(event_data, tx_hash): +def create_round_payout(event_data, tx_hash, chain_id=2): try: - + logger.info(f"create_round_payout: {event_data}, {tx_hash}, {chain_id}") round_id, payout_data = event_data amount = payout_data["amount"] recipient_id = payout_data["recipient_id"] memo = payout_data.get("memo") - stellar_token_acct, _ = Account.objects.get_or_create(defaults={"chain_id":2},id="stellar") + stellar_token_acct, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id},id="stellar") stellar_token, _ = Token.objects.get_or_create( account=stellar_token_acct ) @@ -1714,13 +1759,14 @@ def create_round_payout(event_data, tx_hash): return False -def update_round_payout(event_data, tx_hash): +def update_round_payout(event_data, tx_hash, chain_id=2): try: + logger.info(f"update_round_payout: {event_data}, {tx_hash}, {chain_id}") _, payout_data = event_data payout_on_chain_id = payout_data["id"] amount = payout_data["amount"] paid_at_ms = payout_data.get("paid_at_ms") - recipient_id, _ = Account.objects.get_or_create(id=payout_data["recipient_id"]) + recipient_id, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=payout_data["recipient_id"]) memo = payout_data.get("memo") payout = PotPayout.objects.get(on_chain_id=payout_on_chain_id) payout.amount = amount diff --git a/pots/migrations/0016_alter_potapplication_project.py b/pots/migrations/0016_alter_potapplication_project.py new file mode 100644 index 0000000..fad9ce7 --- /dev/null +++ b/pots/migrations/0016_alter_potapplication_project.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.6 on 2024-11-04 09:56 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ("pots", "0015_alter_potpayout_options_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="potapplication", + name="project", + field=models.ForeignKey( + blank=True, + help_text="Project that applied.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="applications", + to="accounts.account", + ), + ), + ] diff --git a/pots/models.py b/pots/models.py index e7254fc..2c177be 100644 --- a/pots/models.py +++ b/pots/models.py @@ -423,7 +423,7 @@ class PotApplication(models.Model): db_index=True, ) project = models.ForeignKey( - "grantpicks.Project", + Account, on_delete=models.CASCADE, related_name="applications", null=True, From 2ff3dd7c71c20cba93e92dfd0be419075f33492f Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 5 Nov 2024 09:21:48 +0100 Subject: [PATCH 305/454] get chain by name: --- indexer_app/handler.py | 14 ++++----- indexer_app/utils.py | 64 ++++++++++++++++++++++++------------------ 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 69b67b9..48a2eed 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -172,35 +172,35 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess if event_name == "create_round" or event_name == "round_updated": create_or_update_round_async = sync_to_async(create_or_update_round) await create_or_update_round_async( - parsed_log.get("data")[0], receiver_id, now_datetime, 1 + parsed_log.get("data")[0], receiver_id, now_datetime, "NEAR" ) if event_name == "deposit": round_depo = sync_to_async(process_rounds_deposit_event) - await round_depo(parsed_log.get("data")[0], receipt.receipt_id, 1) + await round_depo(parsed_log.get("data")[0], receipt.receipt_id, "NEAR") if event_name == "application_created": create_round_application_async = sync_to_async(create_round_application) await create_round_application_async( - parsed_log.get("data")[0], receipt.receipt_id, 1 + parsed_log.get("data")[0], receipt.receipt_id, "NEAR" ) if event_name == "application_updated": update_application_async = sync_to_async(update_application) await update_application_async( - parsed_log.get("data")[0], receipt.receipt_id, signer_id, 1 + parsed_log.get("data")[0], receipt.receipt_id, signer_id, "NEAR" ) if event_name == "vote": process_vote_event_async = sync_to_async(process_vote_event) await process_vote_event_async( - parsed_log.get("data")[0], receipt.receipt_id, 1 + parsed_log.get("data")[0], receipt.receipt_id, "NEAR" ) if event_name == "payouts_set": create_round_payout_aync = sync_to_async(create_round_payout) await create_round_payout_aync( - parsed_log.get("data")[0], receipt.receipt_id, 1 + parsed_log.get("data")[0], receipt.receipt_id, "NEAR" ) if event_name == "payout_processed": update_round_payout_aync = sync_to_async(update_round_payout) await update_round_payout_aync( - parsed_log.get("data")[0], receipt.receipt_id, 1 + parsed_log.get("data")[0], receipt.receipt_id, "NEAR" ) if event_name == "payouts_challenge_created": pass diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 32fcfc7..e512fbf 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1345,12 +1345,13 @@ def get_ledger_sequence() -> int: return record.block_height -def update_approved_projects(event_data, chain_id=2): +def update_approved_projects(event_data, chain_id="stellar"): round_id, project_ids = event_data[0], event_data[1] with transaction.atomic(): try: - round_obj = Round.objects.get(on_chain_id=round_id, chain_id=chain_id) + chain = Chain.objects.get(name=chain_id) + round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) for ids in project_ids: project = Project.objects.get(id=ids) round_obj.approved_projects.add(project.owner) @@ -1360,7 +1361,7 @@ def update_approved_projects(event_data, chain_id=2): logger.error(f"Error updating application for Round {round_id}: {e}") return False -def update_application(event_data, txhash, reviewer_id=None, chain_id=2): +def update_application(event_data, txhash, reviewer_id=None, chain_id="stellar"): if type(event_data) == list: round_id, application_data, reviewer_id = event_data[0], event_data[1], event_data[2] else: @@ -1369,10 +1370,11 @@ def update_application(event_data, txhash, reviewer_id=None, chain_id=2): with transaction.atomic(): try: - round_obj = Round.objects.get(on_chain_id=round_id, chain_id=chain_id) - applicant = Account.objects.get(id=application_data['applicant_id'], chain_id=chain_id) + chain = Chain.objects.get(name=chain_id) + round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) + applicant = Account.objects.get(id=application_data['applicant_id'], chain=chain) - reviewer = Account.objects.get(id=reviewer_id, chain_id=chain_id) + reviewer = Account.objects.get(id=reviewer_id, chain=chain) status = PotApplicationStatus[application_data['status'].upper()] submitted_at = datetime.fromtimestamp(application_data['submited_ms'] / 1000) @@ -1408,7 +1410,7 @@ def update_application(event_data, txhash, reviewer_id=None, chain_id=2): -def process_vote_event(event_data, tx_hash, chain_id=2): +def process_vote_event(event_data, tx_hash, chain_id="stellar"): try: with transaction.atomic(): if type(event_data) == list: @@ -1417,8 +1419,9 @@ def process_vote_event(event_data, tx_hash, chain_id=2): event_data = event_data['vote'] round_id, vote_data = event_data.get("round_id", 2), event_data - round_obj = Round.objects.get(on_chain_id=round_id, chain_id=chain_id) - voter, _ = Account.objects.get_or_create(id=vote_data['voter'], chain_id=chain_id) + chain = Chain.objects.get(name=chain_id) + round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) + voter, _ = Account.objects.get_or_create(id=vote_data['voter'], chain=chain) voted_at = datetime.fromtimestamp(vote_data['voted_ms'] / 1000) # Create or update the Vote @@ -1458,18 +1461,20 @@ def process_vote_event(event_data, tx_hash, chain_id=2): return False -def process_project_event(event_data, chain_id=2): +def process_project_event(event_data, chain_id="stellar"): try: # Extract project data project_data = event_data logger.info(f"process_project_event: {project_data}, {chain_id}") + chain = Chain.objects.get(name=chain_id) + # Create or get the owner Account - owner, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=project_data['owner']) + owner, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=project_data['owner']) # Create or get the payout Account - payout_address, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=project_data['payout_address']) + payout_address, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=project_data['payout_address']) # Create the Project project, created = Project.objects.update_or_create( @@ -1541,22 +1546,23 @@ def process_project_event(event_data, chain_id=2): -def create_or_update_round(event_data, contract_id, timestamp, chain_id=2): +def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar"): try: logger.info(f"create_or_update_round: {event_data}, {contract_id}, {chain_id}") # Create Round # event_data = event_data.get('round_detail') round_id = event_data.get('id') owner_address = event_data.get('owner') - owner, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=owner_address) - factory_contract, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=contract_id) + chain = Chain.objects.get(name=chain_id) + owner, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=owner_address) + factory_contract, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=contract_id) remaining_dist_address = event_data.get('remaining_dist_address', event_data.get('remaining_funds_redistribution_recipient')) if remaining_dist_address: - remaining_dist_address_obj, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=remaining_dist_address) + remaining_dist_address_obj, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=remaining_dist_address) remaining_dist_by = event_data.get('remaining_dist_by', event_data.get('remaining_funds_redistributed_by')) if remaining_dist_by: - remaining_dist_by_obj, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=remaining_dist_by) + remaining_dist_by_obj, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=remaining_dist_by) near_round_cp = event_data.get('round_complete') if near_round_cp: @@ -1653,7 +1659,7 @@ def process_application_to_round(event_data, tx_hash): -def create_round_application(event_data, tx_hash, chain_id=2): +def create_round_application(event_data, tx_hash, chain_id="stellar"): try: logger.info(f"create_round_application: {event_data}, {tx_hash}, {chain_id}") if type(event_data) == list: @@ -1661,8 +1667,9 @@ def create_round_application(event_data, tx_hash, chain_id=2): else: event_data = event_data['application'] round_id, application_data = event_data["round_id"], event_data - applicant, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=application_data["applicant_id"]) - round_obj = Round.objects.get(on_chain_id=round_id, chain_id=chain_id) + chain = Chain.objects.get(name=chain_id) + applicant, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=application_data["applicant_id"]) + round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) status = PotApplicationStatus[application_data['status'].upper()] logger.info(f"Creating application for round: {round_id}") @@ -1687,7 +1694,7 @@ def create_round_application(event_data, tx_hash, chain_id=2): return False -def process_rounds_deposit_event(event_data, tx_hash, chain_id=2): +def process_rounds_deposit_event(event_data, tx_hash, chain_id="stellar"): try: logger.info(f"process_rounds_deposit_event: {event_data}, {tx_hash}, {chain_id}") # Process deposit event @@ -1696,9 +1703,10 @@ def process_rounds_deposit_event(event_data, tx_hash, chain_id=2): else: event_data = event_data['deposit'] round_id, deposit_data = event_data["round_id"], event_data - round_obj = Round.objects.get(on_chain_id=round_id, chain_id=chain_id) + chain = Chain.objects.get(name=chain_id) + round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) amount = deposit_data["total_amount"] - depositor, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=deposit_data["depositor_id"]) + depositor, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=deposit_data["depositor_id"]) # Create or update a RoundDeposit object deposit, created = RoundDeposit.objects.update_or_create( @@ -1728,7 +1736,7 @@ def process_rounds_deposit_event(event_data, tx_hash, chain_id=2): -def create_round_payout(event_data, tx_hash, chain_id=2): +def create_round_payout(event_data, tx_hash, chain_id="stellar"): try: logger.info(f"create_round_payout: {event_data}, {tx_hash}, {chain_id}") round_id, payout_data = event_data @@ -1736,7 +1744,8 @@ def create_round_payout(event_data, tx_hash, chain_id=2): recipient_id = payout_data["recipient_id"] memo = payout_data.get("memo") - stellar_token_acct, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id},id="stellar") + chain = Chain.objects.get(name=chain_id) + stellar_token_acct, _ = Account.objects.get_or_create(defaults={"chain":chain},id="stellar") stellar_token, _ = Token.objects.get_or_create( account=stellar_token_acct ) @@ -1759,14 +1768,15 @@ def create_round_payout(event_data, tx_hash, chain_id=2): return False -def update_round_payout(event_data, tx_hash, chain_id=2): +def update_round_payout(event_data, tx_hash, chain_id="stellar"): try: logger.info(f"update_round_payout: {event_data}, {tx_hash}, {chain_id}") _, payout_data = event_data payout_on_chain_id = payout_data["id"] amount = payout_data["amount"] paid_at_ms = payout_data.get("paid_at_ms") - recipient_id, _ = Account.objects.get_or_create(defaults={"chain_id":chain_id}, id=payout_data["recipient_id"]) + chain = Chain.objects.get(name=chain_id) + recipient_id, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=payout_data["recipient_id"]) memo = payout_data.get("memo") payout = PotPayout.objects.get(on_chain_id=payout_on_chain_id) payout.amount = amount From c31a00b4667fd8172441fdcf044486b54641e1fb Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 5 Nov 2024 09:26:17 +0100 Subject: [PATCH 306/454] change id to name: --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index e512fbf..43ed83f 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1574,7 +1574,7 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar round_obj, created = Round.objects.update_or_create( on_chain_id=round_id, - chain=Chain.objects.get(id=chain_id), + chain=Chain.objects.get(name=chain_id), defaults={ 'owner': owner, 'factory_contract': factory_contract, From 49f770b703b7bfe280b1943158ba2f3972439efe Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 5 Nov 2024 09:33:04 +0100 Subject: [PATCH 307/454] use name for chain --- indexer_app/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 43ed83f..248773a 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1574,11 +1574,11 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar round_obj, created = Round.objects.update_or_create( on_chain_id=round_id, - chain=Chain.objects.get(name=chain_id), + chain=chain, defaults={ 'owner': owner, 'factory_contract': factory_contract, - 'chain_id': chain_id, + 'chain': chain, 'name': event_data.get('name'), 'description': event_data.get('description'), 'expected_amount': event_data.get('expected_amount'), From be2820c1f23d4089d6f23f6ac879113837aa53fc Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 5 Nov 2024 09:50:11 +0100 Subject: [PATCH 308/454] use onchain id for project get --- indexer_app/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 248773a..07b0a1c 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1353,7 +1353,7 @@ def update_approved_projects(event_data, chain_id="stellar"): chain = Chain.objects.get(name=chain_id) round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) for ids in project_ids: - project = Project.objects.get(id=ids) + project = Project.objects.get(on_chain_id=ids) round_obj.approved_projects.add(project.owner) return True @@ -1443,7 +1443,7 @@ def process_vote_event(event_data, tx_hash, chain_id="stellar"): project_id = pick['voted_project'] else: pair_id = pick['pair_id'] - project_id = Project.objects.get(id=pick['project_id']).owner.id + project_id = Project.objects.get(on_chain_id=pick['project_id']).owner.id # Assuming project_id corresponds to PotApplication id From 26110372f472be87214b1eeea438feaa780ec97f Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 5 Nov 2024 10:13:57 +0100 Subject: [PATCH 309/454] get round details for chain: --- indexer_app/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 07b0a1c..45f7494 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1550,7 +1550,8 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar try: logger.info(f"create_or_update_round: {event_data}, {contract_id}, {chain_id}") # Create Round - # event_data = event_data.get('round_detail') + if chain_id == "NEAR": + event_data = event_data.get('round_detail') round_id = event_data.get('id') owner_address = event_data.get('owner') chain = Chain.objects.get(name=chain_id) From bbeb310eef7f0fec7595184df8aaf70252459c50 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 5 Nov 2024 10:47:26 +0100 Subject: [PATCH 310/454] fix chain id indexing --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 45f7494..3db8d4d 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1438,7 +1438,7 @@ def process_vote_event(event_data, tx_hash, chain_id="stellar"): # Process vote pairs for pick in vote_data['picks']: - if chain_id == 1: + if chain_id == "NEAR": pair_id = pick['pair_id'] project_id = pick['voted_project'] else: From b06976c608b01d54c529b382e4dc7a8f62779749 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 5 Nov 2024 12:43:55 +0100 Subject: [PATCH 311/454] sort round by chain, add constraint to round --- grantpicks/api.py | 10 ++++++ .../0004_alter_round_on_chain_id_and_more.py | 36 +++++++++++++++++++ grantpicks/models.py | 9 +++-- 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 grantpicks/migrations/0004_alter_round_on_chain_id_and_more.py diff --git a/grantpicks/api.py b/grantpicks/api.py index d4c301d..73c40ea 100644 --- a/grantpicks/api.py +++ b/grantpicks/api.py @@ -57,6 +57,12 @@ class RoundsListAPI(APIView, CustomSizePageNumberPagination): location=OpenApiParameter.QUERY, description="Sort by field, e.g., deployed_at, vault_total_deposits", ), + OpenApiParameter( + "chain", + str, + OpenApiParameter.QUERY, + description="Filter projects by chain", + ), *pagination_parameters, ], responses={ @@ -79,6 +85,10 @@ class RoundsListAPI(APIView, CustomSizePageNumberPagination): @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): rounds = Round.objects.all() + chain_param = request.query_params.get("chain") + if chain_param: + # chain = Chain.objects.get(name=chain_param) + rounds = rounds.filter(chain_id=chain_param) sort = request.query_params.get("sort", None) if sort == "deployed_at": rounds = rounds.order_by( diff --git a/grantpicks/migrations/0004_alter_round_on_chain_id_and_more.py b/grantpicks/migrations/0004_alter_round_on_chain_id_and_more.py new file mode 100644 index 0000000..a06f140 --- /dev/null +++ b/grantpicks/migrations/0004_alter_round_on_chain_id_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 5.0.6 on 2024-11-05 10:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("chains", "0003_add_stellar_chain"), + ("grantpicks", "0003_alter_votepair_project"), + ] + + operations = [ + migrations.AlterField( + model_name="round", + name="on_chain_id", + field=models.IntegerField( + help_text="Round ID in contract", verbose_name="contract round ID" + ), + ), + migrations.AlterField( + model_name="rounddeposit", + name="on_chain_id", + field=models.IntegerField( + help_text="Deposit ID in contract", verbose_name="contract deposit ID" + ), + ), + migrations.AlterUniqueTogether( + name="round", + unique_together={("chain", "on_chain_id")}, + ), + migrations.AlterUniqueTogether( + name="rounddeposit", + unique_together={("round", "on_chain_id")}, + ), + ] diff --git a/grantpicks/models.py b/grantpicks/models.py index 9fdd073..28ceaee 100644 --- a/grantpicks/models.py +++ b/grantpicks/models.py @@ -101,7 +101,6 @@ class Round(models.Model): on_chain_id = models.IntegerField( _("contract round ID"), null=False, - unique=True, help_text=_("Round ID in contract"), ) chain = models.ForeignKey( @@ -326,6 +325,9 @@ class Round(models.Model): help_text=_("Round complete date."), ) + class Meta: + unique_together = ('chain', 'on_chain_id') + @@ -357,7 +359,6 @@ def save(self, *args, **kwargs): super().save(*args, **kwargs) - class RoundDeposit(models.Model): id = models.AutoField( _("deposit id"), @@ -367,7 +368,6 @@ class RoundDeposit(models.Model): on_chain_id = models.IntegerField( _("contract deposit ID"), null=False, - unique=True, help_text=_("Deposit ID in contract"), ) round = models.ForeignKey( @@ -436,6 +436,9 @@ class RoundDeposit(models.Model): help_text=_("Transaction hash."), ) + class Meta: + unique_together = ('round', 'on_chain_id') + class Vote(models.Model): round = models.ForeignKey(Round, on_delete=models.CASCADE, related_name='votes') voter = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='votes') From d3752586ec3917ed1dd3ceaee7b6628f6a919706 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 5 Nov 2024 21:24:26 +0100 Subject: [PATCH 312/454] fix stellar status indexing --- indexer_app/utils.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 3db8d4d..1e46e29 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1671,7 +1671,10 @@ def create_round_application(event_data, tx_hash, chain_id="stellar"): chain = Chain.objects.get(name=chain_id) applicant, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=application_data["applicant_id"]) round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) - status = PotApplicationStatus[application_data['status'].upper()] + if chain_id == "NEAR": + status = PotApplicationStatus[application_data['status'].upper()] + else: + status = PotApplicationStatus[application_data['status'][0].upper()] logger.info(f"Creating application for round: {round_id}") appl_defaults = { @@ -1746,9 +1749,9 @@ def create_round_payout(event_data, tx_hash, chain_id="stellar"): memo = payout_data.get("memo") chain = Chain.objects.get(name=chain_id) - stellar_token_acct, _ = Account.objects.get_or_create(defaults={"chain":chain},id="stellar") - stellar_token, _ = Token.objects.get_or_create( - account=stellar_token_acct + token_acct, _ = Account.objects.get_or_create(defaults={"chain":chain},id=chain_id.lower()) + token, _ = Token.objects.get_or_create( + account=token_acct ) payout = PotPayout( @@ -1757,7 +1760,7 @@ def create_round_payout(event_data, tx_hash, chain_id="stellar"): amount=amount, recipient_id=recipient_id, memo=memo, - token=stellar_token, + token=token, paid_at=None, tx_hash=tx_hash, ) From c3afe354d614a5f171196d7591406e8cead6713b Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 6 Nov 2024 09:30:35 +0100 Subject: [PATCH 313/454] serialize project --- grantpicks/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grantpicks/serializers.py b/grantpicks/serializers.py index 5c3c90e..981db1a 100644 --- a/grantpicks/serializers.py +++ b/grantpicks/serializers.py @@ -142,7 +142,7 @@ class Meta: reviews = ApplicationReviewSerializer(many=True) round = RoundSerializer() applicant = AccountSerializer() - project = ProjectSerializer() + project = AccountSerializer() SIMPLE_PROJECT_EXAMPLE = { From 140259eea32dc8ae933ae344083adb24e09e7802 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 6 Nov 2024 21:31:38 +0100 Subject: [PATCH 314/454] default ture to near vault --- indexer_app/utils.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 1e46e29..2a7e2ab 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1565,13 +1565,11 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar if remaining_dist_by: remaining_dist_by_obj, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=remaining_dist_by) - near_round_cp = event_data.get('round_complete') - if near_round_cp: - round_time_stamp = timestamp - elif event_data.get('round_complete_ms'): - round_time_stamp = datetime.fromtimestamp(event_data.get('round_complete_ms') / 1000) + round_time_stamp = datetime.fromtimestamp(event_data.get('round_complete_ms', event_data.get('round_complete')) / 1000) + if chain_id == "NEAR": + use_vault = True else: - round_time_stamp = None + use_vault = event_data.get('use_vault', False) round_obj, created = Round.objects.update_or_create( on_chain_id=round_id, @@ -1588,7 +1586,7 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar 'voting_start': datetime.fromtimestamp(event_data.get('voting_start_ms') / 1000), 'voting_end': datetime.fromtimestamp(event_data.get('voting_end_ms') / 1000), 'use_whitelist': event_data.get('use_whitelist'), - 'use_vault': event_data.get('use_vault', False), + 'use_vault': use_vault, 'num_picks_per_voter': event_data.get('num_picks_per_voter'), 'max_participants': event_data.get('max_participants'), 'allow_applications': event_data.get('allow_applications'), From 461b071b1effc8a0751861d27cc89162d4432506 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 7 Nov 2024 08:58:45 +0100 Subject: [PATCH 315/454] fix vote url --- api/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/urls.py b/api/urls.py index 2c40737..b727e2e 100644 --- a/api/urls.py +++ b/api/urls.py @@ -140,7 +140,7 @@ # grantpicks path("v1/rounds", RoundsListAPI.as_view(), name="rounds_api"), path("v1/round//", RoundDetailAPI.as_view(), name="rounds_api_by_id"), - path("v1/round///votes", ProjectRoundVotesAPI.as_view(), name="project_round_votes_api_by_id"), + path("v1/round///votes", ProjectRoundVotesAPI.as_view(), name="project_round_votes_api_by_id"), path("v1/projects", ProjectListAPI.as_view(), name="projects_api"), path( "v1/rounds//applications", From 6f99880d810df3c7544fe15107460c330fcc4584 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 7 Nov 2024 15:19:02 +0100 Subject: [PATCH 316/454] fix round complete.. --- indexer_app/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 2a7e2ab..e417948 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1565,7 +1565,10 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar if remaining_dist_by: remaining_dist_by_obj, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=remaining_dist_by) - round_time_stamp = datetime.fromtimestamp(event_data.get('round_complete_ms', event_data.get('round_complete')) / 1000) + if event_data.get('round_complete_ms', event_data.get('round_complete')): + round_time_stamp = datetime.fromtimestamp(event_data.get('round_complete_ms', event_data.get('round_complete')) / 1000) + else: + round_time_stamp = None if chain_id == "NEAR": use_vault = True else: From 59da2ecf782cd3aacdc95b8332b65b181eaaf10f Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 15 Nov 2024 06:18:08 +0100 Subject: [PATCH 317/454] fix vote api endpoint --- accounts/serializers.py | 6 ++ base/settings.py | 2 +- grantpicks/api.py | 18 ++++-- ...pair_project_votepair_projects_and_more.py | 37 ++++++++++++ grantpicks/models.py | 10 +++- grantpicks/serializers.py | 31 +++++----- indexer_app/utils.py | 60 ++++++++++++++++++- 7 files changed, 141 insertions(+), 23 deletions(-) create mode 100644 grantpicks/migrations/0005_remove_votepair_project_votepair_projects_and_more.py diff --git a/accounts/serializers.py b/accounts/serializers.py index e3fa2b0..91f5fa5 100644 --- a/accounts/serializers.py +++ b/accounts/serializers.py @@ -51,6 +51,12 @@ class NearSocialProfileDataSerializer(serializers.Serializer): help_text="JSON-stringified array of team member account ID strings", ) +class ProjectInPairSerializer(serializers.ModelSerializer): + project_id = serializers.CharField(source='id') + + class Meta: + model = Account + fields = ['project_id'] class AccountSerializer(serializers.ModelSerializer): class Meta: diff --git a/base/settings.py b/base/settings.py index 84d5d3c..ef16a76 100644 --- a/base/settings.py +++ b/base/settings.py @@ -69,7 +69,7 @@ NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) - +NEAR_GRANTPICKS_CONTRACT_ID = "v2.grantpicks.potlock.testnet" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") # TODO: split settigns file by enviroment if ENVIRONMENT == "testnet": POTLOCK_PATTERN = r'\.potlock\.testnet$' diff --git a/grantpicks/api.py b/grantpicks/api.py index 73c40ea..9356c63 100644 --- a/grantpicks/api.py +++ b/grantpicks/api.py @@ -29,7 +29,7 @@ ) from pots.models import PotPayout -from .models import Project, ProjectStatus, Round, Vote +from .models import Project, ProjectStatus, Round, Vote, VotePair from .serializers import ( PAGINATED_PROJECT_EXAMPLE, PAGINATED_ROUND_APPLICATION_EXAMPLE, @@ -211,10 +211,20 @@ def get(self, request: Request, *args, **kwargs): # Retrieve votes for the specified project in the round - votes = round_obj.votes.filter(pairs__project_id=project_id) # Adjust the filter as needed + # votes = round_obj.votes.filter(pairs__project_id=project_id) # Adjust the filter as needed # vote_pairs = project.vote_pairs.all() - results = self.paginate_queryset(votes, request, view=self) - serializer = VoteSerializer(results, many=True) # Use the appropriate serializer for votes + vote_pairs = VotePair.objects.filter( + vote__round=round_obj, + projects=project_id + ).select_related( + 'vote', + 'vote__voter', + 'voted_project' + ).prefetch_related('projects') + # results = self.paginate_queryset(votes, request, view=self) + results = self.paginate_queryset(vote_pairs, request, view=self) + # serializer = VoteSerializer(results, many=True) # Use the appropriate serializer for votes + serializer = VoteSerializer(results, many=True) return self.get_paginated_response(serializer.data) diff --git a/grantpicks/migrations/0005_remove_votepair_project_votepair_projects_and_more.py b/grantpicks/migrations/0005_remove_votepair_project_votepair_projects_and_more.py new file mode 100644 index 0000000..da65c07 --- /dev/null +++ b/grantpicks/migrations/0005_remove_votepair_project_votepair_projects_and_more.py @@ -0,0 +1,37 @@ +# Generated by Django 5.0.6 on 2024-11-15 03:00 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ("grantpicks", "0004_alter_round_on_chain_id_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="votepair", + name="project", + ), + migrations.AddField( + model_name="votepair", + name="projects", + field=models.ManyToManyField( + related_name="vote_pairs_included_in", to="accounts.account" + ), + ), + migrations.AddField( + model_name="votepair", + name="voted_project", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="vote_pairs_voted_for_in", + to="accounts.account", + ), + ), + ] diff --git a/grantpicks/models.py b/grantpicks/models.py index 28ceaee..457f14f 100644 --- a/grantpicks/models.py +++ b/grantpicks/models.py @@ -458,7 +458,15 @@ class Meta: class VotePair(models.Model): vote = models.ForeignKey(Vote, on_delete=models.CASCADE, related_name='pairs') pair_id = models.PositiveIntegerField() - project = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='vote_pairs') + projects = models.ManyToManyField(Account, related_name='vote_pairs_included_in') + voted_project = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name='vote_pairs_voted_for_in', + null=True, + blank=True + ) + #old_project = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='vote_pairs') class Meta: unique_together = ('vote', 'pair_id') diff --git a/grantpicks/serializers.py b/grantpicks/serializers.py index 981db1a..4293d95 100644 --- a/grantpicks/serializers.py +++ b/grantpicks/serializers.py @@ -1,7 +1,7 @@ from rest_framework import serializers from rest_framework.serializers import ModelSerializer -from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer +from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer, ProjectInPairSerializer from base.serializers import TwoDecimalPlacesField from .models import Project, ProjectContact, ProjectContract, ProjectRepository, Round, Vote, VotePair @@ -267,32 +267,33 @@ class PaginatedRoundApplicationsResponseSerializer(serializers.Serializer): class VotePairSerializer(serializers.ModelSerializer): - project = ProjectSerializer() + project_1 = AccountSerializer() + project_2 = AccountSerializer() + voted_project = AccountSerializer() class Meta: model = VotePair fields = [ 'pair_id', - 'project', + 'project_1', + 'project_2', + 'voted_project', ] class VoteSerializer(serializers.ModelSerializer): - round = serializers.PrimaryKeyRelatedField(queryset=Round.objects.all()) - voter = AccountSerializer() - pairs = VotePairSerializer(many=True) + voter = AccountSerializer(source='vote.voter') + winner = ProjectInPairSerializer(source='voted_project') + pair = serializers.SerializerMethodField() + voted_date = serializers.DateTimeField(source='vote.voted_at') class Meta: - model = Vote - fields = [ - 'id', - 'round', - 'voter', - 'pairs', - 'tx_hash', - 'voted_at', - ] + model = VotePair + fields = ['voter', 'pair_id', 'winner', 'pair', 'voted_date'] + + def get_pair(self, obj): + return [ProjectInPairSerializer(project).data for project in obj.projects.all()] diff --git a/indexer_app/utils.py b/indexer_app/utils.py index e417948..586ab8a 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -2,6 +2,7 @@ import json from datetime import datetime from math import log +from typing import Dict import requests from asgiref.sync import sync_to_async @@ -10,6 +11,7 @@ from django.db import transaction from django.utils import timezone from near_lake_framework.near_primitives import ExecutionOutcome, Receipt +import stellar_sdk from accounts.models import Account from activities.models import Activity @@ -1409,6 +1411,47 @@ def update_application(event_data, txhash, reviewer_id=None, chain_id="stellar") return False +def get_pair_projects(pair_id: int, round_id: int, chain_id: str) -> Dict: + if chain_id == "stellar": + server = stellar_sdk.SorobanServer( + "https://soroban-testnet.stellar.org" + if settings.ENVIRONMENT == "testnet" or settings.ENVIRONMENT == "local" + else "https://horizon.stellar.org" + ) + + contract_id = settings.STELLAR_CONTRACT_ID + function_name = "get_pair_by_index" + parameters = [stellar_sdk.scval.to_uint128(round_id), stellar_sdk.scval.to_uint32(pair_id)] + public_key = "GDRZ47PQ43TA7GCBW22HHRM6FHN644KF23HFNZ76I46HPNBD5Q7YEYLJ" + acct = server.load_account(public_key) + + pair_result = server.simulate_transaction( + transaction_envelope=stellar_sdk.TransactionBuilder( + source_account=acct, + ).append_invoke_contract_function_op( + contract_id, + function_name, + parameters + ) + .set_timeout(30) + .build() + ) + + + if pair_result.results: + xdr = pair_result.results[0].xdr + data = stellar_sdk.scval.to_native(xdr) + return data + else: + url = f"https://rpc.web4.testnet.page/account/{settings.NEAR_GRANTPICKS_CONTRACT_ID}/view/get_pair_by_id?round_id.json={round_id}&pair_id.json={pair_id}" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + return data + else: + logger.error(f"Failed to fetch pair data from NEAR: {response.status_code}") + return None + def process_vote_event(event_data, tx_hash, chain_id="stellar"): try: @@ -1441,18 +1484,31 @@ def process_vote_event(event_data, tx_hash, chain_id="stellar"): if chain_id == "NEAR": pair_id = pick['pair_id'] project_id = pick['voted_project'] + + else: pair_id = pick['pair_id'] project_id = Project.objects.get(on_chain_id=pick['project_id']).owner.id + + pair_data = get_pair_projects(pair_id, round_id, chain_id) + logger.info(f"pair data from contract...:,{pair_data}") + if pair_data: + project_id_1, project_id_2 = pair_data.get('projects') + project_1 = Project.objects.get(on_chain_id=project_id_1).owner.id + project_2 = Project.objects.get(on_chain_id=project_id_2).owner.id + # Assuming project_id corresponds to PotApplication id + - VotePair.objects.update_or_create( + vp, created = VotePair.objects.update_or_create( vote=vote, pair_id=pair_id, - defaults={'project_id': project_id} + defaults={'voted_project_id': project_id} ) + vp.projects.add(project_1) + vp.projects.add(project_2) logger.info(f"Processed vote for Round: {round_id}, Voter: {voter.id}, Project: {project_id}") return True From c3db34f6a624be9065119b4d051eda40faf887b5 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 15 Nov 2024 07:13:39 +0100 Subject: [PATCH 318/454] use id for near project --- indexer_app/utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 586ab8a..48d4ce5 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1494,12 +1494,12 @@ def process_vote_event(event_data, tx_hash, chain_id="stellar"): logger.info(f"pair data from contract...:,{pair_data}") if pair_data: project_id_1, project_id_2 = pair_data.get('projects') - project_1 = Project.objects.get(on_chain_id=project_id_1).owner.id - project_2 = Project.objects.get(on_chain_id=project_id_2).owner.id - - - # Assuming project_id corresponds to PotApplication id - + if chain_id == "stellar": + project_1 = Project.objects.get(on_chain_id=project_id_1).owner.id + project_2 = Project.objects.get(on_chain_id=project_id_2).owner.id + else: + project_1 = project_id_1 + project_2 = project_id_2 vp, created = VotePair.objects.update_or_create( From d808e68dbb6ee2ac81c55d68d1c68282131912d5 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 15 Nov 2024 08:21:18 +0100 Subject: [PATCH 319/454] parse vote event --- indexer_app/utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 48d4ce5..3e660b7 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1449,18 +1449,19 @@ def get_pair_projects(pair_id: int, round_id: int, chain_id: str) -> Dict: data = response.json() return data else: - logger.error(f"Failed to fetch pair data from NEAR: {response.status_code}") + logger.error(f"Failed to fetch pair data from NEAR: {response}") return None def process_vote_event(event_data, tx_hash, chain_id="stellar"): try: + logger.info(f"process_vote_event: {event_data}, {tx_hash}, {chain_id}") with transaction.atomic(): if type(event_data) == list: round_id, vote_data = event_data[0], event_data[1] else: - event_data = event_data['vote'] - round_id, vote_data = event_data.get("round_id", 2), event_data + # vote_event_data = event_data['vote'] + round_id, vote_data = event_data.get("round_id", 2), event_data['vote'] chain = Chain.objects.get(name=chain_id) round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) From dfb2658072d619eca1eac52c77e8d21058914714 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 20 Nov 2024 14:26:25 +0100 Subject: [PATCH 320/454] fix payout creation --- indexer_app/utils.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 3e660b7..f19e6f2 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1378,7 +1378,11 @@ def update_application(event_data, txhash, reviewer_id=None, chain_id="stellar") reviewer = Account.objects.get(id=reviewer_id, chain=chain) - status = PotApplicationStatus[application_data['status'].upper()] + if chain_id == "NEAR": + status = PotApplicationStatus[application_data['status'].upper()] + else: + status = PotApplicationStatus[application_data['status'][0].upper()] + submitted_at = datetime.fromtimestamp(application_data['submited_ms'] / 1000) updated_at = datetime.fromtimestamp(application_data['updated_ms'] / 1000) @@ -1810,10 +1814,11 @@ def create_round_payout(event_data, tx_hash, chain_id="stellar"): token_acct, _ = Account.objects.get_or_create(defaults={"chain":chain},id=chain_id.lower()) token, _ = Token.objects.get_or_create( account=token_acct - ) + ) + round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) payout = PotPayout( - round_id=round_id, + round=round_obj, on_chain_id=payout_data["id"], amount=amount, recipient_id=recipient_id, @@ -1823,7 +1828,7 @@ def create_round_payout(event_data, tx_hash, chain_id="stellar"): tx_hash=tx_hash, ) payout.save() - logger.info(f"Created payout for round {round_id} to {recipient_id} for amount {amount}.") + logger.info(f"Created payout for round {round_id} to {recipient_id} for amount {amount}, on chain {chain_id}") return True except Exception as e: logger.error(f"Error creating round payout: {str(e)}") From 1dbdf61192ec6a6b01245677447ea2a25aa8d406 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 22 Nov 2024 13:44:32 +0100 Subject: [PATCH 321/454] filter by search param --- lists/api.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lists/api.py b/lists/api.py index 12dfc07..04a910b 100644 --- a/lists/api.py +++ b/lists/api.py @@ -1,6 +1,6 @@ import random -from django.db.models import Exists, OuterRef +from django.db.models import Q from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page @@ -132,6 +132,12 @@ class ListRegistrationsAPI(APIView, CustomSizePageNumberPagination): OpenApiParameter.QUERY, description="Filter registrations by category", ), + OpenApiParameter( + "search", + str, + OpenApiParameter.QUERY, + description="Search registrants by name or account ID", + ), *pagination_parameters, ], responses={ @@ -165,6 +171,7 @@ def get(self, request: Request, *args, **kwargs): registrations = list_obj.registrations.select_related().all() status_param = request.query_params.get("status") category_param = request.query_params.get("category") + search_param = request.query_params.get("search") if status_param: if status_param not in ListRegistrationStatus.values: return Response( @@ -176,6 +183,10 @@ def get(self, request: Request, *args, **kwargs): registrations = registrations.filter( registrant__near_social_profile_data__plCategories__iregex=category_regex_pattern ) + if search_param: + registrations = registrations.filter( + Q(registrant__id__icontains=search_param) | Q(registrant__near_social_profile_data__name__icontains=search_param) + ) results = self.paginate_queryset(registrations, request, view=self) serializer = ListRegistrationSerializer(results, many=True) return self.get_paginated_response(serializer.data) From 33937b67d701e3b76442cf738e4fadfcfad2128b Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sun, 1 Dec 2024 21:05:56 +0100 Subject: [PATCH 322/454] api endpoint for json data --- api/urls.py | 7 ++++ pots/api.py | 57 +++++++++++++++++++++++++++++++ pots/last-snapshot-AllVoters.json | 1 + pots/serializers.py | 32 +++++++++++++++++ scripts/after_install.sh | 4 +-- 5 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 pots/last-snapshot-AllVoters.json diff --git a/api/urls.py b/api/urls.py index b727e2e..6d86f37 100644 --- a/api/urls.py +++ b/api/urls.py @@ -27,6 +27,7 @@ ListsListAPI, ) from pots.api import ( + MpdaoUsers, PotApplicationsAPI, PotDetailAPI, PotDonationsAPI, @@ -149,5 +150,11 @@ ), path("v1//projects", AccountProjectListAPI.as_view(), name="user_projects_api"), path("v1///project-stats", ProjectStatsAPI.as_view(), name="projects_api"), + + path( + "v1/mpdao/voter-info", + MpdaoUsers.as_view(), + name="mpdao_api", + ), ] diff --git a/pots/api.py b/pots/api.py index 11b8283..8debe37 100644 --- a/pots/api.py +++ b/pots/api.py @@ -1,3 +1,5 @@ +import os +from django.conf import settings from django.db.models import Q from django.utils import timezone from django.utils.decorators import method_decorator @@ -11,6 +13,7 @@ from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView +import json from accounts.models import Account from accounts.serializers import ( @@ -26,6 +29,7 @@ DonationSerializer, PaginatedDonationsResponseSerializer, ) +from pots.util import SQLiteReader from .models import Pot, PotApplication, PotApplicationStatus, PotFactory from .serializers import ( @@ -42,6 +46,7 @@ PotFactorySerializer, PotPayoutSerializer, PotSerializer, + MpdaoVoterSerializer, ) @@ -308,3 +313,55 @@ def get(self, request: Request, *args, **kwargs): results = self.paginate_queryset(payouts, request, view=self) serializer = PotPayoutSerializer(results, many=True) return self.get_paginated_response(serializer.data) + + +class MpdaoUsers(APIView): + + @extend_schema( + parameters=[ + OpenApiParameter("voter_id", str, OpenApiParameter.PATH, description="NEAR account ID of the voter"), + ], + responses={ + 200: OpenApiResponse( + response=MpdaoVoterSerializer, + description="Returns voter details", + ), + 404: OpenApiResponse(description="Voter not found"), + 500: OpenApiResponse(description="File read error"), + }, + ) + @method_decorator(cache_page(60 * 5)) + def get(self, request: Request, *args, **kwargs): + voter_id = request.query_params.get("voter") + try: + # Read JSON file + json_path = os.path.join(settings.BASE_DIR, 'pots', 'last-snapshot-AllVoters.json') + with open(json_path, 'r') as file: + all_voters = json.load(file) + + # Find voter by ID + voter = next( + (voter for voter in all_voters if voter['voter_id'] == voter_id), + None + ) + + if not voter: + return Response( + {"message": f"Voter with ID {voter_id} not found"}, + status=404 + ) + + serializer = MpdaoVoterSerializer(voter) + return Response(serializer.data) + + except FileNotFoundError: + return Response( + {"message": "Voters snapshot file not found"}, + status=500 + ) + except Exception as e: + return Response( + {"message": f"Error processing voter data: {str(e)}"}, + status=500 + ) + \ No newline at end of file diff --git a/pots/last-snapshot-AllVoters.json b/pots/last-snapshot-AllVoters.json new file mode 100644 index 0000000..fd1a775 --- /dev/null +++ b/pots/last-snapshot-AllVoters.json @@ -0,0 +1 @@ +[{"voter_id":"lucascasp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1447929400","locking_period":300,"voting_power":"7239647000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"7239647000000000000000000000"}]},{"voter_id":"voodooking.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2942789600","locking_period":300,"voting_power":"14713948000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"14713948000000000000000000000"}]},{"voter_id":"kuncho.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000","locking_period":30,"voting_power":"500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"128492199000","locking_period":45,"voting_power":"96369149250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"4153676790","locking_period":300,"voting_power":"20768383950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"96369000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20769033200000000000000000000"}]},{"voter_id":"silkking.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"250000000000","locking_period":240,"voting_power":"1000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"3500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"79200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"917300000000000000000000000000"}]},{"voter_id":"1ce2567f7f49cb34cea72179223ec7fc4ba91077da3e01364d0c729dfbe26467","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7053707400","locking_period":300,"voting_power":"35268537000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"35268537000000000000000000000"}]},{"voter_id":"proxima-centauri.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"177979300210","locking_period":300,"voting_power":"889896501050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"889896501000000000000000000000"}]},{"voter_id":"blazepassion.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000000","locking_period":300,"voting_power":"50000000000000000000000000000","unlocking_started_at":1718642103735,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"alpha-centauri.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"191594836990","locking_period":300,"voting_power":"957974184950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"957974184900000000000000000000"}]},{"voter_id":"0cbf1c4a3da03524dfd9486cf13d404e5f02eb173218d2a512d6991c057fddd3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20064518690","locking_period":300,"voting_power":"100322593450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6737996950000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"93584596500000000000000000000"}]},{"voter_id":"dogon.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"260851958600","locking_period":300,"voting_power":"1304259793000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1304259793000000000000000000000"}]},{"voter_id":"alan777.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"286666722000","locking_period":300,"voting_power":"1433333610000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"100000000","locking_period":150,"voting_power":"250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"10000000","locking_period":180,"voting_power":"30000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"250000000000","locking_period":240,"voting_power":"1000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"2433613610000000000000000000000"}]},{"voter_id":"manzanal.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"594801155900","locking_period":300,"voting_power":"2974005779500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"15000000000","locking_period":300,"voting_power":"75000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"3049005779500000000000000000000"}]},{"voter_id":"brianmai.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8005634731000","locking_period":240,"voting_power":"32022538924000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"chain0ps.poolv1.near","voting_power":"32022538924000000000000000000000"}]},{"voter_id":"raulb.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"86710598600","locking_period":300,"voting_power":"433552993000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"433552993000000000000000000000"}]},{"voter_id":"platoboss.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5512744200","locking_period":90,"voting_power":"8269116300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10769116300000000000000000000","vote_positions":[]},{"voter_id":"cryptogarik.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7822408800","locking_period":300,"voting_power":"39112044000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"cryptogarik.poolv1.near","voting_power":"39112044000000000000000000000"}]},{"voter_id":"zaphire.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"102709473200","locking_period":300,"voting_power":"513547366000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"swissstar.poolv1.near","voting_power":"513547366000000000000000000000"}]},{"voter_id":"f27af9b6ebfa3896bdcaa20514370f12c849f0e9ead55623fb82c11430b0c2ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20000000","locking_period":30,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dexagon.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"58f54b94dae828ebbafeccebfba61c5ff4773a4f1c1b077d6628a64c1a0af742","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"287889851090","locking_period":300,"voting_power":"1439449255450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"59598407900","locking_period":30,"voting_power":"29799203950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"swissstar.poolv1.near","voting_power":"1469248459400000000000000000000"}]},{"voter_id":"6b16e9e129ce06246c42a33f0d225e3d4735c2de26701f21de28276970e601c0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"232967556300","locking_period":90,"voting_power":"349451334450000000000000000000","unlocking_started_at":1728034878253,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"herimax.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"15850679740","locking_period":300,"voting_power":"79253398700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"79253398700000000000000000000"}]},{"voter_id":"0234hoangnd78439.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"735830030","locking_period":300,"voting_power":"3679150150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"3679150100000000000000000000"}]},{"voter_id":"readylayerone.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1345707734600","locking_period":300,"voting_power":"6728538673000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"6728538673000000000000000000000"}]},{"voter_id":"duongka.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3880961200","locking_period":120,"voting_power":"7761922400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"4261922400000000000000000000"}]},{"voter_id":"c245be41fb5aed65ab30e7b4e1a178147e1e131dc95b1f2206b2f53a7596f093","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"69908270","locking_period":300,"voting_power":"349541350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"349541350000000000000000000","vote_positions":[]},{"voter_id":"themicrowave.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"37095762100","locking_period":300,"voting_power":"185478810500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"3736000000","locking_period":300,"voting_power":"18680000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"204158810500000000000000000000"}]},{"voter_id":"e914e4c91d80a845c79bb134d54fafb01bab9357fa44c3b4c38dffb3f0e5cad1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"34123968100","locking_period":300,"voting_power":"170619840500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"175892000","locking_period":90,"voting_power":"263838000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1529750100","locking_period":165,"voting_power":"4206812775000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"717246800","locking_period":120,"voting_power":"1434493600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"75000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"76524984800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"100000000000000000000000000000"}]},{"voter_id":"branermusso.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6364090500","locking_period":300,"voting_power":"31820452500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10095459200","locking_period":300,"voting_power":"50477296000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"82297748500000000000000000000"}]},{"voter_id":"79148e72172096387294d56b0f2f9b366591610254a21e444a479d6adcf38dd3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"137063146500","locking_period":90,"voting_power":"205594719750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"205594719750000000000000000000","vote_positions":[]},{"voter_id":"gilberth.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"102367750","locking_period":300,"voting_power":"511838750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"511838700000000000000000000"}]},{"voter_id":"0beca730ba634786c7e330fb7c71f8510b51a445522ca728fc30f7c722eb3a14","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"15020424000","locking_period":30,"voting_power":"7510212000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7510212000000000000000000000","vote_positions":[]},{"voter_id":"hannah17.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"23554732500","locking_period":300,"voting_power":"117773662500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"117773662500000000000000000000"}]},{"voter_id":"blaze.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"24895319700","locking_period":90,"voting_power":"37342979550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"openshards.poolv1.near","voting_power":"37342979500000000000000000000"}]},{"voter_id":"do0k13.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"508091860340","locking_period":300,"voting_power":"2540459301700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"2540459301700000000000000000000"}]},{"voter_id":"akdude.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"80865985600","locking_period":300,"voting_power":"404329928000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"60000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"61298970000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"20216500000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"262814458000000000000000000000"}]},{"voter_id":"levoslov.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1126852800","locking_period":300,"voting_power":"5634264000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5634264000000000000000000000","vote_positions":[]},{"voter_id":"aiml.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2984500","locking_period":240,"voting_power":"11938000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10529862400","locking_period":300,"voting_power":"52649312000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"52661250000000000000000000000"}]},{"voter_id":"ramgor.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"38088003700","locking_period":300,"voting_power":"190440018500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"190440018500000000000000000000"}]},{"voter_id":"zzoo-2718.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"68988508600","locking_period":180,"voting_power":"206965525800000000000000000000","unlocking_started_at":1720364226345,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"68686063500","locking_period":165,"voting_power":"188886674625000000000000000000","unlocking_started_at":1720364264076,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"ame9986.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2360529000","locking_period":300,"voting_power":"11802645000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6802645000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"openbitlab.poolv1.near","voting_power":"5000000000000000000000000000"}]},{"voter_id":"abrahamrivv.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"34437420300","locking_period":300,"voting_power":"172187101500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"172187101500000000000000000000"}]},{"voter_id":"cacossio.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"754989658520","locking_period":210,"voting_power":"2642463804820000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"201150593900","locking_period":195,"voting_power":"653739430175000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"74821994195000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"221381240800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"121|mpDAO Grants #8 - August 2024-meximalist.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"118|mpDAO Grants #8 - August 2024-marcelorefi.near","voting_power":"500000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"117|mpDAO Grants #8 - August 2024-bagrera.near","voting_power":"250000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"250000000000000000000000000000"}]},{"voter_id":"e87a0f4bbdbc251ca4531be261f7c6821facc2ad076da96a9572acca2049dfe6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"129575448900","locking_period":135,"voting_power":"291544760025000000000000000000","unlocking_started_at":1714994026232,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"skinn.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"76645554700","locking_period":300,"voting_power":"383227773500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"383227773500000000000000000000"}]},{"voter_id":"a2d1b583a0743a892255b9a07eb064d2656589df60ebeffbbcb7f4d32883662c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"172c0cab8eec92d69ba16db4e8ea5bf69149fcc3844008327bcc2c84fa203508","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"9076056900","locking_period":300,"voting_power":"45380284500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"45380284500000000000000000000"}]},{"voter_id":"border_less.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"52897311300","locking_period":300,"voting_power":"264486556500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"264486556500000000000000000000"}]},{"voter_id":"0xf48cAb01aE5b5f3A546f33B7f416E7E203237749.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"790000000","locking_period":120,"voting_power":"1580000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"80000000","locking_period":300,"voting_power":"400000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1977000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"109|mpDAO Grants #7 - June 2024-near-africa.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"mp-cc.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20633333330000","locking_period":225,"voting_power":"77374999987500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"28125000000000","locking_period":90,"voting_power":"42187500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"55030000000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sharpdarts.poolv1.near","voting_power":"2000000987500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"galaxydigital.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"rekt.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staketab.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.pool.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"openshards.poolv1.near","voting_power":"3000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"moonlet.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"3000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fresh.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"grassets.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stablelab.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"tritonone.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"3562499000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"senseinode.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakesstone.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"kiln-1.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dsrvlabs-staking.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"prophet.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearvietnamhub.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"08investinwomen_runbybisontrails.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"gfs-ventures.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"a41.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"bee1stake.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"blockdaemon.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"50000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"150000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"134|mpDAO Grants #8 - August 2024-marcrypted.near","voting_power":"560000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"1500000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"400000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"128|mpDAO Grants #8 - August 2024-aleiranova.near","voting_power":"1250000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"225000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"137|mpDAO Grants #8 - August 2024-criptolaplata.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"335000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"142|mpDAO Grants #8 - August 2024-refibogota.near","voting_power":"200000000000000000000000000000"}]},{"voter_id":"mbbevilacqua_caffepoesia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"536000000","locking_period":300,"voting_power":"2680000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"1729000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"931000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"2be59bfa20787b81b8ffa9a6a6c45714020e2e157dbc1c5636287ca91ebc1aee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"yaneisia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"901208400","locking_period":300,"voting_power":"4506042000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"120245800","locking_period":300,"voting_power":"601229000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"335994910","locking_period":300,"voting_power":"1679974550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"cryptoblossom.poolv1.near","voting_power":"6787245500000000000000000000"}]},{"voter_id":"8901ad3c104f92dba61c1624b74285a481fdc93670f7e307d27512bcf6f83e4b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3751efa63c92312d2324dc19f26c6ec15c869b72d39ef5c852e69067dca585da","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"775000000","locking_period":90,"voting_power":"1162500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"8963000000","locking_period":300,"voting_power":"44815000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"15977500000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"10000000000000000000000000000"}]},{"voter_id":"travbori.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"36093446400","locking_period":90,"voting_power":"54140169600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1791481030","locking_period":300,"voting_power":"8957405150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"63097574700000000000000000000"}]},{"voter_id":"shadowmonarch.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"32406418600","locking_period":300,"voting_power":"162032093000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"162032093000000000000000000000"}]},{"voter_id":"luciotato.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17740840948500","locking_period":90,"voting_power":"26611261422750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"100000000000","locking_period":180,"voting_power":"300000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"40000000000000","locking_period":30,"voting_power":"20000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"35000000000000","locking_period":60,"voting_power":"35000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"69626261422750000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"550000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"1700000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearstar.poolv1.near","voting_power":"1850000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"2180000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"openshards.poolv1.near","voting_power":"985000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"deutschetelekom.poolv1.near","voting_power":"850000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"50000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"101|mpDAO Grants #7 - June 2024-criptolaplata.near","voting_power":"1050000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"85|mpDAO Grants #7 - June 2024-fpsgames.near","voting_power":"350000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"94|mpDAO Grants #7 - June 2024-cryptopr3neur.near","voting_power":"665000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"95|mpDAO Grants #7 - June 2024-edfernandezg.near","voting_power":"45000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"111|mpDAO Grants #7 - June 2024-c0d0c8b3bfcbc17347002733be3e2d17df833d2931c425eaa40bd7e73a4509e3","voting_power":"40000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"102|mpDAO Grants #7 - June 2024-fr-grant.near","voting_power":"400000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"86|mpDAO Grants #7 - June 2024-la-incubadora.near","voting_power":"650000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"20000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"600000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"121|mpDAO Grants #8 - August 2024-meximalist.near","voting_power":"300000000000000000000000000000"}]},{"voter_id":"brunomazzaggio.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"251428084300","locking_period":300,"voting_power":"1257140421500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"1257140421500000000000000000000"}]},{"voter_id":"c3001cd00f76dd7d4e60d4bf6c8b392fa2e724f7367b3b92279f467262f0724e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e515b46dc1c974ca39c5c622d869f7bc73e31d2c995354715a1520e18290fbac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12355013000","locking_period":120,"voting_power":"24710026000000000000000000000","unlocking_started_at":1726266396655,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"biolmoon.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10254082300","locking_period":195,"voting_power":"33325767475000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"75000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"33325767400000000000000000000"}]},{"voter_id":"mex-holding.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"281424231200","locking_period":300,"voting_power":"1407121156000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"1407121156000000000000000000000"}]},{"voter_id":"rosaglez_art.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"2500000000000000000000000000"}]},{"voter_id":"adisa81.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1035323359600","locking_period":300,"voting_power":"5176616798000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5176616798000000000000000000000"}]},{"voter_id":"natto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28715520000","locking_period":90,"voting_power":"43073280000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"770000000","locking_period":30,"voting_power":"385000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"21729000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"21729280000000000000000000000"}]},{"voter_id":"daorecords.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7059590900","locking_period":90,"voting_power":"10589386350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"5289386300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"5300000000000000000000000000"}]},{"voter_id":"alejandro.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17119680100","locking_period":300,"voting_power":"85598400500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"85598400500000000000000000000"}]},{"voter_id":"jarednotjerry.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"209997339990","locking_period":300,"voting_power":"1049986699950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1049986699900000000000000000000"}]},{"voter_id":"wapzp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20033551200","locking_period":300,"voting_power":"100167756000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"100167756000000000000000000000"}]},{"voter_id":"marrown.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27459200900","locking_period":300,"voting_power":"137296004500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"63818410000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.pool.near","voting_power":"73477590000000000000000000000"}]},{"voter_id":"lukelee.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1306488420000","locking_period":300,"voting_power":"6532442100000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"6532442100000000000000000000000"}]},{"voter_id":"anmatig.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"266031800","locking_period":300,"voting_power":"1330159000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"3330000000","locking_period":30,"voting_power":"1665000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1000000","locking_period":225,"voting_power":"3750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2998909000000000000000000000"}]},{"voter_id":"neonong.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"325787524100","locking_period":300,"voting_power":"1628937620500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking-power.poolv1.near","voting_power":"1628937620500000000000000000000"}]},{"voter_id":"luismanac.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"33953313100","locking_period":300,"voting_power":"169766565500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"69767000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"snsmln.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"49999565500000000000000000000"}]},{"voter_id":"max_one.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10627243500","locking_period":300,"voting_power":"53136217500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2327239200","locking_period":300,"voting_power":"11636196000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"424000000","locking_period":300,"voting_power":"2120000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"449000000","locking_period":300,"voting_power":"2245000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"4445212500","locking_period":300,"voting_power":"22226062500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"91363476000000000000000000000"}]},{"voter_id":"sanam1027.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1206000000","locking_period":30,"voting_power":"603000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"353000000","locking_period":90,"voting_power":"529500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"2000000","locking_period":60,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"2491108600","locking_period":45,"voting_power":"1868331450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"402831450000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"atomic-nodes.poolv1.near","voting_power":"1000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"400000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"100000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"50000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"800000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"65|mpDAO Grants #7 - June 2024-auroraafrica.near","voting_power":"50000000000000000000000000"}]},{"voter_id":"7b3735e6e4891b67d6949b37007000f1006c9bc50f7b2ffbc7591b84f4a41d11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"173653480600","locking_period":120,"voting_power":"347306961200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"infiniteloop.poolv1.near","voting_power":"347306961200000000000000000000"}]},{"voter_id":"serik.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2625018100","locking_period":90,"voting_power":"3937527150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3937527150000000000000000000","vote_positions":[]},{"voter_id":"roago.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"223179655390","locking_period":300,"voting_power":"1115898276950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"715898276900000000000000000000"}]},{"voter_id":"3228f4ec8437ab4271b99b7ce0c5d6a82ac28fe7be40ecce1f0920b12eaba915","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16452372830","locking_period":300,"voting_power":"82261864150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1000000","locking_period":300,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1000000","locking_period":135,"voting_power":"2250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"20760795000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"27402183200000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"34106135900000000000000000000"}]},{"voter_id":"6490723760b963cabd9b82382d0faf0f1cc693dc7a9b8bdfd6595dde53bb6559","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"35936943900","locking_period":300,"voting_power":"179684719500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"79684719500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"50000000000000000000000000000"}]},{"voter_id":"stingray.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stingray.poolv1.near","voting_power":"5000000000000000000000000000"}]},{"voter_id":"011050b84e018322932173e1309b8496af96f7792d29a48cf82eaf7d12c24850","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3470000000000","locking_period":300,"voting_power":"17350000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"17350000000000000000000000000000"}]},{"voter_id":"bdaf.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1269125990000","locking_period":270,"voting_power":"5711066955000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"178571430000","locking_period":225,"voting_power":"669642862500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1130952370000","locking_period":225,"voting_power":"4241071387500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"250000000000","locking_period":225,"voting_power":"937500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"861884035070","locking_period":210,"voting_power":"3016594122745000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":5,"amount":"788881924520","locking_period":180,"voting_power":"2366645773560000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":6,"amount":"796553508940","locking_period":300,"voting_power":"3982767544700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":7,"amount":"761904750000","locking_period":120,"voting_power":"1523809500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lavenderfive.poolv1.near","voting_power":"9892331030500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"polkachu.poolv1.near","voting_power":"9212744787800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"1559281205000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"1784741122700000000000000000000"}]},{"voter_id":"marior.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"65158439600","locking_period":300,"voting_power":"325792198000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"30719428500","locking_period":300,"voting_power":"153597142500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"479389340500000000000000000000"}]},{"voter_id":"davletuner.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10495055000","locking_period":120,"voting_power":"20990110000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"3228186500","locking_period":165,"voting_power":"8877512875000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"75176860000","locking_period":300,"voting_power":"375884300000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"75000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"405751922800000000000000000000"}]},{"voter_id":"phandinhtuan.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"123230054400","locking_period":300,"voting_power":"616150272000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"616150272000000000000000000000"}]},{"voter_id":"sivakalathi008.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sharpdarts.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"welldonestake.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"700000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptotribe.poolv1.near","voting_power":"600000000000000000000000000"}]},{"voter_id":"xuanduy86.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"23121411500","locking_period":300,"voting_power":"115607057500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"26799658700","locking_period":300,"voting_power":"133998293500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"742000000","locking_period":300,"voting_power":"3710000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"25463122400","locking_period":300,"voting_power":"127315612000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"380630963000000000000000000000"}]},{"voter_id":"fdea0a6bcd48da25f783feb9b66627698cb00a7b34f064eb3ff59e3367b625de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13550000000","locking_period":300,"voting_power":"67750000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"67750000000000000000000000000","vote_positions":[]},{"voter_id":"b71nw.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7416352400","locking_period":180,"voting_power":"22249057200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"22249057200000000000000000000","vote_positions":[]},{"voter_id":"sivakalathi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"98276654600","locking_period":300,"voting_power":"491383273000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"trust-nodes.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"4ire.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pinrocks.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearfans.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptotribe.poolv1.near","voting_power":"30968291100000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"buildlinks.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"encipher.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"iamcryptobro.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dsrvlabs-staking.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"ni.poolv1.near","voting_power":"16000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"genesislab.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"106|mpDAO Grants #7 - June 2024-ludium.near","voting_power":"2414981900000000000000000000"},{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"2000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"144|mpDAO Grants #8 - August 2024-cryptopr3neur.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"136|mpDAO Grants #8 - August 2024-bstt.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"126|mpDAO Grants #8 - August 2024-5e85fef96bd903eec08d1f59fabdc524d0ac13f44c7a1e08e43de9a01a62a11c","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"128|mpDAO Grants #8 - August 2024-aleiranova.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"137|mpDAO Grants #8 - August 2024-criptolaplata.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"133|mpDAO Grants #8 - August 2024-ffbc3084a4d75ed3c28a7b6c26f44a4ffb43dc3959365d80d70a9660913cbacf","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"117|mpDAO Grants #8 - August 2024-bagrera.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"125|mpDAO Grants #8 - August 2024-xiiw.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"118|mpDAO Grants #8 - August 2024-marcelorefi.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"3000000000000000000000000000"}]},{"voter_id":"15870c8972a9fe6cdb7dfc2df835740108e8674cc170a091cd0ece0b9e4f6cfa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000886200","locking_period":270,"voting_power":"4503987900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"183000000","locking_period":180,"voting_power":"549000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"17349290","locking_period":300,"voting_power":"86746450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"86746450000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"5052987900000000000000000000"}]},{"voter_id":"mansaet.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"193545300200","locking_period":300,"voting_power":"967726501000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"967726501000000000000000000000"}]},{"voter_id":"pathrock.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17006278400","locking_period":90,"voting_power":"25509417600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pathrocknetwork.poolv1.near","voting_power":"25509417600000000000000000000"}]},{"voter_id":"19f7dbc58065da8a3cc636987d646b3844517fef9533b3d86bef4f3e10a194bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"823081344000","locking_period":195,"voting_power":"2675014368000000000000000000000","unlocking_started_at":1726751184719,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"112890669800","locking_period":300,"voting_power":"564453349000000000000000000000","unlocking_started_at":1723052296755,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"39043526300","locking_period":210,"voting_power":"136652342050000000000000000000","unlocking_started_at":1725348921894,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":3,"amount":"29988017300","locking_period":270,"voting_power":"134946077850000000000000000000","unlocking_started_at":1725348888783,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"syrbuss.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"292603590390","locking_period":300,"voting_power":"1463017951950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"1463017951900000000000000000000"}]},{"voter_id":"0rz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"502237362700","locking_period":300,"voting_power":"2511186813500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"2511186813500000000000000000000"}]},{"voter_id":"salpaslan.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"35730495600","locking_period":105,"voting_power":"62528367300000000000000000000","unlocking_started_at":1731416381037,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"sv404771.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"169793351100","locking_period":120,"voting_power":"339586702200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"339586702200000000000000000000"}]},{"voter_id":"6d6ce3d63ba86cce16407b328e9d6b92421f7265be64f3efc9ef32f542176e91","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5781710500","locking_period":300,"voting_power":"28908552500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"here.poolv1.near","voting_power":"28908552500000000000000000000"}]},{"voter_id":"leandrogavidia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1165606140","locking_period":300,"voting_power":"5828030700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5828030700000000000000000000","vote_positions":[]},{"voter_id":"59583199fc8d2b7207ad71f5213b3e7c917cbf07579d0e63fe1d7cebd9a2cd00","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"109173817500","locking_period":300,"voting_power":"545869087500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"40827000000","locking_period":180,"voting_power":"122481000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"30000000000","locking_period":60,"voting_power":"30000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"20000000000","locking_period":30,"voting_power":"10000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"50000000000","locking_period":120,"voting_power":"100000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"87500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"808350000000000000000000000000"}]},{"voter_id":"curmello.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7000016900","locking_period":300,"voting_power":"35000084500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"136|mpDAO Grants #8 - August 2024-bstt.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"5000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"5000084500000000000000000000"}]},{"voter_id":"e0ace3957f5b8a6b2c1903c6ec4c16a69e5c76b231eb1bf259e219ccfc3c63db","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16314629900","locking_period":90,"voting_power":"24471944850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"24471944850000000000000000000","vote_positions":[]},{"voter_id":"0f030b64cedb845906e84bf92076f09218de9a636e0758eab90a7aadb75048b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7272730000","locking_period":165,"voting_power":"20000007500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"}]},{"voter_id":"lematecat.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"32768913580","locking_period":300,"voting_power":"163844567900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"163844567900000000000000000000"}]},{"voter_id":"v3ry.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12303870000","locking_period":270,"voting_power":"55367415000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"211767800000","locking_period":300,"voting_power":"1058839000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"47000000000","locking_period":195,"voting_power":"152750000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"130000000000","locking_period":285,"voting_power":"617500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"15274000000","locking_period":180,"voting_power":"45822000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"350000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"180000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"330000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"160000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"chorusone.poolv1.near","voting_power":"420000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"everstake.poolv1.near","voting_power":"290000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"100278415000000000000000000000"}]},{"voter_id":"21mbulls.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"97814396200","locking_period":300,"voting_power":"489071981000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"finoa.poolv1.near","voting_power":"489071981000000000000000000000"}]},{"voter_id":"crypturion.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"38975358730","locking_period":300,"voting_power":"194876793650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"35000000","locking_period":300,"voting_power":"175000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"129932415500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"65119378100000000000000000000"}]},{"voter_id":"jepian.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"814504100","locking_period":90,"voting_power":"1221756150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"78|mpDAO Grants #7 - June 2024-irvingzcc.near","voting_power":"1221756100000000000000000000"}]},{"voter_id":"rodrigos.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"199000000","locking_period":300,"voting_power":"995000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"995000000000000000000000000"}]},{"voter_id":"3b19d8cefe1b7f4aff0745f0751d616d58b8553b35e6bb821edd301a6ecf2d4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"aleh.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"126501701000","locking_period":90,"voting_power":"189752551500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2049687100","locking_period":300,"voting_power":"10248435500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"33333000000","locking_period":90,"voting_power":"49999500000000000000000000000","unlocking_started_at":1727743009244,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":3,"amount":"71419000000","locking_period":90,"voting_power":"107128500000000000000000000000","unlocking_started_at":1728741366553,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"987000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"50000000000000000000000000000"}]},{"voter_id":"e2f0fa9548b641ffc7f4eac2e19987a3d0b3c502f2e3ad7127c4c4d046b7afad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7000000","locking_period":90,"voting_power":"10500000000000000000000000","unlocking_started_at":1720378414463,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"lovali.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1276699700","locking_period":300,"voting_power":"6383498500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lovali.poolv1.near","voting_power":"6383498500000000000000000000"}]},{"voter_id":"dc431cbf756718c9babdeb6839a11f6b2eb29ce48bad6b250cf4db7eee5efb0b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3340921200","locking_period":300,"voting_power":"16704606000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"85|mpDAO Grants #7 - June 2024-fpsgames.near","voting_power":"10000606000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"64|mpDAO Grants #7 - June 2024-plugrel.near","voting_power":"6704000000000000000000000000"}]},{"voter_id":"jojokeko.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8776537700","locking_period":300,"voting_power":"43882688500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"43882688500000000000000000000"}]},{"voter_id":"snowfa11.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"75000000000","locking_period":180,"voting_power":"225000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"76738000000","locking_period":300,"voting_power":"383690000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"135000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"135000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"138690000000000000000000000000"}]},{"voter_id":"kiskesis.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1575422300","locking_period":300,"voting_power":"7877111500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"7877111500000000000000000000"}]},{"voter_id":"nearukraineguild.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5915000000","locking_period":300,"voting_power":"29575000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"29575000000000000000000000000"}]},{"voter_id":"9c57aa38fecf83600922b69af9c8944742c3e45bcc1138ac534ac8de74ca7753","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"29184000000","locking_period":300,"voting_power":"145920000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"25000000000","locking_period":180,"voting_power":"75000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"110110000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"110810000000000000000000000000"}]},{"voter_id":"quickwaycrypto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8711373700","locking_period":90,"voting_power":"13067060550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"13067060500000000000000000000"}]},{"voter_id":"pamm.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000","locking_period":180,"voting_power":"300000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"3559000000","locking_period":105,"voting_power":"6228250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"3992306400","locking_period":300,"voting_power":"19961532000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"20000000","locking_period":60,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"1176771600","locking_period":90,"voting_power":"1765157400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"4ire.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"3000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"5274939400000000000000000000"}]},{"voter_id":"swapmy.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"204894267800","locking_period":30,"voting_power":"102447133900000000000000000000","unlocking_started_at":1715730266745,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"cesar667.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":90,"voting_power":"4500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"100000000","locking_period":300,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"504500000000000000000000000"}]},{"voter_id":"luckycat.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714829463538,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"5270925700","locking_period":60,"voting_power":"5270925700000000000000000000","unlocking_started_at":1714829615315,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"showme355.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"24801635800","locking_period":90,"voting_power":"37202453700000000000000000000","unlocking_started_at":1714831418807,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"894000000","locking_period":60,"voting_power":"894000000000000000000000000","unlocking_started_at":1714829193720,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"georgemarlow.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000485500","locking_period":300,"voting_power":"500002427500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"604496000000","locking_period":300,"voting_power":"3022480000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"3522482427500000000000000000000"}]},{"voter_id":"uzis.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714829889706,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"45000000","locking_period":60,"voting_power":"45000000000000000000000000","unlocking_started_at":1714829865130,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"konwey.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"135604870900","locking_period":300,"voting_power":"678024354500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"24354500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"678000000000000000000000000000"}]},{"voter_id":"showme.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830101467,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"42000000","locking_period":60,"voting_power":"42000000000000000000000000","unlocking_started_at":1714830119925,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"wwe.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830256184,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"31000000","locking_period":60,"voting_power":"31000000000000000000000000","unlocking_started_at":1714830284122,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"showtime.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830364816,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"34000000","locking_period":60,"voting_power":"34000000000000000000000000","unlocking_started_at":1714830384498,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"okamoto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46000000","locking_period":60,"voting_power":"46000000000000000000000000","unlocking_started_at":1714830512197,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"691593700","locking_period":105,"voting_power":"1210288975000000000000000000","unlocking_started_at":1714830535796,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"usaa.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830649423,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"38000000","locking_period":60,"voting_power":"38000000000000000000000000","unlocking_started_at":1714830678784,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"7andi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830948897,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"jbs.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"711593700","locking_period":90,"voting_power":"1067390550000000000000000000","unlocking_started_at":1714830862740,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"earnestetim.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"507375800","locking_period":300,"voting_power":"2536879000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"109|mpDAO Grants #7 - June 2024-near-africa.near","voting_power":"2536879000000000000000000000"}]},{"voter_id":"vitalpointai.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18080140400","locking_period":30,"voting_power":"9040070200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"epic.poolv1.near","voting_power":"9040070200000000000000000000"}]},{"voter_id":"dcabrera.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"94029815300","locking_period":300,"voting_power":"470149076500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"220149076500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"250000000000000000000000000000"}]},{"voter_id":"71de6add6efe99c57bb02bcacc4be6efaaba9d409403372144d2292db920df39","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"458664942800","locking_period":30,"voting_power":"229332471400000000000000000000","unlocking_started_at":1714723375781,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"albing.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4846207310","locking_period":300,"voting_power":"24231036550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"82912090","locking_period":300,"voting_power":"414560450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"9663757500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"1500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"3000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptogarik.poolv1.near","voting_power":"10481839500000000000000000000"}]},{"voter_id":"ronyle.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13650000000","locking_period":300,"voting_power":"68250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"68250000000000000000000000000","vote_positions":[]},{"voter_id":"5687c659be1c73efa9cc4f5f90e466b39ec929d4c0ba5871a50df092015f49de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"115729500","locking_period":30,"voting_power":"57864750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"57864750000000000000000000","vote_positions":[]},{"voter_id":"imiroslav.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"15000000000","locking_period":120,"voting_power":"30000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4803812600","locking_period":180,"voting_power":"14411437800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"polkachu.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"24411437800000000000000000000"}]},{"voter_id":"sivakalathi006.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100672746900","locking_period":300,"voting_power":"503363734500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ruziev.poolv1.near","voting_power":"12354614200000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sharpdarts.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"ni.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pinrocks.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptotribe.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearfans.poolv1.near","voting_power":"18000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"buildnear.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearvietnamhub.poolv1.near","voting_power":"10009120300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"iamcryptobro.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dsrvlabs-staking.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"89|mpDAO Grants #7 - June 2024-trovelabs.near","voting_power":"2000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"10000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"136|mpDAO Grants #8 - August 2024-bstt.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"128|mpDAO Grants #8 - August 2024-aleiranova.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"133|mpDAO Grants #8 - August 2024-ffbc3084a4d75ed3c28a7b6c26f44a4ffb43dc3959365d80d70a9660913cbacf","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"4000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"118|mpDAO Grants #8 - August 2024-marcelorefi.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"129|mpDAO Grants #8 - August 2024-0xmitz.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"144|mpDAO Grants #8 - August 2024-cryptopr3neur.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"125|mpDAO Grants #8 - August 2024-xiiw.near","voting_power":"3000000000000000000000000000"}]},{"voter_id":"ilya.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"176896422200","locking_period":165,"voting_power":"486465161050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000050000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"323792844400000000000000000000"},{"votable_address":"initiatives","votable_object_id":"96|mpDAO Grants #7 - June 2024-dapplooker.near","voting_power":"30000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"118|mpDAO Grants #8 - August 2024-marcelorefi.near","voting_power":"10000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"108|mpDAO Grants #7 - June 2024-553a5330546aa39850aa0e972bc1bec1c7018575ba141fa6f0a379cf28d46d16","voting_power":"40000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"72672316600000000000000000000"}]},{"voter_id":"a.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"232537338800","locking_period":300,"voting_power":"1162686694000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"1162686694000000000000000000000"}]},{"voter_id":"bestlove89.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"333301900","locking_period":90,"voting_power":"499952850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"499952800000000000000000000"}]},{"voter_id":"pahol.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"73524025800","locking_period":300,"voting_power":"367620129000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"367620129000000000000000000000"}]},{"voter_id":"ktxfauzy.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8834144400","locking_period":300,"voting_power":"44170722000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"44170720000000000000000000000"}]},{"voter_id":"mr_free.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"38000000","locking_period":90,"voting_power":"57000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"57000000000000000000000000","vote_positions":[]},{"voter_id":"173ec8e6ef2f0e328753c1be4779bb5467c02f14ea6b9fe1e69221d7bdc93f6e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"andersonr.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"354251600","locking_period":300,"voting_power":"1771258000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1771258000000000000000000000","vote_positions":[]},{"voter_id":"a4d73137b6ed71e647678dafe514f94e31e2312e2c7a507125c3803bb2bcd475","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20000000000","locking_period":30,"voting_power":"10000000000000000000000000000","unlocking_started_at":1728258996216,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"profit.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6463933370850","locking_period":300,"voting_power":"32319666854250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"5000000","locking_period":300,"voting_power":"25000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"oe.poolv1.near","voting_power":"30000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"2319691854200000000000000000000"}]},{"voter_id":"brzk-93444.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5727419100","locking_period":90,"voting_power":"8591128650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3591128650000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"2000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"3000000000000000000000000000"}]},{"voter_id":"djq.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1395950700","locking_period":30,"voting_power":"697975350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5350000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"697970000000000000000000000"}]},{"voter_id":"e_reece.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2394446500","locking_period":300,"voting_power":"11972232500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"11972232500000000000000000000"}]},{"voter_id":"ollie.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"701794250000","locking_period":300,"voting_power":"3508971250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3508971250000000000000000000000"}]},{"voter_id":"transitmoi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2414219500","locking_period":300,"voting_power":"12071097500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12071097500000000000000000000"}]},{"voter_id":"chloe.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"65916831040","locking_period":300,"voting_power":"329584155200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"25037114700","locking_period":90,"voting_power":"37555672050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"281461072000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"85678755200000000000000000000"}]},{"voter_id":"expenses-meta-pool.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"324785000","locking_period":180,"voting_power":"974355000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"974355000000000000000000000","vote_positions":[]},{"voter_id":"prinwattana.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5153400","locking_period":90,"voting_power":"7730100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7730100000000000000000000","vote_positions":[]},{"voter_id":"fliareccha.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1163826721450","locking_period":300,"voting_power":"5819133607250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"3497000500000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"501119148000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"iamcryptobro.poolv1.near","voting_power":"319000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pathrocknetwork.poolv1.near","voting_power":"502013959200000000000000000000"}]},{"voter_id":"gryptoguru.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"197872100","locking_period":300,"voting_power":"989360500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"300000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"689360500000000000000000000"}]},{"voter_id":"kriptocilginlari.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"45366000","locking_period":300,"voting_power":"226830000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"226830000000000000000000000","vote_positions":[]},{"voter_id":"0x0299b2c88809F5Cd83425c8ea8FffF332a1E6e04.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"liusisi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1500624300","locking_period":90,"voting_power":"2250936450000000000000000000","unlocking_started_at":1717551396822,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"secure.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"794870926300","locking_period":300,"voting_power":"3974354631500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"3974354631500000000000000000000"}]},{"voter_id":"centurion.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"244203961600","locking_period":300,"voting_power":"1221019808000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"1221019808000000000000000000000"}]},{"voter_id":"jaysonkr.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"418517018400","locking_period":300,"voting_power":"2092585092000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"2092585092000000000000000000000"}]},{"voter_id":"serxy.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000000","locking_period":60,"voting_power":"100000000000000000000000000000","unlocking_started_at":1730635967602,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"100000000000","locking_period":270,"voting_power":"450000000000000000000000000000","unlocking_started_at":1730635990484,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0x7bF9a18915D1d40CfDa5ADaa39028F62DC7192E0.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"awsomzs.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18544446700","locking_period":300,"voting_power":"92722233500000000000000000000","unlocking_started_at":1715627876104,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0x15f4dE244107DFd83039927becB74934969087C2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"165283000000","locking_period":180,"voting_power":"495849000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"121|mpDAO Grants #8 - August 2024-meximalist.near","voting_power":"495849000000000000000000000000"}]},{"voter_id":"bochenn.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"31287097200","locking_period":300,"voting_power":"156435486000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"156435486000000000000000000000","vote_positions":[]},{"voter_id":"cryptonum.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10333242300","locking_period":300,"voting_power":"51666211500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakely_io.poolv1.near","voting_power":"15000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"16666211500000000000000000000"}]},{"voter_id":"chiennguyen1985.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"144355687100","locking_period":300,"voting_power":"721778435500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"68849750000","locking_period":300,"voting_power":"344248750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"928327685500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"137699500000000000000000000000"}]},{"voter_id":"pacek.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"242576650800","locking_period":210,"voting_power":"849018277800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"7729000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"720000952400000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"121288325400000000000000000000"}]},{"voter_id":"cunum.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"140598200600","locking_period":300,"voting_power":"702991003000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"cunum.poolv1.near","voting_power":"702991003000000000000000000000"}]},{"voter_id":"chain0ps.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"117241000000","locking_period":300,"voting_power":"586205000000000000000000000000","unlocking_started_at":1729968457538,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"1187500","locking_period":255,"voting_power":"5046875000000000000000000","unlocking_started_at":1717486868740,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"dipsyv.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40000000","locking_period":300,"voting_power":"200000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"200000000000000000000000000"}]},{"voter_id":"3a996e03e7247a0989f546b1b1d02905f53bc8e3fd23920c725d642910f902c8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3632000","locking_period":210,"voting_power":"12712000000000000000000000","unlocking_started_at":1722423551140,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"5000000","locking_period":210,"voting_power":"17500000000000000000000000","unlocking_started_at":1722535624172,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"97140000000","locking_period":195,"voting_power":"315705000000000000000000000000","unlocking_started_at":1725335843062,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":3,"amount":"100000000000","locking_period":300,"voting_power":"500000000000000000000000000000","unlocking_started_at":1729966101525,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"impe83.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000000","locking_period":30,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10000000000","locking_period":90,"voting_power":"15000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakely_io.poolv1.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"133|mpDAO Grants #8 - August 2024-ffbc3084a4d75ed3c28a7b6c26f44a4ffb43dc3959365d80d70a9660913cbacf","voting_power":"5000000000000000000000000000"}]},{"voter_id":"2d36feb3bc40513b31e852100a2b70cdb143bfade40a393a8c04d4931e10175c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000","locking_period":300,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"500000000000000000000000000"}]},{"voter_id":"luzmargaritas.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"615737007700","locking_period":90,"voting_power":"923605511550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"95426921230","locking_period":300,"voting_power":"477134606150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"1140000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_01.poolv1.near","voting_power":"123631511500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nativo.pool.near","voting_power":"39815992700000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"40000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lavenderfive.poolv1.near","voting_power":"48993613500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"8300000000000000000000000000"}]},{"voter_id":"satojandro.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"336741374200","locking_period":300,"voting_power":"1683706871000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"14638000000","locking_period":90,"voting_power":"21957000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"optimusvalidatornetwork.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lux.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"205663871000000000000000000000"}]},{"voter_id":"lovelysugar.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":30,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"320000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"30000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"69|mpDAO Grants #7 - June 2024-odisea.near","voting_power":"50000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"100000000000000000000000000"}]},{"voter_id":"vinhdao286.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000000","locking_period":270,"voting_power":"36000000000000000000000000000","unlocking_started_at":1714882252568,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"566000000","locking_period":300,"voting_power":"2830000000000000000000000000","unlocking_started_at":1714882266639,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"kangaroojack.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"706830800","locking_period":300,"voting_power":"3534154000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"128000000","locking_period":30,"voting_power":"64000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"89|mpDAO Grants #7 - June 2024-trovelabs.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"90|mpDAO Grants #7 - June 2024-nekotoken.near","voting_power":"598154000000000000000000000"}]},{"voter_id":"danieldao.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3787360000","locking_period":300,"voting_power":"18936800000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"18936800000000000000000000000"}]},{"voter_id":"b9dca1592030dcd114852805d6efaf86b93a51c95613ee184233e9604a5d5924","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"plenum.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16226095800","locking_period":300,"voting_power":"81130479000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"81130479000000000000000000000"}]},{"voter_id":"patvg.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"332890200","locking_period":180,"voting_power":"998670600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"104|mpDAO Grants #7 - June 2024-lematecat.near","voting_power":"998670600000000000000000000"}]},{"voter_id":"ebtgroup.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3333333400000","locking_period":90,"voting_power":"5000000100000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000100000000000000000000000","vote_positions":[]},{"voter_id":"ux1.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"400000000000","locking_period":300,"voting_power":"2000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"2000000000000000000000000000000"}]},{"voter_id":"idomoji.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3549130400","locking_period":300,"voting_power":"17745652000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"17745652000000000000000000000"}]},{"voter_id":"skyempire.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"11100474500","locking_period":300,"voting_power":"55502372500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"15000000","locking_period":210,"voting_power":"52500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"55554872500000000000000000000","vote_positions":[]},{"voter_id":"jefe.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6349837300","locking_period":270,"voting_power":"28574267850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"28574267850000000000000000000","vote_positions":[]},{"voter_id":"victorchaos.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"57167811100","locking_period":30,"voting_power":"28583905550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"28583905550000000000000000000","vote_positions":[]},{"voter_id":"0x442d6AF71aD2f51698ef42C3Df8584b78285a7D2.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"028521fdcdb25436d59ca93155651d613b6d0312010fea8f69d1fbebeeb91b15","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"170380300","locking_period":30,"voting_power":"85190150000000000000000000","unlocking_started_at":1730853253861,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"1357971200","locking_period":60,"voting_power":"1357971200000000000000000000","unlocking_started_at":1730853292623,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"247486800","locking_period":90,"voting_power":"371230200000000000000000000","unlocking_started_at":1730853332528,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"854dea7ff905f277012f0f4a1a029494d3ba90352166a89fac32942e6a2d1ef5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1181470160000","locking_period":300,"voting_power":"5907350800000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"polkachu.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"407350800000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"optimusvalidatornetwork.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dexagon.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptogarik.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lavenderfive.poolv1.near","voting_power":"500000000000000000000000000000"}]},{"voter_id":"032890df5f801f63f81175ca1dc53617657dd309b6967814461b344af606942b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"127575925500","locking_period":300,"voting_power":"637879627500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"459000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"178879627500000000000000000000"}]},{"voter_id":"938c83fcf90350e4e5ad7f1a802d3db742718e194efbc77cf5a250742f61aeb9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"157101400","locking_period":300,"voting_power":"785507000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"785507000000000000000000000"}]},{"voter_id":"4ebf8d13c98d4dfe90e272a6ca92657ce87478383313e2c3848374b9fcdd0d9c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1585391570","locking_period":300,"voting_power":"7926957850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"6136078000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1790879800000000000000000000"}]},{"voter_id":"103ddbe67a0e8be99b8515cb9ae78bb9e169cda00db8d29a0fa38aa4814a9b9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1600000000","locking_period":300,"voting_power":"8000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2900000000","locking_period":30,"voting_power":"1450000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1500000000","locking_period":90,"voting_power":"2250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dacmpool.poolv1.near","voting_power":"5700000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000000"}]},{"voter_id":"isk0p4t1x.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1575695300","locking_period":180,"voting_power":"4727085900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4727085900000000000000000000"}]},{"voter_id":"suave.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"42346290500","locking_period":180,"voting_power":"127038871500000000000000000000","unlocking_started_at":1717480136533,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"814000000","locking_period":300,"voting_power":"4070000000000000000000000000","unlocking_started_at":1717480199068,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"1962000000","locking_period":90,"voting_power":"2943000000000000000000000000","unlocking_started_at":1717480213232,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"craftorium.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8682966500","locking_period":120,"voting_power":"17365933000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17365933000000000000000000000","vote_positions":[]},{"voter_id":"wmheng78.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"87927199300","locking_period":120,"voting_power":"175854398600000000000000000000","unlocking_started_at":1731517191195,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"3442041200","locking_period":30,"voting_power":"1721020600000000000000000000","unlocking_started_at":1732458459623,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"bender172319.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"404246500","locking_period":165,"voting_power":"1111677875000000000000000000","unlocking_started_at":1714941537536,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0xmitz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"30530952900","locking_period":300,"voting_power":"152654764500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"152654764500000000000000000000"}]},{"voter_id":"0782f78681fdf8597ea70f16a52b57096e386e192f6c8ee316847808537b06a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"784063850","locking_period":300,"voting_power":"3920319250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3920319250000000000000000000","vote_positions":[]},{"voter_id":"331835d03584caa318219412d054010e5433fd19bdc872c954832f9295d89ff2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"trilli.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"61808170000","locking_period":300,"voting_power":"309040850000000000000000000000","unlocking_started_at":1717009373429,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"laurentcora.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"20000000","locking_period":30,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"3923f6035ff12ae3a2b2de19c3a75f8b13e31fe9b6fd5835f0cefdf7a8bddb52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"78736645600","locking_period":90,"voting_power":"118104968400000000000000000000","unlocking_started_at":1732072368058,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0a561cd515b6c7456452202daecb20971ba9ef68284f96485394a7124716f91e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"kingofkingspages.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"98482160","locking_period":300,"voting_power":"492410800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"342824900000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"21596900000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"smart-stake.poolv1.near","voting_power":"30000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"30000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"142|mpDAO Grants #8 - August 2024-refibogota.near","voting_power":"27000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"136|mpDAO Grants #8 - August 2024-bstt.near","voting_power":"40989000000000000000000000"}]},{"voter_id":"cosine.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"44277800","locking_period":90,"voting_power":"66416700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"51600000000","locking_period":30,"voting_power":"25800000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"minion.poolv1.near","voting_power":"25866416700000000000000000000"}]},{"voter_id":"stakeblock.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"84164564800","locking_period":300,"voting_power":"420822824000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"420822824000000000000000000000","vote_positions":[]},{"voter_id":"xuantd06.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"701000000","locking_period":300,"voting_power":"3505000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"9428345600","locking_period":105,"voting_power":"16499604800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"3299000000","locking_period":300,"voting_power":"16495000000000000000000000000","unlocking_started_at":1717902660136,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"4604800000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000000"}]},{"voter_id":"duongnhuice.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1225005600","locking_period":300,"voting_power":"6125028000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6125028000000000000000000000","vote_positions":[]},{"voter_id":"ac15a0f879f76d5fed6b617171656c40f5232730dcfc4b45799136522537f35b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"35894259300","locking_period":90,"voting_power":"53841388950000000000000000000","unlocking_started_at":1729266569561,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"960000000","locking_period":45,"voting_power":"720000000000000000000000000","unlocking_started_at":1729266127685,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"540576900","locking_period":120,"voting_power":"1081153800000000000000000000","unlocking_started_at":1729266188207,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"bf947d9a9ac8934645559f9cd97da96eddf527be21ef7d7e635ee526bfea1d52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18809136700","locking_period":90,"voting_power":"28213705050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"15000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"13213705000000000000000000000"}]},{"voter_id":"acaryln.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":30,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"335000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"69|mpDAO Grants #7 - June 2024-odisea.near","voting_power":"65000000000000000000000000"}]},{"voter_id":"antiscaliger.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"58327500","locking_period":90,"voting_power":"87491250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"87491250000000000000000000","vote_positions":[]},{"voter_id":"pangdao.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4383355100","locking_period":300,"voting_power":"21916775500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"21916775500000000000000000000","vote_positions":[]},{"voter_id":"avtf.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2501484140100","locking_period":300,"voting_power":"12507420700500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12507420700500000000000000000000"}]},{"voter_id":"sandeephulsur.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"206509764000","locking_period":180,"voting_power":"619529292000000000000000000000","unlocking_started_at":1718557112477,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"imagine_nl.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"44178800","locking_period":45,"voting_power":"33134100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"33134100000000000000000000","vote_positions":[]},{"voter_id":"7ab36cef3907536334b06360c8ecad2f68be4205cd422484135ab579b6fadde4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"196954300","locking_period":90,"voting_power":"295431450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"295431450000000000000000000","vote_positions":[]},{"voter_id":"akaiqn113.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"25000000000","locking_period":300,"voting_power":"125000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"31250000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"93750000000000000000000000000"}]},{"voter_id":"4aafb0fa32afa8bb20a3e15c8ac55126f4bf325c476a6575498cc8af11d05d52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2815258700","locking_period":300,"voting_power":"14076293500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"1407629300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"12668664200000000000000000000"}]},{"voter_id":"tzouf.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40000000000","locking_period":300,"voting_power":"200000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"200000000000000000000000000000","vote_positions":[]},{"voter_id":"fernix.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1548460000","locking_period":150,"voting_power":"3871150000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"3871150000000000000000000000"}]},{"voter_id":"baf318138fe9ae6648a1e06f12f6a7ac079d47c6097eb41737fc1f8c9cd3b71c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2137460000","locking_period":300,"voting_power":"10687300000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"88|mpDAO Grants #7 - June 2024-100diasder.near","voting_power":"10687300000000000000000000000"}]},{"voter_id":"rahulgoel.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"919872500","locking_period":300,"voting_power":"4599362500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4599362500000000000000000000","vote_positions":[]},{"voter_id":"viettrungdoan91.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"49647336300","locking_period":135,"voting_power":"111706506675000000000000000000","unlocking_started_at":1715001641799,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"sailinon.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46872089800","locking_period":30,"voting_power":"23436044900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"23436044900000000000000000000","vote_positions":[]},{"voter_id":"venomity.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"218000000","locking_period":300,"voting_power":"1090000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"1090000000000000000000000000"}]},{"voter_id":"tahuutho.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1548460000","locking_period":240,"voting_power":"6193840000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6193840000000000000000000000"}]},{"voter_id":"mxweb3.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"57674139700","locking_period":300,"voting_power":"288370698500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"288370698500000000000000000000"}]},{"voter_id":"magic.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"59192298500","locking_period":300,"voting_power":"295961492500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"295961492500000000000000000000"}]},{"voter_id":"dollarinveste.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7795086000","locking_period":300,"voting_power":"38975430000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"38975430000000000000000000000"}]},{"voter_id":"wap909.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"424500401120","locking_period":300,"voting_power":"2122502005600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"130000000000","locking_period":300,"voting_power":"650000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"10000000000","locking_period":300,"voting_power":"50000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"35500000000","locking_period":300,"voting_power":"177500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"115505000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"minion.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"here.poolv1.near","voting_power":"230000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"63169390100000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"26005000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"144950000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"105|mpDAO Grants #7 - June 2024-marcelorefi.near","voting_power":"200000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"91|mpDAO Grants #7 - June 2024-4d1fe2e38339dc82b68afd1d09bac3068c30f22f09dc693e8ab6610da8e15c04","voting_power":"220372615500000000000000000000"},{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"200000000000000000000000000000"}]},{"voter_id":"3add6ae62eb60d4edadca209e37012780f32c3d3cf31868496b91f1988bf043a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"37137117500","locking_period":90,"voting_power":"55705676250000000000000000000","unlocking_started_at":1716421523901,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"newnearwallet.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"845742145800","locking_period":150,"voting_power":"2114355364500000000000000000000","unlocking_started_at":1732208388683,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"draconnia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1903014900","locking_period":300,"voting_power":"9515074500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"347000000","locking_period":30,"voting_power":"173500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"9688574500000000000000000000"}]},{"voter_id":"544e14070a33ec9a7e5d34556f5ddaa45411e73d394039cc241fa92e2bc5cfb5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2067778200","locking_period":300,"voting_power":"10338891000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"1000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sharpdarts.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"welldonestake.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zkv_staketosupportprivacy.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"trust-nodes.poolv1.near","voting_power":"300000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"4ire.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pinrocks.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"centurion.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pathrocknetwork.poolv1.near","voting_power":"1667300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"ni.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptotribe.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"637223700000000000000000000"}]},{"voter_id":"vvm25.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"57700000","locking_period":300,"voting_power":"288500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"288500000000000000000000000","vote_positions":[]},{"voter_id":"dund.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1204000000","locking_period":30,"voting_power":"602000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"576000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"67|mpDAO Grants #7 - June 2024-baryo01.near","voting_power":"10000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"112|mpDAO Grants #7 - June 2024-hoangthien1109.near","voting_power":"11000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"69|mpDAO Grants #7 - June 2024-odisea.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"9968.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1050000000","locking_period":30,"voting_power":"525000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"500000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"112|mpDAO Grants #7 - June 2024-hoangthien1109.near","voting_power":"15000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"8000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"67|mpDAO Grants #7 - June 2024-baryo01.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"x999.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1010629400","locking_period":45,"voting_power":"757972050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"710972050000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"112|mpDAO Grants #7 - June 2024-hoangthien1109.near","voting_power":"20000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"10000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"68|mpDAO Grants #7 - June 2024-3sblockchain.near","voting_power":"17000000000000000000000000"}]},{"voter_id":"pablopd.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6741186400","locking_period":300,"voting_power":"33705932000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"33705932000000000000000000000"}]},{"voter_id":"marysol.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"48051052800","locking_period":300,"voting_power":"240255264000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"134|mpDAO Grants #8 - August 2024-marcrypted.near","voting_power":"240255264000000000000000000000"}]},{"voter_id":"cryptomoneymaker.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"31652702800","locking_period":30,"voting_power":"15826351400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4794004400","locking_period":60,"voting_power":"4794004400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"523000000","locking_period":300,"voting_power":"2615000000000000000000000000","unlocking_started_at":1715076609230,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20620355800000000000000000000"}]},{"voter_id":"iosg01.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13100001000000","locking_period":30,"voting_power":"6550000500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6550000500000000000000000000000","vote_positions":[]},{"voter_id":"towanda.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"25500000","locking_period":45,"voting_power":"19125000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1439316500","locking_period":60,"voting_power":"1439316500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"20000000","locking_period":90,"voting_power":"30000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"9500000","locking_period":30,"voting_power":"4750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"1493191500000000000000000000"}]},{"voter_id":"suz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"122153000000","locking_period":90,"voting_power":"183229500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"183229500000000000000000000000"}]},{"voter_id":"nikhil.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5076282200","locking_period":300,"voting_power":"25381411000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"25381411000000000000000000000","vote_positions":[]},{"voter_id":"metapool20.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"101876349400","locking_period":300,"voting_power":"509381747000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"509381747000000000000000000000"}]},{"voter_id":"usainbluntmx.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4967206710","locking_period":300,"voting_power":"24836033550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4836033550000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"20000000000000000000000000000"}]},{"voter_id":"psd.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"800000000000","locking_period":30,"voting_power":"400000000000000000000000000000","unlocking_started_at":1718077736393,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"meximalist.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2248074700","locking_period":300,"voting_power":"11240373500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"11240373500000000000000000000"}]},{"voter_id":"xvii.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"416786600","locking_period":300,"voting_power":"2083933000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"17000000","locking_period":30,"voting_power":"8500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2092433000000000000000000000","vote_positions":[]},{"voter_id":"norelyz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1100000000","locking_period":300,"voting_power":"5500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"191000000","locking_period":90,"voting_power":"286500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5786500000000000000000000000"}]},{"voter_id":"smjunior.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2914829100","locking_period":300,"voting_power":"14574145500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"200000000","locking_period":300,"voting_power":"1000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"10574145500000000000000000000"}]},{"voter_id":"rondell.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"302214342100","locking_period":90,"voting_power":"453321513150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"26138245800","locking_period":105,"voting_power":"45741930150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"499063443300000000000000000000","vote_positions":[]},{"voter_id":"paderuu.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"201000000","locking_period":300,"voting_power":"1005000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"114|mpDAO Grants #7 - June 2024-aksanova33.near","voting_power":"1005000000000000000000000000"}]},{"voter_id":"hos5018.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3163661200","locking_period":300,"voting_power":"15818306000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"15818306000000000000000000000"}]},{"voter_id":"nearmultiverse.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"36833578700","locking_period":300,"voting_power":"184167893500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"419077000","locking_period":300,"voting_power":"2095385000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"186263278500000000000000000000"}]},{"voter_id":"0xAcaa2077b89799b81F390d9cf015Be8d38108A36.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6834000000","locking_period":300,"voting_power":"34170000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11615000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2pstaking.poolv1.near","voting_power":"10555000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"12000000000000000000000000000"}]},{"voter_id":"agusin.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"658762400","locking_period":300,"voting_power":"3293812000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"3293812000000000000000000000"}]},{"voter_id":"3828a8400589b9faa74d53169ee33bad7a2ed3b68bfb01e8c855454391e7b7a7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"42155844100","locking_period":90,"voting_power":"63233766150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"63233766150000000000000000000","vote_positions":[]},{"voter_id":"okpok.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"132894668500","locking_period":300,"voting_power":"664473342500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"26251089900","locking_period":30,"voting_power":"13125544950000000000000000000","unlocking_started_at":1730781596655,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakeseeker.poolv1.near","voting_power":"664473342500000000000000000000"}]},{"voter_id":"bjirken.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"437000000","locking_period":30,"voting_power":"218500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"200000000","locking_period":300,"voting_power":"1000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"535000000","locking_period":90,"voting_power":"802500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2021000000000000000000000000","vote_positions":[]},{"voter_id":"bjorken.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"62169000000","locking_period":270,"voting_power":"279760500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"20000000000","locking_period":135,"voting_power":"45000000000000000000000000000","unlocking_started_at":1725956697284,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"279760500000000000000000000000"}]},{"voter_id":"tiberiu16.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3760603900","locking_period":300,"voting_power":"18803019500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"18803019500000000000000000000"}]},{"voter_id":"samwangg.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3186786600","locking_period":75,"voting_power":"3983483250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3983483250000000000000000000","vote_positions":[]},{"voter_id":"sam_wang.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17500000000","locking_period":75,"voting_power":"21875000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"21875000000000000000000000000","vote_positions":[]},{"voter_id":"flyingcumbancha.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4686891800","locking_period":300,"voting_power":"23434459000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"23434459000000000000000000000"}]},{"voter_id":"izcc.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16211176100","locking_period":300,"voting_power":"81055880500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"115|mpDAO Grants #8 - August 2024-irvingzcc.near","voting_power":"81055880500000000000000000000"}]},{"voter_id":"e474b668ea4731cf923bc78733f14c849b2d2bb78657af863a84dbc670fe746c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"669000000","locking_period":300,"voting_power":"3345000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"3345000000000000000000000000"}]},{"voter_id":"fersoria.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"117000000","locking_period":180,"voting_power":"351000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1000000000","locking_period":180,"voting_power":"3000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"77|mpDAO Grants #7 - June 2024-1724cbdb130e5b0f9bd750f666f4fe00581489fcc294dca4ed055c90c76808fc","voting_power":"3351000000000000000000000000"}]},{"voter_id":"zahrah.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"261000000","locking_period":300,"voting_power":"1305000000000000000000000000","unlocking_started_at":1714795035865,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"e8669b64d048c0b7627a49250cd08407f9020c4ec252350838c29956c53f4a92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"11525635800","locking_period":300,"voting_power":"57628179000000000000000000000","unlocking_started_at":1716310161875,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"jack1265.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7489666000","locking_period":60,"voting_power":"7489666000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7489666000000000000000000000","vote_positions":[]},{"voter_id":"cryptoreumd.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"574343300","locking_period":300,"voting_power":"2871716500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"2871716500000000000000000000"}]},{"voter_id":"c8a73319812a4e49a3d18f097cc5b00c105d724c5451ec5e38c6146c5561c800","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"419828200","locking_period":90,"voting_power":"629742300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"333171200","locking_period":210,"voting_power":"1166099200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1795841500000000000000000000"}]},{"voter_id":"wdz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3624088600","locking_period":300,"voting_power":"18120443000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18120443000000000000000000000","vote_positions":[]},{"voter_id":"imok.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1496000000","locking_period":90,"voting_power":"2244000000000000000000000000","unlocking_started_at":1717345585368,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"car_parts.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6569600000","locking_period":90,"voting_power":"9854400000000000000000000000","unlocking_started_at":1714991687794,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"2296509600","locking_period":300,"voting_power":"11482548000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"1732548000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"9750000000000000000000000000"}]},{"voter_id":"mullthebull.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27830480000","locking_period":240,"voting_power":"111321920000000000000000000000","unlocking_started_at":1728571162160,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"1000000000","locking_period":30,"voting_power":"500000000000000000000000000","unlocking_started_at":1728571173050,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"flokielon.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"93669107700","locking_period":300,"voting_power":"468345538500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"1545538500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"466800000000000000000000000000"}]},{"voter_id":"condog.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28830480000","locking_period":285,"voting_power":"136944780000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"136944780000000000000000000000"}]},{"voter_id":"lastryano.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27027920000","locking_period":285,"voting_power":"128382620000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"128382620000000000000000000000"}]},{"voter_id":"zardoz90.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"24554920000","locking_period":285,"voting_power":"116635870000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"883000000","locking_period":135,"voting_power":"1986750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"118622620000000000000000000000"}]},{"voter_id":"farpost.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"43719400","locking_period":300,"voting_power":"218597000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"218597000000000000000000000","vote_positions":[]},{"voter_id":"cuongdcdev.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3944328600","locking_period":300,"voting_power":"19721643000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"721000000","locking_period":30,"voting_power":"360500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"buildnear.poolv1.near","voting_power":"20082143000000000000000000000"}]},{"voter_id":"nostory.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"800000","locking_period":60,"voting_power":"800000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"734556000","locking_period":300,"voting_power":"3672780000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"1000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"1000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"1000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"673580000000000000000000000"}]},{"voter_id":"309872076a518c9c96935b959d13e2f23546113bc6ea41941e5562b65cbf7085","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"669000000","locking_period":165,"voting_power":"1839750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1839750000000000000000000000","vote_positions":[]},{"voter_id":"0x56D994c08CdC5c9189B4d6863129e5F195C6E8F2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"15034000000","locking_period":120,"voting_power":"30068000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"30068000000000000000000000000"}]},{"voter_id":"0x16E44d8F1BEc8fb6c6770ABE8E8EF199B7723c9a.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27095000000","locking_period":300,"voting_power":"135475000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"135475000000000000000000000000"}]},{"voter_id":"0xA4243e13bD28fa9e0e67704E381cb98d06240C93.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"30000000000","locking_period":300,"voting_power":"150000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"here.poolv1.near","voting_power":"150000000000000000000000000000"}]},{"voter_id":"0x22B052CbBf01cfE2EC5A8424a7d361f376aEaEb0.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"50032000000","locking_period":300,"voting_power":"250160000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"250160000000000000000000000000"}]},{"voter_id":"0xEA3e3612175a894362f3062785adC133D9b98f10.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"285827000000","locking_period":300,"voting_power":"1429135000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"929135000000000000000000000000"}]},{"voter_id":"c47093dc9f12d71135b4940a0744944b7936b0d4458bc1feab0eafb4d3d1249d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":270,"voting_power":"18000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[]},{"voter_id":"0x2929f6FEDD4897236fB97A059aaA1fe057adeB0e.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"30767000000","locking_period":300,"voting_power":"153835000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"153835000000000000000000000000"}]},{"voter_id":"0xE3Ecd1907caa757A4a5C9DA32d7AA34005681C1e.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16251000000","locking_period":300,"voting_power":"81255000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"81255000000000000000000000000"}]},{"voter_id":"0xf5597b4094058efe44fe61C8f2844823Fc566e3c.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"650000000","locking_period":300,"voting_power":"3250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"3250000000000000000000000000"}]},{"voter_id":"0x1430b0DF64C0126c6184D2f28b1cF505f39147b2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"19921000000","locking_period":300,"voting_power":"99605000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"99605000000000000000000000000"}]},{"voter_id":"0x1fb751200fda2aBa12BA2C254c3c02d23E06dB8D.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1508000000","locking_period":300,"voting_power":"7540000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"7540000000000000000000000000"}]},{"voter_id":"0xC479d61faFAd7374af8eE1902d3797E8cCAb0522.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6460000000","locking_period":300,"voting_power":"32300000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"32300000000000000000000000000"}]},{"voter_id":"5d7e273e59ce62b306698ce7fb9a8b885264be2ce90980254c85b361d9d8d71b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"invoices.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"246428600500","locking_period":300,"voting_power":"1232143002500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"1232143002500000000000000000000"}]},{"voter_id":"pat.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6823431700","locking_period":300,"voting_power":"34117158500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"oharanodes.poolv1.near","voting_power":"34117158500000000000000000000"}]},{"voter_id":"seanvan1411.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"31670100000","locking_period":225,"voting_power":"118762875000000000000000000000","unlocking_started_at":1725956985812,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"50000000000","locking_period":225,"voting_power":"187500000000000000000000000000","unlocking_started_at":1725956931014,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"2349000000","locking_period":285,"voting_power":"11157750000000000000000000000","unlocking_started_at":1725957070629,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"number9dream.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12029171300","locking_period":150,"voting_power":"30072928250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"30072928200000000000000000000"}]},{"voter_id":"e5fb81d59683c942333b8f420ab667dcc33beeea5227c19350ca0c1c6b7439dd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1089218300","locking_period":300,"voting_power":"5446091500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"75|mpDAO Grants #7 - June 2024-abrahamrivv.near","voting_power":"2700000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"2746091500000000000000000000"}]},{"voter_id":"ihyshan.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10055324400","locking_period":300,"voting_power":"50276622000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"651000000","locking_period":90,"voting_power":"976500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"51253122000000000000000000000"}]},{"voter_id":"0x32292056970DCe4B9528d71642F765f14E0F1042.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1315000000","locking_period":300,"voting_power":"6575000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6575000000000000000000000000","vote_positions":[]},{"voter_id":"0x8989964863cf4dbaedFFbb4F43Abcf54DA5F5f1b.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1410209000000","locking_period":300,"voting_power":"7051045000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"7051045000000000000000000000000"}]},{"voter_id":"0x1256E7992564AB22e332532472c916Bd8D1e1Ca7.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1735000000","locking_period":120,"voting_power":"3470000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3470000000000000000000000000","vote_positions":[]},{"voter_id":"ivanovic.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"48900983500","locking_period":300,"voting_power":"244504917500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"244504917500000000000000000000"}]},{"voter_id":"developer.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1040497000000","locking_period":90,"voting_power":"1560745500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"650100000000","locking_period":165,"voting_power":"1787775000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"90000000000","locking_period":300,"voting_power":"450000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"10000000000","locking_period":120,"voting_power":"20000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"1000783000000","locking_period":105,"voting_power":"1751370250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2534890750000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"950000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearstar.poolv1.near","voting_power":"1900000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"101|mpDAO Grants #7 - June 2024-criptolaplata.near","voting_power":"150000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"87|mpDAO Grants #7 - June 2024-97c131e898ddae0f081783a3d43dfef5434e098728fed3276eeece5ab5a7a1c6","voting_power":"35000000000000000000000000000"}]},{"voter_id":"e7bfa469cc055cb12f4d90e551e5cb128fda19400da59768b0963cde89fc774d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"natcrypto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"848677601500","locking_period":150,"voting_power":"2121694003750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"47498000000","locking_period":120,"voting_power":"94996000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dexagon.poolv1.near","voting_power":"222000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"rekt.poolv1.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"101|mpDAO Grants #7 - June 2024-criptolaplata.near","voting_power":"350000003700000000000000000000"},{"votable_address":"initiatives","votable_object_id":"79|mpDAO Grants #7 - June 2024-478f0f81f81824a1db9f1c434b73e2c800e2992069a2d0484d050a86406a4402","voting_power":"95000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"81|mpDAO Grants #7 - June 2024-herdaomexico.near","voting_power":"150000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"94|mpDAO Grants #7 - June 2024-cryptopr3neur.near","voting_power":"190000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"120000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"107|mpDAO Grants #7 - June 2024-ae1ddf13d147c6afdb5e0d53274ea31a10f12206bebfa082a231df5a6cfa82d9","voting_power":"220000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"102|mpDAO Grants #7 - June 2024-fr-grant.near","voting_power":"400000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"108|mpDAO Grants #7 - June 2024-553a5330546aa39850aa0e972bc1bec1c7018575ba141fa6f0a379cf28d46d16","voting_power":"169690000000000000000000000000"}]},{"voter_id":"0xCF6d2f6E2839ac50f49a76c1DA88f82F62d29B74.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1200000000","locking_period":300,"voting_power":"6000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000000","vote_positions":[]},{"voter_id":"eventosmetapool.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000","locking_period":300,"voting_power":"50000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"50000000000000000000000000"}]},{"voter_id":"davidmo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17578467300","locking_period":90,"voting_power":"26367700950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"26367700900000000000000000000"}]},{"voter_id":"0x36D93bAC8ffcc3af98cCf3A5A1b2588df5508256.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1418000000","locking_period":105,"voting_power":"2481500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2481500000000000000000000000","vote_positions":[]},{"voter_id":"smile143.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":90,"voting_power":"1500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"1500000000000000000000000000"}]},{"voter_id":"0x7AbAEb445D6c0036B386c1Ccd0c4d58c528AC450.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"0xcc7B864e79fA2eB1Aa43905A74815fac8Dd99fc7.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18472000000","locking_period":300,"voting_power":"92360000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"92360000000000000000000000000"}]},{"voter_id":"0xFFaCD7e55e79fABeD8E6096A5ab325AE38A5faC8.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"75937000000","locking_period":300,"voting_power":"379685000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"379685000000000000000000000000"}]},{"voter_id":"0x26B0Bb09478A8423b53b2854dEfFb8A61A889725.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"102861000000","locking_period":300,"voting_power":"514305000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"514305000000000000000000000000"}]},{"voter_id":"forklift.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2702185900","locking_period":300,"voting_power":"13510929500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"8815929500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"mpdao.poolv1.near","voting_power":"4695000000000000000000000000"}]},{"voter_id":"zzw.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27062790900","locking_period":300,"voting_power":"135313954500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"128913954500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"6400000000000000000000000000"}]},{"voter_id":"nonothenono.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7002268300","locking_period":30,"voting_power":"3501134150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"50000000","locking_period":90,"voting_power":"75000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"3576134100000000000000000000"}]},{"voter_id":"0xEa36e8E31df3eC5dC2350589697dDF346Bc58E24.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1265000000","locking_period":300,"voting_power":"6325000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6325000000000000000000000000","vote_positions":[]},{"voter_id":"0x092cfF73c77a9de794D25b0088DeD0e430733dbb.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8309000000","locking_period":300,"voting_power":"41545000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"77|mpDAO Grants #7 - June 2024-1724cbdb130e5b0f9bd750f666f4fe00581489fcc294dca4ed055c90c76808fc","voting_power":"41545000000000000000000000000"}]},{"voter_id":"alansicairos.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18106969590","locking_period":300,"voting_power":"90534847950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40376184850000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"50158663100000000000000000000"}]},{"voter_id":"mathieutetreault.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5981299200","locking_period":180,"voting_power":"17943897600000000000000000000","unlocking_started_at":1717697591586,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0x4246FF0452B95780122E84bdFfe5Ba9371E1d7a2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"488000000","locking_period":300,"voting_power":"2440000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"121|mpDAO Grants #8 - August 2024-meximalist.near","voting_power":"1000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"1440000000000000000000000000"}]},{"voter_id":"edfernandezg.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13261924800","locking_period":180,"voting_power":"39785774400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"39785774400000000000000000000"}]},{"voter_id":"0xE351625D4E07D185880fdcc3a8FA6E2558d2F470.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16000000000","locking_period":300,"voting_power":"80000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"80000000000000000000000000000"}]},{"voter_id":"14152d8789b96ce70fbe4b105194eeb85052b06a00c839b8b7de70928ab865fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"108876603300","locking_period":90,"voting_power":"163314904950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1798000000","locking_period":90,"voting_power":"2697000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4950000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"66011900000000000000000000000"}]},{"voter_id":"vinh1024.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13958814100","locking_period":45,"voting_power":"10469110575000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10469110575000000000000000000","vote_positions":[]},{"voter_id":"haytham.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"35854400","locking_period":90,"voting_power":"53781600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"53781600000000000000000000","vote_positions":[]},{"voter_id":"0xF7b188d84192e23049dA2E659ddC2d79D7C85e1d.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"550000000","locking_period":300,"voting_power":"2750000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"2750000000000000000000000000"}]},{"voter_id":"a9f49c2f4c066b83c3e71d5ca3ff097c7c6a09707386298da42b52c5927a25bc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12010780500","locking_period":90,"voting_power":"18016170750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"18016170700000000000000000000"}]},{"voter_id":"test_near.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000","locking_period":30,"voting_power":"500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10759382620","locking_period":300,"voting_power":"53796913100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"25000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"28797413100000000000000000000"}]},{"voter_id":"oulrij.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"369566900","locking_period":90,"voting_power":"554350350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"554350350000000000000000000","vote_positions":[]},{"voter_id":"a79cee9b44db1224c8a1f8b3819309d1ce1df8832aee8bf99ba5848269eaa2de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28746963100","locking_period":90,"voting_power":"43120444650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"43120444650000000000000000000","vote_positions":[]},{"voter_id":"marek505.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46639121900","locking_period":30,"voting_power":"23319560950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"23319560950000000000000000000","vote_positions":[]},{"voter_id":"f627b3e2e72da7b2dd98fafa91ed7b56b658142e1e4c0a33d8147dcc081de775","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"jasp83.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"33829927500","locking_period":30,"voting_power":"16914963750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"134|mpDAO Grants #8 - August 2024-marcrypted.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"3450000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"133|mpDAO Grants #8 - August 2024-ffbc3084a4d75ed3c28a7b6c26f44a4ffb43dc3959365d80d70a9660913cbacf","voting_power":"3464963700000000000000000000"}]},{"voter_id":"724d3919c74c7d07b1be7359acea2ba45f244fa2da476fa4988cbc1b9338f967","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5fd80e617970059ecc99bc0b6542eacc7da5288361918dc31a637918b937921d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"179119020600","locking_period":90,"voting_power":"268678530900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"268678530900000000000000000000"}]},{"voter_id":"155ab93ae7dba33a94552113da59100609c32c29df1faa39fdffcacf51ace7ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"nickharrington.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1674280100","locking_period":300,"voting_power":"8371400500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8371400500000000000000000000","vote_positions":[]},{"voter_id":"khoacoi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1198231100","locking_period":105,"voting_power":"2096904425000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"299557825000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"1797346600000000000000000000"}]},{"voter_id":"rockpath.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"282264232200","locking_period":90,"voting_power":"423396348300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pathrocknetwork.poolv1.near","voting_power":"423396348300000000000000000000"}]},{"voter_id":"300525dbba750911263e4f330c25aa340463745b94246b866b714fa0f8ef867c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"margarine-escapist-agonizing-epilogue.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"47161820000","locking_period":180,"voting_power":"141485460000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"176410527700","locking_period":300,"voting_power":"882052638500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1023538098500000000000000000000","vote_positions":[]},{"voter_id":"170721a8d15577b6c359de7132195102f9a89c0362244038e382197d40e4011a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0xc8B19839ae371bd541F20B15c3A3CB82BFB6A6C6.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1450000000","locking_period":300,"voting_power":"7250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7250000000000000000000000000","vote_positions":[]},{"voter_id":"pedrompg311.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10791900","locking_period":60,"voting_power":"10791900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"liver.pool.near","voting_power":"2000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"111|mpDAO Grants #7 - June 2024-c0d0c8b3bfcbc17347002733be3e2d17df833d2931c425eaa40bd7e73a4509e3","voting_power":"8791900000000000000000000"}]},{"voter_id":"0x3f6b5Df4a6fDAE2b58e3F5aA7037E68E1Eac9188.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1500000000","locking_period":300,"voting_power":"7500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"7500000000000000000000000000"}]},{"voter_id":"8fc648574b6aafaf9a966fb173827a3efcf3fc2b56bef501910246df43dcaa50","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"26695179500","locking_period":30,"voting_power":"13347589750000000000000000000","unlocking_started_at":1715956794887,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"daddyarbitrage.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5920373200","locking_period":300,"voting_power":"29601866000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"29601866000000000000000000000"}]},{"voter_id":"vandal.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6931309200","locking_period":90,"voting_power":"10396963800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"10396963800000000000000000000"}]},{"voter_id":"vandao.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10964420300","locking_period":90,"voting_power":"16446630450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"16446630400000000000000000000"}]},{"voter_id":"9896.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"403071612900","locking_period":90,"voting_power":"604607419350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"604607419300000000000000000000"}]},{"voter_id":"zkjandro.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"520518981000","locking_period":300,"voting_power":"2602594905000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"2602594905000000000000000000000"}]},{"voter_id":"decollation.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"299392600","locking_period":300,"voting_power":"1496963000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1496963000000000000000000000","vote_positions":[]},{"voter_id":"zuenti.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"239648900","locking_period":300,"voting_power":"1198244500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1198244500000000000000000000"}]},{"voter_id":"kentnearvn.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000000","vote_positions":[]},{"voter_id":"orangejoe.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8705295200","locking_period":300,"voting_power":"43526476000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"43526470000000000000000000000"}]},{"voter_id":"dcc81d49b62bf89e1a07de111c32aa89fb4b6859e8bd47fa73052df9e3599244","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1884816698900","locking_period":30,"voting_power":"942408349450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"942408349450000000000000000000","vote_positions":[]},{"voter_id":"0x86874cD0aa311E56593CD045F35dbc038057D614.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2486000000","locking_period":30,"voting_power":"1243000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"243000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"1000000000000000000000000000"}]},{"voter_id":"augie.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"90932694400","locking_period":300,"voting_power":"454663472000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"454663472000000000000000000000"}]},{"voter_id":"0xA67Dd142A1395b23607f47C1b262C973f6258E7E.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"jablko.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1852644700","locking_period":300,"voting_power":"9263223500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4705289400000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"4557934100000000000000000000"}]},{"voter_id":"apitchdeck.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10152269900","locking_period":300,"voting_power":"50761349500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50761349500000000000000000000","vote_positions":[]},{"voter_id":"chloethedev.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"89282812370","locking_period":300,"voting_power":"446414061850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"164292100","locking_period":30,"voting_power":"82146050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"443228017600000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"3268190300000000000000000000"}]},{"voter_id":"kaymens.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"81900000","locking_period":300,"voting_power":"409500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"409500000000000000000000000"}]},{"voter_id":"4a6f65.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"53694286400","locking_period":300,"voting_power":"268471432000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"268471432000000000000000000000"}]},{"voter_id":"adeusa.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"224293500","locking_period":300,"voting_power":"1121467500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"1121467500000000000000000000"}]},{"voter_id":"a5c354d15df4256bc5bcc1996965a7bdc60db7f6cbdd342e16583f62b63bc0ae","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":1727996056671,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"a0394de7c6bdb3fa4ae09a25853e467f3fe5223b8f4917a5ff223195018ede14","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"140234200","locking_period":90,"voting_power":"210351300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"restake.poolv1.near","voting_power":"20350000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"190001300000000000000000000"}]},{"voter_id":"7baced7c2334e6e975e2ea6965af15fdae7deba588594049692392f985287eb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"kaion.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"400000000","locking_period":90,"voting_power":"600000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2586055900","locking_period":300,"voting_power":"12930279500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"13530279500000000000000000000"}]},{"voter_id":"donsober.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1299019500","locking_period":120,"voting_power":"2598039000000000000000000000","unlocking_started_at":1716481383787,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"8fe8e86771824d3880fe18798f56baf5c24640850ebdc142ba2b9760cd5718cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"47285461100","locking_period":150,"voting_power":"118213652750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"118213652700000000000000000000"}]},{"voter_id":"0x1968145D78FE9799871522a067cbc161CE5603a8.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"swissstar.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000000","vote_positions":[]},{"voter_id":"0x5650F30f719e6E9C0D00b2fdd20C7ba8649337C0.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"14881000000","locking_period":300,"voting_power":"74405000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"74405000000000000000000000000"}]},{"voter_id":"jared.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"138363492500","locking_period":300,"voting_power":"691817462500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"691817462500000000000000000000"}]},{"voter_id":"bloqnode.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"14293000","locking_period":180,"voting_power":"42879000000000000000000000","unlocking_started_at":1716765769311,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0xC3F836EC06A2202af23e59997A613CA0722F35d1.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"osc.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5432906700","locking_period":300,"voting_power":"27164533500000000000000000000","unlocking_started_at":1716842243888,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"47e7f13cbaf178c66d2c12d93593513a0f406188c40e72977f9b1a4deed15958","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"43049110","locking_period":300,"voting_power":"215245550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"215245500000000000000000000"}]},{"voter_id":"cozybird.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"301008270300","locking_period":300,"voting_power":"1505041351500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1505041351500000000000000000000"}]},{"voter_id":"walapril.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"821421300","locking_period":300,"voting_power":"4107106500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"2344580500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"1762526000000000000000000000"}]},{"voter_id":"profit2.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"601830443400","locking_period":300,"voting_power":"3009152217000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"3009152217000000000000000000000"}]},{"voter_id":"4083b50cb907509eba10b56a41300f719d341175cfb92c819bd07a4fe3efddb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1034519658100","locking_period":300,"voting_power":"5172598290500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearkoreahub.poolv1.near","voting_power":"5172598290500000000000000000000"}]},{"voter_id":"bianca.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2556353500","locking_period":300,"voting_power":"12781767500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1448605500","locking_period":30,"voting_power":"724302750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"13506070200000000000000000000"}]},{"voter_id":"huktodev.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7164006000","locking_period":240,"voting_power":"28656024000000000000000000000","unlocking_started_at":1722119926238,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"nedlar7.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"300090000","locking_period":90,"voting_power":"450135000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"450135000000000000000000000"}]},{"voter_id":"phars.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1295718700","locking_period":75,"voting_power":"1619648375000000000000000000","unlocking_started_at":1732333528450,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0x773c24F007Ef074CacD382be18e5857785837D4a.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"201000000","locking_period":300,"voting_power":"1005000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"1005000000000000000000000000"}]},{"voter_id":"arttrader.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46585365400","locking_period":120,"voting_power":"93170730800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"93170730800000000000000000000","vote_positions":[]},{"voter_id":"0x12f5ecd00fe3851f4d7eB2bf77E72E3299f2C868.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"0xb5621490079D534D737B6B8837947FB376D5F629.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"0x263779E7338DEf14C431D6301F97Cc846D69C099.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"0x7844317568728BdC9475E939553A2A0446D19E2e.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"dosisweb3.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18578826400","locking_period":120,"voting_power":"37157652800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"128|mpDAO Grants #8 - August 2024-aleiranova.near","voting_power":"37157652800000000000000000000"}]},{"voter_id":"0x6DEe4a729430078CD281988Ac76292cf3B27e2c0.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"692000000","locking_period":300,"voting_power":"3460000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3460000000000000000000000000","vote_positions":[]},{"voter_id":"0x13DF946B95ee60922257af5811423ea9dc353F36.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1250000000","locking_period":300,"voting_power":"6250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"6250000000000000000000000000"}]},{"voter_id":"0x93857Ad03a9Ab1226D909FFa49BcA37e70e4D32d.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7959000000","locking_period":300,"voting_power":"39795000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"32835000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"86|mpDAO Grants #7 - June 2024-la-incubadora.near","voting_power":"6301000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"70|mpDAO Grants #7 - June 2024-a5732a8195200f4203a633cea3dc154b696ed628d2ed4a322f214849f2247307","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"77|mpDAO Grants #7 - June 2024-1724cbdb130e5b0f9bd750f666f4fe00581489fcc294dca4ed055c90c76808fc","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"75|mpDAO Grants #7 - June 2024-abrahamrivv.near","voting_power":"300000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"79|mpDAO Grants #7 - June 2024-478f0f81f81824a1db9f1c434b73e2c800e2992069a2d0484d050a86406a4402","voting_power":"50000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"69000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"67|mpDAO Grants #7 - June 2024-baryo01.near","voting_power":"40000000000000000000000000"}]},{"voter_id":"21819.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"103204600","locking_period":300,"voting_power":"516023000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"516023000000000000000000000"}]},{"voter_id":"marmaj.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"106206400","locking_period":300,"voting_power":"531032000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"17000000","locking_period":90,"voting_power":"25500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"556532000000000000000000000"}]},{"voter_id":"manyoil.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"74151250700","locking_period":300,"voting_power":"370756253500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"370756253500000000000000000000"}]},{"voter_id":"tensors.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2942857100000","locking_period":300,"voting_power":"14714285500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"9714285500000000000000000000000"}]},{"voter_id":"0xAa3F1faAa3a93Bb5799A8464b14F0FE427fC231f.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"21281000000","locking_period":300,"voting_power":"106405000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"106405000000000000000000000000"}]},{"voter_id":"nftgol.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"200000000","locking_period":195,"voting_power":"650000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"23000000","locking_period":300,"voting_power":"115000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"465000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"70|mpDAO Grants #7 - June 2024-a5732a8195200f4203a633cea3dc154b696ed628d2ed4a322f214849f2247307","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"78|mpDAO Grants #7 - June 2024-irvingzcc.near","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"73|mpDAO Grants #7 - June 2024-mrcoingdl.near","voting_power":"100000000000000000000000000"}]},{"voter_id":"happypenguin.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"261734447600","locking_period":300,"voting_power":"1308672238000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1308672238000000000000000000000"}]},{"voter_id":"7ed4c34d7903e97120fc95415f4c68b61d4594ed6108bbacd73eaaae96eccb62","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000","locking_period":300,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"480000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"10000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"114|mpDAO Grants #7 - June 2024-aksanova33.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"9656a3cd3da0d65563dce927c9c184808a7c9d7d077d199c999ebe65ef526af9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"667000000","locking_period":90,"voting_power":"1000500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"633000000","locking_period":90,"voting_power":"949500000000000000000000000","unlocking_started_at":1726063035006,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"500000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"114|mpDAO Grants #7 - June 2024-aksanova33.near","voting_power":"200000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"112|mpDAO Grants #7 - June 2024-hoangthien1109.near","voting_power":"200000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"200000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"200000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"200000000000000000000000000"}]},{"voter_id":"5d0680d19b8b51114209be84e5545049fd39055736a470e69edc8b1b45530758","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1152654900","locking_period":300,"voting_power":"5763274500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5760774500000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"500000000000000000000000"}]},{"voter_id":"0x18869e75ADdB335FeFDD5aaE02d98326EABCC6d9.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"24296000000","locking_period":300,"voting_power":"121480000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"121480000000000000000000000000"}]},{"voter_id":"0xCdDE67a6E1666a1d56BaB73DCed9bEA132895Ba9.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1390000000","locking_period":120,"voting_power":"2780000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1159000000","locking_period":300,"voting_power":"5795000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"8575000000000000000000000000"}]},{"voter_id":"67fe33da5d926910ec480377ea76d119efb39486e779db0735cdaaba11ec862b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":225,"voting_power":"11250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9750000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"88|mpDAO Grants #7 - June 2024-100diasder.near","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"69|mpDAO Grants #7 - June 2024-odisea.near","voting_power":"500000000000000000000000"}]},{"voter_id":"37e9df77934c86417c090e62bcc174730061cb3fc1fa2eadfad96709ebc1c7ac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9200000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"84|mpDAO Grants #7 - June 2024-613d92d562c428aa478083dc0be04321ef76d48d836610f63cf9948122d4b1cd","voting_power":"300000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"500000000000000000000000"}]},{"voter_id":"f3e4b457a4f0dc8b7130f347b90370f2da12174c92c8e2c2ff72d3d9a07e202f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9400000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"82|mpDAO Grants #7 - June 2024-2f267735d0046f372f1cb6cb6f6c6201bd66b0c3cdf03a4f5ad9fc3877000cbc","voting_power":"500000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"100000000000000000000000"}]},{"voter_id":"yamadori.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"42637421300","locking_period":300,"voting_power":"213187106500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"100100106500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"113087000000000000000000000000"}]},{"voter_id":"rogservin.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"669000000","locking_period":300,"voting_power":"3345000000000000000000000000","unlocking_started_at":1718692127142,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0xEe1140ab647Bf0bD4Aa9603376Ed95a969243096.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"happypenguin1.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"245260656500","locking_period":300,"voting_power":"1226303282500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1226303282500000000000000000000"}]},{"voter_id":"cybersecurity_rok.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40235154800","locking_period":300,"voting_power":"201175774000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"201175774000000000000000000000"}]},{"voter_id":"0x55b8e2c4AE5951D1A8e77d0E513a6E598Ee0bE86.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"23323000000","locking_period":300,"voting_power":"116615000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"epic.poolv1.near","voting_power":"116615000000000000000000000000"}]},{"voter_id":"venomity268.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"458730000","locking_period":300,"voting_power":"2293650000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"2293650000000000000000000000"}]},{"voter_id":"0x837667C7C5cB49609ed5BFA5d3B2f1bbae81d8bA.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2263000000","locking_period":120,"voting_power":"4526000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2742000000","locking_period":300,"voting_power":"13710000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"18236000000000000000000000000"}]},{"voter_id":"takeshi7.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2229447900","locking_period":300,"voting_power":"11147239500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"11147239500000000000000000000"}]},{"voter_id":"bradrussell.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8870168900","locking_period":90,"voting_power":"13305253350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"67|mpDAO Grants #7 - June 2024-baryo01.near","voting_power":"13305253300000000000000000000"}]},{"voter_id":"0xD2F5090E322E1Ef6f4F0699Bbaf5b68992eAa330.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1173000000","locking_period":30,"voting_power":"586500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"893000000","locking_period":300,"voting_power":"4465000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"68|mpDAO Grants #7 - June 2024-3sblockchain.near","voting_power":"5051500000000000000000000000"}]},{"voter_id":"0x21F713f686fcF7b9ab4D2f144eCe611A97b15f41.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6551000000","locking_period":300,"voting_power":"32755000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"65|mpDAO Grants #7 - June 2024-auroraafrica.near","voting_power":"32755000000000000000000000000"}]},{"voter_id":"happypenguin2.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100824695000","locking_period":300,"voting_power":"504123475000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"504123475000000000000000000000"}]},{"voter_id":"0xF42Cd128cDc69bC8d1e7eacC413342064Ce07121.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1200000000","locking_period":300,"voting_power":"6000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"68|mpDAO Grants #7 - June 2024-3sblockchain.near","voting_power":"6000000000000000000000000000"}]},{"voter_id":"0xAa6f8999a3675d755d471b0c8Be8735453abE7C1.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1200000000","locking_period":300,"voting_power":"6000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"68|mpDAO Grants #7 - June 2024-3sblockchain.near","voting_power":"6000000000000000000000000000"}]},{"voter_id":"fionng.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000","locking_period":90,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"0xfC86655494C7B2A9bDFC014B6819A8Ed6D31fc76.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5114000000","locking_period":300,"voting_power":"25570000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"134|mpDAO Grants #8 - August 2024-marcrypted.near","voting_power":"25570000000000000000000000000"}]},{"voter_id":"funkycarioca.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"125461937590","locking_period":300,"voting_power":"627309687950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"580505508500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"46804179400000000000000000000"}]},{"voter_id":"0xe668da86f28f94F36aEb1a21F1f5fBDC681233E5.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1570000000","locking_period":300,"voting_power":"7850000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"850000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"106|mpDAO Grants #7 - June 2024-ludium.near","voting_power":"2000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"105|mpDAO Grants #7 - June 2024-marcelorefi.near","voting_power":"2000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"1000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"2000000000000000000000000000"}]},{"voter_id":"0xB1ea931b49e6671f2CdC67e5704B763a244Dee2d.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"324000000","locking_period":120,"voting_power":"648000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"250000000","locking_period":210,"voting_power":"875000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"1523000000000000000000000000"}]},{"voter_id":"15eb7941392955eb2b0a28fc9bdafcd94c8d7526b140005dbaac8be965a1d204","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1711315010","locking_period":300,"voting_power":"8556575050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"polkachu.poolv1.near","voting_power":"5756042500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"2800532500000000000000000000"}]},{"voter_id":"ledelegated.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000000000","locking_period":120,"voting_power":"4000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"210000000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"76|mpDAO Grants #7 - June 2024-latambuilder.near","voting_power":"1200000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"108|mpDAO Grants #7 - June 2024-553a5330546aa39850aa0e972bc1bec1c7018575ba141fa6f0a379cf28d46d16","voting_power":"700000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"77|mpDAO Grants #7 - June 2024-1724cbdb130e5b0f9bd750f666f4fe00581489fcc294dca4ed055c90c76808fc","voting_power":"350000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"94|mpDAO Grants #7 - June 2024-cryptopr3neur.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"92|mpDAO Grants #7 - June 2024-musicaw3.near","voting_power":"80000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"95|mpDAO Grants #7 - June 2024-edfernandezg.near","voting_power":"120000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"100000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"98|mpDAO Grants #7 - June 2024-fdea0a6bcd48da25f783feb9b66627698cb00a7b34f064eb3ff59e3367b625de","voting_power":"420000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"450000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"85|mpDAO Grants #7 - June 2024-fpsgames.near","voting_power":"70000000000000000000000000000"}]},{"voter_id":"hannahh.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"237185100","locking_period":300,"voting_power":"1185925500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"1185925500000000000000000000"}]},{"voter_id":"0xC1a7E732413D919333d6C9c42b02E29deD5c84E6.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"541000000","locking_period":300,"voting_power":"2705000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"94|mpDAO Grants #7 - June 2024-cryptopr3neur.near","voting_power":"2705000000000000000000000000"}]},{"voter_id":"lele.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2465311826400","locking_period":90,"voting_power":"3697967739600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3697967739600000000000000000000","vote_positions":[]},{"voter_id":"0x5504E7AeA0cf0582f7db6fD9C810f3810425a6c2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2438000000","locking_period":300,"voting_power":"12190000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nacioncrypto-parceros.poolv1.near","voting_power":"6000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"6190000000000000000000000000"}]},{"voter_id":"solari.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"250000000000","locking_period":300,"voting_power":"1250000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"1250000000000000000000000000000"}]},{"voter_id":"ea4ddb1c9a3e1a65438b5cd1069f8b9d324601d9f80cc207fd21aee433a129d4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"200000000000","locking_period":300,"voting_power":"1000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_01.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearvietnamhub.poolv1.near","voting_power":"200000000000000000000000000000"}]},{"voter_id":"misfitsmedia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1590446008510","locking_period":300,"voting_power":"7952230042550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"2500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dexagon.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"308218130100000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakely_io.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"144011912400000000000000000000"}]},{"voter_id":"68cd5e5436d2013eb95aeadf595d889a0ae79ea00a86996d64a3ca8f397275da","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":195,"voting_power":"13000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[]},{"voter_id":"0x34a9E0fc6dBBd4144dFEB3b6895900f519436E06.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"624000000","locking_period":120,"voting_power":"1248000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"451000000","locking_period":210,"voting_power":"1578500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"2826500000000000000000000000"}]},{"voter_id":"fedb6b1f672872f7d1aab1ea9dd4a6c1b7781c9f23645ded83307acd071e2a7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"0xCeFF651357EAC55845e3F33a5623e23C5Df8BB47.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000000","locking_period":300,"voting_power":"2500000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1500000000000000000000000000000"}]},{"voter_id":"0x0dA891f618e773f3b765bcE938c8DB1c55D2A588.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"90782000000","locking_period":300,"voting_power":"453910000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"453910000000000000000000000000"}]},{"voter_id":"cquang.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18429890800","locking_period":60,"voting_power":"18429890800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1720462600","locking_period":90,"voting_power":"2580693900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"21010584700000000000000000000","vote_positions":[]},{"voter_id":"0xEd88424737639e3c891F3f2357E82f33676d98C1.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12315000000","locking_period":300,"voting_power":"61575000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"61575000000000000000000000000"}]},{"voter_id":"27904ea9d2b34fd4cc2a7397b0331ee42526fcb368dd34e2e6a9498e3d8b20cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"30811635900","locking_period":285,"voting_power":"146355270525000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"146355270525000000000000000000","vote_positions":[]},{"voter_id":"ac426054401e2565ad5083b43a1f832e02701e86a83a586d57581bef4ac9a67e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"0x8c7b8474dC370703Ac10D95d91e8f4D26ce41797.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000000","locking_period":300,"voting_power":"500000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"iamcryptobro.poolv1.near","voting_power":"200000000000000000000000000000"}]},{"voter_id":"palomaetienne.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"667879810","locking_period":120,"voting_power":"1335759620000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"1335759600000000000000000000"}]},{"voter_id":"meta-pulgas.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"174540033280","locking_period":300,"voting_power":"872700166400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10843520810","locking_period":300,"voting_power":"54217604050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"926917770400000000000000000000"}]},{"voter_id":"kenrou-it.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"55008403300","locking_period":300,"voting_power":"275042016500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"275042016500000000000000000000"}]},{"voter_id":"345329086d811eb171962ecfcfca5e95752bc5d21d1fbb90f6a2242130d8aa1c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1931646300","locking_period":120,"voting_power":"3863292600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"3863292600000000000000000000"}]},{"voter_id":"vinc.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"75590282400","locking_period":300,"voting_power":"377951412000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"377951412000000000000000000000"}]},{"voter_id":"mizu.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":285,"voting_power":"9500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"43927100","locking_period":300,"voting_power":"219635500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"229135500000000000000000000","vote_positions":[]},{"voter_id":"babami.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"305751800","locking_period":60,"voting_power":"305751800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"731186000","locking_period":90,"voting_power":"1096779000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1402530800000000000000000000","vote_positions":[]},{"voter_id":"lucasdeprueba.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1121063700","locking_period":300,"voting_power":"5605318500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"5605318500000000000000000000"}]},{"voter_id":"9e3c217ede31b75aac8436ddfbbe8ca71a75808dd5f8069a4e52749d9dd6711e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"598762800","locking_period":120,"voting_power":"1197525600000000000000000000","unlocking_started_at":1723178304409,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"gridmanservo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"88963850000","locking_period":120,"voting_power":"177927700000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"177927700000000000000000000000"}]},{"voter_id":"0x71610bC1aCF75d39fCf962076D97bE17cd7105Ac.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1938000000","locking_period":300,"voting_power":"9690000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9690000000000000000000000000","vote_positions":[]},{"voter_id":"millyrodriguez.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"446738100","locking_period":120,"voting_power":"893476200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"353551270","locking_period":300,"voting_power":"1767756350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"2661232500000000000000000000"}]},{"voter_id":"0xfc10B4f288Dfcc996EB819021934536095a7Ce90.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"725000000","locking_period":300,"voting_power":"3625000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"3625000000000000000000000000"}]},{"voter_id":"mp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"105798270600","locking_period":180,"voting_power":"317394811800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4831000000","locking_period":90,"voting_power":"7246500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"150000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"74631311800000000000000000000"}]},{"voter_id":"bef43bd27b338103ac85d2b0387fe2412306b9800877e66331e3095f36bb5b77","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"151929000000","locking_period":120,"voting_power":"303858000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"303858000000000000000000000000","vote_positions":[]},{"voter_id":"0x137c265E744687bdBC897FF288d2F94C42b62387.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5658000000","locking_period":300,"voting_power":"28290000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"28290000000000000000000000000","vote_positions":[]},{"voter_id":"c11e452e9d223ada5937b4e867a992fbff3dbc8113a3bef712ff1f84e707d052","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"23000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"cristian_nacional.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"41000000","locking_period":30,"voting_power":"20500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"50000000","locking_period":90,"voting_power":"75000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2595500000000000000000000000"}]},{"voter_id":"juanes.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"48000000","locking_period":60,"voting_power":"48000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2548000000000000000000000000"}]},{"voter_id":"blntmain.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3043797600","locking_period":90,"voting_power":"4565696400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"210000000","locking_period":30,"voting_power":"105000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4470696400000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"200000000000000000000000000"}]},{"voter_id":"angie_pao.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"41000000","locking_period":30,"voting_power":"20500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2520500000000000000000000000"}]},{"voter_id":"0xa2d237ecF8F29689Bf86d72f97beBe9EF85220A8.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"34643000000","locking_period":300,"voting_power":"173215000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"173215000000000000000000000000"}]},{"voter_id":"3e4cee760d5da969b8ec3f4fb9eb8f49a0b116a23ba6b34ee58ed5fd2825c063","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"589000000","locking_period":300,"voting_power":"2945000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"945000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"2000000000000000000000000000"}]},{"voter_id":"0xA802EedBa8C8C9959cC7cb47CA536a06D485DcfB.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1506000000","locking_period":300,"voting_power":"7530000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"7530000000000000000000000000"}]},{"voter_id":"0x1497848AAF8F20d7F4e32501FF3D240e388Ba235.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"91065000000","locking_period":300,"voting_power":"455325000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"142070000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"313255000000000000000000000000"}]},{"voter_id":"27d9ecdd62122688ad5f957b75b7e49d534a09990ef4cc51a792bff893df5732","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2869ed8b3674730dd29cdf01fdf29005c85d8adce6b7cbd8b958f49de445a6b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6e57fb53931fe3b39af8c654587c1336a9a1e421edb6198c829ed61102ff125a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"5439daea621f0c5a5f845d19e779463934590b4f41b683f1f03e4ea03222006f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"20000000000000000000000000"}]},{"voter_id":"40a6d21456433e6c270130d72437460019245779582d477794f1bdd57ecb6a2c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"34000000","locking_period":300,"voting_power":"170000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"119|mpDAO Grants #8 - August 2024-wuipod.near","voting_power":"5000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"50000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"50000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"50000000000000000000000000"}]},{"voter_id":"080e6108d06914a004453c4f849897417cd87579688d316ac53e3c46a3a0197b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"551a3a5188be41e7c27af497a6b786f6af33ba43c58e9106a68fe99134f65fca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"dc046584136b3be4de731441a95c2a602c25cf006f37b783e340b916a597994b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"79f4a7a847a95acb0ee11959852bb76d30d650b878c96a136688e8aecd1b83d5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bf80de337d4ab1660da7b7f4b843df1380e166fb923d175a18d372263fd09280","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ce231ad89a500d2641cc4b71ae6400a4a9522bf263b3c11900a26073cc126f29","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"4f085a61ce00d684798f1e83d0e438e9f1be98622b33f1db512566c554268663","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"871d7c902ab362e6c777a3fe7b702b0077b174a404c13d6e551a47b0acf570a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6ec6bc9dc928b72b4363fb7cc5c3abe92f810a3b612dcc457d13ee696a78897c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"3000000000000000000000000"}]},{"voter_id":"ccd9f3cfad712e6f5fddc0a7c8935be20083824a62f780a49a1802f8d8ca0358","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4e283f659382e0611553695153424adb66e6222e0ee5a024ed98e8962a20900b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"465d7752c059302ce8eb9ec94c0141d650e072ad20fa6eeb304841b1d3693a0e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"8d89586d5043e09131c2536b0ea2624e6be60e501d31042d77810e3736cbfb2f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9b62c473cf563f33409cd8de6c95a7aa50e36fab9ee3ae665c06acb4e3d3ed41","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"82c358635147b474d097f8c8af8f88d1ec07fa7fc300ca461004e0869ccad68f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"145f614111e81b7e43cd133599d63cd5b2559a15b435de66fb14cd266945a29f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"724d617b38099343899ce0e774ca65a8161d1df6d64ab41c689f618a881c7c2e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"37488f54cd9af0e9688ce56e13f026197437402ba0038cd5c929d1f2f1e15573","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"27c783b47b0155afceec13739ff7f48c25fa052d94dac9d29905a81c97641187","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c9e350fd02b7f51b3789fc142896ba8f59732ea7c4303e3f5ad38377f9b042c1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"ddb1700afe3bd55f34f6daf7b60e3c17946eab8e6d165ef72342c8fcb83ed48c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"acc95270fb0aca46d0e43a8e8b67d8542aa07a9ae4c960e0e22142a58db7039a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":240,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"727175ee48957b652c4eb9e1f3386e5ae4b1e04fb90a920922741364216336b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3aa31f123dbcd4623c2242403d92da66549f137cf36076ae846604483d914f70","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"509eb55b76a82eac23605481aa402bc9203d6a44c378a13bde10b6639b43a50e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d04e08aac303494787086c069d5610cb473d1f89085a4fe801378fed9fde9837","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"56e80825d408c2c9dda78bf1f02be638678b65d672752e8c9716d2f40520d983","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d6254335ff5aebd172073d28a1328373f2a55c8cd7e3b1e6a4db00cfceeeab43","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7b5b78c071c8f8381752135e3aab8f7db9d000224e6bfba092a0d6a4265f99ff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"15c3596cf7391ccc414095e6c6275fd1df3b21b705d8c9b3ea9d86a2d74df757","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"45eaf934e0497dfdf044426aba4ea2a71698747054b0026705eae32ae08fba82","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"30e156790e33afc94d79e5a011a86fe321df75b290cebd41128d9475ea3a798e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c4a6a8fbc679ba0178931f874f6c8889a15293715e62302adc82a5056fe56c52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4df8cdf0c996c60267782f1ea4f715b4457a0e255b64444b7667c5802e3a15e4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9ad0acfa09155485e83b7ca2fdff74778a5213f3a1fc458ac124006730df5288","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4c96376bd93c70a029006ad678dbd0bd23c9581c0826b50f49e18355de6a4624","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d8db0f4398c25971a15ed897df6952ac56ef7f1696e171b10f0a74b3abd52c63","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f0e6a55316073c638b8fba4768aa10a44569be63afa4bad08f7900a98d1cefe7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"600f83be2d72903dcba587f44b9c23055ce944d028a7a2bb1b3477347ba1f9b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"28000000","locking_period":180,"voting_power":"84000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"92000000000000000000000000","vote_positions":[]},{"voter_id":"b9a6051bf04d420d3bead6eae864f9625738035440ef98d0416d24fce4b031c7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7dd3535ece7b1272c91009f1b64c146cca76c2a33d2bb45850ad6acd3cae641a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d150624872f14dc33334051fe65560eeb3bc29649dfcda1b4378b0ef2e5bd858","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"45ddf8c502c4d473262e48e15edca49764fe287ee7608f837adb9ac4451e6d54","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3d910f506564edec55243439dd320e1cccc9d974c8bbae1c3f84c1a624b4d0e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"d60246859abb16dfdfffa0f9a8853e151aaec35aaef63f6a5e779e2816f4ca02","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"40fb70571de5befb4288d1349e748bd4f66673eda7ebb2414edb0f08b420d048","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4cd3bd3730d2170fe8a08d8bcd8a8d65caa6e70d4dd9c21fac3140c0ceaae2e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"28f019d94ea494bc9b6fb6f80e1fc9b9e1f64cd3f01a818dcc110df215c12c51","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"df6af62cde824f1107570bc89c29e9ea77ed7cdc90946851e79c5cb97d319bd3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[]},{"voter_id":"b3c6d1641621bdf7d6c5dfb5ee79b6d4a7db01e0e3e00b51a0a8196b593e128c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cdb63c30bcfe7f8639be1f4450b54fb3c93dcc94d6fea4fac2a817c19beb6e9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"d58324802e2d1b14768c23cd97ee96b9d07f1998c949d7086b2d0bdbfa2f2044","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"126d31434377f67c1d9239c4ee08a9f4096b528a32d07fd3392c62ce57668347","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1e61b6b2f78fe3183e1d1e8c84653911175e1e4c7e513317522318595d3bbd41","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"879685fac06351252a970f57a7e97f080af01157a48c615b62773a58dedc8f21","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"21bb7e4a279588b15caf9f8f2c6830b0977d05b7f7eed520d9db4823ca8d75ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"51de4a543301f6a463d38254610d240d4250ef4839539247ed5a84dd4df6dda6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bf5ac95ee86e9cb3bb3ec2cdce2f4ee6960d0e04a768644127ed308d62093f53","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"adcaa3c13e241dfeadb60395804319a8eac4c8e1fc7a2f10af7044b56ae958a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[]},{"voter_id":"fdf274f98c681f001c70e3c7b1cc07c20b30540a45df4a494b8a570522bc0051","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"3d2c4a4b8008e32675af08c2291eb71ad0152368d0e9ac0388568590e488605e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a72465717e9a01592dba7417cf3286cf6ef5fe6f566e4b7618c9a325e832f87d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6de9ad273eb4cb144933aa96adfa2d13d2972f1f28149ebe8e576315314d33fc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4d247b55e9c962cc0a91e176cdc203cd29793fdb3ed9929d35c94dea04c46846","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"94b993f2e816c9df4498513dd1ad0c0d5b4faba22450a048508a90ed5b322033","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"635dcf64c3f388cc28f201f0638a1fd4902ff06b9d38a373fcbfe8d31876c073","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"69f814b94f50e0103dbe6188143f92ed677249129e2909081bdf9d350171ee92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9ac5f9a4826d44cab50a4445c1c5661e53354dc5c9bcc2ace728ec8bbb9fb0bf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"0628b0e0b88939a9f0dd7d8090ead5a44444249e1a02fbb21c9ee48bcc7ab589","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1deeef63c1a662bb80dd272e9b1592441177abcca0a014b98a59be9c3267f65e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"61e62c1d6a197fc777aa86cdbd7900090573bdb164a896220d676fe893de62f0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fb721bbd98b3314881c3221790ecb609b9fc9e0b03bcc3b9397bc661f7998d0a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"784c13604c4ee56b6f04b3ec81191669afb4feffb37addd54c0533999288a966","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5a7e4cd1c0807e267673c87c8c1aafa838238e081e5bc4fb3eb74b07157cbd48","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"032822c2df35abd5d64267ac3646fa5cbac384931ea8eb910d765c3de5f4ee68","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"64265e3de75158f61b9ef55c4706cfe915bb0e2f93612a28f68fca4ea3dbadda","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":210,"voting_power":"7000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[]},{"voter_id":"0aea9dae0b824a5b1ddffc82a085525e272b27604d7a23e86273638cb8bccaa9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"25ed418ec4f404b648014a7a67132185819133130e342a20270ea0bcb38423d0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"566333ac4be558d078caae7e9f03c229769ef622d526f64d0329288cf5ed3e64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5e615ab58113cb49ce825e9e8b03970612e7989d85241e03ebf176a8031dd03c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"29dc126b65387ce46a7c310c66e54de928eca68d5a5105df7e2407484e89ac21","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"fe3bd303add191acffeb5c9e68c6180bb73ef457011486327c40fbc210e1bd44","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"12a7d19c45d66f8ff9ce283472113db8cfba5b02c3d355f1bf99e0b9a9d3ae12","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bf4ed5f03e7250a191193e90ac855f2cd10d6cd6a4252ff85ca9b25a65e886a7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e3119c5646d65b71b93c2eefd0ee7e9c04ec4703230394f7cbd5180dd87bfe05","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"31e6e8ec45ba36b296f72527326b8bdc3dc4cb5e599f79aaef259c6157015510","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e09601a6d8a9aa54d386d828decea23618326f3f8d03e9c74777525d36b4dda9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c8a0f58e6cdd59193251586eef3516144b6ca5b6d65145d3ef51637b6fd3a7bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"41a6dbc79d3e999b6dd61446e7d6b960d8b91567d63144834bf5774cd755d14c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"64bc5f956a96ea17b4ad8afedecba9da9afda7e15e21911edd87a01208cf17cb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a41f794e9a12a4c7bb20e50f0e4a90a8f8954b005153e9d7f0155759c1c556e5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f87f4c6759572dc6791bc40f32f43d92be0b2e11898b0351fe489db89a21d941","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"17365cf3621fc7f67a1309721c23090f61b3a56055b25bb3ea46eacd5edf115f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d3d94792f5c9725ec20ef667ed79b6dc1d19375ebe4d8088b4934feee207c2ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d103904336958830b76f2f0be437c2d4012fdf9b7f7c0a820403fbe0b2df118a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":300,"voting_power":"40000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40000000000000000000000000","vote_positions":[]},{"voter_id":"b52167c20ba7e35decd8cb9bf835af742076f3388a1df1dbda2ef4f65d91bb7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"fadda6343ec00cb11386628439d387f14233b54e5bd6340e3b2b06d264899ec5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bf6b208fb2b64889932936f61be74f08d24d42ece2ba00183f3d76f10239b98b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c6348568406a803ef2a0cb907bc4d82f40e02d331718b0e7f56e0f7028010056","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6b4bde35f494dbe6b8b6592ed9347fefc612b354e0602ea18dc1a4b54fcda75e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6fc39b6b6d644b3eff8d85a9ba5f5cb299b52536687a362e04312f04a7ab0ee4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12000000","locking_period":300,"voting_power":"60000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"60000000000000000000000000","vote_positions":[]},{"voter_id":"bddd78c0d1c07bdb7b19af336de2dfeaae85989ccbaecf3b921fad2703ac0be7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"50108439a98338304ce9636d91076eebbc2c985edd8619afd37c34eede2d3655","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"821924554de471723ce40bcaa6cba15ade3457af1d7ed400ca4a087822fb8415","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"ffe787370c5814bfb5c8a4e8182ef6274c08a1c5a161f6db5fa5373af629d105","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3e51d1513014c4f048ba22f71c941155d63012062c10927e4d23418acb972fc6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9d1dceae30161bd8abb3003ebdc2a823f8a6d48f8f9992a914272be21303d6cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6424f1549815b2d8998adadf36298cfd9767521bd5041bafb23c8ba47af240dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ad7237013330c862c5f80b6dc35a1e8bd010a6e8c286af8f23373cd81cd52ad6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8d90c3359d7556f72ebb7947251370dbc77a2a09945baf7e1c1319520267b808","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1cdde58faf7ec2e95e218bfb5c595bdde431f86071703b36a5e281e6ffe0259a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5035c53bc6be0c600ecdcaf7b62dc6a94fa3d9aae167c2a66d40eec4e2044b24","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8acef5812594f20a9036819abfd6223e355addb393b0643788e046db02e973cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13360750","locking_period":120,"voting_power":"26721500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"16721500000000000000000000"}]},{"voter_id":"8a7fc02d17e2be9903ebb4b399dafa0867e4b9577b76ee07cac347b291a1f037","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b07568896b9db361abb53fe69ed590d1150c1bf73af14ba5bf7e6c202c6a647b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b510ab77eb2855b4990bd717c81f4c33e09ad7b88a6c8bcf9b9c12107ff681f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"74b8e0a54db38de6119c53e9e69221cd6d1d0396d3753298f35274e7425f88e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d4e22847e7b0de6ffd12cea613c78dfb185e9b806cbe014b3535cedf93bf6b6d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4c5bad1a24ca50f884c33d95d167e4660e9df4d101c6bb8cf92f909ce91ae055","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a189bdbee8efcf4b24faf7e9a942e1de8b8a4cfcc87abf48bb9d20004a24231f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"342817a5eb29a5d21773b90702d8b2f91571fde1d81439248e62527f0ec45f11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cc84c5ae29a5235bb61a2901726b7577c32cf6852134e4756a52f279dd83f5b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":300,"voting_power":"40000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40000000000000000000000000","vote_positions":[]},{"voter_id":"25ddcc13fba3e88679f74a5b17ff996e7ec03ad7545f74bdd1636b2bfa453010","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"854c054401631858556be5083fcccf159d48edd8636cb3b79ba52c6057acadb3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1aacbea312d4197b0113e669a7b69075e6d03562402e7f7a504eda02740cbb56","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b0432ae83eaca897ef4bbdd0bfdef1d85dc663320eeba22650e2dd6f4826ea64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3fbc1a6f724071613889480d9b9ac30ea5c834f94c99195164b6cabf2b45843c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8ff8b942169412f9e77f9285b7987c6529c06680231488e254dc6c6324454468","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"241da5c7cdb1e6e1827a71b832d1c20f039e66a759152df2c7b7680a3965b7c7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"dc79e82f7b1f6a2d72ee59db296d0f2e71607a842f06f74bb2df7f6d2190a47a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"29725629aa22082c6bd7381f27c60bb5a5cde8256813f205455e5cbe417bbacc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c02f72947575dd33b24706f80a66e727361e805180b4a24de26db3c207b71437","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7f7c40fd9cd0ba99a934f6c6e0bddee27fb2f2719a3c81b6a1c892f6bd5f8244","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2228749d4a327c1b4ce63b4d2d1d900b2bad5e778858df27f3bcb46468b16914","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ace8c16bc981fdb2f9e8717ea84afb896b30cb33591e11aad48728fc512a7dd5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5c2262ab1caaa245a74e6eff6e0c7fe9dc11be9ffa83953df15983964a511d85","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1711058907148abafc17d6b2992c739029368bd60932e278f340eb8eafbf2d39","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cc4668f6804c5eab5cd0fbcda10f12eb2e7d42611e6f5bf4d8b211b7bc549710","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8dd96458ee039a98fc1bbc9dd1318c52d84fd0dda34af7955367dd4ac889a61c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2715d97fc5bd0357834e083bed8b734f55d38655df4267ccc39f088da4837c7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f36fb70ebe8b6ea903d31f27c740bd55efcba15f8514968d98b479ed408fd2e6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"126|mpDAO Grants #8 - August 2024-5e85fef96bd903eec08d1f59fabdc524d0ac13f44c7a1e08e43de9a01a62a11c","voting_power":"5000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"10000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"709a48e088e2fce23feb548f488ed76c03d7e6fa20aaed60d866f4ec855b1f9b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"eb562c458208824d6931a32326e2d8aa02317aa44f3e6d352346d51ad172ae23","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"797d81ab560875d044dd525113c326482fff507ad3ef5c4cd88038841152a8a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b8f64970d4ad1dcb0a1b055841669e13d6c1f3415b0e97c92c0639142e45b7b6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"de8696cd0eac0f7d0967acef1a01bc8448f5c10e02a19143a6eff2c070f2e164","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b941bf312c36f1cab1f9772988683a0be8c2f71d793f0bad5267a09fb33a96a8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"992a128740698ea70168fa1c28e1697ff08c30705d8a03866ad9e18c9a42f2fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c5b42afda74918ccabccabcded6730385401035af8aca25946c036a1fbe40680","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"137|mpDAO Grants #8 - August 2024-criptolaplata.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"383e360624305faa2979807b6209d1c9720ae26ecabf23a97b989377516c3e92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6e5bf40489b5db158fcea08ed48b69d5e0f0671507c8cdf40864529d940bd422","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"838cb5c7a7e6a974fb7103275ffb5a6b365d9693e7d2ca079f4dcf4911d1dcfd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[]},{"voter_id":"95b69d7289a749ecf14bd875ed7b19b162aded7c928c2ad0e1ba575ac9bb2130","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b82bc37572d5c4df564aec9df527d5a77834f896efc349ef6ad53c073b4e2993","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e55e71330f336344cc8d335ab945bb8e7af0106e44128226f5218ea3a6a8f69b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a43d454b3e82939a66ded3afa2549fb9654ecac0bc7f82b21557aa52c162191e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a63976644520283c719398ae6c91d00ae1606d2327ad351dbe982fe9fdb0ad7a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"574acb8fc04b1c3ab8ef36f9ead301e43101aa9d7da813bf6b9bad88c1e3f345","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"76581563d230f311acdca2d86f66d2cde14c4f73f9ea7660f8e0512a115b0902","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8b0ebb3093af316bc9af21fa51e9701f662942b44723118260de7bbcf1a27fbb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1f4522ec761f02eb239eba4010fae0973d3f51f806aa625688323b6fbba68d76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"06c40f955f3a998c83036d60892c51b8005779455bf669ff811786cb51f4ba4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"pardaox.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"73691fa9beb8985f59e1f34c7f071f9eb4a2949a3631a2e4506042be629f9120","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d7fa30ab2d1c683f7305d7089ca68ceb47ba88efaf10e0669adc7b2b4c7bf1bf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ba827e34f20b3688d91bd7214c21f7f54544d3e822a5d6a2848e8e0d0db523b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"01e03e5d247fd1d5fec6126619c09ed61c95189cc9d96d43c061296e71f855e6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0ce15fbdc5e027780eab5616ee28e4054d47e02f82e7e64b8b14d59a5d39f256","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3ddc62d20361b09a3cb05ff8ecf98892f7c4463fd2e45effe9ee98e42dfc4ca5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":300,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"96556b0e7d0995242dc09d9131906e3477ff134de62d9d98ca92a5b4abdfea78","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0x55f8832e0012508a4e96dfE4FE0Ebd643a0EaDf0.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"551000000","locking_period":120,"voting_power":"1102000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"1102000000000000000000000000"}]},{"voter_id":"3633caacd5cbbbb750c21223879cfff43f38393d99afb12cec8c02d08858ac66","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"2126bdcdca813671f169f5d2f097f33943a9fb4fa4f623fd1ef121a742ee58a0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0e4d0d7b95f032275189a57bd986e5efa627c7df6ec218f3535fe2432419a3d5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"1fcddb18fe0a004a8d28c6f5e0bf852c21d38e12247ac1cab12bfbbb92e88ab9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a62ee42e81bc7d418c42c51f6e960261521c329247fd79d5eb924598011841fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"70c5332b463ac242414a50569d5497a90a3ec0aa6ea631880cb7ddd0cd0f2b0a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1c77b7bf771188383374724d3da5c76970a2a8264f2d57ecd07fa6e062c98361","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"1e3a0a7b1ac2a3dd8039f15ff082687ebdb6655be9fa9c73232da7db898c6ae0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a376a2a5c4d3c3f48c63b7f5b026bc49ef052ca6983f581caa9a5ae79c1958af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"374dfec531a1274099b31c8c0e95b97764fd74ba8547ddd25ec7fe2e248eedfc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f2c7eac39ea0e049d95489ecca8537a6d9a9250f9bcfa94b1ab1e46e3b3fe78a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"043cc3d656976dc43255fe05a3f096a3f5dcad9aebc505722a61dd34b09145c9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"b6c4a9ea99037c5016352f9e38d631b1c71fb7e7f85f68ee7b3795392027a8ca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"44004e9a37d73f76f4438b2d81551d5d84a5cee477152599f77ccc75a1ffe71a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":120,"voting_power":"16000000000000000000000000","unlocking_started_at":1730684117890,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"6d591903a7eb29a274db75bc836ed7238905564f2f159f037903fd7f4994b64d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"23af65399d4308b87bf7d025d0d754953981a3768262aaeefa4fc8599004655c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7c1f396dc92b40c9f147aba123f9dec892280d0c9c89de8aa85863150d637db7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a53abdc78a549af399d29c11b4d10f40e370c61ba57452e6e7e105e8d34074b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0e59b727b425fbd59713976d554c7a4b61e9dbc87a9258fc2dab03fa977f07de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"421af233e4cce7391ff3717a29865831bc7783b7c475c75db193e95b7cc77a11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"da6515ecacac46fff7ce58aeed698a716f5d2a77bfb6e01e378a5fe2a280645b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"0543ba6076365770e1fc112628b940339b7d0093e955b9226b022b5cf2061214","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"10ce1430d68b7d0734e6be6e98e6a7e0d77f4377a2881d04c47626f10165aafc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3d990fd769cede27d9997be214879a709264e83011e81263839ffac64cb48077","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cec1dc05651fc81257087923d5500aeb4fcf73bafc3196cc0a7b08ee8e89d9c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fc448953e31f51dc0830bc616f2870a2e8a8d1a30712b271d80eb1de83183029","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"518a04e19c9880ae8a8d23cf377eae98c4851841aac17381bb6e1bd05e681839","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8332370664fd8ab7553209000b17d0cfcf09f5c496c28c849e5fae0b356725c9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f02609877a26b5a6330a2425c723cc543fb6d1877a9787ab28c456df63428227","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0x87242d1A0C2A35260A500524903e2F7fD6DdBA8c.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"14200000000","locking_period":300,"voting_power":"71000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"71000000000000000000000000000"}]},{"voter_id":"a754373922d2facc53564d06d9c3ec90c6bfe65d3d0274a48b83160ead333cdc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bb8df3e665063dc3d45e04eaebf021fa44872d6932664c2711e77f361ea7e057","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e52e3366a5a3896cb50fdfc959eaaf2f4447fc65eaaa3e422d75a2d1e422b39b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"eda933687deca3faa0f7dc0b0f5abeaab0735863cdad0ced8505f40585c02a2c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5a5c1df8434f9d2efc1ce2f1ca954e80d19973c312751fd9408ddc7c974a99df","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e4f7e99949c97212d1147293d3ccdcd60007bb3b85b0a998dac8c1301996f8ec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4e61247339ec607616c8414642f48776ed371d1c0cca79cfe30caa3d1ae976e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c19653aaff75cc876d71dacd9265a439168fcaf88e028056a22745b1d34bb111","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"be1d942eb5906688e80ff883f6b8dab81691ecc82b86eaa1ddcdc8adc90d302e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d2edcc1a3e301374d81298e68bdbcf6a6ebfca2d9581c43c36bbb6a70c79bd11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bb7817cf789b1caf0d6b1316606e48b65b01dc64bf24cf68dcdc78a30a413791","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"773e24f843824e90b43e9bc5de9e3fca913c6f0854f1b5295e9b547f5416b836","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4fefc08752464cc32a8ba5b3a908f3ffbac02e71ab0393f499535d3c77ad89bb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c0af5ced052e574333cb1c8b75d2a4c5aa6cd3433012eb1871c74f34432276ce","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2be302dfaa09f5e7f20ff681d3ac30d594fc2f6fc7643a865d9c7087b71c9888","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e754183eb049b8af3659f4bc1cc57633e5a58affbba13ff8416095a714012480","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"5304e92a7d44c49ec6132f52a99d715af8a9af6b87293bfa686d20363804071d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"515e9eb55b737d743431d7f13b42b7ef97b4df3defd1a009f4e7aa1fcce40ca5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"58bb3e20e84e518d96276cdda6fdccac28797e265985c133dad3bcc1d32ad55a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b6c45bd64e530f369ff152e7274f05faa97626f9747e711cb264acff2dfce145","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"db490978dedc9591c5564de517d9016b7ec26d0b1e32598d112b3aed5d3c5d44","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"975a9a87e364f4c65795df909bfcca13a9001c511b1e23e66d73429e10b1af20","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1ca99bead19333c375e999bfbed7954e819e4b106c3020cb53f475cbde25f7de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"9b2603375f93a971f7058a12793802e626237bb9886ac8b090ebffb538b14e45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b1ae289e35121155c7d86de6e470795c9cb72f0c92ba1fcd845ef5959b76b636","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4a67d87de0d5ee202d8de5a0c47a2f970c28611d1952897941dac7396089cbf1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1ad70dd704941ad7ff7b0cba80f5ba38ad51fcb24ae86746c68a794560cf3ac1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"455673ba2b0f868c4929b35fcd0ffc43d9c385a506a93f1a4ca1ad85ffeed5c9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e41658db79aa59b0a68dea644337b87c3f0f415323b68e21e75c22b41aa94d1d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fbf5f11886b30c7748ef7e186edfa33e9d003cfd9f490568c6fb0f6448feb5b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"eae77cf00f20f48c3222465f2657f4155b158bcff7ef1d4d2a4a59daae75ac10","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7b60e6eda63bb71517423f58677a9a0e30035644e8f6dd1347737bf6f089b816","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"0x9cCBbC021888Ad9F572F44681401172BeCcA76A4.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"36245000000","locking_period":120,"voting_power":"72490000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"72490000000000000000000000000","vote_positions":[]},{"voter_id":"6c6b881fa3991bfc090d07779d7af49071273ae1e0fb2b1274daf47994da4e8a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7dde0a27720b6490887c301e6cf4861cff61ec3a7a7460fae79473311c260e32","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cb1271c1b72c8e2d9e160d5d6e71b85749236da7433e120534ac1dcd11635c67","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d8c01019cfb4f94a4622a8e64c73445a5ffded6f0714aad1719894ec994b2a4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9134a58890b1749e417076929bb7aa95d8efd6ee8e8cc55069cd1782a8664130","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8d38a492a20136fad38b04e0f48cdb8a9873b7d174bd3a416db17905e91616d7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f0ec367be4c59c05f36862a736ffa8f2e1f7805851d56f547964de2fe8ab4cbb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"17d1e322e42abf9b49246b4e54f7fa387fe687f2aeea6fa74b1ca00cfe5d2464","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5bdca8fd83adc36b70d884688cf9dab89600f2e035925d51b3ea53a604dc7af6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"efdfecee9b258dcc2eb0ab156bd8c79926cf7db9a005b9548174f86481190e52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"20803c47d004df56673a08767e71a67977ccf66d01692ee9c9ede101456ee4c8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"4bc615441e3789fdaf2135e0ca6d6a1cad473e4f21492585494c0539a43d4b4c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"20f7e9405de349d839bd0c6c3322311c73eacf7054d9d8e54936878cd39f8b8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a8cf0344672f720c39ce905438977cf267efefc500a099b50fb7f1b6dde61684","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a94ed3d0e1e831cf125d9f5b8cdbf2ebfdff86e2c44ea63252ca1184b10c06c4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"aa3b8f24a65a7f6a06cae02b13c41f86066408af5b44c80adcc1e1c9ad089f4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"979aa7167db0b18d6a7121f2d64f0fae18041f47b0ae5992072b67542e6b7983","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bb8fdcd3a83a5ac151644faa98b40de05fd3736356f8e95c03c9262938c2df4b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"80472814c00e4c1cec25e4291907b42b0cf2e322c42e87a3bb30d921fec64b15","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cf24343ee9e23ebe2df6b9cc689071bfe3d6dcbc30444ec328b9743e3d111eed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"ec4560a9c20168089603407a0a1d43bf26f9f19f7f58434b40e5b91383cc42c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5fb73daaa28e215656c48f377e6bcf93325b3fe46381ed30784fe7fef676b1bf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"96691dd13f1680b71816e8e262641d435c82448197b619fc0e768bf92e100d79","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7f86464b01ee932eb8e07a6f92883e3b140121cffc4e63b4a579116f2649af2f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7ff81419e0ed0c08404b0ba6d9bd693a0c6af1524a33a4127f58fbf1d0db7943","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"669c9d8008e2ebf1ff07f73fc29d54654621089298a0f3ddc9b08868cb371d87","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f4b2594fb1cf64f2be6ce9e45cce847a09a8d4ea33609847de67018a19d6c23d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"358268f3de6da1258c6b9d82a09045cf6614da2c663330456df5a6f912997027","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"d0a2ed46a6b71f995ee0bc8eb24623f3f8c5b1b5ea0d9c32cf81fde09b802dbd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"0dbf3d872eb1f003da63139b7fcc9443493e2c3163186e3e626cc236c1ac1ca5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"79cad1aea61e8f4fbe575059ce60a71e093734063df4a3c073a453d9185ffe3c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"44bf54e4b457b65d27c38b0ffb53aa0ad649fd1fff2d18f4554651c0c4ff27de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"55d1d4e702b67edbf0ef197752231644b7ad849e78103fc554fb62528e28c900","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"91d3fd762c2e09e33ef11e812551b194e2c8f6ce37a9120ab1a8d5666885f64d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"33986323569c11499fdfffb13a0c53bc37174e2768e3ba98010ddedd113f2fc4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":300,"voting_power":"40000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40000000000000000000000000","vote_positions":[]},{"voter_id":"9c18bd8c0b09e6f80d0872ff6486ebb4ca4a78d1abe5a0b1d38f7abe3e8469fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"2ab37ab9c9bb48add2ff5ec2f232594a4010aef29169b396c916f7fb7ebc66b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b2d6c345e762593aee56a61eb484c24b814cea9155abd29da3e637c4ea8c30dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5a5dda72c012837e52f874b793069f457139fdc79cb68a026de58f93ff048f80","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8878773f86c030c087865143188340e90da331ac4b46d11fce473e582f5731c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"12846a9a54abe348f6893dd3f70b47162d38a5ecc76cbf77d0292c230fbc1b5b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"11a3698e7b282b744d26eb731823c20409ed94e129697dc0d8c361016832def4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"6192e5b18cec17f047785684f24fec02b594b428a1f7997da7b6f3257a8a31cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"78c8ad2e2bdf6de315a795344d87ddecd95e2f0ccf9c37eedfc7b6ee4e3055d5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6e9ae68249483db066789560625a9df789c0519ec9d5c36be6fec90832aa29c8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5131996c25ae37b776f3586518066ae5e7fd155c03ba1688377ff9ec63437c8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8be05f82f633b625c74ab1dd1c30fd48d032e27beb88fa87f1a0d9dda6d6e649","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"6646b541a73db187e5bf1f0eb03bcd0646a7fce3806f98dabd333cfde7ab5bcf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"caf2c794d5f4f1769a8a9323807c6ade109da32728d74ffb442b2b13c63cb7c7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"af9e86fb4939dad3eed7fb4462938f91f6328338ff0dece34b0f8d5eb94412db","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"61bb815b7c1e5d2937a4da895a0f9384e25b3e45d3d57683c222b2ea28e258de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a21d62077ceaa7599d11e8a2ac51a458b42ff00822037ea8bbc7623a1aebe50c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6048790c11c87428c132ee7e442294a91200cf08e6e8d9708d8227225314910e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3107fc720c0db450d94191df5706d6c9120044317653afb224170c01bf6f8ba5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"16e5109bc4cd76f332b86f44f64639084318cfabc08ccbc97dc87fb84d01fbb6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cfeb6d9bd318bbd6aea2d6279ed0d68a92c0bdd210a307275666121196e64324","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1cacffa12e96e8a6b8087dd5e665b08990538a4d5df300b30a70cf9524bb3ac6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"2936133c84f0c6cb7fe4a3d0099c3e9f2764936098b4cdaf478d9c8e5dd7e1ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"aa42ff1fc4c261045c9620532571e6428c9e63466c887569262cb509b6f97b02","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"1000000000000000000000000"}]},{"voter_id":"07ecb2f7b9ea6c46ad0f1dac4a59c601ef2a024ff644fbd003f6e51b3ae84966","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"e07591162a404fbd6a0295669d2cbf2f94c4a3211eba1ca45a660c2575afe2a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"f6ddfa48d3e9d1307bee36aa33bb25ae61b772571dad90f9a089d0bd3a414360","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"268d56dde9508b3cfd525763ae40a634bba0e3cf58b92a2257ad9541b95a9cee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"92f435efe5cc03013714e2647afa8ad5044da3e62aef4d129171e30c4d835891","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":75,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[]},{"voter_id":"778109628ff45b88b8adca44f1534de6a7643ea3e95c54d29644750d427e9fa3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b6be88ce9708a743d073d3571ee08fa61fbbd33d84b184ce64c7d5bcc855d358","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8c8905aaf07c9ce9ed683f6a30128fdf2aa0688ae1b8836efebf49419d8b6949","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b32fb6fc7767f6e463645a7f96250b68cb6f01cea21e15d36e3b82a02b3f3ef1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a050001d022f25200ebb416b683cfa8c4e2b3b1432819758e8b43006e2b043dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5c12991cc12f19b2089e13beb6b43a03153abce39d9643bbd81089faeec69466","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3ca3a3770bf71111b61151fc8200a7867a9377cea73c552e3e4e4ae1364d518c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4f98c08af540f76c5a5e833ea73e4229829c3bb4f0f5dc04e43fda59c008f3ee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"39a6ff0cb51e454ba62ad44c44d6dcb03a93ad6eb5ed098dd09ff56c63f45552","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000","locking_period":120,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"5000000000000000000000000"}]},{"voter_id":"e5a08b8a611477602027fd8e70ea91321be5881d0de5549f4d06785da0d2c377","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f20f70e7ac9614f75eaf008bf88e14fcde36582aa54816f7de7fa60b890946de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"fb7c04ac2724f64957f4a700f94524955de56fabc1fd0b68323bd08f6aa7cc82","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"27ac911aabbce0844d426372dd5df761b3141a28d393a3a260d5c4d14043bb49","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"747156aec3a7985a93a65a45a9ba62fae52932854951838c982ba52dff86d390","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"556b27a404e10628bc3af098c6bf66d2413b972f7b422eaa2e7a648ed2482d9f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"db32917cca6785ff274a44c38214c6b193266348320571c53e033eb762a48e7a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"379877a63becc5ddbdd82b8f8dfa0b037cf70a708fe679977495e66aedafaad1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0211d26e71677fcdf1d5b30eca7f6a3a0c31ee6f159f2624ca2d7f03fb11459a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"080070b516a6fade0e96e369989d96aad40a0f24c3d5523fbc1d6d5ffff840f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"234b3a0336bf7e99c6b7af8341da3fa15d1925efea131a1e26c8e652e72959b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"977a7fdf4eb8cccb0d902dec1200b9d494ca4d29d7cdbebf8092eb7f1097d103","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"81bc19b1cebfd21359fedf226eca3a91e6a85dc7f5f1cb2e7f497ef0bc9da8bc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"29b355959d1ef5b3c2138bbdc7894800195092c91be06bb02da533a022cb6dde","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d40ffab2f4a449ad010114ed81768458c83c592381cb16b5b0282802aad29d06","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1db3e8c64a5cc8737f6320d199b61b44215f76bd8e4089559e9553d4eaeeafcb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"59576ef3e20c6c3b554bbafbb1519f181c9a999b3308247449b83a7df68864be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"611b89b28a3e150476dd47b4124998df9ddc97e148999ac7cd282839200818ed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"78efa2b0c35ce04ddeb90805c614588bcd267e49e034dcfc1945d57901d17764","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"02a306a560688888c650385e60d13610109f47cf8120b8ae3e4ce4fe645cada7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"28674c93aff46aafec7eccceb3b8ae0b5aeeaf24404314bbc9dcb8080a857f7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9c810841538ec569e6662d08e33881d3931b4ee327f7db6bcb9608308195d3b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"fvear.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"845806810","locking_period":300,"voting_power":"4229034050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2651198150000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"76210900000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"365000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_00.poolv1.near","voting_power":"365000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"n0ok.poolv1.near","voting_power":"365000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"28000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"378625000000000000000000000"}]},{"voter_id":"968e4fb2fbd7a917fe611d0b238a72cfa438f2a1ef8eaf56d25e090abba5db45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4c550bea99297bd787489e1f731a19cf07cfeaa204801743f50418fe17838761","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"153cef0ad7f63df11217436b57f53f2f0402c068a52b66c2afe2890c4e1f1d8a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1c60486b897a47e320bd87a734e71ab15acb786ebbcde3b24ac4944fbfc0d721","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e9d9d20a4d163fa8171186044a5928e9d691b91928f56d01a84477bcd5da85c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3aea087c2064b6106e5d4de20822994617076102176608981acc1c02b161d07c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"be06431e2053a0e1b556e394ef435a34e6a4e0927dc7fc6ff94e019ee9ab6840","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1bfb8eba15f02092bf9f60be2feffe8fdca0c5846fb8ca1afff40baf8a795fd0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16000000","locking_period":60,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"16000000000000000000000000"}]},{"voter_id":"04b4f734c6ca812b1de152f0820dc89c3e15c924b61434289441ab80ddaabeb6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1abab1dc88dbdc9ae81a3a2120cdccb299b35b9613cc1c3ecae49776aa61d235","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b46c33e740e216d1ca19c21866d9b65d56f4569d983215debd2e139b6ec684ec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"20e1695a53629313ec5d6b704e15381870a8c214557cbb3b8c5638ecbd055c49","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"eabf4545d6c7c2b2e1784f5c903a933cdafec44c328777caa7c62cb4ee5704f7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7ecba0546aa291bee1e488b8d8c0c7e97cbfc5b5003abc83de172d30aaa43a68","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3a1012c4b875ca3a928930fb30f81b98230651b2cce98b301c92a31e9cf7c219","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"23b8ad47a89a7e2bba27e8f8f120ed8f21242a7db7927f24cd32767ea5727010","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0c6bfdaa39018ee5628c8646648fa8f52da17b986ebbf25bcb0cb15258b383a8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cd0af57fd8b3d2ec4634d525d7d0652e7f445c02180d9e1c06c3a254e9805ec5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"988db74ac3e3b1e2b72ea5f9e0582029478d0207c17c0331935caba5a3ee72e8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"99fb84c8452dbc39f6d97857056362a53eed0f013ac37a97a8fa22a58213610c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"141d296201665fe1747b54df738ca06a301cd00b72c64169a04dcb705f275f67","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"aba3457fcc299e6598d06d158b16b06387d5d1faa6ee0a09f177ae66fdc792a1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"bd021d197c99f677e94cad2023e1dac554d7a9006df9b34cd970ae14ed6a0033","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5b4144f92912150fe37a77f2a289886e83ba835e1119dcb4154f7621f7c3ad79","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2a4945ac7a9776e606777efbe9f72664972bb5d7f4fe05ef500425a6f5a5e8cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"0d35ec724f873a0d65cd8e57007554bc89a055db33c1521881d6c0971f245abe","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2839e62b4dda8357de530da8615752ef2eae0c59ff5e9038e05264e72ccb3f60","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"20cc1aa754ed9fc403e86d10b2d73670f6ea73bc9acb325b97f71f5f84b1d330","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"add5195e2c922d21d6502dbfec0d618eaaabe272890c399ed4728b2e16feda13","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"aimery.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"81837703700","locking_period":300,"voting_power":"409188518500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"409188518500000000000000000000"}]},{"voter_id":"a30e630c9e462c3beade144ac47f0b45c733b848ab2c62585e428126ba486a4c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"218f4ccd583da5ce4cdda560852c26903502e60d4f6c10064109935ad41c3bea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"98a9ded118d6777f98d5babdb4f3fd67c98220aa6cb5d5e7e3fbecb904f540a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":285,"voting_power":"9500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9500000000000000000000000","vote_positions":[]},{"voter_id":"bbb9c20fe7481a2efc4db7027faed285adf993d8bde8caa8aaa127a59e2ddd7b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8ca3bd3de47b83d129ff38dc26a832bb4e027aa461a153734c6602d9fae09cef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"366180291e47db59ba84be4af67d2e03346778de2a32f6a867529fd271ece335","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"8c7f514bb54f5e022b2261ea399ba744c8727ae9bd7efb1fce95f18ef95ccbf9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b16a0fa8197ac396e38269129cf017be5f341684c255f8e87de67ca2b3725dcf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c0da3cba6ef59fc85b915981a8c2c38fb30cbcba8301b3157a5ba222ebd5e43f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8a26a6887c73ae1508eb2e7509e910a79aceca89c6136014cb730957ea5cbd7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"48fe90cf3ebcfbbcad415b6cabf8e3f9c291e7190e245b633dae2892bf7c4130","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"0f128555cb1271f815ae30af3b2f532640d360ef296f4fd3b7d47a8c228b50aa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"55e3c9bf100bb334b47172fd74a4f478eef9de7cbaf8565a44ee501a483cd822","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4fc8b84fb7e0d7b0836d49b09f9358a40d8dbca07ad4df8e70eea2cdd5252cf6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5ff3346a5a17b687beed2e6f08a3105fc81b3b5a4fc49da4a22861ed41c9b9d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"ec66eee5d0faa13f5aebfa48dae3750218ae745e881b04bfb67dbe983063fa16","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"0cf7fc15d35da02960c2443910832df3dde20feffb97c6cbb30bd7fc3fb2663b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"c42b6562743f5366f9a635ebdcda0bf99e864cb06bc1d88d220b29192e19d71c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"694cc6ed362b437b9288e2115a561e8755e098e638761ee4e5064420f2c0f4b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3946051d6ca5fb7284bf84c22566ab870bef80b41aeee58f2bc04f2c7b7e4886","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f38dbffabe264f9c7de5a25e9c6be72f0095010eeae1af6fdcc4cf9563fb8d5f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"048d834d145df1ea099bd19f00cae600237de148c086feae635b89cf9a7ea3f3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8820980971235ef2ad09c1223e50544510ae3550b5b4d86a208f78703cb63ff8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"343dc87aefe6c6178d94ecd6e06fab476ba9b3a3c0a34c59c1dccda126fac8cd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"28e30f6e7b6e311459aceb56873a62d1e04d0cf1ae8224b1b7aeabbfef6cfa5e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"2f172066f80b20617a293dc3abf2aebeaed2d31e64762e0fe216fbf011cc85fc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9a8adbb0b85fb9c68d5fa68d68d91016d3ac0c81061f46e98843f6233e3d287d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1cd1c03f7b6f8bf6b1a0e10757b7a66ee78ab4529020301a675db35aa83b28cd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c767b3ed24f4c72981a67c32c65ccf95e062a000f0f7046fce5629bc734b5763","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c420eefcac8e2f2e1bc4c134cd3da1e3f907f8baa35e2c0e38f2d9de802c96d5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6d26c0c644d5dd341fd080d99e927cd5224418d481b3a01619709c856672d2c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"336954a8db5b830e45ddd8f97d0fbab53307b15052e1d0a3c87580c1d67af4d2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a8cbfe144d53b0177b40169bbf99846519ab6048c993fc68e9af9e91e4e52dd4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bitju.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40000000000","locking_period":300,"voting_power":"200000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"200000000000000000000000000000","vote_positions":[]},{"voter_id":"5b388c6c0e5af7ea9b01ade25aa942759b27b3e9b279ad4977502d09aeab9165","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c6663fafb7a518edcf63c0b5512fe25841385701d6578ff15c263e6becb5817c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":285,"voting_power":"19000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[]},{"voter_id":"69f8b8969d75657e8cfa363dc97a35c656d614c9006a6b1449d7af19f613eb8a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"92b46b9b10895d44f8a559e145868b00bb0ce027ce390d5f2e4889fc677ed245","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2f19df2453be0ab186530b7b00c176e9083e89eb811d150d4ae6dbaaccd8e8a5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f1b3805a63b7f5a2e7b234b51a7cb96ef30eb075895ff1f7947226c1f036618b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a5f2e552bde4b802eaf7e8972b936ce97ee3a20e57452bb815f5fde179bba304","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"256c4adb705b28bc1b73931faa884eaf527f02e6d9f82dffd0b53f98d4a9139b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e9ef5dadacbe86c996bb2cb1c9b72cbe7b4f90be63dee07644b8d6f03966c800","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"af0a172be54657d9a41f2614b1c5411270de5ddfc5c27d4b400c1cca1044d52d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5bc6316b653d670c80c324acb5969987d1d79c3959a043dcab28e5b93e8755aa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0x8972Aebd5DBa64DF1E0321daA4914687e4E1e252.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8137000000","locking_period":300,"voting_power":"40685000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"40685000000000000000000000000"}]},{"voter_id":"a8141d13043454265ee7627936a1add21f7e66573bef308ab1a61c0945d66a9d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9bee101fca2a9b72a49a8a631f31d4836a024d755e387f3ac36005c7db4b9742","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d6e29896e822a1ca617c2d8aba47c1a422ac94a992e0ce662bc952dd32c25893","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5974613f0cd93fcf396ea4f22d833f0373c75e706a4d422f633202de7e4bfa84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"14fad723702ebe71e528d07bb53c9fe5600d4387dac9ef7834dacd1349807359","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":300,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"5b54be3cf01244e0a028a62a50ba80e907cc2d553caea959bb91386946c4e117","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a377b09035d59f9dfc99c62a2857faa6d07ac33cb8ff5e5cc9d39d52295c0898","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"54032c64a147f7e46a4b321f19fbb4f9c9e612e7a96cdf750d5fa24282f75551","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6d1cac58fc1dd96120ded7fb0f873d1f639381b496e3b28c95b58e4a40101dbe","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"52e3611537ddb25f53ac8cc9f7f73ea6fbb7668d368ed3b929913f54605a7461","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"715d77863f9f1f8e6bfd9dafa164dab8ca78df3fcca90cc858b1ae2e0764e20f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d3f4462cae39f6f6ff030dcfe155eaf6f336ac0249946de7c761e3b2582ad486","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"632b699c6ba3d9fdacabb203bea70e61470cc5fdf8e11b85ec3a0f03a1289ff1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"75ce3c238d9c8e9981bc047b387b0f418dc5aa71cc32a30a10df0b5ed52e90a1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c80d3dbf4a8d934ecf66a525c2769e95e55e4bae575cf7bdceabcea77a869b3f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"41cc33183eadc1a3dae72f083e44bb2e3249700210325ccd0f92323a933715a2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7b41a44ec99cd83e006d52799a01ec2efed57b96cce388c00826479f1ce32d11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"adb186f05024d8c5614e8fb99a21937b5ef2053646aab2825e3b3f68242759e9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"0xF71e509Bd6d79Ba5f5d58A94e995392751949b33.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"538000000","locking_period":300,"voting_power":"2690000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"2690000000000000000000000000"}]},{"voter_id":"7860e6d1a0e49df2b5650b95e81d6c4b3adeddbf3540c61c594277544b9d9c09","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"be607f3dd37e7c084fea48e4e3f54d4653fe704881628061ac9c3db1218e3b66","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8af72d04c28d2c91f984aa9f4285728ef775963fcfac5832d94fc1e64b41e016","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a374c862d2902185c977d3691d669110342efc35e91f39baa7cef4e8b66db6be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"502832de71252cf6cf727bbb1438d10d918e0aee05ff90fa40fda6468df6757b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"773a801799a1274339f3229f4bd3883a7e261f4424330cd198db33cd685b3579","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2038f436ef9570d7a7a03ff4a60c0a4eae453e87ccb1424de3eb46960f5281b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0d32dfbecdd6ca8dc622f39f68c7ab7fbe90c1ba92787a82348234b547dd3d44","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4aa9ccdc769e426fa7df278c66bde305abf0a4b5572925676a756a610f344c7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2fb647129ad58791b8eecca6df2e6803c357336f0c9d888a6659a1f3b2878617","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2c48d0e57d0b0a59d9a497e44a1b837346e00c9e74107a2a33b9e2a42fa0de34","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"370dcaa178b8242d9fb38559babeda641dba3631196a79eccb31a3e51a23d341","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7b9da3fd6a0ceeec24a9fc455e39b08cf137d0ef7474b5c5cf8f2523fc7cf455","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"26c2ed46d1e50444392c5a43eb75497273a8f20decf02e5829048bad3d3fb926","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e431e4b4c01e9cba290ee680004333d4633379355e4db59235d9f618817483b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e54377a28d67afd9efb6daeb0b51fc0f8ae930014f07fdcdac502829be012cb5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fae6c40b8d63a4cc53dec40ebb568315231a43cd9c497a71aa964eceba5dcc8f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"570fd0840ad6ba7b075a39b88a4db335c8ff53aca601f16031d12e9b3a33b9d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"cee75fae578ecc6c43f39738b4f7479e2d1515d8cf5dfa80d45326902ab20a7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"47e9ae9ce20301b91a859cbfa30d87a0a4ca0acedc036be350ce85467d26bc13","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"99ee941cf0726f217e2468bb00596de4709f5c3c1142a2f083c6dbd0d0484399","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6de31832994c462817b5d11456fb7975abc23dd00185e4bdc5ddc209bc567240","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8a49e79ae23c39d28f5411571d8ad2fc6b4e9eb5f69102902a2158042960d052","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"ee542bf6d4cda50ba458f6a7ac037458f54f76e4a6a69a5c70042b2881e7f626","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4b11e4b51e34d3b98bf70576b2f34615aadc7284b8f2602fc81c825876d8f894","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"609fbb460a525cdd54dbc02a2c2ee83337a13624a2550e233b2910ebf3d23dda","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d47890479963e93beddeec274c7e2af5728b25966bd1c480a55b469a29ab6774","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d481fb470c9068c96c8d63846aa3f6a2ff4a4ba7e9e00a89e74cfba52cd08701","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"37765a61d5f6d8b5e18cd0cac8152d5e13a54394f585d8c73c9f67bf4de9892d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"db7cd17d8ea22f47437ec0676c14cd8333614e09b888c2fb542e1a322469409b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"eaf331cc794e6a7dcc7df80bf4ed4d2d8d49f68359b5e0a05d47d1099f840701","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2156236d5579fcbea1f36e16ecd1e236c12952effe4104ac78860a51e1401a4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"aca1e649ce286eac2c96a37c3b815fa5722f029d9acfc8450bb521e68ca7482a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b61f5174bc0e4250aab40c5c80f392ae8f428c175c65fc163a9616fb60862ab7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d7bced7082eab16173fe70bbfb2ddcc3b5e040b759a75c90658334089c1d8f9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"56cf9ab5bf2fb8b587809bb6cbc75a812490bbfb500041db10f84e98fabd4205","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"743cfbcd1c2e69badd884df82cb4f33dfbbb54d42d6cdeab2c34c3833b270c1e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"iriska.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1509978000","locking_period":165,"voting_power":"4152439500000000000000000000","unlocking_started_at":1724422008133,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"57bdb5e1224e10c1ca3248c17cea691fb0f2af381f3205a61cdc267976e2774e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"10afdcbecc7d6799ff4001b88d58a98237bb1d7d6d0cbc6f9dcfd9138a929578","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7397737337822c24256ddea216ba16d3c1539332b3d6cfaf6cdc90410fa80d03","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2574247693b8fa3954b199bf1032c94d448b7ed399a7788ce4df7cce7bec72f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a9d23cb480a38b5a23c7682e9e245a3d120f1e39050a002adb9d2f82d5c6dad2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3721500c3dfd668f9e88270274b361868e4bbfe96bf8de63f0a4d04bfabca434","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f97b119641e1c57386247cb58a91d9562872a01b94aba9efa40b3d1e98f080ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1cafed7e66f4468277c66f24e29cf9332ae32aabdeaa906ee64214c537376e57","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"05aaa7e1174bde3c69d7be67000d6f9f0b2fbe9e5ef91f7b299af73aab15f793","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"67e59e26611aad2b7adc9abde443170cc6656f09f0c22f6cf0c58a57e6852642","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"119|mpDAO Grants #8 - August 2024-wuipod.near","voting_power":"2000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"c683730ce3e1c18dfff909f2d7cf98da0a560a4b2c954d937fd0550de3b9a429","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"abd4d6b1463b65d06fc31993c6ec4caec4956fddb2e55f3e636dd21b37833d0c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4ae6de511d7f09aa0f21ba48fe193532f4ed96761ab4e2db5abdea536c7f767b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3cf07a62452f1efb6dbcd84c12497ab2ef1e14f73f579496bc4c877c114e4cca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a40fe9b4cae23bd42b0740c74145ac5a2074a2c7e6f11367458309cff80d50c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"47770f6bece15d9dab89881bdf5ec7f690cf93162a0de582633ec266535313ca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"f6b01c746da45122f9e15e53ce751be04224b85f6e1dc356ca99b6a5cf42267a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"832c0a931a9ece9318a843ed36d01698a5cc25e28901acda8eea64ca80e90c4c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0352a3f372f076cbcfe8e7feb3fa0e12b97c9ecbc7fee305a5fca2c35cb15aa4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"382725c5b68a51c29ad53b809b76b89e75506d344cd3f5d2dced433f05331afc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"464c7388db9c7e7f13bb8ffdf2d0b19cfc851b7043613170ff74be4362d46827","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12000000","locking_period":120,"voting_power":"24000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"9d4ba8fa1fb2a9f0751e3eafc43e297fe2f02bd02a73530c2110d3c8defb2e58","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"43b1e08662df227161148d9ef496cff7272f4734a1814a69c20152bb06ab5a32","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"5000000000000000000000000"}]},{"voter_id":"1c16087c43f61a40873c673c20df1b30037d2f85bb63785e6ad3a73886383ffa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5798ea934692c8534f18a660efb5d631528ae9bf389dacde28f549391a2f7be7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"331b381bf154e8635c1094a8c36aab95481977d6cc466deffb3a162b0f39d367","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6412c3d502b574775039981ff044aee67d0bf12aaa0a51a7c2f9c45e8e1c8e64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fa71ae0e0943dedb7e152e61b92f1a52da3ec9334bf20aa53bab15e5b34b2be0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1edd67c6bf71c4f2fa845b775c7c7d5b1d90460169bdc414fc1818b5a9d12864","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f14611cbbaf993b982b2abc2a007b0cfc959b38ab33f75806c964aac94aa5cc5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"503357b2fca78b45effb090beb6c739ff1e0c4c6843a85be537cbc819fd7ad9b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bdc429f74fad89db2c7399d5cf5743134ca43024b6cf5cdd97d2aa019d55f201","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b6acc91ea27d18db1cb3d435ccbb9bc7d649cc41190ddd7f792b2b3cbae1c0f7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e3e7978f4558f3496d2e955a5b69e91c3a7a8b47ed01e39a10d2bd3230785a26","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b1abe99021d300cda54c7002d8d1974e9e10d54507a5be1962b72871ef4d8cff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":120,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"74a541779df6025d9547e1234e0882234dec3ffbceb7bf4949430cc4bdd99e04","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5a74d3a12445735a328c7f3900a0b0c4c936e16bf2c2aa0790c112c7c0fdcc5b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cccd57915ab1db1e2d57ce15bb8418e96e19eb77839dd3e839addfcab33305db","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"293177693d0471d225c99cf389693661023ac1b91267f67db618653262b96ff0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"384382ab388324e31c352e6bb75e86eb6a572c629bb774a0e44d6fee44ea5bf2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"64d39c354a9123e95d1e910a0f3ecda8b7c76832684d45a8738417dcf77e031d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"79256c94ca2bf7f044a9bc1ea261bfe791c7c2c15465781833769ae814509005","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"06f1130cbd4e1bc753f92bfcb911dde4903f7343190319ee9f74d0f726b07356","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"554247e1b4f1ae9ffa4fe5d5461fd1a5fb95a39ef40f9d5a79fe882fdf0db6e4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"654385e2daa3abe92030477089afd42d8282bc158b92298a53cb889278cb1319","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0d230d4d080e1129173e2882e9bc7eb81fd74120ac72821508f1fe49c3e307b2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7bb51238af71276fb11e438d11a3a8b2abf64af961012f543c16c2ad72448c4f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"144219d1230ab15c79965c94523b03dd0daed3fe7391590491b9b1f72debb34e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bf53c02015ee30aa29e758e9a39aff0748d1a339fc9eb8013d35740b86f3073b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"ca6651088f5c9df3fadac5ccbf66539dc4c2443ccffa275a698e25f2043755ee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":120,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"340cef1b18f3055485cbf8089af43ab9a17515aab3899f246f18c69f298366ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"af30fd317806b5509e9d46c88bf591ec9153012a0362b203b65cb2fdfb8ec5a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f26a01b5add812aeea6cfd858368da6bc91cbadea42b0a9da890ddad1aaae842","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e4dbeda7c7eeeb286d1de196ba1ccb7210dc3afb0079ffdcb7ee4e62d58f188b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a40e95af191420c80c6e8de486c3afc747bfa4b45eaaed12d80bfca7fc52c6a0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f0f4dffd7a8ee7bca6a2767ff67d3f98727d95d7a9ac964228b4647c4a600f21","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"ef980e018c4a2cf396f7e2cb91e19ab63c7b44f01c4543982d94367e7db536da","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0989636324af45adc99bbe569c0aa23d99a1d02d45003435ad64f78da17b30d6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"071df7e320872934e07730567c0a3970a0fd66e1c4758594687890c17e24f16b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"20a575e5451c6338748efbe652fc48bd4361a836c7ce7214a5b23c7f8f498f4c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"944645dee4e875525ca0f7d9b9722f83d586c5188c54452309c29be832113212","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c00d210b3daa1081187c88259b1f0471c924ca0dfa3f0ee15c54c43258e6358c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"eb46a36c2bcd5dc0a23386118409dcfd7a60b9e11e2291f89d18ec25abca7968","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6d7db62592dfc8505377d26a58caba944cac21a23a5f8e2d4f16eca3fb4409f1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"eb5c7c9b5c0d63050d975a5437ca78c31a3c7d6e90e77d133681429917cd2bec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c5febb2767883864ff8f2a745edbd1848ae86fe7dc6bf5aded178e600c7b5900","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"82cd78de0a45695719fe9826d12a1935360f31d9166ee4c66d400b82d4ba2bbf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"729987990","locking_period":120,"voting_power":"1459975980000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"154525410","locking_period":180,"voting_power":"463576230000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"1923552200000000000000000000"}]},{"voter_id":"2a57f1fca1a177973201e3ffed1e09884add68e07ac9df182c3c94af377fb684","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b93ef028a5bf4f1eb25e2f451fdadb23d5650f7c90b5bb835dabc5dd01026da6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b23a9e7a99a5d53797b2fe085d2c41b05054745a3bdb8a24b68f85ed580a5a52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0xB2736156370F078c0242Caab55E83844DDA53DE1.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10122000000","locking_period":120,"voting_power":"20244000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20244000000000000000000000000","vote_positions":[]},{"voter_id":"f408d5daa540dae1158e697d1fe31eca80b938d07db47a1de852a08332b65d93","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"34fbe29e924d8fa7228f6b7bab35ae0ebcfc3b174829028e573bda46dc4fa199","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12000000","locking_period":300,"voting_power":"60000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"60000000000000000000000000","vote_positions":[]},{"voter_id":"ad56d38ed67ce016eb2d4865faa4e9dd496b2ab459ddf3a08a3a611dc4fd9c5a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7b784e43a91d6756c091f5a3974dacd23b5703e8c0b66794a14c725d47a01104","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5df049b1d953a5fa9b931bea84866420d5da4b4da5e1b9b1ccca235aa8ca5fb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c383a2d14579cb90ade09603a56934f7a20b31d108054e01bbdc1d43396e2427","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5870b96b1feef73ca79970c99da18e686b55660b7ba6dfeece7ba58542389c84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"30f54129c99bebd808722860cf1b679e5f99577525e51f241300696fb87edc7a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"78dc524b3f3dbb3bed995bf98212bb825901b9a365788dd678176893b3e44a62","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"026e7baaaa100dff5ca1bacb6f6918920df407bc61df03f7732f5d72102582ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"69d91026e644c7d5fb8ff5909805e554b0383142fc2cc626523b5837de0e2781","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7893fe2015c6e28f430f57c0050dd8d39a62343206e684501ffe7142fd472bc3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fec14113e1ba2b64e2afd3969ecdbfa8d066a6966b7b76ebe3cee959bd295a5b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"60e2fdab2508c7b41c9a9e1391668da9088143cee3cadff6d2a5992e669afa27","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"32d44416cf554d8699ec0ececc28476e10a858bc35f54805a5ad1f2bf0e29e24","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"276e924dfbeeb6ea6247c47b2ecc6366cbf1feb4891bea2155b51b79f19d5816","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4bb5a3bdba42b8e0a71a330e760b71aa7394ac502cfd48b1de1f3eda2cca6243","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8c1db5e073dc0a7f73fff3c54d39406ff439c7bdc3328ecbf55f53c637d07268","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"54435b771a16dd10648b156f1024642a793311554eb5286360fb77a703fc5386","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"daaf9fc168bce14c4bce46781fb351bbecfd08585995df349f3493e2d7b1b194","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0xA2d177687edcFcd2F1a80b517D9F3c46926d7FC7.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1749000000","locking_period":300,"voting_power":"8745000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"8745000000000000000000000000"}]},{"voter_id":"a6771ea19a0ef3cdbe4dd2b4c78825b43fff8102f8bf212a636a8f53b921ffcc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3d0708206647a5cd587710c7a63cd14c5eb01e14fc008d5cf68c7346bdebe1f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"203388940","locking_period":300,"voting_power":"1016944700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"458586400000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"558358300000000000000000000"}]},{"voter_id":"84abb13bb0ff8cf06c7dfa6c34d932bc7657dba900dc8ebef62e0b9706e7f986","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d320fb5b63688bc7474fed76c4828f73ca562daf4a7498951beae886ec9c3327","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"54826bd1ceb4c72a334f6fdf4cff19cb0c712c4a92ffb5cbffd7a8ff18a01edc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b46fcc01bb836354c4e919d45b01ab3361fcbd813cbb267c9fd0315ad8d129df","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c106ead3f018ee7d89874fdb8d0fa4abb48dfef27f6fd9fbdead0d32e7357f72","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"11eb159bdf4a6af2bba7de1c296de6fc4b42fe08cd480fbe241200388c236f98","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a4cc777e00c8d8651712d1efb70c5ed9fa11ca55fad63a3d53719c503b442f53","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"152b702780039983a80bf5ae0377ac048974d1168dd60f99633eddb607a874cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"98abbe7698fbb7ff95925debb9c441b103ddee49619b5732d139eb008ca6c2d3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"be4f59f8dabafb4c4b366c69538beab7880b176caf420c81fc2e054b4041aa82","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"f94a62226496fe75519d1122e74b5f35864afdc434f09fcf32368fea759b462d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6eac14dfcf7a73c3dab5221f692c74d8b24ae734dd4ce3bf5402d5f3c95e5b6a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"20fe170002ec97b1259a2db0a3615c83e9e34cab2b72ff66b9478a939f6fa3b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"709542d81f7e2f0071e71f5204f61b84b4553493848b71126a06bb991f2fd796","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"319d08cd2e6548ea0dc1ebbabe1887ed6a1fc633f3c02a1f1c90bc67e9b0e710","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ee89d38032daf1946243355676f7f8ec4193220b1e0686091433cd14be6a13f7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5df9df24ced21814c3f7a9a136c66f06e6d3f582f594e10ad11426c9a6596118","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8252858c2ccd4356f2e619631fadc5f5d98b730874c63350e9c481a58ceb53f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"dcc33483161f442c409b50b84660b8116ba9cd21b1e8d4694ac5d1b4491940f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"56d2aeba87c686ad37097f1de4871f76699c2cd293b05b8bf844c2e2bf695fb4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"88e7a1911449255e38fcb03746d589d2b1c2f67225ee5005ac92065a9b30dc2c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c32b56e7ecd9d8abd937824157cdb82920de7120ce472e7b2e403820886f1fa5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"p_maksymilian.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"yourdad.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"450000000000","locking_period":300,"voting_power":"2250000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2250000000000000000000000000000"}]},{"voter_id":"pablo158.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"50000502800","locking_period":300,"voting_power":"250002514000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"31000000","locking_period":300,"voting_power":"155000000000000000000000000","unlocking_started_at":1725148829614,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"250002514000000000000000000000"}]},{"voter_id":"gastar.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"270825909200","locking_period":180,"voting_power":"812477727600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"812477727600000000000000000000"}]},{"voter_id":"f2ec587f5ee2c7fcfa255e41b2e6f2f67c996e9e4c3552f4f61f273039499e27","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"604b24c000b2c1ab3e89ba373cb45413a35f911d7f4064b9b965aa30ec6c3307","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"043248a41c0248dfd6559064e7d1df1336a85ff5576fb33b064f3c1c5d0092ac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"55658976300","locking_period":225,"voting_power":"208721161125000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"208721161125000000000000000000","vote_positions":[]},{"voter_id":"0a66750b293fb20dd177700333717798007ad7e267819a22dd4eff151dd940f9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"blazing.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0x8b88064DB1595E37fdEd18102dFf6933A8b6EE90.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"56000000","locking_period":300,"voting_power":"280000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"280000000000000000000000000","vote_positions":[]},{"voter_id":"7bf0c76a85ec08feb38270492a0b056abb60cd407cd4ba1a4a2d554657d8d47a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"31c032af32864627db6ff2892e29bcc3881ed9a39ee9d92c4bfbc0f92cd9e588","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"b2b0bc9ebda197b4528db733c35829ef06c2c348e69145d1d93a68016cd68edd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"876bad3522a08c12b6fb8c87f9dd1ff47497847a7dbe06136e98fd81b7607cdb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"65a5ef266ef7c7afbacac621204efa5c0cc45bc2adf1b806731248f401595356","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"6cb55a3167c07eb7efca2fbf6831dcc45ac735c1961a7c0ca97f351a7bb591ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"0c17bb6326e97bc7cdc106a80b62b999aafd393d8428b00dc6b524d2b13006ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":285,"voting_power":"19000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[]},{"voter_id":"3e8187bbb403b6e3d736edb55eac2224c0f841a27d847482425d0dcd344d2201","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":165,"voting_power":"8250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"5000000","locking_period":300,"voting_power":"25000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13250000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"0x5A79FC812149748e5E4AC30A36177c27b1327Fe2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1007000000","locking_period":150,"voting_power":"2517500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2517500000000000000000000000","vote_positions":[]},{"voter_id":"07c2d2e3a3870e1b474a36df06f81da56f6721c5b9d5062fe81597380e9a664a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"5e5657a757bb41d7cf7e3513124b02eea8d256649cff6f02311f4dfe5bab19ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000190","locking_period":45,"voting_power":"1500142500000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2000000","locking_period":45,"voting_power":"1500000000000000000000000","unlocking_started_at":1727823434852,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"1000142500000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"500000000000000000000000"}]},{"voter_id":"10db3f7f05dc88700f822671ed0f8a9942ad4656f30cfe98c1efa696a80c85fb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"66b93b85f11c34ffbed818ec41ac7e06104a197b78e8ddfc75ba206658811aad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1681989f595a819ee42ce70d4bbf48d03f58ada6a56eeb6b2c1f24f0f3e998a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a0f39a11339d4623b61dac6e400ca59b4d52caf493caa4cf80a0cbda6f3048af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"fd5885a5df244625a6b929a376d92d3926607697bfd85150665c8aaa5d800a1a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"394a4892b4b4cabc53c1adc3b8f95719340998de22871b30ddced6781ef3bfa1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c39e4f5c58cf64cb3cb8223de94392d297c9db63ba129f905d0d9aaa77727f8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"863de74a71ccfe5c27aef97cba791ef2eb034bc63c6fe10304b2eb5775c7f945","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"2ef3b4abcd52922633f5e262b23daf46c3c6fba22b8f211f2ab99451de77b6d8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"88deac4579cb7e475e8e71bea176164e822623996d40d170844cd8d348afca45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1728060364540,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"3d455049327a89b0b0899d886460c7a83e9d97958675b7ee084e68e2dfba3496","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"6fd94977253928c232bf97391813e8be73bf2b19b40e19666aa73c4b960e7abf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"2af49f0840744bbe06799c5f6f486e581fb5f29d63957207be1d17b7bc359857","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b6da2a6c2a45cac4ee04ada08b2420fc0882bb31a9a7c45cfbdef4754ef75cf2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"cfa89fb3ccf6ec703dc0952fce8b7871d784ab26ccb004689df47bfc3759d757","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"eb5015c8ef417da1af8e12e1060785a04013516dc6f46cf59c0692e2316daea2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a71508ec3db015df53c8fec0db941937e0004767a1b2dd2ba88b752bd0e58330","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1fe374089c2af5490b2298baa8957bfd878f4532c5d32b52b0b88bfd408e5878","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"677472908e6bf3684c2c02305636b2843fca477f3d50b76d0e40efb059f673e8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"58a221e69d7e376a82b7c1ef1861d8274efa8b57005362645b431cbd12b7daae","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a7bdecc94e99b2cabed92ee9d427fe33c302de22068a374b01a2947a12fb8762","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f79e3688dbcc3a3165232cd9702213287c08473da0dacab9b5fa246fbf897e76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"801f584e59ec74a98ac0166632e9d021329f483bd4ad627dc7c46fe03088cce8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"61a924afbfc3a863a9023af4feeaef0f0adfd92033b0d0f38fbacc79157262f9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7e8cd1a470ff2806661ea084a7f92a7b0942f7a122772c4e87f60fe988691bf8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"12a7ec72e6e83ecefcb14432bd4e10584885477a44d3b2d4168978e8dd98f42a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e75cd2459b758dbe5838c0f3516bb095ed11544506c7c8b9cb0127c9a4b64b4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cc739bd26bbf6ab90ce3fb9d41e8af1e85297fae6bb6cf0c052b3007387bcb33","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e22dbab1fea3e1ead4ac65c06009194f0b317dbf7c71a5beef5af0aeb863ffd9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b6e52ddb6a6fdc3114331a6b40ecaed92cbcbda6a678f70a2ae03268f33f4051","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"88f5b2cc701e6193bdc4e279f59de48ffaf0ad9f41e47f0b439d2b1fef101dec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"twinstake.poolv1.near","voting_power":"7000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"chain0ps.poolv1.near","voting_power":"7000000000000000000000000"}]},{"voter_id":"cryptotexty.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"incrypted.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"5d52107bee24bc3c1188558a36eb9ace9995e2c7661cef2238c74c7b66ad377e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"1f738d3ccb02936f70e175e079e4b230dc0aaa207ec3731034c273322c482c8f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"086d7c7345fa7656dd5b34ccdbc59e9f28fb8582b4fce9511bc72b8ebc9bc33e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"716ebf23a6beeb2f427de862aa9d68bb0439daebf2ca54d095c603479d02ded0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6ff6c54242179073ada7d208ba3c837fa45235aad79cef65f706e52e2a6c272c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e291f45549fafd0119e0dcffc07c798506f5284291a5ea6763f43940cc705742","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[]},{"voter_id":"0f99f4b0e982905e106d4eab42d90a8934ef1f53d904b335e4eeb4157054bd77","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"81907e7324689c6eec67e816d32dde0a35668ec2b04f991f745893a52d1b6d92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"c900c1c56b632a20451a2851a5057aab6f540d405c4791f85686360a495f8385","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9103370c1ded2e04077ab3fbf0a708b8fee964ad8f83f12a1f52c880d48e3f80","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"15000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"5b04dea42af9b161a0749ef29132117832ae47fc8fedcc2b8b7137bf588526ca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"96d49a1b072f7c79af02b8137e9af2b80c598955180010eab621cdcf5f99ee5f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"15000000000000000000000000"}]},{"voter_id":"c5256542ea6bda0c1be75fbf5ec199ac9650246761eb3c7eacabf82c9b092243","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f67051f40da221d3f6a75ab243845868f49504235568efa3bbd1ac7f0fd6849a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"9287343d8dc623b0f7842fb82ae7e6fc690ab357d8962d604d6ff5e050c044fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"1404d46e1e74c3a20d03380feec9bd751e495a168cbd33686b53276d4f3c695e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0129f067fb619f07c4246425355e69ce28f4fd6e75f530964d5bada7cee0554d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"370ce704f8bfb182314e71341e9b6e452b4bf835021860342ff71b016f1c437a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"swissstar.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"a8717b724a04e70b211c43eb3f47939c172c6a4700056b593490b3232a751798","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"1e27ce857ed9390752de3bfee95f19759abb8659485bfe381ff5ff474e6c75f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b08aaf79df3eaa758f883cb80ebda2a5d02a138e9742e8db62f4ad0f94fee107","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"21fae2adbd00aa6d5767d49809762ccfb6c3817dd51c5d9e756ba50f9a6cf497","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"5dd1b36605c2e66a2406120b9e5cc881fd2e6d1b9e875acaa5bae8f51dfee5e6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":75,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"8cd828ea41a32218e8bcb02b2b2bb6fb3398c71454ec0c1043d0b046761e689f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a7d1ecf514faa181b20c8ceda44585552879453fca844ee214e27f85d711199a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1f32ae051df4c3ee92c0c455dfa7e06864cfd67f48256f06d92b8234f9c06412","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"36788febe85c0e710fc159f7e89dcb3d6f9428f0e6c4c22c66b902bdf74d8fcd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1edd9241c434318001fe7da7d7db216902c2aed93cfd9cec2052bcc031505479","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"42fd84490f61b1f56ff55cecf838af96f845c9b7557d01af8b8964438ed214fc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1725894076521,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"dcdac03cce7f1daba0910a598c1262e3db850b6a5b1dce9050918dec82713af5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"eef3e67b7eb739704c9e69ca4f4846816347ba0956781abc82c4d92e3c6af6de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5877ac057331689da0184690693aed16951b96f51cbbcb73cf299300ce979549","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"6000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"19f6558955d5f5e72c1dcd0c17428939ebaa857be8a4748aa835f66437fa37ff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c924c78471879a3bbf312c4fd14831c34f3066edb79b0da152e0f8d01266bb7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5fb2e4e80796467b929fbfb15bf7c9fe54aa544d660ed95d3cbefa448bc47027","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"b7ba0977a373409ea548cf6b9a34bdfc7d736de65689c3ab8dca04a668994d68","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2e77ab61f7697845e2b526da55ee186d9a0af93dcd76b955be4155b49daca019","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":105,"voting_power":"7000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[]},{"voter_id":"d18a87234bea429ae49a4143ba758d3529e62613569637a769fa5ddea9728bca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e10573fd1f7c442c8f8cf13177eef62993ea34dc731398a14bf1cfaf5baabbe3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"794998389a05be635573b069fa11113a847da0ea15570743e5b6ae2cdd005d07","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"940b79c23a2277a6c1a0b38ecb826ad674c58275adb0d07225ca1d006bae6ed2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"be08aa1d468366014e5f0999b5f952c947f76f470a01fb104f2f067b18d480c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"971b71d69c14b9e5e4df3aebb5329443cfbbf94b24141a95dcd12a668f3ad925","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"036a4501e25eb2751eb42b241de1cf477c7efd1478d077d7b3d6e6ca6476dd61","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ddc32350dfa3606940362e2e65a2827265d02969c0e351871c51c0c3fc0841cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2dd3b187270076f03989389154d03ef114b52ffd87c6d7c4f12493170f571c63","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"b435a74f78ae96d799e6cf7a67aa2146d7b7afeea2bbba75144c16efe9aa2cb1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"7067c05f8d106f645c046e2c70fad16fbf650d361ae1bf9ec025fe5de3140b52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"fc1c6224dccc835deaaaa7f436de293048ff917f49cfbdf7fc168b61a889a80b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"72792332c9650c9712402218535027510079671ee028557ed77bd1d1742fa843","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":240,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"e9294c0783c4ec2598f5133da7be8152ea9c8cdbb48f12c4e381366b72cebb8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0x0E26Ad20DA3ed9530f34f0cE7CAd089099A5b5cB.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3046000000","locking_period":300,"voting_power":"15230000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"15230000000000000000000000000"}]},{"voter_id":"00c6cdee170e38123763838006c396be990515dee717892eeafd1d78ff4609f1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4e6b3efcae07d074d448b075830a09e8245ea606e3ae5008721d8148ea76b1c2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c7a675506432b1aad8273a1bbb39ac98da78abb068b5ddfdc42918a05b93f4bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"05e3b2fa9d1dc7c29c6980e2cf38815c6bcf456bd76ed2398aaa41694293ca85","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3a91a5d6405a139a92e8ab2c600df1bd6ccbd0b9fbe7f75ed99396653169f292","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"799aac39d99dc1a9f6c0208bc1d06c838ed2aca0d4616387dc6e99df79f131b9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0d71f1a9278ef28f622d5e6266c8f6181cbef45f76c2bc02072a7701dcdaaf61","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0c8b607399b0c2c330cfc5e9d4589151a5a092338d102066c4f0942d9a45782a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"14c859966f54eae431fa6dc83c3be5539372f9b2970d5ba88875dad62d52e71e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ff6fa2987f45ca2fb49273dae1d0532647c0b5cec38a62cd0811ab8e2351b943","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"aa7abe81ff463b52e88a682a25b516ed3874f3c12f44ddd12a6744c34574bb92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0829b5ad8f8b242801656a319110fbbd21c18fdbd5476342c6ca54ff68c83bde","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e16cd0d634f877b0f01efb8676bf85f979deea1f789a7203fdac0cb480e408b6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"andrbenz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28476061200","locking_period":270,"voting_power":"128142275400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"128142275400000000000000000000","vote_positions":[]},{"voter_id":"9ed0b94d100f06195b576c5e8da67112d29953269218e4060d41a3798af97100","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1361ad9c02b77a448db162e06a7ee89d9fe75bab9e94bac626ed4431db14e101","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"kryptokriz.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"59776610","locking_period":300,"voting_power":"298883050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"298883050000000000000000000","vote_positions":[]},{"voter_id":"a81bd780c6fda9db96d87e7cbd3ec6e6787dd6dfbd0c40b5fca3fbe2ca7469a0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f1fbf3b685b1d4e1908eaf14ce19ed5f433eb95138834752b7d88b618e9619b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"64000000","locking_period":180,"voting_power":"192000000000000000000000000","unlocking_started_at":1730687301890,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"62c0051153fdc29123c27033db96b359a00235fce8b95ae6d2a90378ed75a6c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1ae33cf82dfc8d29bbe7bb616c65ce3d8fd4b8142d8f2e1472533c8c11fc5d86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d87cbc6d0749755857760871578e9cdc159d92c3772a1028c298b3b0f81e2d9d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"ca216b3e2c398da4b6c79221c097420883c76e434e2e3653dfddc8cab8d9fdb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"99cac8041fe3fa1ad47524aa416e84c0fbf95f47bdadd940afd0fda3debb3a03","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"379009049af0bcb5721f27bff4f1ad261b68979258235e078f03b3aa74b398de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"91f17c18002344c0330382e6a1e00937b6ac49e61181ac18b1d2e57c9979bdfc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"afdcaf49a9cb610082703daca352427ae75c7d43ed80934fc9a9f04c34bb1d11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"db07287a6fafe6f9c8aa7f9645b398c0a39cf432bed7b3bf3c7de8a94b08be97","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"b5b3238925314d194d1566a26a207222a3cdcf317d8cd9ddb8a99533adfe6e57","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":240,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"61d0087874aa6826490c8f85c60c3d7e49438ef3038aff524129bd4cd44efd4d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"9cb1ecae070cf1d83d0c3cd25aee0b854ff77272ac70d596e6a752ef42ea98e7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d746dc8c86033768f956222b19a2a72de997e01699d62402589ea5574f56593e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":1726093284453,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"4f9a31487a01a0f8784a33b640066d8d4156da92c532e68093dd66072da44397","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8eeef53b2a20b60365f3d674587970c71b17f7e9f8f8f11607b46e373edff141","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7ba8a7ceae452436870d704afc0c52b5039f8845eac11787578a13170265eb86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"81dee9ff2783ff8391df1fe52bbe2a9e991a31cb9e54f33ea2ef3ed50682ed3c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"18f9f3dc9641692f92bf6881c6977d775867160e357719718c81f31abfdd0e5c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"06231ff28eac44415ba813bc8017eadd71de48097fe79bd3b0b91297c99d4ee9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1f2503d03142f5248a3edb5294ab7c382ef11a927be9115a8b449e3a8293ce63","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"64159310","locking_period":300,"voting_power":"320796550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"320796550000000000000000000","vote_positions":[]},{"voter_id":"f8c8909679232c5892284275ae41b022f5cdcfca753cd6486eaa7208c58bd676","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":120,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"9313be167ad99a001af9b81ed2485d1972b64d093cea31a921cc99f349fa20fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"bde958893709fe0b8957f28bdc420dc3755f9f97d505bf4944b4ae891b8c8df7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"70e40ed7c95a6dcd3614909d103804937737a8aa468c4b75141ff5dcb71dde6a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1701c668bec7de27894c60678b3643ca7b451c1ea34df65b0d14e27cd9c554e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0267c26e3a65088ca28a326d3a3d38c1e01750cfbcdcacba44b4d356f16d5789","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"028c9fbfecb62e6956d98f9f6cfce636c4113546754385dda1d53bd049ca01d3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b402408e7315a20ee682024b23333e28744d4c0589270a0dca95a04024b8116b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"58af9c4f6a59a435019250e6694b21555eee80803be9dbd25fb7f26097c844be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"54d2db661030396574700c5bbefe3fcb37b33c7ba464b1ec78538aab677193ac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"db6a6ee86376da9b1027b4e4f797643b08f6f163ac1336fd88ca32a56f7ccf66","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7000000","locking_period":300,"voting_power":"35000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"35000000000000000000000000","vote_positions":[]},{"voter_id":"6dfda20779e375d2fa86c4b946759de61bcd147f38cac297c8904ac0b896faac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"abe2cff100136ee3bdb7067e73f01f9652d785083cd94619ca177d0493c53999","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3659cc1b64562db07abc7c8cddf0e16a909620430de3ab18cecbd917d1338798","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"b35327c1958ac20d43c30aef2810d4138c8b6fc7f457f4321166ce1d548c4f84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"82d89f0afac74d5bdb4aeaefc46190afc734bb1477ccf59ebe0b8f209319632f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a5a2e09c728f31f0c4108fd0f2bcfe0b1cb08b2007ebeeaa7043aec4dfc5e01c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b50ebc176b2c3469c1a471aeb95abdebd67da289fb6013f65e7a6fdf11ccbfbd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4dfdeaa39322742a790e25711cb1ae1a2c919eabd1ea97aa102ea55ce63e7130","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f402e8ae9d5b9a11924db2164452f75b3c4d8c11cbf2836c8e523b6d9419b2cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ece40f7ff08812440c1be6b2cb5f89f93efd09069c411699e2781726ceff9e9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"d8bcbfee95d9a5220bea8c39e7963be59260c5ed4f992e58a63c3f45325ae74f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"25736f1f4db48fb3b70f3d252954ad551de4ee15b7afe0ed07928eeb85378463","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":75,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[]},{"voter_id":"b36aad8ecd4d568196cda5b25fa6aded59546f2860d88c2c4411828254aaa148","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7f75a3e118451ceeb60e3fbe69cafde67faa929fecd0658c7a45f3c3a39827fb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9291aa7a7ceb719954a660976188cf0956ade8441bed1c33b0edd6414d4a28ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4219fb6486dc21c3931ea3a04bd5f89ea0282190924a404c911db17b894cfd0f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"f2b9e607ae630b5bdea7a305c6941defde1904303b155247e427ef79614c24c1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2de5cd18bf2babe3b486ea7f16bce876332575b57ee9c0988a6447cef7397fbc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"41234a7447f36f2653505f38a3c92b9de8baf4c128b4b41d0610c28b469ab24d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"563992878d53c3ef12a5f1a72b2bdd122d27377499541d441c3e9769884f200d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"69f5cf0dd02ce3439b937bc32a014d286542aebb85645338c56c866bea1d914d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"904f6b18b908ebf4f6bee39e7cb80b1fe10510fef3106d3f7248763cefd5e511","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"1d6a58651029e4bc3506e7054a0e9731923afaf4ccec52baac0add9f438dc5f0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3494d3f964d58eac9d8c40c9c4e624b632205f8aa3ccab8842041fd09b6bdd71","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fc52036b5cf9c7d2d82015b0035ce0d2a99ce7b51201ad22113bff3b0c291e7c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19600000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"200000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"200000000000000000000000"}]},{"voter_id":"617937c4266a0f2e2585d37d5cd6bfba7cf4c140b08b7e7fdee3b3a8e08d5052","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2509acc08146306be1bcdeaf0341c10f71613d98af714b63a101343b2ac7de36","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"dfc1d9a2fc74440f7a967d511c13e3788e1728ed95b54a4c8f18fa376b749f00","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"73be1f7e83fb1f61d2479ad2347f4e86f16d78c6245d19b774091fd7e43086a1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7357821ec1998d8b21e5e9e0542ef6dc1577933b825f6032d97433584295f330","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2a9de835bbe6103656549fb96bad8389c872c88b272e7c585ea60d112da5790a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"f0837ad29d4a602895bfe6ae3ab909e6e2e98612a4d153df60db7eb7ae0c48aa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d7551a5d68564271f4a351c6bce7fda2ed04b8e4c37741541fefecf15e36a287","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"17b1d503c7c6fae10bc6f4106f2be7883644f88f390c8bc4d128eaf0f7a83666","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"412d18aa88cbc4b8f79039fc6dc1ccdc3122ac9f234a05fcdf802474d54b1417","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3000000000000000000000000"}]},{"voter_id":"3940ded2a79c6b18ad67a37d287eb5abf7a8a2f388a07d1a9e7654398961c77e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"797b0eddee60b4a86a22dd72e009c4577a5dd89ce68dfc44cfb7e0448de7efe3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"a0b12a2f220110c01b8dc0f596f2ed16689ed49b2b05fca2f6481267a2baedd1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"89904f59c5f1652f16d13675e197727e7c4f414e6abee4e4db393b996f6febea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8d7ba69f8e14eab2567604c1fe7299501ce9c95c96afe844e9c7a7e709276c45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ea568132b0de7d2bae73efeb747b96ff717240ba68f13bf9b5538d1387a23a19","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1dbc329c35ea3645a2aff4940932ee552491819a9a12dd91eac6ee6e6eeb4c05","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"99067b6fb9d2991532910c7d2a5b15aac61b4d0094dcfa7b2ac8f0ba1b9bfb8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"3311a8782754811121b198b06e97b6f192513f4b817a80f734423a76e5dc7ac2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ea3a50632c840f23224371c4245fdd40365f1cd92c4a0edcfe1b4205aeeabd6e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"oharanodes.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"ad99afc29ba51470ddacc72a907fcfb3dbc5e852e0912e3ab33811e7228cb315","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"32be94ecb363ef27eac86531145f110342b980ad47b5bab8d8968481210a605b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"31187eb2b9d331ec171bd8c2b077c28e1555c2c7a810db4b534124c859ff59ce","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6eac46d7bf3fdfc277ebaf3c0da229d35ff2ff690c59f9282cb18cee676923c4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"398af1f074deb4b17e59331e26cca95555b9f97a5f945c3d152e1410a2b8d5e9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"94178855619e1db1b60517cbfcb24a8040fa06ef069fde1cf327b94d62fb36fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"3000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"b66eddf0109408e2219843a79615fa7aed85662230c86f3b49e3ce67c38460d1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2e0a4f8bdb97040e87582ac6aa213e7f259207b71ff6520f043194ea52567f64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"25beeb247abbdd57a434c84f20e285d0ac15f8f0f7f536976110db632603a0ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"2719f07291753763a4b6b7050bc31aea692e06dc9b823ae25f11190fe1071324","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"ad320831599369e368e49b53ba57db4dff1bc7982e4cf82a72081aafc304064a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"239a8302778fea0163e819c8ffd67358b723b6d92608923b2c2cdaf0478a949f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9702b78216f13fc8f52076f7b71f1998c90fb7dcefcd9283a5611e76df535386","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":180,"voting_power":"24000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"24000000000000000000000000","vote_positions":[]},{"voter_id":"773e7fc65a5f109f9bee2f5c91ffc585fa85bf9346dd6813492271a90b52aab9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"ed82f442c4e6711741f5f66bb9cac14224f54b73ef645f170987981b6547ef57","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1729740231422,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"8987ec7bf6d8f200769b47c8cc715e971b0eed2a43a33d46b0ebcb05893db35e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6febc4e10c051c4c62c2ba06f413dc7035daccbf43ba50f7cde74b0fdb30d462","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"10af8566c11d18fcdd8014548e78f3fbe4d64a06e60b28c2348fbfe2b586f024","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"94287d6539cc3f7cc989b44061fc801f2d3e7f5adeebf50e857b0a52dab4f353","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5eb1bb60fa568aa0047c0a4a0e7c76afa1bc6adaa3b43be54b60057e918d056e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"near.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"c7eacc8cd7b1aa245fa1239dfbe4ca1fed011b4d1a49fdd2d15f3d03456e57de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"f4a2483ca749d299a962dc524700d9d8e9b02f8abda848c1e3f0162626440a43","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"041d31e3cac68ef16de9193a0ecd05d8f4d80d485d84168cc2e498cd36c848b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"bcd20a73a7674609917cdcd2c8886fc6e8f424244afa00cca5b6f3828859661b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6dc4173077f05aeed2c35296f1ef5ef590b38047c705eb1f14b0c03ac26277f9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"955796c59627b325b9122da80bb91948d3ba1c3cd1877802403e66abeecd7d95","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"c4af9e7e792949015b26c7041ce561a9c2d7eb09eba9e9a38216b5b291eac134","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"38e7d968a3796d6c8bc9657e5675a8fd3e2db880e1e61b3f22901d173c3da834","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":285,"voting_power":"19000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[]},{"voter_id":"dd.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":1726916110572,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"fd212826abfe9407c793a4ba5fb56ddd285c625504d1bbfc7f992dd30b0e20b9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"dc16eb15c0e8cb16ea369b23b22c475c098d3c6730deedb02f26b4fdec2345ae","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":240,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"0353b18618ab836aaf5f1f2f10e2ef20ecf219beaad19b5b5d34e6a1ba63105d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"853055aaeee2da3f8da4cf0e8888c172e7a2d4fed8038fb20ad5e487e005cddb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"d66054c5889551c464d85d6541f1ba7a98fd4059c45e1c05ba88f5f9cc6b9ea8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"657e262f996d4ea1e4ea3fc1672a83909672dce823c68ae98ea08b327d0c158a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5547ed00ab078368ba1230cdf4900fdb243832d323b5f58f1fe9934dc1e8f119","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5e10ad8fd96b8afbb36e8e849799ce5b1b9e2827adfd6979d3cd177f66299dd6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d3f5100f50fe7576fea0f4e65ca69f24b5a5af2a4b620ae7634c0467acf7b4bb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0512fe7a65a2b12709739d2d38304daa2f2f26a0540d6e4cc89a0e2d4d3d5575","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"56e489f527e12b53aa98211fc42af43aa806aaa4a74839a29e96d80a6cc5876c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_01.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_00.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"ce07a07ae93621005b5903a34536c79bedb775388e02f9bbf1b5d690346fada7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c9a7647181fe241342e172bc22de0d8340e23e0b42f005c1ed2dea1251d899f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"331a4de65ff7675d133097d60887d215006b8c22c0cb544eb1366407726e90f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e218a8c3c6309415d0ab3caf8a1d92b860546e5afe5bac9df278a6f818057f9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f17c91f5e7f24364d25bdb925335b28f3c71c60a302499c4c5fa3089241eae36","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8cccff5f1d40951be03ef48bbfa0c0e840429a5ec4a9b47c3ab44fa75968f5f3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4701f0ecd4bed4c3d0da51781c62d51f8d75382b9fe397b30b4516f704395f4b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"43e72e92b841b4d69a6dee7eb246ab0715ac4abdf810426bafc72812b9f9be45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1469e35d59f82078f264d3acb769fd4d0596e9217043908f56231d5876c5a83b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":270,"voting_power":"18000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[]},{"voter_id":"88026ed9612ef760670530936cf719a9c1ed941a9b2037347b3c00d2612f705a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"749b9e47d52511305c3dd7142bfd705ff8d42f7490b10f7ef8964a1f45fd9902","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"53f807d64a574da293b04f7b9c3c9264f70614a5f76ff33357f6d4772121167d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"913081482b6b36bb3dc6720f63b928022520ae73db5d25d48844016c6950bbd3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"52e7fe6b11a8d29e00f4a871829beef6db11eea951403a32f5a120b793a124e8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3ebf8d92a358e1b41116ad43776347a655a9d8359c4b403eb4b8e15fd096d985","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"8ac028f6b61de7b53858f4010fe7cfe0123130307138aa9c58070916227b5204","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c28e58c2dca82bb50dfc2818364fac2f7edb958a9ed7daaabc704c6484506850","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"dd36c2fce06f9729b713377bda681c8a8270c0ad5b627f99acbe5daf0656de64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c2f4f51b093ca7ccf515a6ac178d60ea3ae0841ad67d9936e93240081a9851f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"30e18e86c24939472a1e9f103d92bb0b1fe31bfaf183576cb320b55a89b54213","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"780cab709ac8d76b9a8f0af5a258652a97943451b4fb5acd18d49776cf6c9210","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"6f7f6a1e29da4d60d8bff0fa37548b2f07016f1a0a6c5c24d04025691ebeb808","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"04287931bef54f9d89d10098c1e8c220a6c074ad781e1c5c9f4d5195b0414b34","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"734fa29022827615039e320580bfc53444a14b05f9596b3351b69ffb013e6f05","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f6942662733ab031d375130b65055f8f1931296af99e8aa21aac47bec1450a50","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c807d05db081db6687f39b5a6a192edc2aadb157703c7e3b59478c7fc3d4fc87","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5b1e0fd9e166a4c2c6500657766c02452fe80666f7e82fdb825546f9e5aa7fdc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"fe8a7476bd941adbfdc000101c5cf29fc1da907a4cab0c548d6992935f2be323","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0bb4de25a67d7713b6f824edc74964787c34555ed4ba33dad966e051f6b1a48f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"47d48c9349cbd5aa879d981ff3e240123d8ec787067b96077655a8d7bc8ce9b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"65ee595525f90d2ae03982f73cb6e0fd38351e558244b55278249f40f5d00f89","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1731301368381,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"458c8092d0ce14bd7a4888991f9150ea0e8853f6c04c5b85a797d8f373d629fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"45955a324834403cd876b0f6a616470996bdfb1d452b167792df92e03f5f2cff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"184da3a9f33dbc566a033631982a2f8662987f89edea9bac90752643a99c468e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"403b34ac996cca15aefb1abb82334629d09776d86e86da7566f517c222b03966","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8b41ac0a4e523373cbc18c4881a006c6c38dba6ffd9f3c616a9cd772b27a225d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"aae0f747860877ec2334fc538226f53e043dba796a65ba2e2beb55a8e5c73f74","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"591a42fd26c0db3553a45cfdd10410cdc580b028f8c3dd64c66a358ff2e8a9dd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"bec075061d6d3730f635592e6b1baeeea225fce2e6c282fdfdadc6bc4454725d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"spectrum.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"finoa.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"gfs-ventures.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"06c826b8882bba0ececf2f575ec384dc61cc1c6437057fbb407e5866cd3603f3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c108ee446be7651161f81480822c404338dedb10a5aeca4af42244ad48a9180b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"faa24b31bbd2cd1fbbabb1ca36c8030c5aa5f3eeda652a12d25f788d41ecbba5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"29c778fc450b3372ff3749544992f2c3fe1722c25b2da520264a186a41fb5193","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"63b5128969e38ed740b3281bcc6020525abbf57670720e0766139ad0bc7f1bb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"de75576eb4b185d6f483d13e45da751e699b36761a67f02419a7768794e94645","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8731464b3f621bc32b4d8ef03f577bd892dd907305b249018931fb47f7f908c0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a8b5e76c9b9837392fc596c0984e1821744b2b7845d0da40d96a75c3328fde60","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5ae7d21596f8f2bcc3ae3bbb430be5f438d48638393c7f9b4f4eaa3ccbef6a49","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"5e53e3e4ed08236538800f388019fb280fe63ea25834ca5daa9aed810e5b6ada","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"bd6770f68773ae3370fd987ba5da50a9831c8bdb0930ca3403f9746c823e7e73","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":195,"voting_power":"13000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[]},{"voter_id":"c570c39ef05d4252adba606388596d69f6cb39f74a6c199f5a876595d94d11c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b4cc20115a204e4c8b3fbcf1b7cf4d448c0d8ada108acd9902558e2da77061a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"304a7bfd37ca7f7a8010436c7c0ca7a49f1f71a978fb9f7df1b9d538332eeb32","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1ea52dd01e3490820d2545d5f298e28c18254ffa74b4409dedf3553a483177f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"a55dea66741d2b4ebca4a61bd98c0ca03cbaf0b6473950f2dd7789bb3fa71e99","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9cf61dfb276914d9684ac21367ef56d4ae74f88944ee7ac1851a732043e17201","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"409dcc41a29f72b2a8921bb86d22cce65412540eb8e5cc35740a7365853b2ef9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"01c45530443a814c411801de32274975c0417b13e4957b6b96be9f703022cde2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"fec07cd91a1759492221e878a352517c40ce7773d21067d9e9483fe5963ad80e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"4e3850f68fa93d677287cdef4f1f6b07f2a2c4f99959c154fbd27bf44b8529de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f01e43067c4bbca2cee4b2b1d663f36899773c99cda12570f120440d5697c7d8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4dd90306bf3fe8e715bf90422fe431b246a15511255d30c36c7edc5c8c934443","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"19527eb0a7f723b836cb7b3451a05cc0555683862b301554e23277f2662272b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"fabianjeff.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1403139400","locking_period":300,"voting_power":"7015697000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"7015697000000000000000000000"}]},{"voter_id":"14552b1770952b0e1e526fb0c0f0f6977337d46d39dbb473f787124d4e5e15ee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9867ea08d088f14d907d9ec26fcb92caf903c6b25a77b002917c9ec8fb4200ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"cb6d60ae1d33d0640850c0f789c3a691dd2c78889e45580575ac034b87bdcb3c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"858366ab6be01a9f23f2b0995ef12da31fc0f33bdef636eb3aedab86000b4039","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"78233ec39df97f2cdeea83dc25207a878f2b6010f48eec05c834da03ee6c83af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"1b2534ee7033416ed536d2d1d776408410791742866443f0f8e1ba9a3934b8cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"agrestus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"861429932b08480ca9eae4e6beaf3cee2d0640ed7a8b2955eeb462ad286d33b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"binancestaking.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"a6d26985e398c5740af9ffaeb9e328c793631228da41cc87af38ee0aa99770ab","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0cc6b0ba4718764969c565da59c398caaba6dad4d75b7354a92221080018e1b8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3921d59929d52d0df19f6dad70f82ac4afdc76422d0827e4eeecc06395b14fed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"62cec87ff6444ae66e7ca49a31943feeed94a201164e240725695619698c7870","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"55cb1cd72969dfb6847e29c03b31fddf4d228f1402824ea9c4d1b304737f3441","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d23570731443a539074d6f662afe0f7fe76417a7349ada3bb39de1ab731e1a72","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2d380e02cfd85f8b939ab12a0689965b72f763a0dcc1d759da04795bbb11bb8f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6edf4c0d8d6578ce8c0d3d35db9e6ed5425eb7ebbc2ff0dad7caabebabba7d10","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f0e77c7f794ddd17fa3d82725190763a965e7afa53ed1770f0c8bab345cd41f1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f5115938c6a7b5ba2c7bb48d0170ccd516201093e9c07d9cbf0dfa68a1f153ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"017814f0dbff4c1aded5fdae80ab2637951a7eb22a50cdccf438bc1aa91a300d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"7f86966cbe527917e55e0ff27871797bf2fcebdf8b0fa0f1653df6ef38ecd063","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"dec42051f69e0c1028979c55ad771797bc42781d2426627e43f819e8b0308c8e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2d206c0ee8e3a31b3f8b9c4a990db7b03af6c5c21502773491906c43ce95aef9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"96b77234f3041e8993c3d2a4466586c92a750b341ae296b32d9e7db47e6c0c59","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"651102751a31dd23a9a6db8d957fce4e3646e8e8157e6b7c165668322171caa0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":195,"voting_power":"9750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9750000000000000000000000","vote_positions":[]},{"voter_id":"053d5559f94b48175da9f65867befe2a165846ed5b67abdd1be4ee788294120a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a3b4b04805c4bf45cbbc3620547e231b5463c9776021ce9960ba8182ce4cf568","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"cdd59c02a8d09de7744d6d108c235783fbbf08b3ef3b7f0b99bc44c90f28444e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"yobo_z.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3257705100","locking_period":300,"voting_power":"16288525500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16288525500000000000000000000","vote_positions":[]},{"voter_id":"5ab140d2221672ff1f6c90d948d13a6e22dfc232b97fbe5fbc9f2ba0b6a44540","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"495253cc729e92aea99325e09ae1380e80e6b43ef3cb4f692f2018c867dfb80e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"58b8ee8a8e5b628e6b48d4d99a49307bf42cc6ff837832dcce8460650675cb5a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d874d8c8d5477f3dd4a81f7410665afe8f90c9b6e18facf7bc26a07a4953e8b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5649ec01a1d0855cf4ca5ea37b46c0aed2ed0171b3a832e73c8fa380b32e8e9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3677d5af605867f06f3f88ead4613530bb534d1f2cb1ba2e79d1a4692dd190b6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0999e054d23231fa8075c8068e28ff735383f8954f6b20b757d9ec0b3d6e160e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"359aea3d930ae6f9bb436a29e8dc6751c86804057f8a6da7d7b65b4b45a20163","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"5d54e98cfe17cad4bd5835510001281b8e68017fd5d0528cb5ac5054b0bd8a28","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"f8f8ee707de2dd41f5459ca9245147eda3435086aa6609d3bcacca7afda32aca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"40f33559d2bf5a34dc46f0a176586f9a6156d945532e46a71ba7710134526569","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"satoukibijusu.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3167889220","locking_period":300,"voting_power":"15839446100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"15839446100000000000000000000"}]},{"voter_id":"pozzer.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4158992350","locking_period":300,"voting_power":"20794961750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20794961700000000000000000000"}]},{"voter_id":"helmangom.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000","locking_period":300,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"500000000000000000000000000"}]},{"voter_id":"yolima.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":90,"voting_power":"750000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"15000000","locking_period":300,"voting_power":"75000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"825000000000000000000000000"}]},{"voter_id":"70c1b68d0e63229955c10cc1521fe85aa69ce74ff0ed81146e46b14525d0a723","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"61cffaf2ede2960f2f5c42725a5c3ee1b9d8c4c8f19b92190011822597f2286c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c2de8b99013c31050b274d77f6d99e7c8a78718cdad69a5561b2e5f3f4b359ec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3a8c4a25c26b16aa179db0f523403e864d13fcb6fe8f454afc67c3a860b9f5b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"711ae8e59518ae9eb84d8323c08b8510b6a901a557a99b8f366d98c68e95d825","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"6d0cca462c8288d2194eebdea56b441eb0427fa1b9087aec091ae69cb943208b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5417b243f5e47c8edf6e3bb14739fc2901282755bfceed9f4d30a01e3630afdd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1ec72cdae4c26124560640ccce558082715bf44f3eaf9e002e6dbe0bad84f6d1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6b1245ca8a8ff39743e8013d6785831a30a11f715bb6fd9f158ddc52d26f88be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3750000","locking_period":300,"voting_power":"18750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"18750000000000000000000000"}]},{"voter_id":"wap1.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46493835500","locking_period":300,"voting_power":"232469177500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"232469177500000000000000000000"}]},{"voter_id":"f8059e22c67be82dc3c189f1a9ccfcdfdb157ee9fb8e798600947dc43b1abf05","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"87fb3e0878bdfa092084e000957fdd55501be0cf683c22b7809aaf6678c44105","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"ed342b0e0718e71354f1f5c9bf364e0a4ba01c3d924079cca3443f468f66d874","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"criptopepememes.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000100310","locking_period":300,"voting_power":"5000501550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000501500000000000000000000"}]},{"voter_id":"7982bf4b7fd2a75fba7c7ae7ef682791d5102e493158b26491a2119f4a893bdc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8918ba96cd3ea1dc9d97a4f5669b02b9522847354f707ca7660e3da5c8829ad7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f8af656dee213507fb14779067870c95d9ffd5a57b3ef9378fa13b85a81b7611","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"80164fde65fa9b7e3a9309cb7e5fdb40a94d1de0811f0cc1cb30f29458564adc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"fd1b6774df49284bfc712042c99608ef010bfc59ed0617772c41d11c33e9127e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"64759931d3006a008af90de61e4d4cb3d95468b69ae01be63c03fa9d2d166163","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2b65b54b408be1fbb32cc70fc7a0ebc4726f1cdcc0150feee4095ea5097cf561","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"dfd830d6b35aee5415c8ae5436cd10b95deace970141434fb64bbbfa4fc7e3a2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7d5f49ae25465761b64a53ff8efbac94269c3068273ed66fab5eab987762d84b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6bdaa49f43e589e6915c1ce64aff8678a79cbe105abe4954486355c466352a04","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ab953a494fccb3a5996cfef4cd1535c424db5eedf3d1c34c8e31194f276431f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"72723a1ce8858eb25ff1db5513cc45fa768423886865202d5feebddffa8b78fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"38b26fa06a6f586156893d52e5440a46a7503f9fcb80c8e1bac05766ec7cfaff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"0xDC068F2B765e7B73d6BD182E50e1e4AE511733C7.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3323000000","locking_period":300,"voting_power":"16615000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"16615000000000000000000000000"}]},{"voter_id":"581267e1c29b956b5f370839e6faf48b6948049289f9b303bdd7bc92ca0a37f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a3e4d1f9359a08670c8adf05ec48c30c5fea4913f393fc1f4f5400e219ed50a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ee1efab27573279b59c076e841b1cd36cebffbef0cd7f3315b5b4a536b670e60","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b95824696418c4b1000df8eb0aa74206081083d7c76ede207b071639b972268c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"17b4a973d3bce2153eba3ebe078c6d7945c4a9f31f9075ebd97c11cee91d409d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"everstake.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"dc6d50a234b525b8315fa3d360385870eb1fc0be2da7b8535910406a061998fc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"6115106cd5873fe67c7895537c9f14be393d1d17f476188ca2eae861f7985646","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fea113a1e2bc2753df1fc8cba18593f1817a6b3e81065e5ffaef8cb63e73eae9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"nearkok2024.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"62796342850","locking_period":300,"voting_power":"313981714250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"183233068700000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"centurion.poolv1.near","voting_power":"85754581800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"snsmln.poolv1.near","voting_power":"44994063700000000000000000000"}]},{"voter_id":"65d8efce60a4a428ab91587c51ad4b5868c25393b8fe4cbd8c5292a16bcf800a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"2168377db8401dab21b3a25f364aaba7370bae972adb42e7b8074b8d905edb76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"92e1c92cfe613c4e6aa789ab2175a4d5da1d939cc3c96cb8b6386850c1ed77a5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"phuongwallet.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"159558600","locking_period":90,"voting_power":"239337900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"239337900000000000000000000","vote_positions":[]},{"voter_id":"12cdca6113ef23cb19c5a16820571fd6a670614b283b31c15e3cfaa9798f530c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e07bdd07a19febaea5a02d5ed96baa5120541f919da6c9cf0472bd286ba765aa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4a8586f03eec496d98ca4e34acc6139ba54bb605df1844e5fbdea2401e9d6519","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"38615dda77d0ca19be35037f59a104595268620606d54d5011259cfdda0c3e52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1a616190fe849bba5e123abade2c07997fa99be76a9b0b6171def3758aad8ee4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2336854890","locking_period":300,"voting_power":"11684274450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"11684274400000000000000000000"}]},{"voter_id":"b95b75066eb4ded1e0326b37100b4934b5a53fcd1bc579b5c6313e4118c13ffa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"66f84b2665be8d6eaad47d66f1403c7c49a82ac899348a0da1b452c219c313d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"d0f4e50101fbed91426ff5d9484d70910699c2e6f833614e56d8f6da1564dee6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"7b81ec482bb6ae18b43b279185601b6dc2614a22b4fa3882adc68e643a9298cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a759603b2906b888f02011a6e1744ce28bc9a062a9759bc3eed6240619fb11c2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ae46cba8067ea098a7fb02275c47c4e2d971106f62cbe80f3a8d508b7f01c4d1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8ab933f3c34638b6a2512b4c72ed3ecd6b953ecbcff08381c0734d47345a06ed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ff868a3da78561e1ba668e61e9a48eb3dbe8b59e0283ce4115701220faae00e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"a3fe35fe92cf48fe2b0835ae79d79f270cbb08e6d596566e00c8e7cc64d1305c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"gilbert_crypto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"85063e3b8ab7f540f67345178b105f665cbe78c764f2e3d942edf2f7b93a53a8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"tequechongo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"209705250600","locking_period":300,"voting_power":"1048526253000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"798526253000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"250000000000000000000000000000"}]},{"voter_id":"8d050b050cce9dc4d563e0aec29b1bfbd8a33daaa9c1825a414dd127c44125be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":165,"voting_power":"11000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"11000000000000000000000000"}]},{"voter_id":"b39b52a0bb1cce31a2e9e0979c9ca830b0bc9d647493e1effd0119eadcd063f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5944fb29764ce2015652b71a9f227f07c50aac0fd2c040072fc483b120e7dc96","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"366779cadb5a3f677d173fc19f83e269949e4b20c2b4f76c22ea5a2bd22bd8c2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5a362dd0389e5dbffb124b117b5207381ee4cb0b3a25a87ddaed99497b7282cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4e2c34d0bcfe1a22208c433663181d681ef5b523dabc58f15deb737714d7d2e1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f581e13e4754154cb65d55bc8888a926eabd87b72bf1b477df60e339d5332d22","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"520e4fe5920cb92ca5a558828f311872b500171af37e86939d1c8b6c0064f23a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f512fd82959f4dc6309c6ec55ee37b89acd8f9b8f1927a64639000a09c5de94a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"338794f543e468cbe878510513f4f053b804d2c7f16cf3bba9915c125aca7865","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"b77848f86f850f0bb3410db647c866a763dbcb3e212b36f3ce983b9cfca8f7bf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"7500e6f197175d63fe6bb329320610613cd69c73f2265e0ec6d7d111c24bd3f1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"53645f23fa05451377f090fd5be0c86378e6bd7c38ba4278cb0559dd3de83bf2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"stasiey.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2562800","locking_period":270,"voting_power":"11532600000000000000000000","unlocking_started_at":1727845268380,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"5f55c67ec742f4219712f384e0ad2179a89375b1cb71facb9b30423ba71beeb0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"127688ebf35cba72ca93c098ff5669d3b21519447934635ebde46ed932cf9390","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c8546fd655a069d04da742ddb13dcacdfdfbc8dd1adca65c20933b5046646abd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1727954002434,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"428b9d2985f68362ea50c5140a62cfda4adb33072a4d33a192c0e921e719b3da","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"844353620","locking_period":210,"voting_power":"2955237670000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"955237670000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"1000000000000000000000000000"}]},{"voter_id":"28b67c5f1937b06eccbc9f54ff91bb2f39707e1799fcaf64473869cbf3062b91","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"885dc03ba460a7fb2b4cb2619d296e5e623897672a0ba19ba3336c15f8156576","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2bd36f58a7076d4af94d2d6b25e725aef22c5a772b541de04969217383c62d74","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"f3f597e0b900d50e3bae762c3b39fee23db58c1b83e8533618864e2c98d1eda6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e8bc0624baeefeae53d8da80510ef456ef3a347322fa094e612de773b4088925","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a19a1ea737f9f5287ab0ca8c8f96e979391af729acb181c222606f1bc5fbeed0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a971627532339ab09c7b456b773e61897ffa9de2238add850ba4596348654cca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"6cb050cfc1f21e73bfe45be82ec69b30b155fa5e4ad28c2255baafa08abfba73","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c9b78867b091ea43a1524f5981a38c0b9e1ccf65aa81f13f76674d25fa2efdb5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":195,"voting_power":"13000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[]},{"voter_id":"696b648d0abd1845b61ba7441f9eeef16c6dc7f4dd948623d7f586b152a4c816","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"649f75c5dcab29445dbdf052e1e9e276947a1ed2bbce75d961c9c1053f5121eb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f787759cd01431376c94e896a39c742d08c49d28d36c996d9fdc5f28fceeb8bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"098f722a8fefba636a5dc9c6af0982c1bee04be46d651ac54fb6995d6095dde1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"972fc0566781e8bfeec11e7daedb13d31e8bc2c75b3f347c944d1aea223b9cd7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"793eedf5b42464a1693684ccd1185d126817cc01a7b8a7d9bef3cf56862a3bd2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fujiya.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"913000000","locking_period":300,"voting_power":"4565000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"4565000000000000000000000000"}]},{"voter_id":"58acbf4b7f7b926ac436ff8e77abf0fa75b1882d493ad8dff823c2d82b7cf5d8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"27ac9fdffb06b1aa2fca9d6b54cddbcd6b42c2d64fe79da764f1d99019bbfc97","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"956ea0eb5453c89a5d3577c8a38772038429508dcf3292ca00335159ab9a4e75","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ccc4f122ff4c8a855946e9ab031afaa6ada9842ee6341a5e756256c9148478f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"5d0475232d88f6137d90fbafe0de8e5f310c4099eb0871e5b3bfa36a11bbf152","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"350e470d0672079dc30e00d58fa4df938be955e8cd96c1abc4792e8a44a066c7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0x43fFbF682b61d1A8d7c3235eb724775c5d8479d1.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"919000000","locking_period":45,"voting_power":"689250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"689250000000000000000000000","vote_positions":[]},{"voter_id":"1fc26af171760834cf7745f9f7ee4f87d2c9953154dd433f576d7fa376d4f9b9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2bde567dd1f9a173090913aaf637b79d4f6b20825e9f22adb218b4da3280d2be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"19df94eafd51c901eeb482d7842c9c6eabf760982b9180a61f23b4deae693f9d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"16fa3136a231d82be6dc4151e1b2bf5af69faede0bab9c3db89d8a61d6f3dcd1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":180,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"7bd462cf8ce8cebe5d0207b3c8877f7a5f081b44ae66869a912b6c69c2df9e31","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"03a03b9c00c17f0fad492f3c9706e18d05cf02f3170fbd695d1cea903b370976","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e1d93d9d35a6685b89a8d490a4e44760bee71a8a011e2f76985fbe466462ee6f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f30497b3d806c7f657168bb8ec33a4076e74d20a8dee09892bc2c51e9d29aa08","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"11439a0196c4ebfa7a619a525cf6d4396f2db3b80d58f5fba7b1b67d578c30f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8047bbbd226db06e2c013691f0510252ad056d18e48e879641de313ea0ea54f3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b303dbe825076daceae57e9ad34c638366a92955f13515411f38987968a2d0c0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"publicai.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"de9ea4ed7e3c0c0c7bd94df110c3582e88bc66a7548c9af3650e4e17f97f7f86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"edf4dfe7a1b674ce90f11d5ee6ba4caf84fe5f06d2c39bfc8262ea4d59c20c26","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"590972a01161f9e9e83306a89a04018316abd177c120d09cdcd4f0e490e15d47","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c12ac397fc7b2b2790cbf98ebcac2199fdefdd17a370d845e9d6976f9ffd17cd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"134172a1fd4542da0b6775f3438b7c9f4f1d2c349653ccc50d066aaf91e0f47f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"fc19ba0b33ce4e550446c1865168b243daaf28b213e550f276094b8507a4a90d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a798ecc53a312c5e8d7dd3110712c403002c16651a9938868da9a0d25978b783","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"dcfb2aee193fb82e8d835584cfcda72b93a2b888d730ee6fc30bdf4db0b67a32","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"d412062f84c4b6f62d8636285957cf15092950cf2a64086c26f092036a234a81","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"56262872280b0d3b7b9e876a5541c0165985819cd875d780115c128433b09e64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f43f055f8f8ab4a19f1115b8f964a2714529833d464d67f25efe6c91dbd253d0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0ea5efd8dbdb1bb1164301ce7e765a3508376f0c981b0e1b4c6e31bc6f7fbb17","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"fc67fd4fc8fb0ed2f23d7c3e2652b1c05eb735d260ec1c8da825f26bac75a6e2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6ecb5bd9ff89776792cf5b67481c0bf2024da338eb3f26f8d0e51d90b29fe122","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2e7b32b069d7c2033765d124bb66d81b4536a9343b5313d7589b3c54fb8a5f64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"54a3ba1946cef80f13f61a16985a83b37b0fa741f266e375bb31ec1622004708","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"7af7571bb8430a7a7eb36f683880a8ddc1c787a5bd6001138d16224e1f679cad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7403e27a12a2bad8a0b8b20ec58036969995dc12e4ed58746214244c323b9824","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"aef93c341b2c011c0d66c02c1be71944bf30b72bf95f7feb433365e7808a7184","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"577351b6875fe01cd21bae7a801005b9e0903e27980b2ca72674e39f96a3322b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4d429d6f231ede9140dd4036e583fba8382aa5c33b10ee0dae858b8223806cca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2c9091ab5cbe39e1b2fcb8d2d53ba835a4e3affc991876075d831dd5cda08c63","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a86e489578f1eeeb01280be825e050b3b146c6490ba294e2b7ed33bae64ef85f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"b95097c242562f7f559ba6258f702de58115f03476de58f39e7ba0b82457d3dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"60215f937d7d1f3e8e64d9426887618d7dbb0b74b4039eb419f18e62aa4d0f73","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4eb8feca6650593dcbcc6d207ebdc79e1a2c5d7ca198ca10234b7d6b619adad9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8a9a0d6251301f67ff14dc3f975aaeb60cbcb1b65c2af57faacbc9b02056876f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"da9f16ec072ff6784975bd95355de433b474e35df547ad45bec8f893fb1af18c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7201fa1ec8d85fdc3cee2a97f753008f0c16090e7a6d7e548153c18ec18886a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e37642588eba06ca8d815ff29d634261ea0a2e000765a0f300c1313ca8f588f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"9e689570c168c03ce867dda3ee4ca178f118613cedf24cfb37e9742d3683f170","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"30788ba6602013dc4ad4ccbc89249f7033885592a45c86ca9ba90ab6c615a8e1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"86e341955db3c5b0c257a3adc6559868f727eb7337687003957e77e9f50460dd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c45eee5daa577588ba1823b5e50f7f6d3e6e0b89aaf1e906e08915c02ed5a9fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"9e1f0e08609015ba44a98bc2bc65a36a54bbfe572f0f82af28b1c699565ce179","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"387dcaff91a38330ae4478e58f7a16aa7b1541a3a511f05312ba7d24213b4b85","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"12783c0f8b89ba5191493a504567ff911f6ed214bdb78b0938350aeb9a360790","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b96b35dbd9a9ee85c2a3253fe950c7caf1ae0354751497b4ffc237a060763f41","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"steepzip7808.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"715231890","locking_period":300,"voting_power":"3576159450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3576159450000000000000000000","vote_positions":[]},{"voter_id":"7bb1c442ba8cf0eda38ea75f906e64cd23faf7a553b6ca398496b83efed4a912","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6712c9215a8d4d1785eb14dcf4263fc75c83c8272fb2fb0a1cec299d5cb88dfb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f309723c8edcce5c9a5f37ff13fba9f5e94df4098e20de6e5569a70a95362561","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"a16c12180a05d927bb632d1e866ae9b44e94e306110e4ada342a53aef0a0c4b8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7d69aec229766e146f142cabf984851f7b9a245f789af16c42f19bbfbb71dfa6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ee341bf8a845be7d320cbeff65338f98705110440e96aa77bafb76fe0d231d47","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c369077e2d67dc2fa5c9ab5850f998d612b865b900d292d585381e5eb9121d88","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":165,"voting_power":"11000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[]},{"voter_id":"c592448235200dceba29176badb491ca62e88f370a9ec81211901bd911acc650","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2da8de65e1a884e8a986afadb161b3c7578cff28c1456480e6b2aa6ef18786f7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d4b5f44ba1bdf0914ca08b6b3d51276925094bc64002232f973c2658a202a8fb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d8bdd8fad8744afebe4afde79423e46893d8f07bdb008b859e5423cb36c9ecc8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"96ed22be26b1f9160d5c354897717c48f3c124d71fb530cd8a39ac1f70a9509a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"afe8f79a4a01796e071075c4c166f95310493a87ab61794e01851262ea31339f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"999539e3892eab4feba02402ae4a7da7a74f1cc4521bc64361b55f27b68177b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1da415707b8efd23d2493b054c94ed60bd412de3bddbb25527904c273586e76f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"560ce982b8e570acf9904713081dfe4800350ee79443a5e4d2a748d06a77d0af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8084bc7b451627f6220836f288ab664268fbcc35a539304707fb614e53d071f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"60cf5f0a460867ac792a7956d2140be363e8a9aa8d1c4ee4e9dd1f8b4a474a01","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bf9b93aad5038bcf14e8c33144e03dec59306fd7c83d4ce5e4fc8b298079dc4d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"84efea18873941aa281771c327d2c56eb5b83ed45a37c242d9616a59ad006295","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b8d00262ed76fe8acae642dff2e747798bca7b2b21613f00989bf6f3c82c1d4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"89d6ad5e9d4f47fe272394f69d569fbc871695f06bcea749c5dd93f5c4e99298","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"eda649651fe662a9c3a3aca337e3fe29ac0855888f75ce1d83e29b94090dff37","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"263b38e1a3f0c8b958f81f7c869f21c814d39293655eb7b3523480f7e1979bac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d5ece77666570b023363ee977931da238d40f1729ead49fe2ebe67cfe58cc65d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"137c7735965909ff7e2eb54bad7496c1420691feb0b5782c8db9bc178472d344","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"74b26d6048231e35574221aec27c89199d44901da7abdd1ef1dce2c2b413c6be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7cd7853c28b29a8fd0087eaf77356eb7e42db747fd3712b3a596b33ff46eb330","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"b6bfc38230727bc1b44bec6be0978e361302cce2f46afa952f3f24513064113c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0768c53bed6e9ed8d39474cad73c9e0377423ca89ba52b1c56f6e72366ead8d6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ef7a75707bd043127d67c5c81a27ea75771ccac20d421c668a7ff42cbc274e0d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1b78269e86dd496396a8cdb03b4adbc977c8f0e6241635d3206ab8257e837c1f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"49fc2c8a18e1e483993d68e4f19d5732cacf0121afe2beabbc4bee05c6c6f755","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fdf3ce5381eca8a4da41fd3a1cd9ed6ce2b08e20cb13a2e55308cdc52b087333","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"958425ab8d1d060f38ca1aec679044a9a486512befeb2803338829ffe54d2272","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f1ca2ef54cf44b53825ac826a7f5e76c15073aa85c06913c0700618fdc1c66a0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"824218459378b259d14765d23d541e9fa74cc68db8177e76b17574e2f996fec3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":105,"voting_power":"7000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[]},{"voter_id":"18ab3f394757e517f81c79f4b07291817e4d37e1569b1288eb20ecba17ccd953","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0d68edaa380d3b43caf5e9889f01f3185ecb40f8d9ee21c5ccea8b0ff1576f00","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bd69bfdda3dac26df4f063103276ac43c6b1cc5262905c7d971ff18f9fe6f6b2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":180,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"752fa01aa18a8db4b86d41730a664be99cefb6532c5dd64371b87e0bf9d63e5d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"fa02e7b2668a034d0381660f5d94fab0d69c62882765e75a560cd31fa74f8624","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c536fa5f98adfb984f927f9b0f77a0d52416ec38f013368283829bf19d28c48b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"62cdf59509ba9b47e163660636d8defcbde16f7cd2d1b621dd48e1269c876f76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8e0530baf3dcf3684771c3d2c07bc0a378d4aa7860d8b0c13b7bdb86d7540537","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"eb465aab58bc08ce7d3a5388c4eb0b3e575b7f5ae9cad03776d89aa3304d1e86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7f2701b349f86aae7672eeb5558a5bcd45c84429c60e53d978fc79d71dd770de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"53ea2c64d401639431eeac02c871722e8d3001ec9e1fd5a47547c8eb8ecb5f6a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"260159bbcef49f178564505cbdf3fa5d9616d541ca7d58c87c63f8f8a7f6fbf0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f981efa9a7e9bc342b60ff6567474203a422c7b84f7a9c80c76ee7ea30651b8b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b6b4573659b5ff320383a04f8e8658b66ceb358f4c2161da60ad2883bda3e601","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2186c821d615fd467182614ebd73354044947c30d3e953902b60f564fb596887","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"804b08c7d09414a38b3b57a41c63782cfb5ab8267b5e523d1e650ac981a3cfde","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2e2860e67f0d62e07467c6bdd346397624c135f05105066bb987992d05feed41","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"6000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearfans.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"4fb13f56bdb0966dde2483e6f46e375f82b9b33a9a758a603d5f41fdd53d862e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"15000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"cd4042d4b3c086415bc74de8b80efcf2be3e8e185b166b68cff11fa5ba005a70","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6a867970fd940fb241ee09337c8c6d261807b1dca49606098e038577e6d8d105","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"30fbd01de34e31bfd43fda0495e3cfe4d8e45840f28ec544b78532713d299eb6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"20a35f57280953e3642d3ecfc7d6f7df85db592028f1e529dedc3725bc82c5e7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"78d2fd202aede7660c902916971b77be2683f7669ef7004aadb7721189ebdbf4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9f1f64b479ba05bc9277eb2076facd1b15a00fb5ad6161a04f1d8c1e68badb97","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":285,"voting_power":"9500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9500000000000000000000000","vote_positions":[]},{"voter_id":"2cb802d5ba0a2622f5ccb8502419c4e1456529fb3c028a5918b530964aaa6d1a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"99f79d2684ce6a26812e74402f152d0c95cd84428973b99566f071bc91c35d0d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"031034bcc3bc3cb014d866ae702cadc0a7b6537a221491de7c4c5d57c5eec4b8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"949581373d9ec756de56b014799fdddc783c3647b9be19a62cd6f3c448100c3a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"8fba25e4d0f7876f16a9c38ef5394baeeb732158b4d25ee94087bdf3235b1a35","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1890c27bdfcae32cab497f36a863995a2bb54b3b60845245de07959989944c90","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f5e0720c9206ed39b9d20d97bdda945e577903f17ce2ea31533ca7aeb9c19fc5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1f06662965ca0e4403af827d12511023a2e10b7dd1dc504dd6e88ece694b1cae","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"publicai.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"1e3b8ad70d718aa9deab36e80c150ab76b1629987b6c3bf485099b7d0fd427ab","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a6d165bc9aa16995c8db9cac737567a36f76a66e0556a79b66d4f168d1775a65","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4875e1580dbf1102c5bba00ee2eb1312893917caacf2c3c9fcc2bd419fac522e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8f8915ee8749a1aa0e5864c398dea18854d15af608260d4a3bee6a75277e1a23","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ef9c686b82fc905ca02723c22ac76009661a6c64f394609895c2078a113d0227","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"56d8d6e362c65a8ccd5e1e4737cc424f08a9454a1a1db50e9d53df2efaffc5ca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9ab864312f4790f2d00a78560a62852f506e14bafd3fa35e1fb8f25b86fb79bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"46301a73c8570bf44d92497faf975ccf6c7e0c17feb655f85f06dac7efd7aba5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"5a97f35372f2da01383980937440d6ab1e1d0b64fee3186136416de95fc445dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"7023cb33e8f263f61db0fb3ccc1f6607585fb26ff7c36e1054375d567e112040","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"11000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"9000000000000000000000000"}]},{"voter_id":"ecdbbfe1e1373ab6d6908cc356d5959f2d7e96e40a1b0f9947d861603a432000","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"94de4b725023249e5e3eb9a24f41d1c1544eb7fe4e726f45d71e47ccfef20d3b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"3abac43efa7e3ec50a5b0f8c92d64b05775a37399f4027b5360a818a97705d84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"13580b4121d74838ca47d393700a4bdb73d6f18c76865552f2ba941c7aa14114","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"37c865a80639dab5cbe9092c1227909a48a97484a4ec5b9192ee690c70b3a8e5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"87a0f5f7f486d36421ee75be87e4afa880e4e07609c71aecc314a8bbc964bd83","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0b39b39be12209feb533025924e4e2b3ad03236529355be22895617e7d9643e6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"eeb62a3aa614b980537d3fe702ff8ff2b576914bded4daf5c340a3bcd1ebd113","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9ee6c7c94aaf752638b179c41b7e564cdebabb4f9d884c300cc3c4ed4251e8b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5e23625d9fb328d395e34307f31566637e7851ae3820e05e4d7be96f02787b58","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b7b68d23b5d14aef2bdd7f823e91ad0ebaf9f8157fe90d219f91350b452eb93b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5114c59696a0dad197bfff09130c75f0a6d8cbd8cdde8a6830a55f771c39d2c9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"3c9136a912b1bf7a579a9041d7ccd57e12bf568c1541a5992dbacb12e8880d7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"d61dfa4a90c507e7a8c5b2d991b4c5d0fe899dd2b34f4aba779769bafd89e521","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"34a1d4fd633517bc2fc4dbb28b44029fa569601a44f06e7ef6e21bb11f44b69f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fdf1f8ba05276e4ca35e183f568d41fca0a6d4742edc6cebfaeda6a6741004e4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"581c9713324e185bb5e089d78c50bbce945ed5e94f50efc9510a07bf0dec92a5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"e2459c14ba6e77e8281e78b8238c733b6cae669ad5c092192da661eb124af123","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"ab8c4dbfc5319f9b55c545d16f72874720779b13a030c2c96c6e3f04f876ed1f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4bc6c6e7277f3e0c7b959fc1c471e611f643618630e6b3d6f8df4955e22a6ff3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0da2b63c2fe5cdaf30785b82fb978aca1f514d9db27eb530132639d29f64e2c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fef5f6439e828315de5556a5a0408d9400a9c96b5c66b90552ecf02b51392d84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"9045ca0cb82c39b15b17695fc3581a7e57bf5e3769911c6160bdd3f2cb7f9fe2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"35a6d521fa6db19b65310c9b60f7b914bb71ba6dc05a8ecb0e27ff38140704b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"51071377511e5865511fe3046dc4915b0297672ede791ad30b6a2cf7bc3e6102","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":120,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"94138351780d90d6b187b398fec24d7535d9262fc1edde2512e46c5856bd4620","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"fa7cfb6e948a7d200b705be5aa210c4f6edb321b7c40277256809b9ad4afd5bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0a8ced2c030aaf89deff67cb9171026240c9383897ace6bd23d5a796d2de1c3a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2d8fd5f7bba3be0a401c5861667b084761f34de4f5ded9c46711eab184cdf47b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"25f57c3a25a706fbdfa7264329b5e13313829022adc517c05da849872b86561a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1c07371ab41c25d020b1be540b708a0c343217ed2b414e3821c1b455081cb3fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"de0f71396d131cdd51b4213678b39fd578a98d3462ac27c76bb4a2af8d74d77c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c6ea0d083074fc708fdebd97f0e2a50be09ec0d4cfbab958e0cf5fb7490380bc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3382657000","locking_period":150,"voting_power":"8456642500000000000000000000","unlocking_started_at":1728737361510,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"fe6568ec104e0b6dd1b2da488675a79e562591b302e3364daaa7eef9e2dd2e98","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bf1cef6dea2de6d4246d4c4d106fc2a5d0de432b559df59e035a117213f40a79","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"33e5927e9b0b9bd5b6d22aea40f133f4fe4ec6ac2d0c2354bd9811886ab173a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"59027400a26e105fb95bacd5ddd00352494cd080d631f05595c4550870367f9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"89504bc30ce010a1b7a2a4d8a5c4efec79ceed68efa5040853928cf4966eb7fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d7fba1b66a6da1f2af3f99fa27c54aabf6efa70128371b4488a39bdf159e07f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"63cc2a23bd4fb0c9fd0802873d6536204962dad14796b4295e9f6982c364a564","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3edeba67f78436e19beffeb44d6a488905cd1df879dd62fc05a4a4e5ebb39562","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6e8ba11c5ff3781a5b0dd8c51765104b8b041522b34fa8ab112dda618af9d4b7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"20d1f4d9b7a25d85f417d936ba78a1f60536d88e809eff23e00dcfa4931fb07b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"77770326000cc3b7971ea022d6454111ede7a8717a65dd9ec961dcd90b896893","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"57fe52ce5e1004ba77ec5e717c04a8a774144ba08b6488f95cae80721a5b95a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7412340cf0eb340eea19cc8d7535bdfe13d07f058d7fd129572a1550368079d2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a8041d4972bb95b130a46628c3d3723d570d15e7447ce953bc32775cce0572a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"035ed71d46ba30594e7885ccea858f67702aa6a41fe6cdc42c3146178df56efc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5af22d303baead509cb1bc62754d9b74645c1c7a6cbe150de27d56243208ed37","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"414aade1d9106e37b238b11dbc2a96c1cacad6a1561f64deafd4c6494afe106c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":285,"voting_power":"19000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[]},{"voter_id":"ac559d22697b15561e132848e8bd77a006bc19cbb63d9bea8de7938d481a6872","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"06641af629600fe2ed817b077cb4a51f7236a2291a76f60eb5f30c471e98d4fb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"6000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"88ceffa65fc65b12d07c9bbdaf6997c4119222262e6dfa51811a3b7110a840b3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16000000","locking_period":300,"voting_power":"80000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"80000000000000000000000000","vote_positions":[]},{"voter_id":"d8b7bc5623e24b15aa33667faa021ac7be40b0fce636fff149e745f03668ee93","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1e6338d7a9e6722c322b75aa58ca739ddc93f84fcbd6faa8b2908537ee661b34","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"621874f42dc8a92b03b96c7215d139a371733b5e112ba81c2095a3d9cb742db4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ab05985f59778516e33410b4e903c10f5900ed8f368a5df3c4bcce09e2c2b4af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9b0f029772601a97ae8911f1986381273406d7539a084ab2a9dd05070cb8970a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":180,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"8345d207889673d2184cb3d43bc454cb1309de9771748feca8660695f09d650c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"7c8df5a614dd03bc7032e5f8cda085bd8fb7949b07f634c7b7d5bd8d29e752c1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2c4dcd85319450df564a5398a4ff540d4b29bbac9d3d685f191f2c1c2609961d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e2893f359d80129b110dcbaf642616415ae69479a598f999c3773795b87babb2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"171b60a93b2b6ec36d23dd4da4eb2db7be12fd9e2a7a9c817f1f57d548236db4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"6c6bb3046b593fcfab01023ecb4f0949e661ef6a9303e6f1feab7b046e67774d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"25c9134f7fdc3beb5327ba1736f036c134dea5b38b932c5152617cd6da0ca8f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"37960c14e445567e14d8d95aa6482f0c83c346d4e470188fc52def85c6d0f40e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4919083710","locking_period":300,"voting_power":"24595418550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14495418550000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5100000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000000000000000000000000000"}]},{"voter_id":"05ba16092ad6e4511830472822fd24054ce78298345a6b3f0d0d81c718f75f3c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a53dd4a9273b537996d27adad820f326d7c92247e73f72e5ad4c950a0b2a5fac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":165,"voting_power":"11000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"3cd7e097c91412aa03ced89ce8999bccef522f8d3c7dad9708be10a1db75fb14","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ae9993c3f4220ae580f93996814590c5870beb55b8f20c826ca3a01fe94cc665","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0fffe694b17604c525344045af2325da740b67f3e4dfcd102b8b284df74aeeb6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"551744580","locking_period":300,"voting_power":"2758722900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"2758722900000000000000000000"}]},{"voter_id":"3f2bec6ff023a1e8af5dfe4475ec1e4565bbf03a4202e846d22c806869d038f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7cc0696ae18072b77576bb98ce21b2379cbc1786d090abc80074087f4799f1d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1d736861fb23a12aa8bf291bf61dfb4025e18290be64dd2dfae7d17f1ded25ed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"bb7d6b1b86a99158217e0acb447f7aed871a347b2eb9221b35d181238687d525","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"650b182ad2a34abfe873c85d87bb48aadac471088d4f04cf9e0ee113a2830689","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"2394aa987d720b6c6d7a550827bee05f7ba85bd637bc398ed3953161ba4e48e0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"16cc9c22353175639bb477993e20bae2a8d44f10ab8b7cc8f665fc4f1591b5c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6d033ef4039162e42bcd7cad32394062302871d61fdbfa66eddd4a7f8f1f676b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b409b131f7f52e47c8385a155d76f121cfdabfff228160d7e661234d9bea7d3d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c5aad27fec4519a12ba439b16edb6cd19a10b2bcc7b08ef439fe21594582ff6f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"184645ab40ba5fa18b77067d400aa8490f1bf882ce0afc1b7cd14d1eda6e73c6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"5f01c5eb8fe66b99991a9563ba36d94ec41f61a95c0877bd2302aaf60b55dd55","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"efcc44df4ca7f33fe34c2d46b6b2cfa5c5c36f0d3c57f3641c76436e8165aa86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6a92e3b7373bcc54cf305416ebb8cd0f48ec0e7179ffee8128a11ec93d0548be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"9c5e7f96bb77be519a14ec554a61186eaceac2ad3ad10c5f1252edad5f978b26","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d51eadcef6e8fb8ed68a6957bc63a30cbb333b42c18feda1091a5949909b44ba","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"da79b5075aaae16925b7015687d9eaffe75b463254f779b5f9b4b066e479aea2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d4d240926943f46a61233aef6de9d2b04113f564a4aa71b0fa1ee9a2ddd0b6e4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5e164fbe9eba8b3840728deb3dbc21d182cd9222185664ef6b131f5cbc23a7a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"6fc798485e717774ca4190104d5b2d8436816db202893e5705ee20122840a646","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7ac59c6aae125d1099cb4a7532654dfd5820d3308d838019e869fda570d96c3e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"528034cd9a2d62d025a5a598abc77f0ed56ac42175690557649fdd04861bf960","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3970c9eab77e1fc90680cb655a723b8e74f72182aea98ab128c174be97dd0900","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"108888faf531d77fccef1dbface1598f5a8612930b8ee30d73f8e6750f8285d1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f4d9b9891a8a150815f0f4bb34820a104f95b4f0c7cb5cf5b4baf91272522f23","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"01a51432d2841f2ae7d47f9c3341422358147ad854f7ce9fe80d92adabd9d28b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e59d08b13cbc0026c3f0679bc531eebea9676feffdd1e18d17a8b8bdbc0a1717","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"378f71d7f63cdabf2bc15008797421cca742027a51a903b18d13c8d08e622669","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"nigelgrif.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"11844140000","locking_period":300,"voting_power":"59220700000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"59220700000000000000000000000","vote_positions":[]},{"voter_id":"29d628055d4bf4e1d4dedcbfcec782413396ae39647965a4a3fa6a4920f5063a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"560242f95b95efa493310a4cf4817a01de68dc7255bf6a5dc1c888a64908ddc4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"c1aecc4eff6a258e393fe225bee9440fff222b5964b9c03389f414211bf7923a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"88564614e6efc575a5cd4933eeed5829fd3724a9d315736b12ee9df4bc3e2155","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"184e7bb7c10a86a7053682efa687617f91cc5e61bf140d0f2099bce6587134fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"d309b4ca65180b2d16c832d93189717e2324da29b866cdb4a884fc2244c8c4f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b3e2069deb7eb9e5153b1d4646cae13aee9d58aa1bd224f9ff232fa78b1f00cd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f6a41f0d45f63a1ab91cb68edc872a59b399fbe3abc211c567a3b67d312952c2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c2043019d508016033dd677dc09ae75e158d6a99a1370949296a4597762e1884","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"219a80486953e2c7b106d34d71ef6e727a69e85464cc8e4c80395777317a4ae7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":165,"voting_power":"11000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[]},{"voter_id":"0xfA8856FD322c1EFee9268aFE1161B66D55fE000c.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"981000000","locking_period":45,"voting_power":"735750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"735750000000000000000000000","vote_positions":[]},{"voter_id":"e708da32a799ae3d1c4c96e5430d367ea1cb0ba3e8ce333e0c712ec230964c7c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"370621690","locking_period":30,"voting_power":"185310845000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"185310845000000000000000000","vote_positions":[]},{"voter_id":"f18c2a44526e39ed4a0b15e3a89cdc0e7a1f72c5d450fd296ab805b775164f7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":195,"voting_power":"13000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"13000000000000000000000000"}]},{"voter_id":"3152f891de86d40d96afdbab8fd744335f87846184cc1ff80ec5182b6e5f91cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"64927b85224261392645eed5e16d343ab702a0ccf672900fa45705ccc7fb53bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"5dc1b7be64c2ac012ea431949927d7b48c5b3fefa0ee4f2348fdc2fc54435596","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5ee1992e99dc4ceb732beb6d2aed958d5c855c4576a21483714cce314333322a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"134b23882c7fe059244a893c686e950d6d3af6cfc8426cf9adca8bd4c59e3958","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"mjt69.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3670430779900","locking_period":300,"voting_power":"18352153899500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"6000000","locking_period":300,"voting_power":"30000000000000000000000000","unlocking_started_at":1730837665884,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"18352153899500000000000000000000"}]},{"voter_id":"3bcae1f4bcad5fec7fb44a94eeff3906be58d5e7785fe9fb000d8c2ae30b4867","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"daddf0a3ff32bdd992b6f90acd1e076d44de96eae317a2492d7a854952783912","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"7fdc9ec27bf1d33019d874549a74a76475c9f91af4748a3ff7f93d2107465398","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"909155dadd03ed49a0e66b374cd2a6bba837cc78de148e301bfb157e80638238","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"55065500","locking_period":270,"voting_power":"247794750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"247794700000000000000000000"}]},{"voter_id":"8d81a5be73da5bb81bb3972bd4ba62e9f3f6a46ec90e9b01c37f942e4e49f837","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"302ccccca97fbbfe0a697b79ac4e4d5c133b2070af796815ef8feae1446614e7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"9122724b26d2a1a75e3a4bf34d6f731fdabaaeb9ec475f8e929bbba7087356bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":75,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[]},{"voter_id":"577a44eb1445093838f070248c81e7f04bf601c8e6aa5aec674e35ae94599fe1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2f7aea4394b33e1c9618f872683e09359304444896780550a761e515bad2e545","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2dc84c98ae6539130b6fbbbbab28d65bac75d036f0c659d53ed64a0b56af9aed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6966534938.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1554242310","locking_period":300,"voting_power":"7771211550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7771211550000000000000000000","vote_positions":[]},{"voter_id":"91aa7ee5bf80249914b3eecfffaa9ac5267fb637e0436c9e2016442efc2d9f6e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"cd8c1ae490113790271e545682861bec0e2fd678a9cf683a1b84637d5ea958b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"72e33ae6636584d80229fe602cee4a84e75bf94c465933673c967ab585422925","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":300,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"8bfc4bf88edbcfb31bf166a2bc6b8a1c380cd4c1910fb83ac9a11d0db96b4e3a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1f4d03d56411be001311befd5bcfdaba2ea86a8a2e6f19d458db45e506dda4db","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a2b16784d1c8857f1b105f278aa47d1849bad39a3b420135da6fdb8398667279","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fc6f5994a75fb16551d77e38327deb088cd393ac2f9bbd1810e7a25a8c620bc5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12458050","locking_period":300,"voting_power":"62290250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"208522290","locking_period":180,"voting_power":"625566870000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"62290200000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"625566900000000000000000000"}]},{"voter_id":"53e9e63b63e056388ab46cd368719bbe92cf75426bd82d847740e0dbb238de7e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6f2e93d7468ed83f9ad12c73070058a240880cd6abf8e7cc832c477d19f729dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9c2ac31195864e97f16efa47a80bec780ecf5c80113a3d0f048208ddb45c6982","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1653ffc74b5114af4fa7dc65b5dcc3593572237b538f79a634e9d50167b17a4f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"95cc99e6c34be962b799a4c57147ec021b291d0dde4c4eba7395fca3a224312c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"486f2b50286a9a9ceec8c117b8a4f4257abf54eb6227c7372b97193e6b57ee82","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"66b50516e9b81a6bcefdcc77b7252e9efbe75a7ecbe5547b12cc32a2d2b2340b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"da6a0b642ec2cc46616d614beeba0619d279d613f7483cda5817262663e16684","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"883e6dfbdb97b9fb819c77bf08e49939d4e8ad8077a412a007dbf14b09db8ad0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"18d80f3cba5e9cdb39f36be8b82d9f5b329c766a4a1d08ea3c139a044c8b9f76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"5d562b8c5f219bc004a4589f401bbcdeea1ca49f774f82b01c11db80acc74fb5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"32773ef3354c91de4210999243c8a3959945c5aabe5883462d34136d3687c5d2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"fe9ac63e88231099c3109bda7efe172400c4a26d05a5184d1fa55bfb023dfe08","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2fb66201ca05646d7ab673879494c6b41f6edf9bbfb815673ca6178450cd0109","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"46c0713da081f4b5ab7342957c4e8839cf0ea844b30bcd2e503aa41ef35498cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"147991100","locking_period":165,"voting_power":"406975525000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1975525000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"400000000000000000000000000"}]},{"voter_id":"enilsom.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1725019780","locking_period":180,"voting_power":"5175059340000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"08investinwomen_runbybisontrails.poolv1.near","voting_power":"1753818300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakeseeker.poolv1.near","voting_power":"1421241000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"2000000000000000000000000000"}]},{"voter_id":"220e66eb1fb826c83392fb45ca200fd5ae880923d3d6a946b4a07a41c335ec15","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5386b447cc3f36ba9a527af864fd408791869d9e31240b6e689969980403b8d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8f130f1775efdfd281068bc34b265eaa8fe4d5f3467549f0649b71a6aa559f9f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":270,"voting_power":"18000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[]},{"voter_id":"932fe9224879bcc29d24259ed85f3ee1054c09f6125ca4b22092fa3fac8ce4cb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e1f695bc9386ef9aac1458b2ef77ebb707c12d16bebdafe76aac4b147cb035c1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"aca2e0f014d3c569ac0a42e2b756895698f79bc913304789f977ca4b1536bc45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"319735156850","locking_period":300,"voting_power":"1598675784250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1598675784250000000000000000000","vote_positions":[]},{"voter_id":"90e6e2155af17b1ce2b657725c45e073d0ef4fc3c7732ec807a1f984358b3f30","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f0d414f84a5e73061c3a13957acd21a31482204828612ac7efcfa7a4ea2ef874","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e40d87f3438933b047637ed4496d2aa3260278b8151fa94facab221870719c27","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5687f214ba1fde7c53f3fd3425a8cac6d262d9a5348f17b1fd35f1868939c5de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"53d78a5bd2aef5e1eed24f4650f34e56950e447a807442df2def1710fed7fee2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"kenniii.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"156234490","locking_period":300,"voting_power":"781172450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"781172400000000000000000000"}]},{"voter_id":"a27764aa0c4e1d958c8aa4daf47e82f660978534f272f7f7880633fd7a1da55c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":195,"voting_power":"6500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6500000000000000000000000","vote_positions":[]},{"voter_id":"269d1203a58d059e0de5dd8239471e5dad46395161090783c0ea8a0fee506a6f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d6393612a2ea1a363c2f2576941158fd3d76043c6fe295dc5e2ccb44c44c6b88","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4cb0952702011c95172829519f932a380d1403ce527d168a10a81649b511caca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8cc6f47501992c8a7b6a95bffbb8c77f63abc53998855136082003fdd97f62a1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cf391844586e97208c649fc529a5ca577b92fb4a704de2713c6113cabf9a7fad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"be18b977d8b955f25b891bd48a15148f1487d2d854b63e3115b0385235be77b7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"rawkilt7666.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"65378236800","locking_period":270,"voting_power":"294202065600000000000000000000","unlocking_started_at":1730202185556,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"1997bdb4cc313efbc55bea1914f9198c744fb7648ebe442aa1638afdc8d72b72","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"drocacoin.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4615380820","locking_period":300,"voting_power":"23076904100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"23076904100000000000000000000"}]},{"voter_id":"brightport7464.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1613566460","locking_period":300,"voting_power":"8067832300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"8067832300000000000000000000"}]},{"voter_id":"a8b1b21c2831afb89191bc164055d3d4e1794a62e88f2bbee3bb0c57d134d435","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5ebb32c260ffef7481fcd1b2f1366e53ca89ad0d4509079af2b6644384276c2a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1e4a9edbd3e540e98e556ec8155c329f0614d7dd36019553ed98ca9b16504492","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b703b2824c63ce4608b4296edd99c5cf4453f6263efcf83941961fba18649daa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5879789e575b68a1473e603b1fdd3b6677e294f9ff47166370455eeeee44ccbc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"acda0f5435d49bac335cfa2d05562d5d47aae55ce752056f6f59a8899587e98d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4fed5e6fccd878ce58f5f9d1667eb2b373f2e3bf14138d3d157e65fe762a532d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lavenderfive.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"everstake.poolv1.near","voting_power":"14000000000000000000000000"}]},{"voter_id":"6293d69adfd8894703a6cf9e80909fd15ef09cb8b24bf1826a329e27d0596d00","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a85dd91380a1b4600f7acb1746d9c1a20289e14457fd5b30d1788690ac4ba978","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"28421d4193047c854916881379f8d449a5cf4f761d0cddadd7f7724a1d8c089b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f79a4b8cca5dd5257f4eb4537b6de3668b318470784c936786b42e7e1a8e0513","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4af7f238d0d22b44423e3d5886e6aaf28ebe76434c5a2b1871a22b4d3be5fb7c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cesarminoru.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28683910","locking_period":180,"voting_power":"86051730000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"86051730000000000000000000","vote_positions":[]},{"voter_id":"5b1bc6c9e87ae7ae1848f1c2d027a8d8b8ada0def01fba2d43fdf50971c70867","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"4961f61c155ed31e8f3b4b4e8566cf0030dc7fc6f984f805345551fdafe4f8a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"b2812b94059352e0ca4ce3f43e929abe82a7d7451c80fcc1821a29a229a963e7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9c35b806b1a6239c6e8fdfc0aa3330253aab1d18a1ae847ae8fb9fdde426cef1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"21974e7bb61a3b60e16fd97cdeeedb57f282901eb89e0cb13f7aaf8b2435348b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"canvithien11.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"277413650","locking_period":120,"voting_power":"554827300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"150394140","locking_period":135,"voting_power":"338386815000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"893214115000000000000000000","vote_positions":[]},{"voter_id":"ff1989020b32bd12221fddd5c4390c24825c10d292594ca68dc8b268affca3ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a4c96786a0f47deca5588e93039e60e1c4384aa3cc7aa1c3c33c506ea354bb01","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9945933c2a0449a33c7000b23f25662e42b6cf198985db54caf75fcfff679d29","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ac861cd64e8e0e9ec5d2b0fcce6c7805b635bd5c2ed23517f5fe330b236ad3a7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ebb3e4d66c8df11e917886ba3e9158387282750b5e0ed341be88276ab904cca2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d2373b2e6a5738007d57e233082ab47042fe47c70df0c8c421d3c99a7ed5b666","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9dc15b9c447653923e7f08570adefcefef774ef6c94a12b7013c6f8d5c434f0d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e718f141c6b4ce9d6f033f9512df3a9e59c4d1afed0d20e150de58b6c59a8966","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"a53049b9a78da7b5b355e580523301fd4d75f2fcb963d53e6901bdea7bf5f301","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"gustavo_paulo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"46513950","locking_period":300,"voting_power":"232569750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"244569700000000000000000000"}]},{"voter_id":"3990bf7e9e4dfe600cec1bdcf2334c169a4885923aee77b7b86d44c16aacd415","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"thepool.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1166667000000","locking_period":300,"voting_power":"5833335000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"5833335000000000000000000000000"}]},{"voter_id":"c186d04bd5c9669566e982f67830b1449a3d7097265090f6cd0b8588ac48207b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"criptodru.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1624186620","locking_period":300,"voting_power":"8120933100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"8120933100000000000000000000"}]},{"voter_id":"omolayom.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000000","vote_positions":[]},{"voter_id":"vitallibrary6200.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2933126390","locking_period":300,"voting_power":"14665631950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"14665631900000000000000000000"}]},{"voter_id":"b667deeff327766f4d87495b3bc42ac775bab6d9b4fd9e148c6f7bcb3867ab87","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5000000000","locking_period":120,"voting_power":"10000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"10000000000000000000000000000"}]},{"voter_id":"85ee65913d3c1b7204775d6e7478de90973b8f606e0193446df5467210d7849a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"628717730","locking_period":300,"voting_power":"3143588650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3143588600000000000000000000"}]},{"voter_id":"nearstreetbets.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40000000000","locking_period":300,"voting_power":"200000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptoblossom.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"50000000000000000000000000000"}]},{"voter_id":"vvr.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"53675901290","locking_period":300,"voting_power":"268379506450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"40709080700000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"227670425700000000000000000000"}]},{"voter_id":"pedrov42.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"591926520","locking_period":300,"voting_power":"2959632600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2959632600000000000000000000"}]},{"voter_id":"ab9036e2eefd2084daf95ee1647f7ba1dcf5f61367d86c96dc63907b766568e9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"101492900","locking_period":180,"voting_power":"304478700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"304478700000000000000000000","vote_positions":[]},{"voter_id":"nuevanear.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40921210","locking_period":60,"voting_power":"40921210000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"30000000","locking_period":30,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"23000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"32921200000000000000000000"}]},{"voter_id":"p0k.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"53099448500","locking_period":300,"voting_power":"265497242500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sicmundus.poolv1.near","voting_power":"265497242500000000000000000000"}]},{"voter_id":"djonecln.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"antokcombine.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"975580000","locking_period":300,"voting_power":"4877900000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"4877900000000000000000000000"}]},{"voter_id":"luisnet11.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"spectrum.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"3380805fd17c737fba9948ee8dbfba8a4de77c1897ae9810f4da098dc6cb9721","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"near-africa.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000000","vote_positions":[]},{"voter_id":"juniorfigueiredo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":30,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"oliver24.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"soundglove7665.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stardust.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"elliotbraem.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3071971400","locking_period":300,"voting_power":"15359857000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15359857000000000000000000000","vote_positions":[]}] \ No newline at end of file diff --git a/pots/serializers.py b/pots/serializers.py index 3f3c722..a9f2180 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -262,3 +262,35 @@ class PaginatedPotPayoutsResponseSerializer(serializers.Serializer): next = serializers.CharField(allow_null=True) previous = serializers.CharField(allow_null=True) results = PotPayoutSerializer(many=True) + + +class LockingPositionSerializer(serializers.Serializer): + index = serializers.IntegerField() + amount = serializers.CharField() + locking_period = serializers.IntegerField() + voting_power = serializers.CharField() + unlocking_started_at = serializers.CharField(allow_null=True) + is_unlocked = serializers.BooleanField() + is_unlocking = serializers.BooleanField() + is_locked = serializers.BooleanField() + + +class VotePositionSerializer(serializers.Serializer): + votable_address = serializers.CharField() + votable_object_id = serializers.CharField() + voting_power = serializers.CharField() + + +class MpdaoVoterSerializer(serializers.Serializer): + voter_id = serializers.CharField() + balance_in_contract = serializers.CharField() + voting_power = serializers.CharField() + locking_positions = LockingPositionSerializer(many=True) + vote_positions = VotePositionSerializer(many=True) + + +class PaginatedMpdaoVotersResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = MpdaoVoterSerializer(many=True) diff --git a/scripts/after_install.sh b/scripts/after_install.sh index c9baee7..da83b8f 100644 --- a/scripts/after_install.sh +++ b/scripts/after_install.sh @@ -71,9 +71,9 @@ poetry run python manage.py collectstatic --noinput >> "$LOG_FILE" 2>&1 # Gracefully reload Gunicorn to apply the changes without downtime echo 'Reloading Gunicorn...' >> "$LOG_FILE" -sudo systemctl kill --signal=HUP gunicorn +sudo systemctl kill --signal=HUP gunicorn-prod echo 'Restarting services...' >> "$LOG_FILE" -sudo systemctl restart celery-indexer-worker celery-beat-worker celery-beat +sudo systemctl restart celery-indexer-worker-prod celery-beat-worker-prod celery-beat-prod echo "$(date '+%Y-%m-%d %H:%M:%S') - after_install.sh completed" >> "$LOG_FILE" \ No newline at end of file From 94682d574196a1f7343e40639703e1935183e4a3 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sun, 1 Dec 2024 21:11:57 +0100 Subject: [PATCH 323/454] fix import --- pots/api.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pots/api.py b/pots/api.py index 8debe37..b38e59e 100644 --- a/pots/api.py +++ b/pots/api.py @@ -29,8 +29,6 @@ DonationSerializer, PaginatedDonationsResponseSerializer, ) -from pots.util import SQLiteReader - from .models import Pot, PotApplication, PotApplicationStatus, PotFactory from .serializers import ( PAGINATED_PAYOUT_EXAMPLE, From 1939f6f287322538c24c6e19b50692bc02cf0995 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 3 Dec 2024 00:38:56 +0100 Subject: [PATCH 324/454] change model field type and add search --- ...ter_round_compliance_period_ms_and_more.py | 33 +++++++++++++++++++ grantpicks/models.py | 4 +-- pots/api.py | 32 ++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 grantpicks/migrations/0006_alter_round_compliance_period_ms_and_more.py diff --git a/grantpicks/migrations/0006_alter_round_compliance_period_ms_and_more.py b/grantpicks/migrations/0006_alter_round_compliance_period_ms_and_more.py new file mode 100644 index 0000000..ddbd990 --- /dev/null +++ b/grantpicks/migrations/0006_alter_round_compliance_period_ms_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.6 on 2024-12-02 23:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("grantpicks", "0005_remove_votepair_project_votepair_projects_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="round", + name="compliance_period_ms", + field=models.PositiveBigIntegerField( + blank=True, + help_text="Compliance period in ms.", + null=True, + verbose_name="compliance period in ms", + ), + ), + migrations.AlterField( + model_name="round", + name="cooldown_period_ms", + field=models.PositiveBigIntegerField( + blank=True, + help_text="Round cooldown period in ms.", + null=True, + verbose_name="cooldown period in ms", + ), + ), + ] diff --git a/grantpicks/models.py b/grantpicks/models.py index 457f14f..acc2195 100644 --- a/grantpicks/models.py +++ b/grantpicks/models.py @@ -227,7 +227,7 @@ class Round(models.Model): blank=True, help_text=_("Round cooldown end date."), ) - cooldown_period_ms = models.PositiveIntegerField( + cooldown_period_ms = models.PositiveBigIntegerField( _("cooldown period in ms"), null=True, blank=True, @@ -238,7 +238,7 @@ class Round(models.Model): null=False, help_text=_("Compliance req desc."), ) - compliance_period_ms = models.PositiveIntegerField( + compliance_period_ms = models.PositiveBigIntegerField( _("compliance period in ms"), null=True, blank=True, diff --git a/pots/api.py b/pots/api.py index b38e59e..791599e 100644 --- a/pots/api.py +++ b/pots/api.py @@ -154,6 +154,13 @@ class PotApplicationsAPI(APIView, CustomSizePageNumberPagination): required=False, description="Filter by application status", ), + OpenApiParameter( + "search", + str, + OpenApiParameter.QUERY, + required=False, + description="Search by applicant name or account ID", + ), *pagination_parameters, ], responses={ @@ -182,6 +189,15 @@ def get(self, request: Request, *args, **kwargs): return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) applications = pot.applications.all() + + search_param = request.query_params.get("search") + if search_param: + applications = applications.filter( + Q(applicant__id__icontains=search_param) | + Q(applicant__name__icontains=search_param) + ) + + # Handle status filter status_param = request.query_params.get("status") if status_param: if status_param not in PotApplicationStatus.values: @@ -189,6 +205,7 @@ def get(self, request: Request, *args, **kwargs): {"message": f"Invalid status value: {status_param}"}, status=400 ) applications = applications.filter(status=status_param) + results = self.paginate_queryset(applications, request, view=self) serializer = PotApplicationSerializer(results, many=True) return self.get_paginated_response(serializer.data) @@ -280,6 +297,13 @@ class PotPayoutsAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ OpenApiParameter("pot_id", str, OpenApiParameter.PATH), + OpenApiParameter( + "search", + str, + OpenApiParameter.QUERY, + required=False, + description="Search by recipient name or account ID", + ), *pagination_parameters, ], responses={ @@ -308,6 +332,14 @@ def get(self, request: Request, *args, **kwargs): return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) payouts = pot.payouts.all() + + search_param = request.query_params.get("search") + if search_param: + payouts = payouts.filter( + Q(recipient__id__icontains=search_param) | + Q(recipient__name__icontains=search_param) + ) + results = self.paginate_queryset(payouts, request, view=self) serializer = PotPayoutSerializer(results, many=True) return self.get_paginated_response(serializer.data) From eefd51e413c375db10fca90c782074773204518e Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 3 Dec 2024 02:27:23 +0100 Subject: [PATCH 325/454] fix search --- pots/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pots/api.py b/pots/api.py index 791599e..f4b0f0e 100644 --- a/pots/api.py +++ b/pots/api.py @@ -194,7 +194,7 @@ def get(self, request: Request, *args, **kwargs): if search_param: applications = applications.filter( Q(applicant__id__icontains=search_param) | - Q(applicant__name__icontains=search_param) + Q(applicant__near_social_profile_data__name__icontains=search_param) ) # Handle status filter @@ -337,7 +337,7 @@ def get(self, request: Request, *args, **kwargs): if search_param: payouts = payouts.filter( Q(recipient__id__icontains=search_param) | - Q(recipient__name__icontains=search_param) + Q(recipient__near_social_profile_data__name__icontains=search_param) ) results = self.paginate_queryset(payouts, request, view=self) @@ -349,7 +349,7 @@ class MpdaoUsers(APIView): @extend_schema( parameters=[ - OpenApiParameter("voter_id", str, OpenApiParameter.PATH, description="NEAR account ID of the voter"), + OpenApiParameter("voter_id", str, OpenApiParameter.QUERY, description="NEAR account ID of the voter"), ], responses={ 200: OpenApiResponse( From da8c6a03e8eef5ea2f2a00bb3be1845870d559e7 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 3 Dec 2024 02:35:04 +0100 Subject: [PATCH 326/454] fix query param: --- pots/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pots/api.py b/pots/api.py index f4b0f0e..7677a60 100644 --- a/pots/api.py +++ b/pots/api.py @@ -349,7 +349,7 @@ class MpdaoUsers(APIView): @extend_schema( parameters=[ - OpenApiParameter("voter_id", str, OpenApiParameter.QUERY, description="NEAR account ID of the voter"), + OpenApiParameter("voter_id", str, OpenApiParameter.QUERY, required=False, description="NEAR account ID of the voter"), ], responses={ 200: OpenApiResponse( From e2c28e401957f1f0d4e6341029d83ff3b61dc891 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 3 Dec 2024 03:58:14 +0100 Subject: [PATCH 327/454] fix query param name --- pots/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pots/api.py b/pots/api.py index 7677a60..b4bd46b 100644 --- a/pots/api.py +++ b/pots/api.py @@ -362,7 +362,7 @@ class MpdaoUsers(APIView): ) @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): - voter_id = request.query_params.get("voter") + voter_id = request.query_params.get("voter_id") try: # Read JSON file json_path = os.path.join(settings.BASE_DIR, 'pots', 'last-snapshot-AllVoters.json') From 69f83f5e7ba5bb9c172cab4d7c8b6d331b10f896 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 4 Dec 2024 04:27:46 +0100 Subject: [PATCH 328/454] add staging to factory pattern --- pots/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pots/utils.py b/pots/utils.py index 3e8d585..1a2711a 100644 --- a/pots/utils.py +++ b/pots/utils.py @@ -5,7 +5,7 @@ BASE_PATTERN = ( r"v\d+\.potfactory\.potlock\.testnet" if settings.ENVIRONMENT == "testnet" - else r"v\d+\.potfactory\.potlock\.near" + else (r"staging\.potfactory\.potlock\.near" if settings.ENVIRONMENT == "dev" else r"v\d+\.potfactory\.potlock\.near") ) From 5f233b3c37c150ccf169dacb47ea38349a34a86d Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 4 Dec 2024 14:26:36 +0100 Subject: [PATCH 329/454] fix regex for pot factory --- pots/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pots/utils.py b/pots/utils.py index 1a2711a..98f5ac5 100644 --- a/pots/utils.py +++ b/pots/utils.py @@ -5,18 +5,18 @@ BASE_PATTERN = ( r"v\d+\.potfactory\.potlock\.testnet" if settings.ENVIRONMENT == "testnet" - else (r"staging\.potfactory\.potlock\.near" if settings.ENVIRONMENT == "dev" else r"v\d+\.potfactory\.potlock\.near") + else (r"potfactory\.staging\.potlock\.near" if settings.ENVIRONMENT == "dev" else r"v\d+\.potfactory\.potlock\.near") ) def match_pot_factory_pattern(receiver): - """Matches the base pot factory version pattern without a subaccount. NB: does not currently handle testnet factory.""" + """Matches the base pot factory version pattern without a subaccount.""" pattern = f"^{BASE_PATTERN}$" return bool(re.match(pattern, receiver)) def match_pot_subaccount_pattern(receiver): - """Matches the pot factory version pattern with a subaccount. NB: does not currently handle testnet factory.""" + """Matches the pot factory version pattern with a subaccount.""" pattern = f"^[a-zA-Z0-9_-]+\.{BASE_PATTERN}$" return bool(re.match(pattern, receiver)) From 23d9f6711e27c02863c77ac8d8ac0b51807dc789 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 4 Dec 2024 17:23:40 +0100 Subject: [PATCH 330/454] get data from dict --- indexer_app/utils.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index f19e6f2..d4f68d7 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -177,10 +177,8 @@ async def handle_new_pot( data["public_round_end_ms"] / 1000 ), "registry_provider": data["registry_provider"], - "min_matching_pool_donation_amount": data[ - "min_matching_pool_donation_amount" - ] or "0", - "sybil_wrapper_provider": data["sybil_wrapper_provider"], + "min_matching_pool_donation_amount": data.get("min_matching_pool_donation_amount") or "0", + "sybil_wrapper_provider": data.get("sybil_wrapper_provider"), "custom_sybil_checks": data.get("custom_sybil_checks"), "custom_min_threshold_score": data.get("custom_min_threshold_score"), "referral_fee_matching_pool_basis_points": data[ @@ -195,7 +193,7 @@ async def handle_new_pot( "matching_pool_donations_count": 0, "total_public_donations": "0", "public_donations_count": 0, - "cooldown_period_ms": None, + "cooldown_period_ms": data.get("cooldown_end_ms"), "all_paid_out": False, "protocol_config_provider": data["protocol_config_provider"], } From d27087b5d61f7f74cd1050553b3e9431f1b7ff35 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 9 Dec 2024 15:01:31 +0100 Subject: [PATCH 331/454] cors enable staging --- base/settings.py | 8 ++++++++ pots/last-snapshot-AllVoters.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index ef16a76..6c1d643 100644 --- a/base/settings.py +++ b/base/settings.py @@ -196,6 +196,14 @@ "https://testnet.potlock.app", "https://testnet.potlock.io" ] +elif ENVIRONMENT == "dev": + CORS_ALLOWED_ORIGINS = [ + "http://localhost:3000", + "http://127.0.0.1:8080", + "https://staging.alpha.potlock.app", + "http://dev.local", + "https://dev.local", + ] else: CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", diff --git a/pots/last-snapshot-AllVoters.json b/pots/last-snapshot-AllVoters.json index fd1a775..e3fa94c 100644 --- a/pots/last-snapshot-AllVoters.json +++ b/pots/last-snapshot-AllVoters.json @@ -1 +1 @@ -[{"voter_id":"lucascasp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1447929400","locking_period":300,"voting_power":"7239647000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"7239647000000000000000000000"}]},{"voter_id":"voodooking.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2942789600","locking_period":300,"voting_power":"14713948000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"14713948000000000000000000000"}]},{"voter_id":"kuncho.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000","locking_period":30,"voting_power":"500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"128492199000","locking_period":45,"voting_power":"96369149250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"4153676790","locking_period":300,"voting_power":"20768383950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"96369000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20769033200000000000000000000"}]},{"voter_id":"silkking.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"250000000000","locking_period":240,"voting_power":"1000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"3500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"79200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"917300000000000000000000000000"}]},{"voter_id":"1ce2567f7f49cb34cea72179223ec7fc4ba91077da3e01364d0c729dfbe26467","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7053707400","locking_period":300,"voting_power":"35268537000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"35268537000000000000000000000"}]},{"voter_id":"proxima-centauri.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"177979300210","locking_period":300,"voting_power":"889896501050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"889896501000000000000000000000"}]},{"voter_id":"blazepassion.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000000","locking_period":300,"voting_power":"50000000000000000000000000000","unlocking_started_at":1718642103735,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"alpha-centauri.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"191594836990","locking_period":300,"voting_power":"957974184950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"957974184900000000000000000000"}]},{"voter_id":"0cbf1c4a3da03524dfd9486cf13d404e5f02eb173218d2a512d6991c057fddd3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20064518690","locking_period":300,"voting_power":"100322593450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6737996950000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"93584596500000000000000000000"}]},{"voter_id":"dogon.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"260851958600","locking_period":300,"voting_power":"1304259793000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1304259793000000000000000000000"}]},{"voter_id":"alan777.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"286666722000","locking_period":300,"voting_power":"1433333610000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"100000000","locking_period":150,"voting_power":"250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"10000000","locking_period":180,"voting_power":"30000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"250000000000","locking_period":240,"voting_power":"1000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"2433613610000000000000000000000"}]},{"voter_id":"manzanal.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"594801155900","locking_period":300,"voting_power":"2974005779500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"15000000000","locking_period":300,"voting_power":"75000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"3049005779500000000000000000000"}]},{"voter_id":"brianmai.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8005634731000","locking_period":240,"voting_power":"32022538924000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"chain0ps.poolv1.near","voting_power":"32022538924000000000000000000000"}]},{"voter_id":"raulb.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"86710598600","locking_period":300,"voting_power":"433552993000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"433552993000000000000000000000"}]},{"voter_id":"platoboss.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5512744200","locking_period":90,"voting_power":"8269116300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10769116300000000000000000000","vote_positions":[]},{"voter_id":"cryptogarik.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7822408800","locking_period":300,"voting_power":"39112044000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"cryptogarik.poolv1.near","voting_power":"39112044000000000000000000000"}]},{"voter_id":"zaphire.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"102709473200","locking_period":300,"voting_power":"513547366000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"swissstar.poolv1.near","voting_power":"513547366000000000000000000000"}]},{"voter_id":"f27af9b6ebfa3896bdcaa20514370f12c849f0e9ead55623fb82c11430b0c2ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20000000","locking_period":30,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dexagon.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"58f54b94dae828ebbafeccebfba61c5ff4773a4f1c1b077d6628a64c1a0af742","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"287889851090","locking_period":300,"voting_power":"1439449255450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"59598407900","locking_period":30,"voting_power":"29799203950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"swissstar.poolv1.near","voting_power":"1469248459400000000000000000000"}]},{"voter_id":"6b16e9e129ce06246c42a33f0d225e3d4735c2de26701f21de28276970e601c0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"232967556300","locking_period":90,"voting_power":"349451334450000000000000000000","unlocking_started_at":1728034878253,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"herimax.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"15850679740","locking_period":300,"voting_power":"79253398700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"79253398700000000000000000000"}]},{"voter_id":"0234hoangnd78439.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"735830030","locking_period":300,"voting_power":"3679150150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"3679150100000000000000000000"}]},{"voter_id":"readylayerone.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1345707734600","locking_period":300,"voting_power":"6728538673000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"6728538673000000000000000000000"}]},{"voter_id":"duongka.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3880961200","locking_period":120,"voting_power":"7761922400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"4261922400000000000000000000"}]},{"voter_id":"c245be41fb5aed65ab30e7b4e1a178147e1e131dc95b1f2206b2f53a7596f093","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"69908270","locking_period":300,"voting_power":"349541350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"349541350000000000000000000","vote_positions":[]},{"voter_id":"themicrowave.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"37095762100","locking_period":300,"voting_power":"185478810500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"3736000000","locking_period":300,"voting_power":"18680000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"204158810500000000000000000000"}]},{"voter_id":"e914e4c91d80a845c79bb134d54fafb01bab9357fa44c3b4c38dffb3f0e5cad1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"34123968100","locking_period":300,"voting_power":"170619840500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"175892000","locking_period":90,"voting_power":"263838000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1529750100","locking_period":165,"voting_power":"4206812775000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"717246800","locking_period":120,"voting_power":"1434493600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"75000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"76524984800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"100000000000000000000000000000"}]},{"voter_id":"branermusso.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6364090500","locking_period":300,"voting_power":"31820452500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10095459200","locking_period":300,"voting_power":"50477296000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"82297748500000000000000000000"}]},{"voter_id":"79148e72172096387294d56b0f2f9b366591610254a21e444a479d6adcf38dd3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"137063146500","locking_period":90,"voting_power":"205594719750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"205594719750000000000000000000","vote_positions":[]},{"voter_id":"gilberth.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"102367750","locking_period":300,"voting_power":"511838750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"511838700000000000000000000"}]},{"voter_id":"0beca730ba634786c7e330fb7c71f8510b51a445522ca728fc30f7c722eb3a14","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"15020424000","locking_period":30,"voting_power":"7510212000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7510212000000000000000000000","vote_positions":[]},{"voter_id":"hannah17.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"23554732500","locking_period":300,"voting_power":"117773662500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"117773662500000000000000000000"}]},{"voter_id":"blaze.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"24895319700","locking_period":90,"voting_power":"37342979550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"openshards.poolv1.near","voting_power":"37342979500000000000000000000"}]},{"voter_id":"do0k13.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"508091860340","locking_period":300,"voting_power":"2540459301700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"2540459301700000000000000000000"}]},{"voter_id":"akdude.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"80865985600","locking_period":300,"voting_power":"404329928000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"60000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"61298970000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"20216500000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"262814458000000000000000000000"}]},{"voter_id":"levoslov.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1126852800","locking_period":300,"voting_power":"5634264000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5634264000000000000000000000","vote_positions":[]},{"voter_id":"aiml.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2984500","locking_period":240,"voting_power":"11938000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10529862400","locking_period":300,"voting_power":"52649312000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"52661250000000000000000000000"}]},{"voter_id":"ramgor.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"38088003700","locking_period":300,"voting_power":"190440018500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"190440018500000000000000000000"}]},{"voter_id":"zzoo-2718.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"68988508600","locking_period":180,"voting_power":"206965525800000000000000000000","unlocking_started_at":1720364226345,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"68686063500","locking_period":165,"voting_power":"188886674625000000000000000000","unlocking_started_at":1720364264076,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"ame9986.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2360529000","locking_period":300,"voting_power":"11802645000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6802645000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"openbitlab.poolv1.near","voting_power":"5000000000000000000000000000"}]},{"voter_id":"abrahamrivv.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"34437420300","locking_period":300,"voting_power":"172187101500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"172187101500000000000000000000"}]},{"voter_id":"cacossio.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"754989658520","locking_period":210,"voting_power":"2642463804820000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"201150593900","locking_period":195,"voting_power":"653739430175000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"74821994195000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"221381240800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"121|mpDAO Grants #8 - August 2024-meximalist.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"118|mpDAO Grants #8 - August 2024-marcelorefi.near","voting_power":"500000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"117|mpDAO Grants #8 - August 2024-bagrera.near","voting_power":"250000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"250000000000000000000000000000"}]},{"voter_id":"e87a0f4bbdbc251ca4531be261f7c6821facc2ad076da96a9572acca2049dfe6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"129575448900","locking_period":135,"voting_power":"291544760025000000000000000000","unlocking_started_at":1714994026232,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"skinn.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"76645554700","locking_period":300,"voting_power":"383227773500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"383227773500000000000000000000"}]},{"voter_id":"a2d1b583a0743a892255b9a07eb064d2656589df60ebeffbbcb7f4d32883662c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"172c0cab8eec92d69ba16db4e8ea5bf69149fcc3844008327bcc2c84fa203508","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"9076056900","locking_period":300,"voting_power":"45380284500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"45380284500000000000000000000"}]},{"voter_id":"border_less.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"52897311300","locking_period":300,"voting_power":"264486556500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"264486556500000000000000000000"}]},{"voter_id":"0xf48cAb01aE5b5f3A546f33B7f416E7E203237749.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"790000000","locking_period":120,"voting_power":"1580000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"80000000","locking_period":300,"voting_power":"400000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1977000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"109|mpDAO Grants #7 - June 2024-near-africa.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"mp-cc.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20633333330000","locking_period":225,"voting_power":"77374999987500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"28125000000000","locking_period":90,"voting_power":"42187500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"55030000000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sharpdarts.poolv1.near","voting_power":"2000000987500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"galaxydigital.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"rekt.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staketab.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.pool.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"openshards.poolv1.near","voting_power":"3000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"moonlet.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"3000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fresh.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"grassets.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stablelab.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"tritonone.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"3562499000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"senseinode.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakesstone.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"kiln-1.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dsrvlabs-staking.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"prophet.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearvietnamhub.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"08investinwomen_runbybisontrails.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"gfs-ventures.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"a41.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"bee1stake.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"blockdaemon.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"50000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"150000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"134|mpDAO Grants #8 - August 2024-marcrypted.near","voting_power":"560000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"1500000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"400000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"128|mpDAO Grants #8 - August 2024-aleiranova.near","voting_power":"1250000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"225000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"137|mpDAO Grants #8 - August 2024-criptolaplata.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"335000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"142|mpDAO Grants #8 - August 2024-refibogota.near","voting_power":"200000000000000000000000000000"}]},{"voter_id":"mbbevilacqua_caffepoesia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"536000000","locking_period":300,"voting_power":"2680000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"1729000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"931000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"2be59bfa20787b81b8ffa9a6a6c45714020e2e157dbc1c5636287ca91ebc1aee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"yaneisia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"901208400","locking_period":300,"voting_power":"4506042000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"120245800","locking_period":300,"voting_power":"601229000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"335994910","locking_period":300,"voting_power":"1679974550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"cryptoblossom.poolv1.near","voting_power":"6787245500000000000000000000"}]},{"voter_id":"8901ad3c104f92dba61c1624b74285a481fdc93670f7e307d27512bcf6f83e4b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3751efa63c92312d2324dc19f26c6ec15c869b72d39ef5c852e69067dca585da","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"775000000","locking_period":90,"voting_power":"1162500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"8963000000","locking_period":300,"voting_power":"44815000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"15977500000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"10000000000000000000000000000"}]},{"voter_id":"travbori.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"36093446400","locking_period":90,"voting_power":"54140169600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1791481030","locking_period":300,"voting_power":"8957405150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"63097574700000000000000000000"}]},{"voter_id":"shadowmonarch.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"32406418600","locking_period":300,"voting_power":"162032093000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"162032093000000000000000000000"}]},{"voter_id":"luciotato.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17740840948500","locking_period":90,"voting_power":"26611261422750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"100000000000","locking_period":180,"voting_power":"300000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"40000000000000","locking_period":30,"voting_power":"20000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"35000000000000","locking_period":60,"voting_power":"35000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"69626261422750000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"550000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"1700000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearstar.poolv1.near","voting_power":"1850000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"2180000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"openshards.poolv1.near","voting_power":"985000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"deutschetelekom.poolv1.near","voting_power":"850000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"50000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"101|mpDAO Grants #7 - June 2024-criptolaplata.near","voting_power":"1050000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"85|mpDAO Grants #7 - June 2024-fpsgames.near","voting_power":"350000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"94|mpDAO Grants #7 - June 2024-cryptopr3neur.near","voting_power":"665000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"95|mpDAO Grants #7 - June 2024-edfernandezg.near","voting_power":"45000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"111|mpDAO Grants #7 - June 2024-c0d0c8b3bfcbc17347002733be3e2d17df833d2931c425eaa40bd7e73a4509e3","voting_power":"40000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"102|mpDAO Grants #7 - June 2024-fr-grant.near","voting_power":"400000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"86|mpDAO Grants #7 - June 2024-la-incubadora.near","voting_power":"650000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"20000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"600000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"121|mpDAO Grants #8 - August 2024-meximalist.near","voting_power":"300000000000000000000000000000"}]},{"voter_id":"brunomazzaggio.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"251428084300","locking_period":300,"voting_power":"1257140421500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"1257140421500000000000000000000"}]},{"voter_id":"c3001cd00f76dd7d4e60d4bf6c8b392fa2e724f7367b3b92279f467262f0724e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e515b46dc1c974ca39c5c622d869f7bc73e31d2c995354715a1520e18290fbac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12355013000","locking_period":120,"voting_power":"24710026000000000000000000000","unlocking_started_at":1726266396655,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"biolmoon.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10254082300","locking_period":195,"voting_power":"33325767475000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"75000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"33325767400000000000000000000"}]},{"voter_id":"mex-holding.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"281424231200","locking_period":300,"voting_power":"1407121156000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"1407121156000000000000000000000"}]},{"voter_id":"rosaglez_art.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"2500000000000000000000000000"}]},{"voter_id":"adisa81.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1035323359600","locking_period":300,"voting_power":"5176616798000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5176616798000000000000000000000"}]},{"voter_id":"natto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28715520000","locking_period":90,"voting_power":"43073280000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"770000000","locking_period":30,"voting_power":"385000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"21729000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"21729280000000000000000000000"}]},{"voter_id":"daorecords.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7059590900","locking_period":90,"voting_power":"10589386350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"5289386300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"5300000000000000000000000000"}]},{"voter_id":"alejandro.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17119680100","locking_period":300,"voting_power":"85598400500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"85598400500000000000000000000"}]},{"voter_id":"jarednotjerry.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"209997339990","locking_period":300,"voting_power":"1049986699950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1049986699900000000000000000000"}]},{"voter_id":"wapzp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20033551200","locking_period":300,"voting_power":"100167756000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"100167756000000000000000000000"}]},{"voter_id":"marrown.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27459200900","locking_period":300,"voting_power":"137296004500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"63818410000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.pool.near","voting_power":"73477590000000000000000000000"}]},{"voter_id":"lukelee.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1306488420000","locking_period":300,"voting_power":"6532442100000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"6532442100000000000000000000000"}]},{"voter_id":"anmatig.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"266031800","locking_period":300,"voting_power":"1330159000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"3330000000","locking_period":30,"voting_power":"1665000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1000000","locking_period":225,"voting_power":"3750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2998909000000000000000000000"}]},{"voter_id":"neonong.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"325787524100","locking_period":300,"voting_power":"1628937620500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking-power.poolv1.near","voting_power":"1628937620500000000000000000000"}]},{"voter_id":"luismanac.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"33953313100","locking_period":300,"voting_power":"169766565500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"69767000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"snsmln.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"49999565500000000000000000000"}]},{"voter_id":"max_one.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10627243500","locking_period":300,"voting_power":"53136217500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2327239200","locking_period":300,"voting_power":"11636196000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"424000000","locking_period":300,"voting_power":"2120000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"449000000","locking_period":300,"voting_power":"2245000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"4445212500","locking_period":300,"voting_power":"22226062500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"91363476000000000000000000000"}]},{"voter_id":"sanam1027.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1206000000","locking_period":30,"voting_power":"603000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"353000000","locking_period":90,"voting_power":"529500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"2000000","locking_period":60,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"2491108600","locking_period":45,"voting_power":"1868331450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"402831450000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"atomic-nodes.poolv1.near","voting_power":"1000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"400000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"100000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"50000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"800000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"65|mpDAO Grants #7 - June 2024-auroraafrica.near","voting_power":"50000000000000000000000000"}]},{"voter_id":"7b3735e6e4891b67d6949b37007000f1006c9bc50f7b2ffbc7591b84f4a41d11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"173653480600","locking_period":120,"voting_power":"347306961200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"infiniteloop.poolv1.near","voting_power":"347306961200000000000000000000"}]},{"voter_id":"serik.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2625018100","locking_period":90,"voting_power":"3937527150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3937527150000000000000000000","vote_positions":[]},{"voter_id":"roago.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"223179655390","locking_period":300,"voting_power":"1115898276950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"715898276900000000000000000000"}]},{"voter_id":"3228f4ec8437ab4271b99b7ce0c5d6a82ac28fe7be40ecce1f0920b12eaba915","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16452372830","locking_period":300,"voting_power":"82261864150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1000000","locking_period":300,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1000000","locking_period":135,"voting_power":"2250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"20760795000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"27402183200000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"34106135900000000000000000000"}]},{"voter_id":"6490723760b963cabd9b82382d0faf0f1cc693dc7a9b8bdfd6595dde53bb6559","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"35936943900","locking_period":300,"voting_power":"179684719500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"79684719500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"50000000000000000000000000000"}]},{"voter_id":"stingray.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stingray.poolv1.near","voting_power":"5000000000000000000000000000"}]},{"voter_id":"011050b84e018322932173e1309b8496af96f7792d29a48cf82eaf7d12c24850","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3470000000000","locking_period":300,"voting_power":"17350000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"17350000000000000000000000000000"}]},{"voter_id":"bdaf.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1269125990000","locking_period":270,"voting_power":"5711066955000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"178571430000","locking_period":225,"voting_power":"669642862500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1130952370000","locking_period":225,"voting_power":"4241071387500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"250000000000","locking_period":225,"voting_power":"937500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"861884035070","locking_period":210,"voting_power":"3016594122745000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":5,"amount":"788881924520","locking_period":180,"voting_power":"2366645773560000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":6,"amount":"796553508940","locking_period":300,"voting_power":"3982767544700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":7,"amount":"761904750000","locking_period":120,"voting_power":"1523809500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lavenderfive.poolv1.near","voting_power":"9892331030500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"polkachu.poolv1.near","voting_power":"9212744787800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"1559281205000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"1784741122700000000000000000000"}]},{"voter_id":"marior.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"65158439600","locking_period":300,"voting_power":"325792198000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"30719428500","locking_period":300,"voting_power":"153597142500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"479389340500000000000000000000"}]},{"voter_id":"davletuner.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10495055000","locking_period":120,"voting_power":"20990110000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"3228186500","locking_period":165,"voting_power":"8877512875000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"75176860000","locking_period":300,"voting_power":"375884300000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"75000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"405751922800000000000000000000"}]},{"voter_id":"phandinhtuan.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"123230054400","locking_period":300,"voting_power":"616150272000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"616150272000000000000000000000"}]},{"voter_id":"sivakalathi008.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sharpdarts.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"welldonestake.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"700000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptotribe.poolv1.near","voting_power":"600000000000000000000000000"}]},{"voter_id":"xuanduy86.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"23121411500","locking_period":300,"voting_power":"115607057500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"26799658700","locking_period":300,"voting_power":"133998293500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"742000000","locking_period":300,"voting_power":"3710000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"25463122400","locking_period":300,"voting_power":"127315612000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"380630963000000000000000000000"}]},{"voter_id":"fdea0a6bcd48da25f783feb9b66627698cb00a7b34f064eb3ff59e3367b625de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13550000000","locking_period":300,"voting_power":"67750000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"67750000000000000000000000000","vote_positions":[]},{"voter_id":"b71nw.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7416352400","locking_period":180,"voting_power":"22249057200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"22249057200000000000000000000","vote_positions":[]},{"voter_id":"sivakalathi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"98276654600","locking_period":300,"voting_power":"491383273000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"trust-nodes.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"4ire.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pinrocks.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearfans.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptotribe.poolv1.near","voting_power":"30968291100000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"buildlinks.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"encipher.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"iamcryptobro.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dsrvlabs-staking.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"ni.poolv1.near","voting_power":"16000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"genesislab.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"106|mpDAO Grants #7 - June 2024-ludium.near","voting_power":"2414981900000000000000000000"},{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"2000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"144|mpDAO Grants #8 - August 2024-cryptopr3neur.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"136|mpDAO Grants #8 - August 2024-bstt.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"126|mpDAO Grants #8 - August 2024-5e85fef96bd903eec08d1f59fabdc524d0ac13f44c7a1e08e43de9a01a62a11c","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"128|mpDAO Grants #8 - August 2024-aleiranova.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"137|mpDAO Grants #8 - August 2024-criptolaplata.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"133|mpDAO Grants #8 - August 2024-ffbc3084a4d75ed3c28a7b6c26f44a4ffb43dc3959365d80d70a9660913cbacf","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"117|mpDAO Grants #8 - August 2024-bagrera.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"125|mpDAO Grants #8 - August 2024-xiiw.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"118|mpDAO Grants #8 - August 2024-marcelorefi.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"3000000000000000000000000000"}]},{"voter_id":"15870c8972a9fe6cdb7dfc2df835740108e8674cc170a091cd0ece0b9e4f6cfa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000886200","locking_period":270,"voting_power":"4503987900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"183000000","locking_period":180,"voting_power":"549000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"17349290","locking_period":300,"voting_power":"86746450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"86746450000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"5052987900000000000000000000"}]},{"voter_id":"mansaet.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"193545300200","locking_period":300,"voting_power":"967726501000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"967726501000000000000000000000"}]},{"voter_id":"pathrock.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17006278400","locking_period":90,"voting_power":"25509417600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pathrocknetwork.poolv1.near","voting_power":"25509417600000000000000000000"}]},{"voter_id":"19f7dbc58065da8a3cc636987d646b3844517fef9533b3d86bef4f3e10a194bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"823081344000","locking_period":195,"voting_power":"2675014368000000000000000000000","unlocking_started_at":1726751184719,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"112890669800","locking_period":300,"voting_power":"564453349000000000000000000000","unlocking_started_at":1723052296755,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"39043526300","locking_period":210,"voting_power":"136652342050000000000000000000","unlocking_started_at":1725348921894,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":3,"amount":"29988017300","locking_period":270,"voting_power":"134946077850000000000000000000","unlocking_started_at":1725348888783,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"syrbuss.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"292603590390","locking_period":300,"voting_power":"1463017951950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"1463017951900000000000000000000"}]},{"voter_id":"0rz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"502237362700","locking_period":300,"voting_power":"2511186813500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"2511186813500000000000000000000"}]},{"voter_id":"salpaslan.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"35730495600","locking_period":105,"voting_power":"62528367300000000000000000000","unlocking_started_at":1731416381037,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"sv404771.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"169793351100","locking_period":120,"voting_power":"339586702200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"339586702200000000000000000000"}]},{"voter_id":"6d6ce3d63ba86cce16407b328e9d6b92421f7265be64f3efc9ef32f542176e91","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5781710500","locking_period":300,"voting_power":"28908552500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"here.poolv1.near","voting_power":"28908552500000000000000000000"}]},{"voter_id":"leandrogavidia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1165606140","locking_period":300,"voting_power":"5828030700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5828030700000000000000000000","vote_positions":[]},{"voter_id":"59583199fc8d2b7207ad71f5213b3e7c917cbf07579d0e63fe1d7cebd9a2cd00","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"109173817500","locking_period":300,"voting_power":"545869087500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"40827000000","locking_period":180,"voting_power":"122481000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"30000000000","locking_period":60,"voting_power":"30000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"20000000000","locking_period":30,"voting_power":"10000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"50000000000","locking_period":120,"voting_power":"100000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"87500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"808350000000000000000000000000"}]},{"voter_id":"curmello.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7000016900","locking_period":300,"voting_power":"35000084500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"136|mpDAO Grants #8 - August 2024-bstt.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"5000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"5000084500000000000000000000"}]},{"voter_id":"e0ace3957f5b8a6b2c1903c6ec4c16a69e5c76b231eb1bf259e219ccfc3c63db","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16314629900","locking_period":90,"voting_power":"24471944850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"24471944850000000000000000000","vote_positions":[]},{"voter_id":"0f030b64cedb845906e84bf92076f09218de9a636e0758eab90a7aadb75048b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7272730000","locking_period":165,"voting_power":"20000007500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"}]},{"voter_id":"lematecat.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"32768913580","locking_period":300,"voting_power":"163844567900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"163844567900000000000000000000"}]},{"voter_id":"v3ry.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12303870000","locking_period":270,"voting_power":"55367415000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"211767800000","locking_period":300,"voting_power":"1058839000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"47000000000","locking_period":195,"voting_power":"152750000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"130000000000","locking_period":285,"voting_power":"617500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"15274000000","locking_period":180,"voting_power":"45822000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"350000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"180000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"330000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"160000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"chorusone.poolv1.near","voting_power":"420000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"everstake.poolv1.near","voting_power":"290000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"100278415000000000000000000000"}]},{"voter_id":"21mbulls.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"97814396200","locking_period":300,"voting_power":"489071981000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"finoa.poolv1.near","voting_power":"489071981000000000000000000000"}]},{"voter_id":"crypturion.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"38975358730","locking_period":300,"voting_power":"194876793650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"35000000","locking_period":300,"voting_power":"175000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"129932415500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"65119378100000000000000000000"}]},{"voter_id":"jepian.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"814504100","locking_period":90,"voting_power":"1221756150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"78|mpDAO Grants #7 - June 2024-irvingzcc.near","voting_power":"1221756100000000000000000000"}]},{"voter_id":"rodrigos.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"199000000","locking_period":300,"voting_power":"995000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"995000000000000000000000000"}]},{"voter_id":"3b19d8cefe1b7f4aff0745f0751d616d58b8553b35e6bb821edd301a6ecf2d4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"aleh.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"126501701000","locking_period":90,"voting_power":"189752551500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2049687100","locking_period":300,"voting_power":"10248435500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"33333000000","locking_period":90,"voting_power":"49999500000000000000000000000","unlocking_started_at":1727743009244,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":3,"amount":"71419000000","locking_period":90,"voting_power":"107128500000000000000000000000","unlocking_started_at":1728741366553,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"987000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"50000000000000000000000000000"}]},{"voter_id":"e2f0fa9548b641ffc7f4eac2e19987a3d0b3c502f2e3ad7127c4c4d046b7afad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7000000","locking_period":90,"voting_power":"10500000000000000000000000","unlocking_started_at":1720378414463,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"lovali.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1276699700","locking_period":300,"voting_power":"6383498500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lovali.poolv1.near","voting_power":"6383498500000000000000000000"}]},{"voter_id":"dc431cbf756718c9babdeb6839a11f6b2eb29ce48bad6b250cf4db7eee5efb0b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3340921200","locking_period":300,"voting_power":"16704606000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"85|mpDAO Grants #7 - June 2024-fpsgames.near","voting_power":"10000606000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"64|mpDAO Grants #7 - June 2024-plugrel.near","voting_power":"6704000000000000000000000000"}]},{"voter_id":"jojokeko.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8776537700","locking_period":300,"voting_power":"43882688500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"43882688500000000000000000000"}]},{"voter_id":"snowfa11.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"75000000000","locking_period":180,"voting_power":"225000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"76738000000","locking_period":300,"voting_power":"383690000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"135000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"135000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"138690000000000000000000000000"}]},{"voter_id":"kiskesis.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1575422300","locking_period":300,"voting_power":"7877111500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"7877111500000000000000000000"}]},{"voter_id":"nearukraineguild.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5915000000","locking_period":300,"voting_power":"29575000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"29575000000000000000000000000"}]},{"voter_id":"9c57aa38fecf83600922b69af9c8944742c3e45bcc1138ac534ac8de74ca7753","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"29184000000","locking_period":300,"voting_power":"145920000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"25000000000","locking_period":180,"voting_power":"75000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"110110000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"110810000000000000000000000000"}]},{"voter_id":"quickwaycrypto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8711373700","locking_period":90,"voting_power":"13067060550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"13067060500000000000000000000"}]},{"voter_id":"pamm.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000","locking_period":180,"voting_power":"300000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"3559000000","locking_period":105,"voting_power":"6228250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"3992306400","locking_period":300,"voting_power":"19961532000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"20000000","locking_period":60,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"1176771600","locking_period":90,"voting_power":"1765157400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"4ire.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"3000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"5274939400000000000000000000"}]},{"voter_id":"swapmy.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"204894267800","locking_period":30,"voting_power":"102447133900000000000000000000","unlocking_started_at":1715730266745,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"cesar667.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":90,"voting_power":"4500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"100000000","locking_period":300,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"504500000000000000000000000"}]},{"voter_id":"luckycat.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714829463538,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"5270925700","locking_period":60,"voting_power":"5270925700000000000000000000","unlocking_started_at":1714829615315,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"showme355.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"24801635800","locking_period":90,"voting_power":"37202453700000000000000000000","unlocking_started_at":1714831418807,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"894000000","locking_period":60,"voting_power":"894000000000000000000000000","unlocking_started_at":1714829193720,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"georgemarlow.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000485500","locking_period":300,"voting_power":"500002427500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"604496000000","locking_period":300,"voting_power":"3022480000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"3522482427500000000000000000000"}]},{"voter_id":"uzis.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714829889706,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"45000000","locking_period":60,"voting_power":"45000000000000000000000000","unlocking_started_at":1714829865130,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"konwey.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"135604870900","locking_period":300,"voting_power":"678024354500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"24354500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"678000000000000000000000000000"}]},{"voter_id":"showme.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830101467,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"42000000","locking_period":60,"voting_power":"42000000000000000000000000","unlocking_started_at":1714830119925,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"wwe.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830256184,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"31000000","locking_period":60,"voting_power":"31000000000000000000000000","unlocking_started_at":1714830284122,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"showtime.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830364816,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"34000000","locking_period":60,"voting_power":"34000000000000000000000000","unlocking_started_at":1714830384498,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"okamoto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46000000","locking_period":60,"voting_power":"46000000000000000000000000","unlocking_started_at":1714830512197,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"691593700","locking_period":105,"voting_power":"1210288975000000000000000000","unlocking_started_at":1714830535796,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"usaa.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830649423,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"38000000","locking_period":60,"voting_power":"38000000000000000000000000","unlocking_started_at":1714830678784,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"7andi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830948897,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"jbs.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"711593700","locking_period":90,"voting_power":"1067390550000000000000000000","unlocking_started_at":1714830862740,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"earnestetim.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"507375800","locking_period":300,"voting_power":"2536879000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"109|mpDAO Grants #7 - June 2024-near-africa.near","voting_power":"2536879000000000000000000000"}]},{"voter_id":"vitalpointai.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18080140400","locking_period":30,"voting_power":"9040070200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"epic.poolv1.near","voting_power":"9040070200000000000000000000"}]},{"voter_id":"dcabrera.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"94029815300","locking_period":300,"voting_power":"470149076500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"220149076500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"250000000000000000000000000000"}]},{"voter_id":"71de6add6efe99c57bb02bcacc4be6efaaba9d409403372144d2292db920df39","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"458664942800","locking_period":30,"voting_power":"229332471400000000000000000000","unlocking_started_at":1714723375781,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"albing.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4846207310","locking_period":300,"voting_power":"24231036550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"82912090","locking_period":300,"voting_power":"414560450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"9663757500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"1500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"3000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptogarik.poolv1.near","voting_power":"10481839500000000000000000000"}]},{"voter_id":"ronyle.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13650000000","locking_period":300,"voting_power":"68250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"68250000000000000000000000000","vote_positions":[]},{"voter_id":"5687c659be1c73efa9cc4f5f90e466b39ec929d4c0ba5871a50df092015f49de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"115729500","locking_period":30,"voting_power":"57864750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"57864750000000000000000000","vote_positions":[]},{"voter_id":"imiroslav.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"15000000000","locking_period":120,"voting_power":"30000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4803812600","locking_period":180,"voting_power":"14411437800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"polkachu.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"24411437800000000000000000000"}]},{"voter_id":"sivakalathi006.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100672746900","locking_period":300,"voting_power":"503363734500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ruziev.poolv1.near","voting_power":"12354614200000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sharpdarts.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"ni.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pinrocks.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptotribe.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearfans.poolv1.near","voting_power":"18000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"buildnear.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearvietnamhub.poolv1.near","voting_power":"10009120300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"iamcryptobro.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dsrvlabs-staking.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"89|mpDAO Grants #7 - June 2024-trovelabs.near","voting_power":"2000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"10000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"136|mpDAO Grants #8 - August 2024-bstt.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"128|mpDAO Grants #8 - August 2024-aleiranova.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"133|mpDAO Grants #8 - August 2024-ffbc3084a4d75ed3c28a7b6c26f44a4ffb43dc3959365d80d70a9660913cbacf","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"4000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"118|mpDAO Grants #8 - August 2024-marcelorefi.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"129|mpDAO Grants #8 - August 2024-0xmitz.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"144|mpDAO Grants #8 - August 2024-cryptopr3neur.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"125|mpDAO Grants #8 - August 2024-xiiw.near","voting_power":"3000000000000000000000000000"}]},{"voter_id":"ilya.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"176896422200","locking_period":165,"voting_power":"486465161050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000050000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"323792844400000000000000000000"},{"votable_address":"initiatives","votable_object_id":"96|mpDAO Grants #7 - June 2024-dapplooker.near","voting_power":"30000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"118|mpDAO Grants #8 - August 2024-marcelorefi.near","voting_power":"10000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"108|mpDAO Grants #7 - June 2024-553a5330546aa39850aa0e972bc1bec1c7018575ba141fa6f0a379cf28d46d16","voting_power":"40000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"72672316600000000000000000000"}]},{"voter_id":"a.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"232537338800","locking_period":300,"voting_power":"1162686694000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"1162686694000000000000000000000"}]},{"voter_id":"bestlove89.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"333301900","locking_period":90,"voting_power":"499952850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"499952800000000000000000000"}]},{"voter_id":"pahol.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"73524025800","locking_period":300,"voting_power":"367620129000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"367620129000000000000000000000"}]},{"voter_id":"ktxfauzy.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8834144400","locking_period":300,"voting_power":"44170722000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"44170720000000000000000000000"}]},{"voter_id":"mr_free.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"38000000","locking_period":90,"voting_power":"57000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"57000000000000000000000000","vote_positions":[]},{"voter_id":"173ec8e6ef2f0e328753c1be4779bb5467c02f14ea6b9fe1e69221d7bdc93f6e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"andersonr.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"354251600","locking_period":300,"voting_power":"1771258000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1771258000000000000000000000","vote_positions":[]},{"voter_id":"a4d73137b6ed71e647678dafe514f94e31e2312e2c7a507125c3803bb2bcd475","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20000000000","locking_period":30,"voting_power":"10000000000000000000000000000","unlocking_started_at":1728258996216,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"profit.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6463933370850","locking_period":300,"voting_power":"32319666854250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"5000000","locking_period":300,"voting_power":"25000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"oe.poolv1.near","voting_power":"30000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"2319691854200000000000000000000"}]},{"voter_id":"brzk-93444.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5727419100","locking_period":90,"voting_power":"8591128650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3591128650000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"2000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"3000000000000000000000000000"}]},{"voter_id":"djq.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1395950700","locking_period":30,"voting_power":"697975350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5350000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"697970000000000000000000000"}]},{"voter_id":"e_reece.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2394446500","locking_period":300,"voting_power":"11972232500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"11972232500000000000000000000"}]},{"voter_id":"ollie.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"701794250000","locking_period":300,"voting_power":"3508971250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3508971250000000000000000000000"}]},{"voter_id":"transitmoi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2414219500","locking_period":300,"voting_power":"12071097500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12071097500000000000000000000"}]},{"voter_id":"chloe.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"65916831040","locking_period":300,"voting_power":"329584155200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"25037114700","locking_period":90,"voting_power":"37555672050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"281461072000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"85678755200000000000000000000"}]},{"voter_id":"expenses-meta-pool.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"324785000","locking_period":180,"voting_power":"974355000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"974355000000000000000000000","vote_positions":[]},{"voter_id":"prinwattana.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5153400","locking_period":90,"voting_power":"7730100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7730100000000000000000000","vote_positions":[]},{"voter_id":"fliareccha.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1163826721450","locking_period":300,"voting_power":"5819133607250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"3497000500000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"501119148000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"iamcryptobro.poolv1.near","voting_power":"319000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pathrocknetwork.poolv1.near","voting_power":"502013959200000000000000000000"}]},{"voter_id":"gryptoguru.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"197872100","locking_period":300,"voting_power":"989360500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"300000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"689360500000000000000000000"}]},{"voter_id":"kriptocilginlari.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"45366000","locking_period":300,"voting_power":"226830000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"226830000000000000000000000","vote_positions":[]},{"voter_id":"0x0299b2c88809F5Cd83425c8ea8FffF332a1E6e04.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"liusisi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1500624300","locking_period":90,"voting_power":"2250936450000000000000000000","unlocking_started_at":1717551396822,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"secure.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"794870926300","locking_period":300,"voting_power":"3974354631500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"3974354631500000000000000000000"}]},{"voter_id":"centurion.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"244203961600","locking_period":300,"voting_power":"1221019808000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"1221019808000000000000000000000"}]},{"voter_id":"jaysonkr.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"418517018400","locking_period":300,"voting_power":"2092585092000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"2092585092000000000000000000000"}]},{"voter_id":"serxy.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000000","locking_period":60,"voting_power":"100000000000000000000000000000","unlocking_started_at":1730635967602,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"100000000000","locking_period":270,"voting_power":"450000000000000000000000000000","unlocking_started_at":1730635990484,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0x7bF9a18915D1d40CfDa5ADaa39028F62DC7192E0.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"awsomzs.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18544446700","locking_period":300,"voting_power":"92722233500000000000000000000","unlocking_started_at":1715627876104,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0x15f4dE244107DFd83039927becB74934969087C2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"165283000000","locking_period":180,"voting_power":"495849000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"121|mpDAO Grants #8 - August 2024-meximalist.near","voting_power":"495849000000000000000000000000"}]},{"voter_id":"bochenn.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"31287097200","locking_period":300,"voting_power":"156435486000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"156435486000000000000000000000","vote_positions":[]},{"voter_id":"cryptonum.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10333242300","locking_period":300,"voting_power":"51666211500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakely_io.poolv1.near","voting_power":"15000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"16666211500000000000000000000"}]},{"voter_id":"chiennguyen1985.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"144355687100","locking_period":300,"voting_power":"721778435500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"68849750000","locking_period":300,"voting_power":"344248750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"928327685500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"137699500000000000000000000000"}]},{"voter_id":"pacek.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"242576650800","locking_period":210,"voting_power":"849018277800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"7729000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"720000952400000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"121288325400000000000000000000"}]},{"voter_id":"cunum.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"140598200600","locking_period":300,"voting_power":"702991003000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"cunum.poolv1.near","voting_power":"702991003000000000000000000000"}]},{"voter_id":"chain0ps.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"117241000000","locking_period":300,"voting_power":"586205000000000000000000000000","unlocking_started_at":1729968457538,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"1187500","locking_period":255,"voting_power":"5046875000000000000000000","unlocking_started_at":1717486868740,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"dipsyv.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40000000","locking_period":300,"voting_power":"200000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"200000000000000000000000000"}]},{"voter_id":"3a996e03e7247a0989f546b1b1d02905f53bc8e3fd23920c725d642910f902c8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3632000","locking_period":210,"voting_power":"12712000000000000000000000","unlocking_started_at":1722423551140,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"5000000","locking_period":210,"voting_power":"17500000000000000000000000","unlocking_started_at":1722535624172,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"97140000000","locking_period":195,"voting_power":"315705000000000000000000000000","unlocking_started_at":1725335843062,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":3,"amount":"100000000000","locking_period":300,"voting_power":"500000000000000000000000000000","unlocking_started_at":1729966101525,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"impe83.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000000","locking_period":30,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10000000000","locking_period":90,"voting_power":"15000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakely_io.poolv1.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"133|mpDAO Grants #8 - August 2024-ffbc3084a4d75ed3c28a7b6c26f44a4ffb43dc3959365d80d70a9660913cbacf","voting_power":"5000000000000000000000000000"}]},{"voter_id":"2d36feb3bc40513b31e852100a2b70cdb143bfade40a393a8c04d4931e10175c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000","locking_period":300,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"500000000000000000000000000"}]},{"voter_id":"luzmargaritas.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"615737007700","locking_period":90,"voting_power":"923605511550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"95426921230","locking_period":300,"voting_power":"477134606150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"1140000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_01.poolv1.near","voting_power":"123631511500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nativo.pool.near","voting_power":"39815992700000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"40000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lavenderfive.poolv1.near","voting_power":"48993613500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"8300000000000000000000000000"}]},{"voter_id":"satojandro.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"336741374200","locking_period":300,"voting_power":"1683706871000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"14638000000","locking_period":90,"voting_power":"21957000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"optimusvalidatornetwork.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lux.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"205663871000000000000000000000"}]},{"voter_id":"lovelysugar.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":30,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"320000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"30000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"69|mpDAO Grants #7 - June 2024-odisea.near","voting_power":"50000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"100000000000000000000000000"}]},{"voter_id":"vinhdao286.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000000","locking_period":270,"voting_power":"36000000000000000000000000000","unlocking_started_at":1714882252568,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"566000000","locking_period":300,"voting_power":"2830000000000000000000000000","unlocking_started_at":1714882266639,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"kangaroojack.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"706830800","locking_period":300,"voting_power":"3534154000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"128000000","locking_period":30,"voting_power":"64000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"89|mpDAO Grants #7 - June 2024-trovelabs.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"90|mpDAO Grants #7 - June 2024-nekotoken.near","voting_power":"598154000000000000000000000"}]},{"voter_id":"danieldao.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3787360000","locking_period":300,"voting_power":"18936800000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"18936800000000000000000000000"}]},{"voter_id":"b9dca1592030dcd114852805d6efaf86b93a51c95613ee184233e9604a5d5924","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"plenum.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16226095800","locking_period":300,"voting_power":"81130479000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"81130479000000000000000000000"}]},{"voter_id":"patvg.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"332890200","locking_period":180,"voting_power":"998670600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"104|mpDAO Grants #7 - June 2024-lematecat.near","voting_power":"998670600000000000000000000"}]},{"voter_id":"ebtgroup.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3333333400000","locking_period":90,"voting_power":"5000000100000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000100000000000000000000000","vote_positions":[]},{"voter_id":"ux1.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"400000000000","locking_period":300,"voting_power":"2000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"2000000000000000000000000000000"}]},{"voter_id":"idomoji.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3549130400","locking_period":300,"voting_power":"17745652000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"17745652000000000000000000000"}]},{"voter_id":"skyempire.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"11100474500","locking_period":300,"voting_power":"55502372500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"15000000","locking_period":210,"voting_power":"52500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"55554872500000000000000000000","vote_positions":[]},{"voter_id":"jefe.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6349837300","locking_period":270,"voting_power":"28574267850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"28574267850000000000000000000","vote_positions":[]},{"voter_id":"victorchaos.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"57167811100","locking_period":30,"voting_power":"28583905550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"28583905550000000000000000000","vote_positions":[]},{"voter_id":"0x442d6AF71aD2f51698ef42C3Df8584b78285a7D2.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"028521fdcdb25436d59ca93155651d613b6d0312010fea8f69d1fbebeeb91b15","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"170380300","locking_period":30,"voting_power":"85190150000000000000000000","unlocking_started_at":1730853253861,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"1357971200","locking_period":60,"voting_power":"1357971200000000000000000000","unlocking_started_at":1730853292623,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"247486800","locking_period":90,"voting_power":"371230200000000000000000000","unlocking_started_at":1730853332528,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"854dea7ff905f277012f0f4a1a029494d3ba90352166a89fac32942e6a2d1ef5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1181470160000","locking_period":300,"voting_power":"5907350800000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"polkachu.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"407350800000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"optimusvalidatornetwork.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dexagon.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptogarik.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lavenderfive.poolv1.near","voting_power":"500000000000000000000000000000"}]},{"voter_id":"032890df5f801f63f81175ca1dc53617657dd309b6967814461b344af606942b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"127575925500","locking_period":300,"voting_power":"637879627500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"459000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"178879627500000000000000000000"}]},{"voter_id":"938c83fcf90350e4e5ad7f1a802d3db742718e194efbc77cf5a250742f61aeb9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"157101400","locking_period":300,"voting_power":"785507000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"785507000000000000000000000"}]},{"voter_id":"4ebf8d13c98d4dfe90e272a6ca92657ce87478383313e2c3848374b9fcdd0d9c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1585391570","locking_period":300,"voting_power":"7926957850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"6136078000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1790879800000000000000000000"}]},{"voter_id":"103ddbe67a0e8be99b8515cb9ae78bb9e169cda00db8d29a0fa38aa4814a9b9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1600000000","locking_period":300,"voting_power":"8000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2900000000","locking_period":30,"voting_power":"1450000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1500000000","locking_period":90,"voting_power":"2250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dacmpool.poolv1.near","voting_power":"5700000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000000"}]},{"voter_id":"isk0p4t1x.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1575695300","locking_period":180,"voting_power":"4727085900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4727085900000000000000000000"}]},{"voter_id":"suave.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"42346290500","locking_period":180,"voting_power":"127038871500000000000000000000","unlocking_started_at":1717480136533,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"814000000","locking_period":300,"voting_power":"4070000000000000000000000000","unlocking_started_at":1717480199068,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"1962000000","locking_period":90,"voting_power":"2943000000000000000000000000","unlocking_started_at":1717480213232,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"craftorium.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8682966500","locking_period":120,"voting_power":"17365933000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17365933000000000000000000000","vote_positions":[]},{"voter_id":"wmheng78.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"87927199300","locking_period":120,"voting_power":"175854398600000000000000000000","unlocking_started_at":1731517191195,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"3442041200","locking_period":30,"voting_power":"1721020600000000000000000000","unlocking_started_at":1732458459623,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"bender172319.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"404246500","locking_period":165,"voting_power":"1111677875000000000000000000","unlocking_started_at":1714941537536,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0xmitz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"30530952900","locking_period":300,"voting_power":"152654764500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"152654764500000000000000000000"}]},{"voter_id":"0782f78681fdf8597ea70f16a52b57096e386e192f6c8ee316847808537b06a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"784063850","locking_period":300,"voting_power":"3920319250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3920319250000000000000000000","vote_positions":[]},{"voter_id":"331835d03584caa318219412d054010e5433fd19bdc872c954832f9295d89ff2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"trilli.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"61808170000","locking_period":300,"voting_power":"309040850000000000000000000000","unlocking_started_at":1717009373429,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"laurentcora.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"20000000","locking_period":30,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"3923f6035ff12ae3a2b2de19c3a75f8b13e31fe9b6fd5835f0cefdf7a8bddb52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"78736645600","locking_period":90,"voting_power":"118104968400000000000000000000","unlocking_started_at":1732072368058,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0a561cd515b6c7456452202daecb20971ba9ef68284f96485394a7124716f91e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"kingofkingspages.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"98482160","locking_period":300,"voting_power":"492410800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"342824900000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"21596900000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"smart-stake.poolv1.near","voting_power":"30000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"30000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"142|mpDAO Grants #8 - August 2024-refibogota.near","voting_power":"27000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"136|mpDAO Grants #8 - August 2024-bstt.near","voting_power":"40989000000000000000000000"}]},{"voter_id":"cosine.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"44277800","locking_period":90,"voting_power":"66416700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"51600000000","locking_period":30,"voting_power":"25800000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"minion.poolv1.near","voting_power":"25866416700000000000000000000"}]},{"voter_id":"stakeblock.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"84164564800","locking_period":300,"voting_power":"420822824000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"420822824000000000000000000000","vote_positions":[]},{"voter_id":"xuantd06.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"701000000","locking_period":300,"voting_power":"3505000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"9428345600","locking_period":105,"voting_power":"16499604800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"3299000000","locking_period":300,"voting_power":"16495000000000000000000000000","unlocking_started_at":1717902660136,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"4604800000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000000"}]},{"voter_id":"duongnhuice.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1225005600","locking_period":300,"voting_power":"6125028000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6125028000000000000000000000","vote_positions":[]},{"voter_id":"ac15a0f879f76d5fed6b617171656c40f5232730dcfc4b45799136522537f35b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"35894259300","locking_period":90,"voting_power":"53841388950000000000000000000","unlocking_started_at":1729266569561,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"960000000","locking_period":45,"voting_power":"720000000000000000000000000","unlocking_started_at":1729266127685,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"540576900","locking_period":120,"voting_power":"1081153800000000000000000000","unlocking_started_at":1729266188207,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"bf947d9a9ac8934645559f9cd97da96eddf527be21ef7d7e635ee526bfea1d52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18809136700","locking_period":90,"voting_power":"28213705050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"15000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"13213705000000000000000000000"}]},{"voter_id":"acaryln.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":30,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"335000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"69|mpDAO Grants #7 - June 2024-odisea.near","voting_power":"65000000000000000000000000"}]},{"voter_id":"antiscaliger.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"58327500","locking_period":90,"voting_power":"87491250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"87491250000000000000000000","vote_positions":[]},{"voter_id":"pangdao.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4383355100","locking_period":300,"voting_power":"21916775500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"21916775500000000000000000000","vote_positions":[]},{"voter_id":"avtf.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2501484140100","locking_period":300,"voting_power":"12507420700500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12507420700500000000000000000000"}]},{"voter_id":"sandeephulsur.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"206509764000","locking_period":180,"voting_power":"619529292000000000000000000000","unlocking_started_at":1718557112477,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"imagine_nl.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"44178800","locking_period":45,"voting_power":"33134100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"33134100000000000000000000","vote_positions":[]},{"voter_id":"7ab36cef3907536334b06360c8ecad2f68be4205cd422484135ab579b6fadde4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"196954300","locking_period":90,"voting_power":"295431450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"295431450000000000000000000","vote_positions":[]},{"voter_id":"akaiqn113.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"25000000000","locking_period":300,"voting_power":"125000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"31250000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"93750000000000000000000000000"}]},{"voter_id":"4aafb0fa32afa8bb20a3e15c8ac55126f4bf325c476a6575498cc8af11d05d52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2815258700","locking_period":300,"voting_power":"14076293500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"1407629300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"12668664200000000000000000000"}]},{"voter_id":"tzouf.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40000000000","locking_period":300,"voting_power":"200000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"200000000000000000000000000000","vote_positions":[]},{"voter_id":"fernix.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1548460000","locking_period":150,"voting_power":"3871150000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"3871150000000000000000000000"}]},{"voter_id":"baf318138fe9ae6648a1e06f12f6a7ac079d47c6097eb41737fc1f8c9cd3b71c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2137460000","locking_period":300,"voting_power":"10687300000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"88|mpDAO Grants #7 - June 2024-100diasder.near","voting_power":"10687300000000000000000000000"}]},{"voter_id":"rahulgoel.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"919872500","locking_period":300,"voting_power":"4599362500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4599362500000000000000000000","vote_positions":[]},{"voter_id":"viettrungdoan91.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"49647336300","locking_period":135,"voting_power":"111706506675000000000000000000","unlocking_started_at":1715001641799,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"sailinon.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46872089800","locking_period":30,"voting_power":"23436044900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"23436044900000000000000000000","vote_positions":[]},{"voter_id":"venomity.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"218000000","locking_period":300,"voting_power":"1090000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"1090000000000000000000000000"}]},{"voter_id":"tahuutho.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1548460000","locking_period":240,"voting_power":"6193840000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6193840000000000000000000000"}]},{"voter_id":"mxweb3.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"57674139700","locking_period":300,"voting_power":"288370698500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"288370698500000000000000000000"}]},{"voter_id":"magic.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"59192298500","locking_period":300,"voting_power":"295961492500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"295961492500000000000000000000"}]},{"voter_id":"dollarinveste.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7795086000","locking_period":300,"voting_power":"38975430000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"38975430000000000000000000000"}]},{"voter_id":"wap909.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"424500401120","locking_period":300,"voting_power":"2122502005600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"130000000000","locking_period":300,"voting_power":"650000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"10000000000","locking_period":300,"voting_power":"50000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"35500000000","locking_period":300,"voting_power":"177500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"115505000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"minion.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"here.poolv1.near","voting_power":"230000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"63169390100000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"26005000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"144950000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"105|mpDAO Grants #7 - June 2024-marcelorefi.near","voting_power":"200000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"91|mpDAO Grants #7 - June 2024-4d1fe2e38339dc82b68afd1d09bac3068c30f22f09dc693e8ab6610da8e15c04","voting_power":"220372615500000000000000000000"},{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"200000000000000000000000000000"}]},{"voter_id":"3add6ae62eb60d4edadca209e37012780f32c3d3cf31868496b91f1988bf043a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"37137117500","locking_period":90,"voting_power":"55705676250000000000000000000","unlocking_started_at":1716421523901,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"newnearwallet.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"845742145800","locking_period":150,"voting_power":"2114355364500000000000000000000","unlocking_started_at":1732208388683,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"draconnia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1903014900","locking_period":300,"voting_power":"9515074500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"347000000","locking_period":30,"voting_power":"173500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"9688574500000000000000000000"}]},{"voter_id":"544e14070a33ec9a7e5d34556f5ddaa45411e73d394039cc241fa92e2bc5cfb5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2067778200","locking_period":300,"voting_power":"10338891000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"1000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sharpdarts.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"welldonestake.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zkv_staketosupportprivacy.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"trust-nodes.poolv1.near","voting_power":"300000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"4ire.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pinrocks.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"centurion.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pathrocknetwork.poolv1.near","voting_power":"1667300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"ni.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptotribe.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"637223700000000000000000000"}]},{"voter_id":"vvm25.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"57700000","locking_period":300,"voting_power":"288500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"288500000000000000000000000","vote_positions":[]},{"voter_id":"dund.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1204000000","locking_period":30,"voting_power":"602000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"576000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"67|mpDAO Grants #7 - June 2024-baryo01.near","voting_power":"10000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"112|mpDAO Grants #7 - June 2024-hoangthien1109.near","voting_power":"11000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"69|mpDAO Grants #7 - June 2024-odisea.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"9968.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1050000000","locking_period":30,"voting_power":"525000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"500000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"112|mpDAO Grants #7 - June 2024-hoangthien1109.near","voting_power":"15000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"8000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"67|mpDAO Grants #7 - June 2024-baryo01.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"x999.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1010629400","locking_period":45,"voting_power":"757972050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"710972050000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"112|mpDAO Grants #7 - June 2024-hoangthien1109.near","voting_power":"20000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"10000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"68|mpDAO Grants #7 - June 2024-3sblockchain.near","voting_power":"17000000000000000000000000"}]},{"voter_id":"pablopd.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6741186400","locking_period":300,"voting_power":"33705932000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"33705932000000000000000000000"}]},{"voter_id":"marysol.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"48051052800","locking_period":300,"voting_power":"240255264000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"134|mpDAO Grants #8 - August 2024-marcrypted.near","voting_power":"240255264000000000000000000000"}]},{"voter_id":"cryptomoneymaker.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"31652702800","locking_period":30,"voting_power":"15826351400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4794004400","locking_period":60,"voting_power":"4794004400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"523000000","locking_period":300,"voting_power":"2615000000000000000000000000","unlocking_started_at":1715076609230,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20620355800000000000000000000"}]},{"voter_id":"iosg01.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13100001000000","locking_period":30,"voting_power":"6550000500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6550000500000000000000000000000","vote_positions":[]},{"voter_id":"towanda.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"25500000","locking_period":45,"voting_power":"19125000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1439316500","locking_period":60,"voting_power":"1439316500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"20000000","locking_period":90,"voting_power":"30000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"9500000","locking_period":30,"voting_power":"4750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"1493191500000000000000000000"}]},{"voter_id":"suz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"122153000000","locking_period":90,"voting_power":"183229500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"183229500000000000000000000000"}]},{"voter_id":"nikhil.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5076282200","locking_period":300,"voting_power":"25381411000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"25381411000000000000000000000","vote_positions":[]},{"voter_id":"metapool20.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"101876349400","locking_period":300,"voting_power":"509381747000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"509381747000000000000000000000"}]},{"voter_id":"usainbluntmx.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4967206710","locking_period":300,"voting_power":"24836033550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4836033550000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"20000000000000000000000000000"}]},{"voter_id":"psd.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"800000000000","locking_period":30,"voting_power":"400000000000000000000000000000","unlocking_started_at":1718077736393,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"meximalist.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2248074700","locking_period":300,"voting_power":"11240373500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"11240373500000000000000000000"}]},{"voter_id":"xvii.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"416786600","locking_period":300,"voting_power":"2083933000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"17000000","locking_period":30,"voting_power":"8500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2092433000000000000000000000","vote_positions":[]},{"voter_id":"norelyz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1100000000","locking_period":300,"voting_power":"5500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"191000000","locking_period":90,"voting_power":"286500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5786500000000000000000000000"}]},{"voter_id":"smjunior.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2914829100","locking_period":300,"voting_power":"14574145500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"200000000","locking_period":300,"voting_power":"1000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"10574145500000000000000000000"}]},{"voter_id":"rondell.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"302214342100","locking_period":90,"voting_power":"453321513150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"26138245800","locking_period":105,"voting_power":"45741930150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"499063443300000000000000000000","vote_positions":[]},{"voter_id":"paderuu.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"201000000","locking_period":300,"voting_power":"1005000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"114|mpDAO Grants #7 - June 2024-aksanova33.near","voting_power":"1005000000000000000000000000"}]},{"voter_id":"hos5018.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3163661200","locking_period":300,"voting_power":"15818306000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"15818306000000000000000000000"}]},{"voter_id":"nearmultiverse.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"36833578700","locking_period":300,"voting_power":"184167893500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"419077000","locking_period":300,"voting_power":"2095385000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"186263278500000000000000000000"}]},{"voter_id":"0xAcaa2077b89799b81F390d9cf015Be8d38108A36.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6834000000","locking_period":300,"voting_power":"34170000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11615000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2pstaking.poolv1.near","voting_power":"10555000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"12000000000000000000000000000"}]},{"voter_id":"agusin.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"658762400","locking_period":300,"voting_power":"3293812000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"3293812000000000000000000000"}]},{"voter_id":"3828a8400589b9faa74d53169ee33bad7a2ed3b68bfb01e8c855454391e7b7a7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"42155844100","locking_period":90,"voting_power":"63233766150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"63233766150000000000000000000","vote_positions":[]},{"voter_id":"okpok.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"132894668500","locking_period":300,"voting_power":"664473342500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"26251089900","locking_period":30,"voting_power":"13125544950000000000000000000","unlocking_started_at":1730781596655,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakeseeker.poolv1.near","voting_power":"664473342500000000000000000000"}]},{"voter_id":"bjirken.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"437000000","locking_period":30,"voting_power":"218500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"200000000","locking_period":300,"voting_power":"1000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"535000000","locking_period":90,"voting_power":"802500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2021000000000000000000000000","vote_positions":[]},{"voter_id":"bjorken.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"62169000000","locking_period":270,"voting_power":"279760500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"20000000000","locking_period":135,"voting_power":"45000000000000000000000000000","unlocking_started_at":1725956697284,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"279760500000000000000000000000"}]},{"voter_id":"tiberiu16.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3760603900","locking_period":300,"voting_power":"18803019500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"18803019500000000000000000000"}]},{"voter_id":"samwangg.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3186786600","locking_period":75,"voting_power":"3983483250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3983483250000000000000000000","vote_positions":[]},{"voter_id":"sam_wang.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17500000000","locking_period":75,"voting_power":"21875000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"21875000000000000000000000000","vote_positions":[]},{"voter_id":"flyingcumbancha.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4686891800","locking_period":300,"voting_power":"23434459000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"23434459000000000000000000000"}]},{"voter_id":"izcc.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16211176100","locking_period":300,"voting_power":"81055880500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"115|mpDAO Grants #8 - August 2024-irvingzcc.near","voting_power":"81055880500000000000000000000"}]},{"voter_id":"e474b668ea4731cf923bc78733f14c849b2d2bb78657af863a84dbc670fe746c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"669000000","locking_period":300,"voting_power":"3345000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"3345000000000000000000000000"}]},{"voter_id":"fersoria.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"117000000","locking_period":180,"voting_power":"351000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1000000000","locking_period":180,"voting_power":"3000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"77|mpDAO Grants #7 - June 2024-1724cbdb130e5b0f9bd750f666f4fe00581489fcc294dca4ed055c90c76808fc","voting_power":"3351000000000000000000000000"}]},{"voter_id":"zahrah.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"261000000","locking_period":300,"voting_power":"1305000000000000000000000000","unlocking_started_at":1714795035865,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"e8669b64d048c0b7627a49250cd08407f9020c4ec252350838c29956c53f4a92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"11525635800","locking_period":300,"voting_power":"57628179000000000000000000000","unlocking_started_at":1716310161875,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"jack1265.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7489666000","locking_period":60,"voting_power":"7489666000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7489666000000000000000000000","vote_positions":[]},{"voter_id":"cryptoreumd.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"574343300","locking_period":300,"voting_power":"2871716500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"2871716500000000000000000000"}]},{"voter_id":"c8a73319812a4e49a3d18f097cc5b00c105d724c5451ec5e38c6146c5561c800","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"419828200","locking_period":90,"voting_power":"629742300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"333171200","locking_period":210,"voting_power":"1166099200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1795841500000000000000000000"}]},{"voter_id":"wdz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3624088600","locking_period":300,"voting_power":"18120443000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18120443000000000000000000000","vote_positions":[]},{"voter_id":"imok.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1496000000","locking_period":90,"voting_power":"2244000000000000000000000000","unlocking_started_at":1717345585368,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"car_parts.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6569600000","locking_period":90,"voting_power":"9854400000000000000000000000","unlocking_started_at":1714991687794,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"2296509600","locking_period":300,"voting_power":"11482548000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"1732548000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"9750000000000000000000000000"}]},{"voter_id":"mullthebull.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27830480000","locking_period":240,"voting_power":"111321920000000000000000000000","unlocking_started_at":1728571162160,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"1000000000","locking_period":30,"voting_power":"500000000000000000000000000","unlocking_started_at":1728571173050,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"flokielon.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"93669107700","locking_period":300,"voting_power":"468345538500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"1545538500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"466800000000000000000000000000"}]},{"voter_id":"condog.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28830480000","locking_period":285,"voting_power":"136944780000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"136944780000000000000000000000"}]},{"voter_id":"lastryano.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27027920000","locking_period":285,"voting_power":"128382620000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"128382620000000000000000000000"}]},{"voter_id":"zardoz90.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"24554920000","locking_period":285,"voting_power":"116635870000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"883000000","locking_period":135,"voting_power":"1986750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"118622620000000000000000000000"}]},{"voter_id":"farpost.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"43719400","locking_period":300,"voting_power":"218597000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"218597000000000000000000000","vote_positions":[]},{"voter_id":"cuongdcdev.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3944328600","locking_period":300,"voting_power":"19721643000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"721000000","locking_period":30,"voting_power":"360500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"buildnear.poolv1.near","voting_power":"20082143000000000000000000000"}]},{"voter_id":"nostory.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"800000","locking_period":60,"voting_power":"800000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"734556000","locking_period":300,"voting_power":"3672780000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"1000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"1000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"1000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"673580000000000000000000000"}]},{"voter_id":"309872076a518c9c96935b959d13e2f23546113bc6ea41941e5562b65cbf7085","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"669000000","locking_period":165,"voting_power":"1839750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1839750000000000000000000000","vote_positions":[]},{"voter_id":"0x56D994c08CdC5c9189B4d6863129e5F195C6E8F2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"15034000000","locking_period":120,"voting_power":"30068000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"30068000000000000000000000000"}]},{"voter_id":"0x16E44d8F1BEc8fb6c6770ABE8E8EF199B7723c9a.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27095000000","locking_period":300,"voting_power":"135475000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"135475000000000000000000000000"}]},{"voter_id":"0xA4243e13bD28fa9e0e67704E381cb98d06240C93.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"30000000000","locking_period":300,"voting_power":"150000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"here.poolv1.near","voting_power":"150000000000000000000000000000"}]},{"voter_id":"0x22B052CbBf01cfE2EC5A8424a7d361f376aEaEb0.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"50032000000","locking_period":300,"voting_power":"250160000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"250160000000000000000000000000"}]},{"voter_id":"0xEA3e3612175a894362f3062785adC133D9b98f10.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"285827000000","locking_period":300,"voting_power":"1429135000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"929135000000000000000000000000"}]},{"voter_id":"c47093dc9f12d71135b4940a0744944b7936b0d4458bc1feab0eafb4d3d1249d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":270,"voting_power":"18000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[]},{"voter_id":"0x2929f6FEDD4897236fB97A059aaA1fe057adeB0e.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"30767000000","locking_period":300,"voting_power":"153835000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"153835000000000000000000000000"}]},{"voter_id":"0xE3Ecd1907caa757A4a5C9DA32d7AA34005681C1e.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16251000000","locking_period":300,"voting_power":"81255000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"81255000000000000000000000000"}]},{"voter_id":"0xf5597b4094058efe44fe61C8f2844823Fc566e3c.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"650000000","locking_period":300,"voting_power":"3250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"3250000000000000000000000000"}]},{"voter_id":"0x1430b0DF64C0126c6184D2f28b1cF505f39147b2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"19921000000","locking_period":300,"voting_power":"99605000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"99605000000000000000000000000"}]},{"voter_id":"0x1fb751200fda2aBa12BA2C254c3c02d23E06dB8D.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1508000000","locking_period":300,"voting_power":"7540000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"7540000000000000000000000000"}]},{"voter_id":"0xC479d61faFAd7374af8eE1902d3797E8cCAb0522.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6460000000","locking_period":300,"voting_power":"32300000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"32300000000000000000000000000"}]},{"voter_id":"5d7e273e59ce62b306698ce7fb9a8b885264be2ce90980254c85b361d9d8d71b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"invoices.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"246428600500","locking_period":300,"voting_power":"1232143002500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"1232143002500000000000000000000"}]},{"voter_id":"pat.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6823431700","locking_period":300,"voting_power":"34117158500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"oharanodes.poolv1.near","voting_power":"34117158500000000000000000000"}]},{"voter_id":"seanvan1411.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"31670100000","locking_period":225,"voting_power":"118762875000000000000000000000","unlocking_started_at":1725956985812,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"50000000000","locking_period":225,"voting_power":"187500000000000000000000000000","unlocking_started_at":1725956931014,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"2349000000","locking_period":285,"voting_power":"11157750000000000000000000000","unlocking_started_at":1725957070629,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"number9dream.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12029171300","locking_period":150,"voting_power":"30072928250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"30072928200000000000000000000"}]},{"voter_id":"e5fb81d59683c942333b8f420ab667dcc33beeea5227c19350ca0c1c6b7439dd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1089218300","locking_period":300,"voting_power":"5446091500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"75|mpDAO Grants #7 - June 2024-abrahamrivv.near","voting_power":"2700000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"2746091500000000000000000000"}]},{"voter_id":"ihyshan.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10055324400","locking_period":300,"voting_power":"50276622000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"651000000","locking_period":90,"voting_power":"976500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"51253122000000000000000000000"}]},{"voter_id":"0x32292056970DCe4B9528d71642F765f14E0F1042.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1315000000","locking_period":300,"voting_power":"6575000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6575000000000000000000000000","vote_positions":[]},{"voter_id":"0x8989964863cf4dbaedFFbb4F43Abcf54DA5F5f1b.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1410209000000","locking_period":300,"voting_power":"7051045000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"7051045000000000000000000000000"}]},{"voter_id":"0x1256E7992564AB22e332532472c916Bd8D1e1Ca7.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1735000000","locking_period":120,"voting_power":"3470000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3470000000000000000000000000","vote_positions":[]},{"voter_id":"ivanovic.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"48900983500","locking_period":300,"voting_power":"244504917500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"244504917500000000000000000000"}]},{"voter_id":"developer.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1040497000000","locking_period":90,"voting_power":"1560745500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"650100000000","locking_period":165,"voting_power":"1787775000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"90000000000","locking_period":300,"voting_power":"450000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"10000000000","locking_period":120,"voting_power":"20000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"1000783000000","locking_period":105,"voting_power":"1751370250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2534890750000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"950000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearstar.poolv1.near","voting_power":"1900000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"101|mpDAO Grants #7 - June 2024-criptolaplata.near","voting_power":"150000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"87|mpDAO Grants #7 - June 2024-97c131e898ddae0f081783a3d43dfef5434e098728fed3276eeece5ab5a7a1c6","voting_power":"35000000000000000000000000000"}]},{"voter_id":"e7bfa469cc055cb12f4d90e551e5cb128fda19400da59768b0963cde89fc774d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"natcrypto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"848677601500","locking_period":150,"voting_power":"2121694003750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"47498000000","locking_period":120,"voting_power":"94996000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dexagon.poolv1.near","voting_power":"222000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"rekt.poolv1.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"101|mpDAO Grants #7 - June 2024-criptolaplata.near","voting_power":"350000003700000000000000000000"},{"votable_address":"initiatives","votable_object_id":"79|mpDAO Grants #7 - June 2024-478f0f81f81824a1db9f1c434b73e2c800e2992069a2d0484d050a86406a4402","voting_power":"95000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"81|mpDAO Grants #7 - June 2024-herdaomexico.near","voting_power":"150000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"94|mpDAO Grants #7 - June 2024-cryptopr3neur.near","voting_power":"190000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"120000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"107|mpDAO Grants #7 - June 2024-ae1ddf13d147c6afdb5e0d53274ea31a10f12206bebfa082a231df5a6cfa82d9","voting_power":"220000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"102|mpDAO Grants #7 - June 2024-fr-grant.near","voting_power":"400000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"108|mpDAO Grants #7 - June 2024-553a5330546aa39850aa0e972bc1bec1c7018575ba141fa6f0a379cf28d46d16","voting_power":"169690000000000000000000000000"}]},{"voter_id":"0xCF6d2f6E2839ac50f49a76c1DA88f82F62d29B74.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1200000000","locking_period":300,"voting_power":"6000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000000","vote_positions":[]},{"voter_id":"eventosmetapool.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000","locking_period":300,"voting_power":"50000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"50000000000000000000000000"}]},{"voter_id":"davidmo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17578467300","locking_period":90,"voting_power":"26367700950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"26367700900000000000000000000"}]},{"voter_id":"0x36D93bAC8ffcc3af98cCf3A5A1b2588df5508256.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1418000000","locking_period":105,"voting_power":"2481500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2481500000000000000000000000","vote_positions":[]},{"voter_id":"smile143.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":90,"voting_power":"1500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"1500000000000000000000000000"}]},{"voter_id":"0x7AbAEb445D6c0036B386c1Ccd0c4d58c528AC450.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"0xcc7B864e79fA2eB1Aa43905A74815fac8Dd99fc7.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18472000000","locking_period":300,"voting_power":"92360000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"92360000000000000000000000000"}]},{"voter_id":"0xFFaCD7e55e79fABeD8E6096A5ab325AE38A5faC8.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"75937000000","locking_period":300,"voting_power":"379685000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"379685000000000000000000000000"}]},{"voter_id":"0x26B0Bb09478A8423b53b2854dEfFb8A61A889725.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"102861000000","locking_period":300,"voting_power":"514305000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"514305000000000000000000000000"}]},{"voter_id":"forklift.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2702185900","locking_period":300,"voting_power":"13510929500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"8815929500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"mpdao.poolv1.near","voting_power":"4695000000000000000000000000"}]},{"voter_id":"zzw.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27062790900","locking_period":300,"voting_power":"135313954500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"128913954500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"6400000000000000000000000000"}]},{"voter_id":"nonothenono.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7002268300","locking_period":30,"voting_power":"3501134150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"50000000","locking_period":90,"voting_power":"75000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"3576134100000000000000000000"}]},{"voter_id":"0xEa36e8E31df3eC5dC2350589697dDF346Bc58E24.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1265000000","locking_period":300,"voting_power":"6325000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6325000000000000000000000000","vote_positions":[]},{"voter_id":"0x092cfF73c77a9de794D25b0088DeD0e430733dbb.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8309000000","locking_period":300,"voting_power":"41545000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"77|mpDAO Grants #7 - June 2024-1724cbdb130e5b0f9bd750f666f4fe00581489fcc294dca4ed055c90c76808fc","voting_power":"41545000000000000000000000000"}]},{"voter_id":"alansicairos.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18106969590","locking_period":300,"voting_power":"90534847950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40376184850000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"50158663100000000000000000000"}]},{"voter_id":"mathieutetreault.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5981299200","locking_period":180,"voting_power":"17943897600000000000000000000","unlocking_started_at":1717697591586,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0x4246FF0452B95780122E84bdFfe5Ba9371E1d7a2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"488000000","locking_period":300,"voting_power":"2440000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"121|mpDAO Grants #8 - August 2024-meximalist.near","voting_power":"1000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"1440000000000000000000000000"}]},{"voter_id":"edfernandezg.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13261924800","locking_period":180,"voting_power":"39785774400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"39785774400000000000000000000"}]},{"voter_id":"0xE351625D4E07D185880fdcc3a8FA6E2558d2F470.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16000000000","locking_period":300,"voting_power":"80000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"80000000000000000000000000000"}]},{"voter_id":"14152d8789b96ce70fbe4b105194eeb85052b06a00c839b8b7de70928ab865fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"108876603300","locking_period":90,"voting_power":"163314904950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1798000000","locking_period":90,"voting_power":"2697000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4950000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"66011900000000000000000000000"}]},{"voter_id":"vinh1024.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13958814100","locking_period":45,"voting_power":"10469110575000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10469110575000000000000000000","vote_positions":[]},{"voter_id":"haytham.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"35854400","locking_period":90,"voting_power":"53781600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"53781600000000000000000000","vote_positions":[]},{"voter_id":"0xF7b188d84192e23049dA2E659ddC2d79D7C85e1d.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"550000000","locking_period":300,"voting_power":"2750000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"2750000000000000000000000000"}]},{"voter_id":"a9f49c2f4c066b83c3e71d5ca3ff097c7c6a09707386298da42b52c5927a25bc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12010780500","locking_period":90,"voting_power":"18016170750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"18016170700000000000000000000"}]},{"voter_id":"test_near.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000","locking_period":30,"voting_power":"500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10759382620","locking_period":300,"voting_power":"53796913100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"25000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"28797413100000000000000000000"}]},{"voter_id":"oulrij.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"369566900","locking_period":90,"voting_power":"554350350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"554350350000000000000000000","vote_positions":[]},{"voter_id":"a79cee9b44db1224c8a1f8b3819309d1ce1df8832aee8bf99ba5848269eaa2de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28746963100","locking_period":90,"voting_power":"43120444650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"43120444650000000000000000000","vote_positions":[]},{"voter_id":"marek505.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46639121900","locking_period":30,"voting_power":"23319560950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"23319560950000000000000000000","vote_positions":[]},{"voter_id":"f627b3e2e72da7b2dd98fafa91ed7b56b658142e1e4c0a33d8147dcc081de775","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"jasp83.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"33829927500","locking_period":30,"voting_power":"16914963750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"134|mpDAO Grants #8 - August 2024-marcrypted.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"3450000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"133|mpDAO Grants #8 - August 2024-ffbc3084a4d75ed3c28a7b6c26f44a4ffb43dc3959365d80d70a9660913cbacf","voting_power":"3464963700000000000000000000"}]},{"voter_id":"724d3919c74c7d07b1be7359acea2ba45f244fa2da476fa4988cbc1b9338f967","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5fd80e617970059ecc99bc0b6542eacc7da5288361918dc31a637918b937921d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"179119020600","locking_period":90,"voting_power":"268678530900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"268678530900000000000000000000"}]},{"voter_id":"155ab93ae7dba33a94552113da59100609c32c29df1faa39fdffcacf51ace7ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"nickharrington.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1674280100","locking_period":300,"voting_power":"8371400500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8371400500000000000000000000","vote_positions":[]},{"voter_id":"khoacoi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1198231100","locking_period":105,"voting_power":"2096904425000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"299557825000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"1797346600000000000000000000"}]},{"voter_id":"rockpath.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"282264232200","locking_period":90,"voting_power":"423396348300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pathrocknetwork.poolv1.near","voting_power":"423396348300000000000000000000"}]},{"voter_id":"300525dbba750911263e4f330c25aa340463745b94246b866b714fa0f8ef867c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"margarine-escapist-agonizing-epilogue.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"47161820000","locking_period":180,"voting_power":"141485460000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"176410527700","locking_period":300,"voting_power":"882052638500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1023538098500000000000000000000","vote_positions":[]},{"voter_id":"170721a8d15577b6c359de7132195102f9a89c0362244038e382197d40e4011a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0xc8B19839ae371bd541F20B15c3A3CB82BFB6A6C6.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1450000000","locking_period":300,"voting_power":"7250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7250000000000000000000000000","vote_positions":[]},{"voter_id":"pedrompg311.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10791900","locking_period":60,"voting_power":"10791900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"liver.pool.near","voting_power":"2000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"111|mpDAO Grants #7 - June 2024-c0d0c8b3bfcbc17347002733be3e2d17df833d2931c425eaa40bd7e73a4509e3","voting_power":"8791900000000000000000000"}]},{"voter_id":"0x3f6b5Df4a6fDAE2b58e3F5aA7037E68E1Eac9188.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1500000000","locking_period":300,"voting_power":"7500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"7500000000000000000000000000"}]},{"voter_id":"8fc648574b6aafaf9a966fb173827a3efcf3fc2b56bef501910246df43dcaa50","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"26695179500","locking_period":30,"voting_power":"13347589750000000000000000000","unlocking_started_at":1715956794887,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"daddyarbitrage.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5920373200","locking_period":300,"voting_power":"29601866000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"29601866000000000000000000000"}]},{"voter_id":"vandal.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6931309200","locking_period":90,"voting_power":"10396963800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"10396963800000000000000000000"}]},{"voter_id":"vandao.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10964420300","locking_period":90,"voting_power":"16446630450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"16446630400000000000000000000"}]},{"voter_id":"9896.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"403071612900","locking_period":90,"voting_power":"604607419350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"604607419300000000000000000000"}]},{"voter_id":"zkjandro.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"520518981000","locking_period":300,"voting_power":"2602594905000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"2602594905000000000000000000000"}]},{"voter_id":"decollation.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"299392600","locking_period":300,"voting_power":"1496963000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1496963000000000000000000000","vote_positions":[]},{"voter_id":"zuenti.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"239648900","locking_period":300,"voting_power":"1198244500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1198244500000000000000000000"}]},{"voter_id":"kentnearvn.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000000","vote_positions":[]},{"voter_id":"orangejoe.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8705295200","locking_period":300,"voting_power":"43526476000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"43526470000000000000000000000"}]},{"voter_id":"dcc81d49b62bf89e1a07de111c32aa89fb4b6859e8bd47fa73052df9e3599244","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1884816698900","locking_period":30,"voting_power":"942408349450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"942408349450000000000000000000","vote_positions":[]},{"voter_id":"0x86874cD0aa311E56593CD045F35dbc038057D614.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2486000000","locking_period":30,"voting_power":"1243000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"243000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"1000000000000000000000000000"}]},{"voter_id":"augie.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"90932694400","locking_period":300,"voting_power":"454663472000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"454663472000000000000000000000"}]},{"voter_id":"0xA67Dd142A1395b23607f47C1b262C973f6258E7E.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"jablko.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1852644700","locking_period":300,"voting_power":"9263223500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4705289400000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"4557934100000000000000000000"}]},{"voter_id":"apitchdeck.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10152269900","locking_period":300,"voting_power":"50761349500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50761349500000000000000000000","vote_positions":[]},{"voter_id":"chloethedev.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"89282812370","locking_period":300,"voting_power":"446414061850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"164292100","locking_period":30,"voting_power":"82146050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"443228017600000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"3268190300000000000000000000"}]},{"voter_id":"kaymens.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"81900000","locking_period":300,"voting_power":"409500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"409500000000000000000000000"}]},{"voter_id":"4a6f65.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"53694286400","locking_period":300,"voting_power":"268471432000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"268471432000000000000000000000"}]},{"voter_id":"adeusa.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"224293500","locking_period":300,"voting_power":"1121467500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"1121467500000000000000000000"}]},{"voter_id":"a5c354d15df4256bc5bcc1996965a7bdc60db7f6cbdd342e16583f62b63bc0ae","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":1727996056671,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"a0394de7c6bdb3fa4ae09a25853e467f3fe5223b8f4917a5ff223195018ede14","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"140234200","locking_period":90,"voting_power":"210351300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"restake.poolv1.near","voting_power":"20350000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"190001300000000000000000000"}]},{"voter_id":"7baced7c2334e6e975e2ea6965af15fdae7deba588594049692392f985287eb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"kaion.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"400000000","locking_period":90,"voting_power":"600000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2586055900","locking_period":300,"voting_power":"12930279500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"13530279500000000000000000000"}]},{"voter_id":"donsober.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1299019500","locking_period":120,"voting_power":"2598039000000000000000000000","unlocking_started_at":1716481383787,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"8fe8e86771824d3880fe18798f56baf5c24640850ebdc142ba2b9760cd5718cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"47285461100","locking_period":150,"voting_power":"118213652750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"118213652700000000000000000000"}]},{"voter_id":"0x1968145D78FE9799871522a067cbc161CE5603a8.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"swissstar.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000000","vote_positions":[]},{"voter_id":"0x5650F30f719e6E9C0D00b2fdd20C7ba8649337C0.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"14881000000","locking_period":300,"voting_power":"74405000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"74405000000000000000000000000"}]},{"voter_id":"jared.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"138363492500","locking_period":300,"voting_power":"691817462500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"691817462500000000000000000000"}]},{"voter_id":"bloqnode.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"14293000","locking_period":180,"voting_power":"42879000000000000000000000","unlocking_started_at":1716765769311,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0xC3F836EC06A2202af23e59997A613CA0722F35d1.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"osc.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5432906700","locking_period":300,"voting_power":"27164533500000000000000000000","unlocking_started_at":1716842243888,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"47e7f13cbaf178c66d2c12d93593513a0f406188c40e72977f9b1a4deed15958","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"43049110","locking_period":300,"voting_power":"215245550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"215245500000000000000000000"}]},{"voter_id":"cozybird.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"301008270300","locking_period":300,"voting_power":"1505041351500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1505041351500000000000000000000"}]},{"voter_id":"walapril.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"821421300","locking_period":300,"voting_power":"4107106500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"2344580500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"1762526000000000000000000000"}]},{"voter_id":"profit2.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"601830443400","locking_period":300,"voting_power":"3009152217000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"3009152217000000000000000000000"}]},{"voter_id":"4083b50cb907509eba10b56a41300f719d341175cfb92c819bd07a4fe3efddb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1034519658100","locking_period":300,"voting_power":"5172598290500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearkoreahub.poolv1.near","voting_power":"5172598290500000000000000000000"}]},{"voter_id":"bianca.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2556353500","locking_period":300,"voting_power":"12781767500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1448605500","locking_period":30,"voting_power":"724302750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"13506070200000000000000000000"}]},{"voter_id":"huktodev.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7164006000","locking_period":240,"voting_power":"28656024000000000000000000000","unlocking_started_at":1722119926238,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"nedlar7.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"300090000","locking_period":90,"voting_power":"450135000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"450135000000000000000000000"}]},{"voter_id":"phars.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1295718700","locking_period":75,"voting_power":"1619648375000000000000000000","unlocking_started_at":1732333528450,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0x773c24F007Ef074CacD382be18e5857785837D4a.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"201000000","locking_period":300,"voting_power":"1005000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"1005000000000000000000000000"}]},{"voter_id":"arttrader.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46585365400","locking_period":120,"voting_power":"93170730800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"93170730800000000000000000000","vote_positions":[]},{"voter_id":"0x12f5ecd00fe3851f4d7eB2bf77E72E3299f2C868.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"0xb5621490079D534D737B6B8837947FB376D5F629.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"0x263779E7338DEf14C431D6301F97Cc846D69C099.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"0x7844317568728BdC9475E939553A2A0446D19E2e.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"dosisweb3.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18578826400","locking_period":120,"voting_power":"37157652800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"128|mpDAO Grants #8 - August 2024-aleiranova.near","voting_power":"37157652800000000000000000000"}]},{"voter_id":"0x6DEe4a729430078CD281988Ac76292cf3B27e2c0.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"692000000","locking_period":300,"voting_power":"3460000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3460000000000000000000000000","vote_positions":[]},{"voter_id":"0x13DF946B95ee60922257af5811423ea9dc353F36.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1250000000","locking_period":300,"voting_power":"6250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"6250000000000000000000000000"}]},{"voter_id":"0x93857Ad03a9Ab1226D909FFa49BcA37e70e4D32d.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7959000000","locking_period":300,"voting_power":"39795000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"32835000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"86|mpDAO Grants #7 - June 2024-la-incubadora.near","voting_power":"6301000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"70|mpDAO Grants #7 - June 2024-a5732a8195200f4203a633cea3dc154b696ed628d2ed4a322f214849f2247307","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"77|mpDAO Grants #7 - June 2024-1724cbdb130e5b0f9bd750f666f4fe00581489fcc294dca4ed055c90c76808fc","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"75|mpDAO Grants #7 - June 2024-abrahamrivv.near","voting_power":"300000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"79|mpDAO Grants #7 - June 2024-478f0f81f81824a1db9f1c434b73e2c800e2992069a2d0484d050a86406a4402","voting_power":"50000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"69000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"67|mpDAO Grants #7 - June 2024-baryo01.near","voting_power":"40000000000000000000000000"}]},{"voter_id":"21819.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"103204600","locking_period":300,"voting_power":"516023000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"516023000000000000000000000"}]},{"voter_id":"marmaj.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"106206400","locking_period":300,"voting_power":"531032000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"17000000","locking_period":90,"voting_power":"25500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"556532000000000000000000000"}]},{"voter_id":"manyoil.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"74151250700","locking_period":300,"voting_power":"370756253500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"370756253500000000000000000000"}]},{"voter_id":"tensors.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2942857100000","locking_period":300,"voting_power":"14714285500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"9714285500000000000000000000000"}]},{"voter_id":"0xAa3F1faAa3a93Bb5799A8464b14F0FE427fC231f.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"21281000000","locking_period":300,"voting_power":"106405000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"106405000000000000000000000000"}]},{"voter_id":"nftgol.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"200000000","locking_period":195,"voting_power":"650000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"23000000","locking_period":300,"voting_power":"115000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"465000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"70|mpDAO Grants #7 - June 2024-a5732a8195200f4203a633cea3dc154b696ed628d2ed4a322f214849f2247307","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"78|mpDAO Grants #7 - June 2024-irvingzcc.near","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"73|mpDAO Grants #7 - June 2024-mrcoingdl.near","voting_power":"100000000000000000000000000"}]},{"voter_id":"happypenguin.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"261734447600","locking_period":300,"voting_power":"1308672238000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1308672238000000000000000000000"}]},{"voter_id":"7ed4c34d7903e97120fc95415f4c68b61d4594ed6108bbacd73eaaae96eccb62","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000","locking_period":300,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"480000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"10000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"114|mpDAO Grants #7 - June 2024-aksanova33.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"9656a3cd3da0d65563dce927c9c184808a7c9d7d077d199c999ebe65ef526af9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"667000000","locking_period":90,"voting_power":"1000500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"633000000","locking_period":90,"voting_power":"949500000000000000000000000","unlocking_started_at":1726063035006,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"500000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"114|mpDAO Grants #7 - June 2024-aksanova33.near","voting_power":"200000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"112|mpDAO Grants #7 - June 2024-hoangthien1109.near","voting_power":"200000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"200000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"200000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"200000000000000000000000000"}]},{"voter_id":"5d0680d19b8b51114209be84e5545049fd39055736a470e69edc8b1b45530758","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1152654900","locking_period":300,"voting_power":"5763274500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5760774500000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"500000000000000000000000"}]},{"voter_id":"0x18869e75ADdB335FeFDD5aaE02d98326EABCC6d9.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"24296000000","locking_period":300,"voting_power":"121480000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"121480000000000000000000000000"}]},{"voter_id":"0xCdDE67a6E1666a1d56BaB73DCed9bEA132895Ba9.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1390000000","locking_period":120,"voting_power":"2780000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1159000000","locking_period":300,"voting_power":"5795000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"8575000000000000000000000000"}]},{"voter_id":"67fe33da5d926910ec480377ea76d119efb39486e779db0735cdaaba11ec862b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":225,"voting_power":"11250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9750000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"88|mpDAO Grants #7 - June 2024-100diasder.near","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"69|mpDAO Grants #7 - June 2024-odisea.near","voting_power":"500000000000000000000000"}]},{"voter_id":"37e9df77934c86417c090e62bcc174730061cb3fc1fa2eadfad96709ebc1c7ac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9200000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"84|mpDAO Grants #7 - June 2024-613d92d562c428aa478083dc0be04321ef76d48d836610f63cf9948122d4b1cd","voting_power":"300000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"500000000000000000000000"}]},{"voter_id":"f3e4b457a4f0dc8b7130f347b90370f2da12174c92c8e2c2ff72d3d9a07e202f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9400000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"82|mpDAO Grants #7 - June 2024-2f267735d0046f372f1cb6cb6f6c6201bd66b0c3cdf03a4f5ad9fc3877000cbc","voting_power":"500000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"100000000000000000000000"}]},{"voter_id":"yamadori.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"42637421300","locking_period":300,"voting_power":"213187106500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"100100106500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"113087000000000000000000000000"}]},{"voter_id":"rogservin.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"669000000","locking_period":300,"voting_power":"3345000000000000000000000000","unlocking_started_at":1718692127142,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0xEe1140ab647Bf0bD4Aa9603376Ed95a969243096.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"happypenguin1.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"245260656500","locking_period":300,"voting_power":"1226303282500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1226303282500000000000000000000"}]},{"voter_id":"cybersecurity_rok.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40235154800","locking_period":300,"voting_power":"201175774000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"201175774000000000000000000000"}]},{"voter_id":"0x55b8e2c4AE5951D1A8e77d0E513a6E598Ee0bE86.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"23323000000","locking_period":300,"voting_power":"116615000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"epic.poolv1.near","voting_power":"116615000000000000000000000000"}]},{"voter_id":"venomity268.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"458730000","locking_period":300,"voting_power":"2293650000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"2293650000000000000000000000"}]},{"voter_id":"0x837667C7C5cB49609ed5BFA5d3B2f1bbae81d8bA.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2263000000","locking_period":120,"voting_power":"4526000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2742000000","locking_period":300,"voting_power":"13710000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"18236000000000000000000000000"}]},{"voter_id":"takeshi7.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2229447900","locking_period":300,"voting_power":"11147239500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"11147239500000000000000000000"}]},{"voter_id":"bradrussell.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8870168900","locking_period":90,"voting_power":"13305253350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"67|mpDAO Grants #7 - June 2024-baryo01.near","voting_power":"13305253300000000000000000000"}]},{"voter_id":"0xD2F5090E322E1Ef6f4F0699Bbaf5b68992eAa330.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1173000000","locking_period":30,"voting_power":"586500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"893000000","locking_period":300,"voting_power":"4465000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"68|mpDAO Grants #7 - June 2024-3sblockchain.near","voting_power":"5051500000000000000000000000"}]},{"voter_id":"0x21F713f686fcF7b9ab4D2f144eCe611A97b15f41.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6551000000","locking_period":300,"voting_power":"32755000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"65|mpDAO Grants #7 - June 2024-auroraafrica.near","voting_power":"32755000000000000000000000000"}]},{"voter_id":"happypenguin2.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100824695000","locking_period":300,"voting_power":"504123475000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"504123475000000000000000000000"}]},{"voter_id":"0xF42Cd128cDc69bC8d1e7eacC413342064Ce07121.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1200000000","locking_period":300,"voting_power":"6000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"68|mpDAO Grants #7 - June 2024-3sblockchain.near","voting_power":"6000000000000000000000000000"}]},{"voter_id":"0xAa6f8999a3675d755d471b0c8Be8735453abE7C1.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1200000000","locking_period":300,"voting_power":"6000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"68|mpDAO Grants #7 - June 2024-3sblockchain.near","voting_power":"6000000000000000000000000000"}]},{"voter_id":"fionng.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000","locking_period":90,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"0xfC86655494C7B2A9bDFC014B6819A8Ed6D31fc76.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5114000000","locking_period":300,"voting_power":"25570000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"134|mpDAO Grants #8 - August 2024-marcrypted.near","voting_power":"25570000000000000000000000000"}]},{"voter_id":"funkycarioca.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"125461937590","locking_period":300,"voting_power":"627309687950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"580505508500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"46804179400000000000000000000"}]},{"voter_id":"0xe668da86f28f94F36aEb1a21F1f5fBDC681233E5.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1570000000","locking_period":300,"voting_power":"7850000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"850000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"106|mpDAO Grants #7 - June 2024-ludium.near","voting_power":"2000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"105|mpDAO Grants #7 - June 2024-marcelorefi.near","voting_power":"2000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"1000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"2000000000000000000000000000"}]},{"voter_id":"0xB1ea931b49e6671f2CdC67e5704B763a244Dee2d.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"324000000","locking_period":120,"voting_power":"648000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"250000000","locking_period":210,"voting_power":"875000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"1523000000000000000000000000"}]},{"voter_id":"15eb7941392955eb2b0a28fc9bdafcd94c8d7526b140005dbaac8be965a1d204","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1711315010","locking_period":300,"voting_power":"8556575050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"polkachu.poolv1.near","voting_power":"5756042500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"2800532500000000000000000000"}]},{"voter_id":"ledelegated.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000000000","locking_period":120,"voting_power":"4000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"210000000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"76|mpDAO Grants #7 - June 2024-latambuilder.near","voting_power":"1200000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"108|mpDAO Grants #7 - June 2024-553a5330546aa39850aa0e972bc1bec1c7018575ba141fa6f0a379cf28d46d16","voting_power":"700000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"77|mpDAO Grants #7 - June 2024-1724cbdb130e5b0f9bd750f666f4fe00581489fcc294dca4ed055c90c76808fc","voting_power":"350000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"94|mpDAO Grants #7 - June 2024-cryptopr3neur.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"92|mpDAO Grants #7 - June 2024-musicaw3.near","voting_power":"80000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"95|mpDAO Grants #7 - June 2024-edfernandezg.near","voting_power":"120000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"100000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"98|mpDAO Grants #7 - June 2024-fdea0a6bcd48da25f783feb9b66627698cb00a7b34f064eb3ff59e3367b625de","voting_power":"420000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"450000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"85|mpDAO Grants #7 - June 2024-fpsgames.near","voting_power":"70000000000000000000000000000"}]},{"voter_id":"hannahh.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"237185100","locking_period":300,"voting_power":"1185925500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"1185925500000000000000000000"}]},{"voter_id":"0xC1a7E732413D919333d6C9c42b02E29deD5c84E6.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"541000000","locking_period":300,"voting_power":"2705000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"94|mpDAO Grants #7 - June 2024-cryptopr3neur.near","voting_power":"2705000000000000000000000000"}]},{"voter_id":"lele.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2465311826400","locking_period":90,"voting_power":"3697967739600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3697967739600000000000000000000","vote_positions":[]},{"voter_id":"0x5504E7AeA0cf0582f7db6fD9C810f3810425a6c2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2438000000","locking_period":300,"voting_power":"12190000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nacioncrypto-parceros.poolv1.near","voting_power":"6000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"6190000000000000000000000000"}]},{"voter_id":"solari.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"250000000000","locking_period":300,"voting_power":"1250000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"1250000000000000000000000000000"}]},{"voter_id":"ea4ddb1c9a3e1a65438b5cd1069f8b9d324601d9f80cc207fd21aee433a129d4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"200000000000","locking_period":300,"voting_power":"1000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_01.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearvietnamhub.poolv1.near","voting_power":"200000000000000000000000000000"}]},{"voter_id":"misfitsmedia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1590446008510","locking_period":300,"voting_power":"7952230042550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"2500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dexagon.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"308218130100000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakely_io.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"144011912400000000000000000000"}]},{"voter_id":"68cd5e5436d2013eb95aeadf595d889a0ae79ea00a86996d64a3ca8f397275da","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":195,"voting_power":"13000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[]},{"voter_id":"0x34a9E0fc6dBBd4144dFEB3b6895900f519436E06.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"624000000","locking_period":120,"voting_power":"1248000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"451000000","locking_period":210,"voting_power":"1578500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"2826500000000000000000000000"}]},{"voter_id":"fedb6b1f672872f7d1aab1ea9dd4a6c1b7781c9f23645ded83307acd071e2a7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"0xCeFF651357EAC55845e3F33a5623e23C5Df8BB47.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000000","locking_period":300,"voting_power":"2500000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1500000000000000000000000000000"}]},{"voter_id":"0x0dA891f618e773f3b765bcE938c8DB1c55D2A588.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"90782000000","locking_period":300,"voting_power":"453910000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"453910000000000000000000000000"}]},{"voter_id":"cquang.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18429890800","locking_period":60,"voting_power":"18429890800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1720462600","locking_period":90,"voting_power":"2580693900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"21010584700000000000000000000","vote_positions":[]},{"voter_id":"0xEd88424737639e3c891F3f2357E82f33676d98C1.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12315000000","locking_period":300,"voting_power":"61575000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"61575000000000000000000000000"}]},{"voter_id":"27904ea9d2b34fd4cc2a7397b0331ee42526fcb368dd34e2e6a9498e3d8b20cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"30811635900","locking_period":285,"voting_power":"146355270525000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"146355270525000000000000000000","vote_positions":[]},{"voter_id":"ac426054401e2565ad5083b43a1f832e02701e86a83a586d57581bef4ac9a67e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"0x8c7b8474dC370703Ac10D95d91e8f4D26ce41797.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000000","locking_period":300,"voting_power":"500000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"iamcryptobro.poolv1.near","voting_power":"200000000000000000000000000000"}]},{"voter_id":"palomaetienne.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"667879810","locking_period":120,"voting_power":"1335759620000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"1335759600000000000000000000"}]},{"voter_id":"meta-pulgas.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"174540033280","locking_period":300,"voting_power":"872700166400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10843520810","locking_period":300,"voting_power":"54217604050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"926917770400000000000000000000"}]},{"voter_id":"kenrou-it.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"55008403300","locking_period":300,"voting_power":"275042016500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"275042016500000000000000000000"}]},{"voter_id":"345329086d811eb171962ecfcfca5e95752bc5d21d1fbb90f6a2242130d8aa1c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1931646300","locking_period":120,"voting_power":"3863292600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"3863292600000000000000000000"}]},{"voter_id":"vinc.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"75590282400","locking_period":300,"voting_power":"377951412000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"377951412000000000000000000000"}]},{"voter_id":"mizu.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":285,"voting_power":"9500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"43927100","locking_period":300,"voting_power":"219635500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"229135500000000000000000000","vote_positions":[]},{"voter_id":"babami.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"305751800","locking_period":60,"voting_power":"305751800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"731186000","locking_period":90,"voting_power":"1096779000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1402530800000000000000000000","vote_positions":[]},{"voter_id":"lucasdeprueba.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1121063700","locking_period":300,"voting_power":"5605318500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"5605318500000000000000000000"}]},{"voter_id":"9e3c217ede31b75aac8436ddfbbe8ca71a75808dd5f8069a4e52749d9dd6711e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"598762800","locking_period":120,"voting_power":"1197525600000000000000000000","unlocking_started_at":1723178304409,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"gridmanservo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"88963850000","locking_period":120,"voting_power":"177927700000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"177927700000000000000000000000"}]},{"voter_id":"0x71610bC1aCF75d39fCf962076D97bE17cd7105Ac.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1938000000","locking_period":300,"voting_power":"9690000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9690000000000000000000000000","vote_positions":[]},{"voter_id":"millyrodriguez.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"446738100","locking_period":120,"voting_power":"893476200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"353551270","locking_period":300,"voting_power":"1767756350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"2661232500000000000000000000"}]},{"voter_id":"0xfc10B4f288Dfcc996EB819021934536095a7Ce90.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"725000000","locking_period":300,"voting_power":"3625000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"3625000000000000000000000000"}]},{"voter_id":"mp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"105798270600","locking_period":180,"voting_power":"317394811800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4831000000","locking_period":90,"voting_power":"7246500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"150000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"74631311800000000000000000000"}]},{"voter_id":"bef43bd27b338103ac85d2b0387fe2412306b9800877e66331e3095f36bb5b77","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"151929000000","locking_period":120,"voting_power":"303858000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"303858000000000000000000000000","vote_positions":[]},{"voter_id":"0x137c265E744687bdBC897FF288d2F94C42b62387.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5658000000","locking_period":300,"voting_power":"28290000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"28290000000000000000000000000","vote_positions":[]},{"voter_id":"c11e452e9d223ada5937b4e867a992fbff3dbc8113a3bef712ff1f84e707d052","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"23000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"cristian_nacional.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"41000000","locking_period":30,"voting_power":"20500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"50000000","locking_period":90,"voting_power":"75000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2595500000000000000000000000"}]},{"voter_id":"juanes.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"48000000","locking_period":60,"voting_power":"48000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2548000000000000000000000000"}]},{"voter_id":"blntmain.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3043797600","locking_period":90,"voting_power":"4565696400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"210000000","locking_period":30,"voting_power":"105000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4470696400000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"200000000000000000000000000"}]},{"voter_id":"angie_pao.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"41000000","locking_period":30,"voting_power":"20500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2520500000000000000000000000"}]},{"voter_id":"0xa2d237ecF8F29689Bf86d72f97beBe9EF85220A8.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"34643000000","locking_period":300,"voting_power":"173215000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"173215000000000000000000000000"}]},{"voter_id":"3e4cee760d5da969b8ec3f4fb9eb8f49a0b116a23ba6b34ee58ed5fd2825c063","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"589000000","locking_period":300,"voting_power":"2945000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"945000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"2000000000000000000000000000"}]},{"voter_id":"0xA802EedBa8C8C9959cC7cb47CA536a06D485DcfB.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1506000000","locking_period":300,"voting_power":"7530000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"7530000000000000000000000000"}]},{"voter_id":"0x1497848AAF8F20d7F4e32501FF3D240e388Ba235.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"91065000000","locking_period":300,"voting_power":"455325000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"142070000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"313255000000000000000000000000"}]},{"voter_id":"27d9ecdd62122688ad5f957b75b7e49d534a09990ef4cc51a792bff893df5732","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2869ed8b3674730dd29cdf01fdf29005c85d8adce6b7cbd8b958f49de445a6b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6e57fb53931fe3b39af8c654587c1336a9a1e421edb6198c829ed61102ff125a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"5439daea621f0c5a5f845d19e779463934590b4f41b683f1f03e4ea03222006f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"20000000000000000000000000"}]},{"voter_id":"40a6d21456433e6c270130d72437460019245779582d477794f1bdd57ecb6a2c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"34000000","locking_period":300,"voting_power":"170000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"119|mpDAO Grants #8 - August 2024-wuipod.near","voting_power":"5000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"50000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"50000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"50000000000000000000000000"}]},{"voter_id":"080e6108d06914a004453c4f849897417cd87579688d316ac53e3c46a3a0197b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"551a3a5188be41e7c27af497a6b786f6af33ba43c58e9106a68fe99134f65fca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"dc046584136b3be4de731441a95c2a602c25cf006f37b783e340b916a597994b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"79f4a7a847a95acb0ee11959852bb76d30d650b878c96a136688e8aecd1b83d5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bf80de337d4ab1660da7b7f4b843df1380e166fb923d175a18d372263fd09280","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ce231ad89a500d2641cc4b71ae6400a4a9522bf263b3c11900a26073cc126f29","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"4f085a61ce00d684798f1e83d0e438e9f1be98622b33f1db512566c554268663","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"871d7c902ab362e6c777a3fe7b702b0077b174a404c13d6e551a47b0acf570a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6ec6bc9dc928b72b4363fb7cc5c3abe92f810a3b612dcc457d13ee696a78897c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"3000000000000000000000000"}]},{"voter_id":"ccd9f3cfad712e6f5fddc0a7c8935be20083824a62f780a49a1802f8d8ca0358","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4e283f659382e0611553695153424adb66e6222e0ee5a024ed98e8962a20900b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"465d7752c059302ce8eb9ec94c0141d650e072ad20fa6eeb304841b1d3693a0e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"8d89586d5043e09131c2536b0ea2624e6be60e501d31042d77810e3736cbfb2f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9b62c473cf563f33409cd8de6c95a7aa50e36fab9ee3ae665c06acb4e3d3ed41","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"82c358635147b474d097f8c8af8f88d1ec07fa7fc300ca461004e0869ccad68f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"145f614111e81b7e43cd133599d63cd5b2559a15b435de66fb14cd266945a29f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"724d617b38099343899ce0e774ca65a8161d1df6d64ab41c689f618a881c7c2e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"37488f54cd9af0e9688ce56e13f026197437402ba0038cd5c929d1f2f1e15573","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"27c783b47b0155afceec13739ff7f48c25fa052d94dac9d29905a81c97641187","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c9e350fd02b7f51b3789fc142896ba8f59732ea7c4303e3f5ad38377f9b042c1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"ddb1700afe3bd55f34f6daf7b60e3c17946eab8e6d165ef72342c8fcb83ed48c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"acc95270fb0aca46d0e43a8e8b67d8542aa07a9ae4c960e0e22142a58db7039a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":240,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"727175ee48957b652c4eb9e1f3386e5ae4b1e04fb90a920922741364216336b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3aa31f123dbcd4623c2242403d92da66549f137cf36076ae846604483d914f70","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"509eb55b76a82eac23605481aa402bc9203d6a44c378a13bde10b6639b43a50e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d04e08aac303494787086c069d5610cb473d1f89085a4fe801378fed9fde9837","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"56e80825d408c2c9dda78bf1f02be638678b65d672752e8c9716d2f40520d983","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d6254335ff5aebd172073d28a1328373f2a55c8cd7e3b1e6a4db00cfceeeab43","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7b5b78c071c8f8381752135e3aab8f7db9d000224e6bfba092a0d6a4265f99ff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"15c3596cf7391ccc414095e6c6275fd1df3b21b705d8c9b3ea9d86a2d74df757","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"45eaf934e0497dfdf044426aba4ea2a71698747054b0026705eae32ae08fba82","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"30e156790e33afc94d79e5a011a86fe321df75b290cebd41128d9475ea3a798e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c4a6a8fbc679ba0178931f874f6c8889a15293715e62302adc82a5056fe56c52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4df8cdf0c996c60267782f1ea4f715b4457a0e255b64444b7667c5802e3a15e4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9ad0acfa09155485e83b7ca2fdff74778a5213f3a1fc458ac124006730df5288","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4c96376bd93c70a029006ad678dbd0bd23c9581c0826b50f49e18355de6a4624","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d8db0f4398c25971a15ed897df6952ac56ef7f1696e171b10f0a74b3abd52c63","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f0e6a55316073c638b8fba4768aa10a44569be63afa4bad08f7900a98d1cefe7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"600f83be2d72903dcba587f44b9c23055ce944d028a7a2bb1b3477347ba1f9b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"28000000","locking_period":180,"voting_power":"84000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"92000000000000000000000000","vote_positions":[]},{"voter_id":"b9a6051bf04d420d3bead6eae864f9625738035440ef98d0416d24fce4b031c7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7dd3535ece7b1272c91009f1b64c146cca76c2a33d2bb45850ad6acd3cae641a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d150624872f14dc33334051fe65560eeb3bc29649dfcda1b4378b0ef2e5bd858","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"45ddf8c502c4d473262e48e15edca49764fe287ee7608f837adb9ac4451e6d54","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3d910f506564edec55243439dd320e1cccc9d974c8bbae1c3f84c1a624b4d0e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"d60246859abb16dfdfffa0f9a8853e151aaec35aaef63f6a5e779e2816f4ca02","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"40fb70571de5befb4288d1349e748bd4f66673eda7ebb2414edb0f08b420d048","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4cd3bd3730d2170fe8a08d8bcd8a8d65caa6e70d4dd9c21fac3140c0ceaae2e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"28f019d94ea494bc9b6fb6f80e1fc9b9e1f64cd3f01a818dcc110df215c12c51","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"df6af62cde824f1107570bc89c29e9ea77ed7cdc90946851e79c5cb97d319bd3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[]},{"voter_id":"b3c6d1641621bdf7d6c5dfb5ee79b6d4a7db01e0e3e00b51a0a8196b593e128c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cdb63c30bcfe7f8639be1f4450b54fb3c93dcc94d6fea4fac2a817c19beb6e9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"d58324802e2d1b14768c23cd97ee96b9d07f1998c949d7086b2d0bdbfa2f2044","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"126d31434377f67c1d9239c4ee08a9f4096b528a32d07fd3392c62ce57668347","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1e61b6b2f78fe3183e1d1e8c84653911175e1e4c7e513317522318595d3bbd41","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"879685fac06351252a970f57a7e97f080af01157a48c615b62773a58dedc8f21","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"21bb7e4a279588b15caf9f8f2c6830b0977d05b7f7eed520d9db4823ca8d75ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"51de4a543301f6a463d38254610d240d4250ef4839539247ed5a84dd4df6dda6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bf5ac95ee86e9cb3bb3ec2cdce2f4ee6960d0e04a768644127ed308d62093f53","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"adcaa3c13e241dfeadb60395804319a8eac4c8e1fc7a2f10af7044b56ae958a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[]},{"voter_id":"fdf274f98c681f001c70e3c7b1cc07c20b30540a45df4a494b8a570522bc0051","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"3d2c4a4b8008e32675af08c2291eb71ad0152368d0e9ac0388568590e488605e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a72465717e9a01592dba7417cf3286cf6ef5fe6f566e4b7618c9a325e832f87d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6de9ad273eb4cb144933aa96adfa2d13d2972f1f28149ebe8e576315314d33fc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4d247b55e9c962cc0a91e176cdc203cd29793fdb3ed9929d35c94dea04c46846","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"94b993f2e816c9df4498513dd1ad0c0d5b4faba22450a048508a90ed5b322033","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"635dcf64c3f388cc28f201f0638a1fd4902ff06b9d38a373fcbfe8d31876c073","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"69f814b94f50e0103dbe6188143f92ed677249129e2909081bdf9d350171ee92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9ac5f9a4826d44cab50a4445c1c5661e53354dc5c9bcc2ace728ec8bbb9fb0bf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"0628b0e0b88939a9f0dd7d8090ead5a44444249e1a02fbb21c9ee48bcc7ab589","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1deeef63c1a662bb80dd272e9b1592441177abcca0a014b98a59be9c3267f65e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"61e62c1d6a197fc777aa86cdbd7900090573bdb164a896220d676fe893de62f0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fb721bbd98b3314881c3221790ecb609b9fc9e0b03bcc3b9397bc661f7998d0a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"784c13604c4ee56b6f04b3ec81191669afb4feffb37addd54c0533999288a966","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5a7e4cd1c0807e267673c87c8c1aafa838238e081e5bc4fb3eb74b07157cbd48","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"032822c2df35abd5d64267ac3646fa5cbac384931ea8eb910d765c3de5f4ee68","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"64265e3de75158f61b9ef55c4706cfe915bb0e2f93612a28f68fca4ea3dbadda","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":210,"voting_power":"7000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[]},{"voter_id":"0aea9dae0b824a5b1ddffc82a085525e272b27604d7a23e86273638cb8bccaa9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"25ed418ec4f404b648014a7a67132185819133130e342a20270ea0bcb38423d0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"566333ac4be558d078caae7e9f03c229769ef622d526f64d0329288cf5ed3e64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5e615ab58113cb49ce825e9e8b03970612e7989d85241e03ebf176a8031dd03c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"29dc126b65387ce46a7c310c66e54de928eca68d5a5105df7e2407484e89ac21","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"fe3bd303add191acffeb5c9e68c6180bb73ef457011486327c40fbc210e1bd44","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"12a7d19c45d66f8ff9ce283472113db8cfba5b02c3d355f1bf99e0b9a9d3ae12","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bf4ed5f03e7250a191193e90ac855f2cd10d6cd6a4252ff85ca9b25a65e886a7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e3119c5646d65b71b93c2eefd0ee7e9c04ec4703230394f7cbd5180dd87bfe05","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"31e6e8ec45ba36b296f72527326b8bdc3dc4cb5e599f79aaef259c6157015510","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e09601a6d8a9aa54d386d828decea23618326f3f8d03e9c74777525d36b4dda9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c8a0f58e6cdd59193251586eef3516144b6ca5b6d65145d3ef51637b6fd3a7bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"41a6dbc79d3e999b6dd61446e7d6b960d8b91567d63144834bf5774cd755d14c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"64bc5f956a96ea17b4ad8afedecba9da9afda7e15e21911edd87a01208cf17cb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a41f794e9a12a4c7bb20e50f0e4a90a8f8954b005153e9d7f0155759c1c556e5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f87f4c6759572dc6791bc40f32f43d92be0b2e11898b0351fe489db89a21d941","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"17365cf3621fc7f67a1309721c23090f61b3a56055b25bb3ea46eacd5edf115f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d3d94792f5c9725ec20ef667ed79b6dc1d19375ebe4d8088b4934feee207c2ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d103904336958830b76f2f0be437c2d4012fdf9b7f7c0a820403fbe0b2df118a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":300,"voting_power":"40000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40000000000000000000000000","vote_positions":[]},{"voter_id":"b52167c20ba7e35decd8cb9bf835af742076f3388a1df1dbda2ef4f65d91bb7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"fadda6343ec00cb11386628439d387f14233b54e5bd6340e3b2b06d264899ec5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bf6b208fb2b64889932936f61be74f08d24d42ece2ba00183f3d76f10239b98b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c6348568406a803ef2a0cb907bc4d82f40e02d331718b0e7f56e0f7028010056","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6b4bde35f494dbe6b8b6592ed9347fefc612b354e0602ea18dc1a4b54fcda75e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6fc39b6b6d644b3eff8d85a9ba5f5cb299b52536687a362e04312f04a7ab0ee4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12000000","locking_period":300,"voting_power":"60000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"60000000000000000000000000","vote_positions":[]},{"voter_id":"bddd78c0d1c07bdb7b19af336de2dfeaae85989ccbaecf3b921fad2703ac0be7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"50108439a98338304ce9636d91076eebbc2c985edd8619afd37c34eede2d3655","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"821924554de471723ce40bcaa6cba15ade3457af1d7ed400ca4a087822fb8415","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"ffe787370c5814bfb5c8a4e8182ef6274c08a1c5a161f6db5fa5373af629d105","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3e51d1513014c4f048ba22f71c941155d63012062c10927e4d23418acb972fc6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9d1dceae30161bd8abb3003ebdc2a823f8a6d48f8f9992a914272be21303d6cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6424f1549815b2d8998adadf36298cfd9767521bd5041bafb23c8ba47af240dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ad7237013330c862c5f80b6dc35a1e8bd010a6e8c286af8f23373cd81cd52ad6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8d90c3359d7556f72ebb7947251370dbc77a2a09945baf7e1c1319520267b808","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1cdde58faf7ec2e95e218bfb5c595bdde431f86071703b36a5e281e6ffe0259a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5035c53bc6be0c600ecdcaf7b62dc6a94fa3d9aae167c2a66d40eec4e2044b24","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8acef5812594f20a9036819abfd6223e355addb393b0643788e046db02e973cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13360750","locking_period":120,"voting_power":"26721500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"16721500000000000000000000"}]},{"voter_id":"8a7fc02d17e2be9903ebb4b399dafa0867e4b9577b76ee07cac347b291a1f037","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b07568896b9db361abb53fe69ed590d1150c1bf73af14ba5bf7e6c202c6a647b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b510ab77eb2855b4990bd717c81f4c33e09ad7b88a6c8bcf9b9c12107ff681f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"74b8e0a54db38de6119c53e9e69221cd6d1d0396d3753298f35274e7425f88e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d4e22847e7b0de6ffd12cea613c78dfb185e9b806cbe014b3535cedf93bf6b6d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4c5bad1a24ca50f884c33d95d167e4660e9df4d101c6bb8cf92f909ce91ae055","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a189bdbee8efcf4b24faf7e9a942e1de8b8a4cfcc87abf48bb9d20004a24231f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"342817a5eb29a5d21773b90702d8b2f91571fde1d81439248e62527f0ec45f11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cc84c5ae29a5235bb61a2901726b7577c32cf6852134e4756a52f279dd83f5b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":300,"voting_power":"40000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40000000000000000000000000","vote_positions":[]},{"voter_id":"25ddcc13fba3e88679f74a5b17ff996e7ec03ad7545f74bdd1636b2bfa453010","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"854c054401631858556be5083fcccf159d48edd8636cb3b79ba52c6057acadb3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1aacbea312d4197b0113e669a7b69075e6d03562402e7f7a504eda02740cbb56","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b0432ae83eaca897ef4bbdd0bfdef1d85dc663320eeba22650e2dd6f4826ea64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3fbc1a6f724071613889480d9b9ac30ea5c834f94c99195164b6cabf2b45843c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8ff8b942169412f9e77f9285b7987c6529c06680231488e254dc6c6324454468","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"241da5c7cdb1e6e1827a71b832d1c20f039e66a759152df2c7b7680a3965b7c7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"dc79e82f7b1f6a2d72ee59db296d0f2e71607a842f06f74bb2df7f6d2190a47a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"29725629aa22082c6bd7381f27c60bb5a5cde8256813f205455e5cbe417bbacc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c02f72947575dd33b24706f80a66e727361e805180b4a24de26db3c207b71437","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7f7c40fd9cd0ba99a934f6c6e0bddee27fb2f2719a3c81b6a1c892f6bd5f8244","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2228749d4a327c1b4ce63b4d2d1d900b2bad5e778858df27f3bcb46468b16914","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ace8c16bc981fdb2f9e8717ea84afb896b30cb33591e11aad48728fc512a7dd5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5c2262ab1caaa245a74e6eff6e0c7fe9dc11be9ffa83953df15983964a511d85","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1711058907148abafc17d6b2992c739029368bd60932e278f340eb8eafbf2d39","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cc4668f6804c5eab5cd0fbcda10f12eb2e7d42611e6f5bf4d8b211b7bc549710","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8dd96458ee039a98fc1bbc9dd1318c52d84fd0dda34af7955367dd4ac889a61c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2715d97fc5bd0357834e083bed8b734f55d38655df4267ccc39f088da4837c7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f36fb70ebe8b6ea903d31f27c740bd55efcba15f8514968d98b479ed408fd2e6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"126|mpDAO Grants #8 - August 2024-5e85fef96bd903eec08d1f59fabdc524d0ac13f44c7a1e08e43de9a01a62a11c","voting_power":"5000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"10000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"709a48e088e2fce23feb548f488ed76c03d7e6fa20aaed60d866f4ec855b1f9b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"eb562c458208824d6931a32326e2d8aa02317aa44f3e6d352346d51ad172ae23","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"797d81ab560875d044dd525113c326482fff507ad3ef5c4cd88038841152a8a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b8f64970d4ad1dcb0a1b055841669e13d6c1f3415b0e97c92c0639142e45b7b6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"de8696cd0eac0f7d0967acef1a01bc8448f5c10e02a19143a6eff2c070f2e164","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b941bf312c36f1cab1f9772988683a0be8c2f71d793f0bad5267a09fb33a96a8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"992a128740698ea70168fa1c28e1697ff08c30705d8a03866ad9e18c9a42f2fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c5b42afda74918ccabccabcded6730385401035af8aca25946c036a1fbe40680","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"137|mpDAO Grants #8 - August 2024-criptolaplata.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"383e360624305faa2979807b6209d1c9720ae26ecabf23a97b989377516c3e92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6e5bf40489b5db158fcea08ed48b69d5e0f0671507c8cdf40864529d940bd422","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"838cb5c7a7e6a974fb7103275ffb5a6b365d9693e7d2ca079f4dcf4911d1dcfd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[]},{"voter_id":"95b69d7289a749ecf14bd875ed7b19b162aded7c928c2ad0e1ba575ac9bb2130","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b82bc37572d5c4df564aec9df527d5a77834f896efc349ef6ad53c073b4e2993","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e55e71330f336344cc8d335ab945bb8e7af0106e44128226f5218ea3a6a8f69b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a43d454b3e82939a66ded3afa2549fb9654ecac0bc7f82b21557aa52c162191e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a63976644520283c719398ae6c91d00ae1606d2327ad351dbe982fe9fdb0ad7a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"574acb8fc04b1c3ab8ef36f9ead301e43101aa9d7da813bf6b9bad88c1e3f345","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"76581563d230f311acdca2d86f66d2cde14c4f73f9ea7660f8e0512a115b0902","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8b0ebb3093af316bc9af21fa51e9701f662942b44723118260de7bbcf1a27fbb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1f4522ec761f02eb239eba4010fae0973d3f51f806aa625688323b6fbba68d76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"06c40f955f3a998c83036d60892c51b8005779455bf669ff811786cb51f4ba4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"pardaox.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"73691fa9beb8985f59e1f34c7f071f9eb4a2949a3631a2e4506042be629f9120","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d7fa30ab2d1c683f7305d7089ca68ceb47ba88efaf10e0669adc7b2b4c7bf1bf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ba827e34f20b3688d91bd7214c21f7f54544d3e822a5d6a2848e8e0d0db523b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"01e03e5d247fd1d5fec6126619c09ed61c95189cc9d96d43c061296e71f855e6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0ce15fbdc5e027780eab5616ee28e4054d47e02f82e7e64b8b14d59a5d39f256","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3ddc62d20361b09a3cb05ff8ecf98892f7c4463fd2e45effe9ee98e42dfc4ca5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":300,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"96556b0e7d0995242dc09d9131906e3477ff134de62d9d98ca92a5b4abdfea78","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0x55f8832e0012508a4e96dfE4FE0Ebd643a0EaDf0.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"551000000","locking_period":120,"voting_power":"1102000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"1102000000000000000000000000"}]},{"voter_id":"3633caacd5cbbbb750c21223879cfff43f38393d99afb12cec8c02d08858ac66","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"2126bdcdca813671f169f5d2f097f33943a9fb4fa4f623fd1ef121a742ee58a0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0e4d0d7b95f032275189a57bd986e5efa627c7df6ec218f3535fe2432419a3d5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"1fcddb18fe0a004a8d28c6f5e0bf852c21d38e12247ac1cab12bfbbb92e88ab9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a62ee42e81bc7d418c42c51f6e960261521c329247fd79d5eb924598011841fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"70c5332b463ac242414a50569d5497a90a3ec0aa6ea631880cb7ddd0cd0f2b0a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1c77b7bf771188383374724d3da5c76970a2a8264f2d57ecd07fa6e062c98361","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"1e3a0a7b1ac2a3dd8039f15ff082687ebdb6655be9fa9c73232da7db898c6ae0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a376a2a5c4d3c3f48c63b7f5b026bc49ef052ca6983f581caa9a5ae79c1958af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"374dfec531a1274099b31c8c0e95b97764fd74ba8547ddd25ec7fe2e248eedfc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f2c7eac39ea0e049d95489ecca8537a6d9a9250f9bcfa94b1ab1e46e3b3fe78a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"043cc3d656976dc43255fe05a3f096a3f5dcad9aebc505722a61dd34b09145c9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"b6c4a9ea99037c5016352f9e38d631b1c71fb7e7f85f68ee7b3795392027a8ca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"44004e9a37d73f76f4438b2d81551d5d84a5cee477152599f77ccc75a1ffe71a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":120,"voting_power":"16000000000000000000000000","unlocking_started_at":1730684117890,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"6d591903a7eb29a274db75bc836ed7238905564f2f159f037903fd7f4994b64d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"23af65399d4308b87bf7d025d0d754953981a3768262aaeefa4fc8599004655c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7c1f396dc92b40c9f147aba123f9dec892280d0c9c89de8aa85863150d637db7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a53abdc78a549af399d29c11b4d10f40e370c61ba57452e6e7e105e8d34074b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0e59b727b425fbd59713976d554c7a4b61e9dbc87a9258fc2dab03fa977f07de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"421af233e4cce7391ff3717a29865831bc7783b7c475c75db193e95b7cc77a11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"da6515ecacac46fff7ce58aeed698a716f5d2a77bfb6e01e378a5fe2a280645b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"0543ba6076365770e1fc112628b940339b7d0093e955b9226b022b5cf2061214","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"10ce1430d68b7d0734e6be6e98e6a7e0d77f4377a2881d04c47626f10165aafc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3d990fd769cede27d9997be214879a709264e83011e81263839ffac64cb48077","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cec1dc05651fc81257087923d5500aeb4fcf73bafc3196cc0a7b08ee8e89d9c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fc448953e31f51dc0830bc616f2870a2e8a8d1a30712b271d80eb1de83183029","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"518a04e19c9880ae8a8d23cf377eae98c4851841aac17381bb6e1bd05e681839","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8332370664fd8ab7553209000b17d0cfcf09f5c496c28c849e5fae0b356725c9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f02609877a26b5a6330a2425c723cc543fb6d1877a9787ab28c456df63428227","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0x87242d1A0C2A35260A500524903e2F7fD6DdBA8c.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"14200000000","locking_period":300,"voting_power":"71000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"71000000000000000000000000000"}]},{"voter_id":"a754373922d2facc53564d06d9c3ec90c6bfe65d3d0274a48b83160ead333cdc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bb8df3e665063dc3d45e04eaebf021fa44872d6932664c2711e77f361ea7e057","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e52e3366a5a3896cb50fdfc959eaaf2f4447fc65eaaa3e422d75a2d1e422b39b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"eda933687deca3faa0f7dc0b0f5abeaab0735863cdad0ced8505f40585c02a2c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5a5c1df8434f9d2efc1ce2f1ca954e80d19973c312751fd9408ddc7c974a99df","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e4f7e99949c97212d1147293d3ccdcd60007bb3b85b0a998dac8c1301996f8ec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4e61247339ec607616c8414642f48776ed371d1c0cca79cfe30caa3d1ae976e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c19653aaff75cc876d71dacd9265a439168fcaf88e028056a22745b1d34bb111","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"be1d942eb5906688e80ff883f6b8dab81691ecc82b86eaa1ddcdc8adc90d302e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d2edcc1a3e301374d81298e68bdbcf6a6ebfca2d9581c43c36bbb6a70c79bd11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bb7817cf789b1caf0d6b1316606e48b65b01dc64bf24cf68dcdc78a30a413791","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"773e24f843824e90b43e9bc5de9e3fca913c6f0854f1b5295e9b547f5416b836","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4fefc08752464cc32a8ba5b3a908f3ffbac02e71ab0393f499535d3c77ad89bb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c0af5ced052e574333cb1c8b75d2a4c5aa6cd3433012eb1871c74f34432276ce","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2be302dfaa09f5e7f20ff681d3ac30d594fc2f6fc7643a865d9c7087b71c9888","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e754183eb049b8af3659f4bc1cc57633e5a58affbba13ff8416095a714012480","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"5304e92a7d44c49ec6132f52a99d715af8a9af6b87293bfa686d20363804071d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"515e9eb55b737d743431d7f13b42b7ef97b4df3defd1a009f4e7aa1fcce40ca5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"58bb3e20e84e518d96276cdda6fdccac28797e265985c133dad3bcc1d32ad55a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b6c45bd64e530f369ff152e7274f05faa97626f9747e711cb264acff2dfce145","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"db490978dedc9591c5564de517d9016b7ec26d0b1e32598d112b3aed5d3c5d44","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"975a9a87e364f4c65795df909bfcca13a9001c511b1e23e66d73429e10b1af20","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1ca99bead19333c375e999bfbed7954e819e4b106c3020cb53f475cbde25f7de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"9b2603375f93a971f7058a12793802e626237bb9886ac8b090ebffb538b14e45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b1ae289e35121155c7d86de6e470795c9cb72f0c92ba1fcd845ef5959b76b636","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4a67d87de0d5ee202d8de5a0c47a2f970c28611d1952897941dac7396089cbf1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1ad70dd704941ad7ff7b0cba80f5ba38ad51fcb24ae86746c68a794560cf3ac1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"455673ba2b0f868c4929b35fcd0ffc43d9c385a506a93f1a4ca1ad85ffeed5c9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e41658db79aa59b0a68dea644337b87c3f0f415323b68e21e75c22b41aa94d1d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fbf5f11886b30c7748ef7e186edfa33e9d003cfd9f490568c6fb0f6448feb5b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"eae77cf00f20f48c3222465f2657f4155b158bcff7ef1d4d2a4a59daae75ac10","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7b60e6eda63bb71517423f58677a9a0e30035644e8f6dd1347737bf6f089b816","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"0x9cCBbC021888Ad9F572F44681401172BeCcA76A4.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"36245000000","locking_period":120,"voting_power":"72490000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"72490000000000000000000000000","vote_positions":[]},{"voter_id":"6c6b881fa3991bfc090d07779d7af49071273ae1e0fb2b1274daf47994da4e8a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7dde0a27720b6490887c301e6cf4861cff61ec3a7a7460fae79473311c260e32","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cb1271c1b72c8e2d9e160d5d6e71b85749236da7433e120534ac1dcd11635c67","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d8c01019cfb4f94a4622a8e64c73445a5ffded6f0714aad1719894ec994b2a4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9134a58890b1749e417076929bb7aa95d8efd6ee8e8cc55069cd1782a8664130","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8d38a492a20136fad38b04e0f48cdb8a9873b7d174bd3a416db17905e91616d7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f0ec367be4c59c05f36862a736ffa8f2e1f7805851d56f547964de2fe8ab4cbb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"17d1e322e42abf9b49246b4e54f7fa387fe687f2aeea6fa74b1ca00cfe5d2464","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5bdca8fd83adc36b70d884688cf9dab89600f2e035925d51b3ea53a604dc7af6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"efdfecee9b258dcc2eb0ab156bd8c79926cf7db9a005b9548174f86481190e52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"20803c47d004df56673a08767e71a67977ccf66d01692ee9c9ede101456ee4c8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"4bc615441e3789fdaf2135e0ca6d6a1cad473e4f21492585494c0539a43d4b4c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"20f7e9405de349d839bd0c6c3322311c73eacf7054d9d8e54936878cd39f8b8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a8cf0344672f720c39ce905438977cf267efefc500a099b50fb7f1b6dde61684","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a94ed3d0e1e831cf125d9f5b8cdbf2ebfdff86e2c44ea63252ca1184b10c06c4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"aa3b8f24a65a7f6a06cae02b13c41f86066408af5b44c80adcc1e1c9ad089f4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"979aa7167db0b18d6a7121f2d64f0fae18041f47b0ae5992072b67542e6b7983","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bb8fdcd3a83a5ac151644faa98b40de05fd3736356f8e95c03c9262938c2df4b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"80472814c00e4c1cec25e4291907b42b0cf2e322c42e87a3bb30d921fec64b15","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cf24343ee9e23ebe2df6b9cc689071bfe3d6dcbc30444ec328b9743e3d111eed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"ec4560a9c20168089603407a0a1d43bf26f9f19f7f58434b40e5b91383cc42c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5fb73daaa28e215656c48f377e6bcf93325b3fe46381ed30784fe7fef676b1bf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"96691dd13f1680b71816e8e262641d435c82448197b619fc0e768bf92e100d79","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7f86464b01ee932eb8e07a6f92883e3b140121cffc4e63b4a579116f2649af2f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7ff81419e0ed0c08404b0ba6d9bd693a0c6af1524a33a4127f58fbf1d0db7943","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"669c9d8008e2ebf1ff07f73fc29d54654621089298a0f3ddc9b08868cb371d87","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f4b2594fb1cf64f2be6ce9e45cce847a09a8d4ea33609847de67018a19d6c23d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"358268f3de6da1258c6b9d82a09045cf6614da2c663330456df5a6f912997027","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"d0a2ed46a6b71f995ee0bc8eb24623f3f8c5b1b5ea0d9c32cf81fde09b802dbd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"0dbf3d872eb1f003da63139b7fcc9443493e2c3163186e3e626cc236c1ac1ca5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"79cad1aea61e8f4fbe575059ce60a71e093734063df4a3c073a453d9185ffe3c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"44bf54e4b457b65d27c38b0ffb53aa0ad649fd1fff2d18f4554651c0c4ff27de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"55d1d4e702b67edbf0ef197752231644b7ad849e78103fc554fb62528e28c900","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"91d3fd762c2e09e33ef11e812551b194e2c8f6ce37a9120ab1a8d5666885f64d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"33986323569c11499fdfffb13a0c53bc37174e2768e3ba98010ddedd113f2fc4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":300,"voting_power":"40000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40000000000000000000000000","vote_positions":[]},{"voter_id":"9c18bd8c0b09e6f80d0872ff6486ebb4ca4a78d1abe5a0b1d38f7abe3e8469fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"2ab37ab9c9bb48add2ff5ec2f232594a4010aef29169b396c916f7fb7ebc66b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b2d6c345e762593aee56a61eb484c24b814cea9155abd29da3e637c4ea8c30dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5a5dda72c012837e52f874b793069f457139fdc79cb68a026de58f93ff048f80","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8878773f86c030c087865143188340e90da331ac4b46d11fce473e582f5731c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"12846a9a54abe348f6893dd3f70b47162d38a5ecc76cbf77d0292c230fbc1b5b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"11a3698e7b282b744d26eb731823c20409ed94e129697dc0d8c361016832def4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"6192e5b18cec17f047785684f24fec02b594b428a1f7997da7b6f3257a8a31cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"78c8ad2e2bdf6de315a795344d87ddecd95e2f0ccf9c37eedfc7b6ee4e3055d5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6e9ae68249483db066789560625a9df789c0519ec9d5c36be6fec90832aa29c8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5131996c25ae37b776f3586518066ae5e7fd155c03ba1688377ff9ec63437c8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8be05f82f633b625c74ab1dd1c30fd48d032e27beb88fa87f1a0d9dda6d6e649","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"6646b541a73db187e5bf1f0eb03bcd0646a7fce3806f98dabd333cfde7ab5bcf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"caf2c794d5f4f1769a8a9323807c6ade109da32728d74ffb442b2b13c63cb7c7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"af9e86fb4939dad3eed7fb4462938f91f6328338ff0dece34b0f8d5eb94412db","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"61bb815b7c1e5d2937a4da895a0f9384e25b3e45d3d57683c222b2ea28e258de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a21d62077ceaa7599d11e8a2ac51a458b42ff00822037ea8bbc7623a1aebe50c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6048790c11c87428c132ee7e442294a91200cf08e6e8d9708d8227225314910e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3107fc720c0db450d94191df5706d6c9120044317653afb224170c01bf6f8ba5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"16e5109bc4cd76f332b86f44f64639084318cfabc08ccbc97dc87fb84d01fbb6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cfeb6d9bd318bbd6aea2d6279ed0d68a92c0bdd210a307275666121196e64324","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1cacffa12e96e8a6b8087dd5e665b08990538a4d5df300b30a70cf9524bb3ac6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"2936133c84f0c6cb7fe4a3d0099c3e9f2764936098b4cdaf478d9c8e5dd7e1ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"aa42ff1fc4c261045c9620532571e6428c9e63466c887569262cb509b6f97b02","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"1000000000000000000000000"}]},{"voter_id":"07ecb2f7b9ea6c46ad0f1dac4a59c601ef2a024ff644fbd003f6e51b3ae84966","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"e07591162a404fbd6a0295669d2cbf2f94c4a3211eba1ca45a660c2575afe2a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"f6ddfa48d3e9d1307bee36aa33bb25ae61b772571dad90f9a089d0bd3a414360","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"268d56dde9508b3cfd525763ae40a634bba0e3cf58b92a2257ad9541b95a9cee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"92f435efe5cc03013714e2647afa8ad5044da3e62aef4d129171e30c4d835891","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":75,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[]},{"voter_id":"778109628ff45b88b8adca44f1534de6a7643ea3e95c54d29644750d427e9fa3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b6be88ce9708a743d073d3571ee08fa61fbbd33d84b184ce64c7d5bcc855d358","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8c8905aaf07c9ce9ed683f6a30128fdf2aa0688ae1b8836efebf49419d8b6949","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b32fb6fc7767f6e463645a7f96250b68cb6f01cea21e15d36e3b82a02b3f3ef1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a050001d022f25200ebb416b683cfa8c4e2b3b1432819758e8b43006e2b043dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5c12991cc12f19b2089e13beb6b43a03153abce39d9643bbd81089faeec69466","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3ca3a3770bf71111b61151fc8200a7867a9377cea73c552e3e4e4ae1364d518c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4f98c08af540f76c5a5e833ea73e4229829c3bb4f0f5dc04e43fda59c008f3ee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"39a6ff0cb51e454ba62ad44c44d6dcb03a93ad6eb5ed098dd09ff56c63f45552","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000","locking_period":120,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"5000000000000000000000000"}]},{"voter_id":"e5a08b8a611477602027fd8e70ea91321be5881d0de5549f4d06785da0d2c377","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f20f70e7ac9614f75eaf008bf88e14fcde36582aa54816f7de7fa60b890946de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"fb7c04ac2724f64957f4a700f94524955de56fabc1fd0b68323bd08f6aa7cc82","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"27ac911aabbce0844d426372dd5df761b3141a28d393a3a260d5c4d14043bb49","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"747156aec3a7985a93a65a45a9ba62fae52932854951838c982ba52dff86d390","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"556b27a404e10628bc3af098c6bf66d2413b972f7b422eaa2e7a648ed2482d9f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"db32917cca6785ff274a44c38214c6b193266348320571c53e033eb762a48e7a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"379877a63becc5ddbdd82b8f8dfa0b037cf70a708fe679977495e66aedafaad1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0211d26e71677fcdf1d5b30eca7f6a3a0c31ee6f159f2624ca2d7f03fb11459a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"080070b516a6fade0e96e369989d96aad40a0f24c3d5523fbc1d6d5ffff840f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"234b3a0336bf7e99c6b7af8341da3fa15d1925efea131a1e26c8e652e72959b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"977a7fdf4eb8cccb0d902dec1200b9d494ca4d29d7cdbebf8092eb7f1097d103","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"81bc19b1cebfd21359fedf226eca3a91e6a85dc7f5f1cb2e7f497ef0bc9da8bc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"29b355959d1ef5b3c2138bbdc7894800195092c91be06bb02da533a022cb6dde","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d40ffab2f4a449ad010114ed81768458c83c592381cb16b5b0282802aad29d06","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1db3e8c64a5cc8737f6320d199b61b44215f76bd8e4089559e9553d4eaeeafcb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"59576ef3e20c6c3b554bbafbb1519f181c9a999b3308247449b83a7df68864be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"611b89b28a3e150476dd47b4124998df9ddc97e148999ac7cd282839200818ed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"78efa2b0c35ce04ddeb90805c614588bcd267e49e034dcfc1945d57901d17764","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"02a306a560688888c650385e60d13610109f47cf8120b8ae3e4ce4fe645cada7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"28674c93aff46aafec7eccceb3b8ae0b5aeeaf24404314bbc9dcb8080a857f7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9c810841538ec569e6662d08e33881d3931b4ee327f7db6bcb9608308195d3b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"fvear.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"845806810","locking_period":300,"voting_power":"4229034050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2651198150000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"76210900000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"365000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_00.poolv1.near","voting_power":"365000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"n0ok.poolv1.near","voting_power":"365000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"28000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"378625000000000000000000000"}]},{"voter_id":"968e4fb2fbd7a917fe611d0b238a72cfa438f2a1ef8eaf56d25e090abba5db45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4c550bea99297bd787489e1f731a19cf07cfeaa204801743f50418fe17838761","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"153cef0ad7f63df11217436b57f53f2f0402c068a52b66c2afe2890c4e1f1d8a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1c60486b897a47e320bd87a734e71ab15acb786ebbcde3b24ac4944fbfc0d721","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e9d9d20a4d163fa8171186044a5928e9d691b91928f56d01a84477bcd5da85c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3aea087c2064b6106e5d4de20822994617076102176608981acc1c02b161d07c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"be06431e2053a0e1b556e394ef435a34e6a4e0927dc7fc6ff94e019ee9ab6840","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1bfb8eba15f02092bf9f60be2feffe8fdca0c5846fb8ca1afff40baf8a795fd0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16000000","locking_period":60,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"16000000000000000000000000"}]},{"voter_id":"04b4f734c6ca812b1de152f0820dc89c3e15c924b61434289441ab80ddaabeb6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1abab1dc88dbdc9ae81a3a2120cdccb299b35b9613cc1c3ecae49776aa61d235","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b46c33e740e216d1ca19c21866d9b65d56f4569d983215debd2e139b6ec684ec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"20e1695a53629313ec5d6b704e15381870a8c214557cbb3b8c5638ecbd055c49","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"eabf4545d6c7c2b2e1784f5c903a933cdafec44c328777caa7c62cb4ee5704f7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7ecba0546aa291bee1e488b8d8c0c7e97cbfc5b5003abc83de172d30aaa43a68","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3a1012c4b875ca3a928930fb30f81b98230651b2cce98b301c92a31e9cf7c219","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"23b8ad47a89a7e2bba27e8f8f120ed8f21242a7db7927f24cd32767ea5727010","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0c6bfdaa39018ee5628c8646648fa8f52da17b986ebbf25bcb0cb15258b383a8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cd0af57fd8b3d2ec4634d525d7d0652e7f445c02180d9e1c06c3a254e9805ec5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"988db74ac3e3b1e2b72ea5f9e0582029478d0207c17c0331935caba5a3ee72e8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"99fb84c8452dbc39f6d97857056362a53eed0f013ac37a97a8fa22a58213610c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"141d296201665fe1747b54df738ca06a301cd00b72c64169a04dcb705f275f67","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"aba3457fcc299e6598d06d158b16b06387d5d1faa6ee0a09f177ae66fdc792a1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"bd021d197c99f677e94cad2023e1dac554d7a9006df9b34cd970ae14ed6a0033","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5b4144f92912150fe37a77f2a289886e83ba835e1119dcb4154f7621f7c3ad79","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2a4945ac7a9776e606777efbe9f72664972bb5d7f4fe05ef500425a6f5a5e8cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"0d35ec724f873a0d65cd8e57007554bc89a055db33c1521881d6c0971f245abe","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2839e62b4dda8357de530da8615752ef2eae0c59ff5e9038e05264e72ccb3f60","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"20cc1aa754ed9fc403e86d10b2d73670f6ea73bc9acb325b97f71f5f84b1d330","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"add5195e2c922d21d6502dbfec0d618eaaabe272890c399ed4728b2e16feda13","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"aimery.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"81837703700","locking_period":300,"voting_power":"409188518500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"409188518500000000000000000000"}]},{"voter_id":"a30e630c9e462c3beade144ac47f0b45c733b848ab2c62585e428126ba486a4c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"218f4ccd583da5ce4cdda560852c26903502e60d4f6c10064109935ad41c3bea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"98a9ded118d6777f98d5babdb4f3fd67c98220aa6cb5d5e7e3fbecb904f540a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":285,"voting_power":"9500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9500000000000000000000000","vote_positions":[]},{"voter_id":"bbb9c20fe7481a2efc4db7027faed285adf993d8bde8caa8aaa127a59e2ddd7b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8ca3bd3de47b83d129ff38dc26a832bb4e027aa461a153734c6602d9fae09cef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"366180291e47db59ba84be4af67d2e03346778de2a32f6a867529fd271ece335","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"8c7f514bb54f5e022b2261ea399ba744c8727ae9bd7efb1fce95f18ef95ccbf9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b16a0fa8197ac396e38269129cf017be5f341684c255f8e87de67ca2b3725dcf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c0da3cba6ef59fc85b915981a8c2c38fb30cbcba8301b3157a5ba222ebd5e43f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8a26a6887c73ae1508eb2e7509e910a79aceca89c6136014cb730957ea5cbd7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"48fe90cf3ebcfbbcad415b6cabf8e3f9c291e7190e245b633dae2892bf7c4130","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"0f128555cb1271f815ae30af3b2f532640d360ef296f4fd3b7d47a8c228b50aa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"55e3c9bf100bb334b47172fd74a4f478eef9de7cbaf8565a44ee501a483cd822","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4fc8b84fb7e0d7b0836d49b09f9358a40d8dbca07ad4df8e70eea2cdd5252cf6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5ff3346a5a17b687beed2e6f08a3105fc81b3b5a4fc49da4a22861ed41c9b9d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"ec66eee5d0faa13f5aebfa48dae3750218ae745e881b04bfb67dbe983063fa16","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"0cf7fc15d35da02960c2443910832df3dde20feffb97c6cbb30bd7fc3fb2663b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"c42b6562743f5366f9a635ebdcda0bf99e864cb06bc1d88d220b29192e19d71c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"694cc6ed362b437b9288e2115a561e8755e098e638761ee4e5064420f2c0f4b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3946051d6ca5fb7284bf84c22566ab870bef80b41aeee58f2bc04f2c7b7e4886","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f38dbffabe264f9c7de5a25e9c6be72f0095010eeae1af6fdcc4cf9563fb8d5f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"048d834d145df1ea099bd19f00cae600237de148c086feae635b89cf9a7ea3f3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8820980971235ef2ad09c1223e50544510ae3550b5b4d86a208f78703cb63ff8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"343dc87aefe6c6178d94ecd6e06fab476ba9b3a3c0a34c59c1dccda126fac8cd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"28e30f6e7b6e311459aceb56873a62d1e04d0cf1ae8224b1b7aeabbfef6cfa5e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"2f172066f80b20617a293dc3abf2aebeaed2d31e64762e0fe216fbf011cc85fc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9a8adbb0b85fb9c68d5fa68d68d91016d3ac0c81061f46e98843f6233e3d287d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1cd1c03f7b6f8bf6b1a0e10757b7a66ee78ab4529020301a675db35aa83b28cd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c767b3ed24f4c72981a67c32c65ccf95e062a000f0f7046fce5629bc734b5763","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c420eefcac8e2f2e1bc4c134cd3da1e3f907f8baa35e2c0e38f2d9de802c96d5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6d26c0c644d5dd341fd080d99e927cd5224418d481b3a01619709c856672d2c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"336954a8db5b830e45ddd8f97d0fbab53307b15052e1d0a3c87580c1d67af4d2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a8cbfe144d53b0177b40169bbf99846519ab6048c993fc68e9af9e91e4e52dd4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bitju.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40000000000","locking_period":300,"voting_power":"200000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"200000000000000000000000000000","vote_positions":[]},{"voter_id":"5b388c6c0e5af7ea9b01ade25aa942759b27b3e9b279ad4977502d09aeab9165","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c6663fafb7a518edcf63c0b5512fe25841385701d6578ff15c263e6becb5817c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":285,"voting_power":"19000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[]},{"voter_id":"69f8b8969d75657e8cfa363dc97a35c656d614c9006a6b1449d7af19f613eb8a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"92b46b9b10895d44f8a559e145868b00bb0ce027ce390d5f2e4889fc677ed245","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2f19df2453be0ab186530b7b00c176e9083e89eb811d150d4ae6dbaaccd8e8a5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f1b3805a63b7f5a2e7b234b51a7cb96ef30eb075895ff1f7947226c1f036618b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a5f2e552bde4b802eaf7e8972b936ce97ee3a20e57452bb815f5fde179bba304","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"256c4adb705b28bc1b73931faa884eaf527f02e6d9f82dffd0b53f98d4a9139b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e9ef5dadacbe86c996bb2cb1c9b72cbe7b4f90be63dee07644b8d6f03966c800","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"af0a172be54657d9a41f2614b1c5411270de5ddfc5c27d4b400c1cca1044d52d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5bc6316b653d670c80c324acb5969987d1d79c3959a043dcab28e5b93e8755aa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0x8972Aebd5DBa64DF1E0321daA4914687e4E1e252.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8137000000","locking_period":300,"voting_power":"40685000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"40685000000000000000000000000"}]},{"voter_id":"a8141d13043454265ee7627936a1add21f7e66573bef308ab1a61c0945d66a9d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9bee101fca2a9b72a49a8a631f31d4836a024d755e387f3ac36005c7db4b9742","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d6e29896e822a1ca617c2d8aba47c1a422ac94a992e0ce662bc952dd32c25893","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5974613f0cd93fcf396ea4f22d833f0373c75e706a4d422f633202de7e4bfa84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"14fad723702ebe71e528d07bb53c9fe5600d4387dac9ef7834dacd1349807359","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":300,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"5b54be3cf01244e0a028a62a50ba80e907cc2d553caea959bb91386946c4e117","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a377b09035d59f9dfc99c62a2857faa6d07ac33cb8ff5e5cc9d39d52295c0898","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"54032c64a147f7e46a4b321f19fbb4f9c9e612e7a96cdf750d5fa24282f75551","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6d1cac58fc1dd96120ded7fb0f873d1f639381b496e3b28c95b58e4a40101dbe","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"52e3611537ddb25f53ac8cc9f7f73ea6fbb7668d368ed3b929913f54605a7461","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"715d77863f9f1f8e6bfd9dafa164dab8ca78df3fcca90cc858b1ae2e0764e20f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d3f4462cae39f6f6ff030dcfe155eaf6f336ac0249946de7c761e3b2582ad486","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"632b699c6ba3d9fdacabb203bea70e61470cc5fdf8e11b85ec3a0f03a1289ff1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"75ce3c238d9c8e9981bc047b387b0f418dc5aa71cc32a30a10df0b5ed52e90a1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c80d3dbf4a8d934ecf66a525c2769e95e55e4bae575cf7bdceabcea77a869b3f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"41cc33183eadc1a3dae72f083e44bb2e3249700210325ccd0f92323a933715a2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7b41a44ec99cd83e006d52799a01ec2efed57b96cce388c00826479f1ce32d11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"adb186f05024d8c5614e8fb99a21937b5ef2053646aab2825e3b3f68242759e9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"0xF71e509Bd6d79Ba5f5d58A94e995392751949b33.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"538000000","locking_period":300,"voting_power":"2690000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"2690000000000000000000000000"}]},{"voter_id":"7860e6d1a0e49df2b5650b95e81d6c4b3adeddbf3540c61c594277544b9d9c09","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"be607f3dd37e7c084fea48e4e3f54d4653fe704881628061ac9c3db1218e3b66","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8af72d04c28d2c91f984aa9f4285728ef775963fcfac5832d94fc1e64b41e016","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a374c862d2902185c977d3691d669110342efc35e91f39baa7cef4e8b66db6be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"502832de71252cf6cf727bbb1438d10d918e0aee05ff90fa40fda6468df6757b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"773a801799a1274339f3229f4bd3883a7e261f4424330cd198db33cd685b3579","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2038f436ef9570d7a7a03ff4a60c0a4eae453e87ccb1424de3eb46960f5281b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0d32dfbecdd6ca8dc622f39f68c7ab7fbe90c1ba92787a82348234b547dd3d44","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4aa9ccdc769e426fa7df278c66bde305abf0a4b5572925676a756a610f344c7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2fb647129ad58791b8eecca6df2e6803c357336f0c9d888a6659a1f3b2878617","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2c48d0e57d0b0a59d9a497e44a1b837346e00c9e74107a2a33b9e2a42fa0de34","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"370dcaa178b8242d9fb38559babeda641dba3631196a79eccb31a3e51a23d341","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7b9da3fd6a0ceeec24a9fc455e39b08cf137d0ef7474b5c5cf8f2523fc7cf455","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"26c2ed46d1e50444392c5a43eb75497273a8f20decf02e5829048bad3d3fb926","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e431e4b4c01e9cba290ee680004333d4633379355e4db59235d9f618817483b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e54377a28d67afd9efb6daeb0b51fc0f8ae930014f07fdcdac502829be012cb5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fae6c40b8d63a4cc53dec40ebb568315231a43cd9c497a71aa964eceba5dcc8f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"570fd0840ad6ba7b075a39b88a4db335c8ff53aca601f16031d12e9b3a33b9d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"cee75fae578ecc6c43f39738b4f7479e2d1515d8cf5dfa80d45326902ab20a7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"47e9ae9ce20301b91a859cbfa30d87a0a4ca0acedc036be350ce85467d26bc13","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"99ee941cf0726f217e2468bb00596de4709f5c3c1142a2f083c6dbd0d0484399","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6de31832994c462817b5d11456fb7975abc23dd00185e4bdc5ddc209bc567240","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8a49e79ae23c39d28f5411571d8ad2fc6b4e9eb5f69102902a2158042960d052","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"ee542bf6d4cda50ba458f6a7ac037458f54f76e4a6a69a5c70042b2881e7f626","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4b11e4b51e34d3b98bf70576b2f34615aadc7284b8f2602fc81c825876d8f894","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"609fbb460a525cdd54dbc02a2c2ee83337a13624a2550e233b2910ebf3d23dda","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d47890479963e93beddeec274c7e2af5728b25966bd1c480a55b469a29ab6774","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d481fb470c9068c96c8d63846aa3f6a2ff4a4ba7e9e00a89e74cfba52cd08701","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"37765a61d5f6d8b5e18cd0cac8152d5e13a54394f585d8c73c9f67bf4de9892d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"db7cd17d8ea22f47437ec0676c14cd8333614e09b888c2fb542e1a322469409b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"eaf331cc794e6a7dcc7df80bf4ed4d2d8d49f68359b5e0a05d47d1099f840701","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2156236d5579fcbea1f36e16ecd1e236c12952effe4104ac78860a51e1401a4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"aca1e649ce286eac2c96a37c3b815fa5722f029d9acfc8450bb521e68ca7482a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b61f5174bc0e4250aab40c5c80f392ae8f428c175c65fc163a9616fb60862ab7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d7bced7082eab16173fe70bbfb2ddcc3b5e040b759a75c90658334089c1d8f9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"56cf9ab5bf2fb8b587809bb6cbc75a812490bbfb500041db10f84e98fabd4205","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"743cfbcd1c2e69badd884df82cb4f33dfbbb54d42d6cdeab2c34c3833b270c1e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"iriska.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1509978000","locking_period":165,"voting_power":"4152439500000000000000000000","unlocking_started_at":1724422008133,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"57bdb5e1224e10c1ca3248c17cea691fb0f2af381f3205a61cdc267976e2774e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"10afdcbecc7d6799ff4001b88d58a98237bb1d7d6d0cbc6f9dcfd9138a929578","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7397737337822c24256ddea216ba16d3c1539332b3d6cfaf6cdc90410fa80d03","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2574247693b8fa3954b199bf1032c94d448b7ed399a7788ce4df7cce7bec72f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a9d23cb480a38b5a23c7682e9e245a3d120f1e39050a002adb9d2f82d5c6dad2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3721500c3dfd668f9e88270274b361868e4bbfe96bf8de63f0a4d04bfabca434","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f97b119641e1c57386247cb58a91d9562872a01b94aba9efa40b3d1e98f080ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1cafed7e66f4468277c66f24e29cf9332ae32aabdeaa906ee64214c537376e57","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"05aaa7e1174bde3c69d7be67000d6f9f0b2fbe9e5ef91f7b299af73aab15f793","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"67e59e26611aad2b7adc9abde443170cc6656f09f0c22f6cf0c58a57e6852642","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"119|mpDAO Grants #8 - August 2024-wuipod.near","voting_power":"2000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"c683730ce3e1c18dfff909f2d7cf98da0a560a4b2c954d937fd0550de3b9a429","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"abd4d6b1463b65d06fc31993c6ec4caec4956fddb2e55f3e636dd21b37833d0c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4ae6de511d7f09aa0f21ba48fe193532f4ed96761ab4e2db5abdea536c7f767b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3cf07a62452f1efb6dbcd84c12497ab2ef1e14f73f579496bc4c877c114e4cca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a40fe9b4cae23bd42b0740c74145ac5a2074a2c7e6f11367458309cff80d50c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"47770f6bece15d9dab89881bdf5ec7f690cf93162a0de582633ec266535313ca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"f6b01c746da45122f9e15e53ce751be04224b85f6e1dc356ca99b6a5cf42267a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"832c0a931a9ece9318a843ed36d01698a5cc25e28901acda8eea64ca80e90c4c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0352a3f372f076cbcfe8e7feb3fa0e12b97c9ecbc7fee305a5fca2c35cb15aa4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"382725c5b68a51c29ad53b809b76b89e75506d344cd3f5d2dced433f05331afc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"464c7388db9c7e7f13bb8ffdf2d0b19cfc851b7043613170ff74be4362d46827","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12000000","locking_period":120,"voting_power":"24000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"9d4ba8fa1fb2a9f0751e3eafc43e297fe2f02bd02a73530c2110d3c8defb2e58","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"43b1e08662df227161148d9ef496cff7272f4734a1814a69c20152bb06ab5a32","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"5000000000000000000000000"}]},{"voter_id":"1c16087c43f61a40873c673c20df1b30037d2f85bb63785e6ad3a73886383ffa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5798ea934692c8534f18a660efb5d631528ae9bf389dacde28f549391a2f7be7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"331b381bf154e8635c1094a8c36aab95481977d6cc466deffb3a162b0f39d367","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6412c3d502b574775039981ff044aee67d0bf12aaa0a51a7c2f9c45e8e1c8e64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fa71ae0e0943dedb7e152e61b92f1a52da3ec9334bf20aa53bab15e5b34b2be0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1edd67c6bf71c4f2fa845b775c7c7d5b1d90460169bdc414fc1818b5a9d12864","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f14611cbbaf993b982b2abc2a007b0cfc959b38ab33f75806c964aac94aa5cc5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"503357b2fca78b45effb090beb6c739ff1e0c4c6843a85be537cbc819fd7ad9b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bdc429f74fad89db2c7399d5cf5743134ca43024b6cf5cdd97d2aa019d55f201","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b6acc91ea27d18db1cb3d435ccbb9bc7d649cc41190ddd7f792b2b3cbae1c0f7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e3e7978f4558f3496d2e955a5b69e91c3a7a8b47ed01e39a10d2bd3230785a26","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b1abe99021d300cda54c7002d8d1974e9e10d54507a5be1962b72871ef4d8cff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":120,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"74a541779df6025d9547e1234e0882234dec3ffbceb7bf4949430cc4bdd99e04","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5a74d3a12445735a328c7f3900a0b0c4c936e16bf2c2aa0790c112c7c0fdcc5b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cccd57915ab1db1e2d57ce15bb8418e96e19eb77839dd3e839addfcab33305db","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"293177693d0471d225c99cf389693661023ac1b91267f67db618653262b96ff0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"384382ab388324e31c352e6bb75e86eb6a572c629bb774a0e44d6fee44ea5bf2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"64d39c354a9123e95d1e910a0f3ecda8b7c76832684d45a8738417dcf77e031d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"79256c94ca2bf7f044a9bc1ea261bfe791c7c2c15465781833769ae814509005","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"06f1130cbd4e1bc753f92bfcb911dde4903f7343190319ee9f74d0f726b07356","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"554247e1b4f1ae9ffa4fe5d5461fd1a5fb95a39ef40f9d5a79fe882fdf0db6e4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"654385e2daa3abe92030477089afd42d8282bc158b92298a53cb889278cb1319","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0d230d4d080e1129173e2882e9bc7eb81fd74120ac72821508f1fe49c3e307b2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7bb51238af71276fb11e438d11a3a8b2abf64af961012f543c16c2ad72448c4f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"144219d1230ab15c79965c94523b03dd0daed3fe7391590491b9b1f72debb34e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bf53c02015ee30aa29e758e9a39aff0748d1a339fc9eb8013d35740b86f3073b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"ca6651088f5c9df3fadac5ccbf66539dc4c2443ccffa275a698e25f2043755ee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":120,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"340cef1b18f3055485cbf8089af43ab9a17515aab3899f246f18c69f298366ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"af30fd317806b5509e9d46c88bf591ec9153012a0362b203b65cb2fdfb8ec5a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f26a01b5add812aeea6cfd858368da6bc91cbadea42b0a9da890ddad1aaae842","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e4dbeda7c7eeeb286d1de196ba1ccb7210dc3afb0079ffdcb7ee4e62d58f188b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a40e95af191420c80c6e8de486c3afc747bfa4b45eaaed12d80bfca7fc52c6a0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f0f4dffd7a8ee7bca6a2767ff67d3f98727d95d7a9ac964228b4647c4a600f21","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"ef980e018c4a2cf396f7e2cb91e19ab63c7b44f01c4543982d94367e7db536da","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0989636324af45adc99bbe569c0aa23d99a1d02d45003435ad64f78da17b30d6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"071df7e320872934e07730567c0a3970a0fd66e1c4758594687890c17e24f16b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"20a575e5451c6338748efbe652fc48bd4361a836c7ce7214a5b23c7f8f498f4c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"944645dee4e875525ca0f7d9b9722f83d586c5188c54452309c29be832113212","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c00d210b3daa1081187c88259b1f0471c924ca0dfa3f0ee15c54c43258e6358c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"eb46a36c2bcd5dc0a23386118409dcfd7a60b9e11e2291f89d18ec25abca7968","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6d7db62592dfc8505377d26a58caba944cac21a23a5f8e2d4f16eca3fb4409f1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"eb5c7c9b5c0d63050d975a5437ca78c31a3c7d6e90e77d133681429917cd2bec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c5febb2767883864ff8f2a745edbd1848ae86fe7dc6bf5aded178e600c7b5900","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"82cd78de0a45695719fe9826d12a1935360f31d9166ee4c66d400b82d4ba2bbf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"729987990","locking_period":120,"voting_power":"1459975980000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"154525410","locking_period":180,"voting_power":"463576230000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"1923552200000000000000000000"}]},{"voter_id":"2a57f1fca1a177973201e3ffed1e09884add68e07ac9df182c3c94af377fb684","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b93ef028a5bf4f1eb25e2f451fdadb23d5650f7c90b5bb835dabc5dd01026da6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b23a9e7a99a5d53797b2fe085d2c41b05054745a3bdb8a24b68f85ed580a5a52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0xB2736156370F078c0242Caab55E83844DDA53DE1.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10122000000","locking_period":120,"voting_power":"20244000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20244000000000000000000000000","vote_positions":[]},{"voter_id":"f408d5daa540dae1158e697d1fe31eca80b938d07db47a1de852a08332b65d93","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"34fbe29e924d8fa7228f6b7bab35ae0ebcfc3b174829028e573bda46dc4fa199","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12000000","locking_period":300,"voting_power":"60000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"60000000000000000000000000","vote_positions":[]},{"voter_id":"ad56d38ed67ce016eb2d4865faa4e9dd496b2ab459ddf3a08a3a611dc4fd9c5a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7b784e43a91d6756c091f5a3974dacd23b5703e8c0b66794a14c725d47a01104","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5df049b1d953a5fa9b931bea84866420d5da4b4da5e1b9b1ccca235aa8ca5fb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c383a2d14579cb90ade09603a56934f7a20b31d108054e01bbdc1d43396e2427","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5870b96b1feef73ca79970c99da18e686b55660b7ba6dfeece7ba58542389c84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"30f54129c99bebd808722860cf1b679e5f99577525e51f241300696fb87edc7a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"78dc524b3f3dbb3bed995bf98212bb825901b9a365788dd678176893b3e44a62","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"026e7baaaa100dff5ca1bacb6f6918920df407bc61df03f7732f5d72102582ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"69d91026e644c7d5fb8ff5909805e554b0383142fc2cc626523b5837de0e2781","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7893fe2015c6e28f430f57c0050dd8d39a62343206e684501ffe7142fd472bc3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fec14113e1ba2b64e2afd3969ecdbfa8d066a6966b7b76ebe3cee959bd295a5b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"60e2fdab2508c7b41c9a9e1391668da9088143cee3cadff6d2a5992e669afa27","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"32d44416cf554d8699ec0ececc28476e10a858bc35f54805a5ad1f2bf0e29e24","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"276e924dfbeeb6ea6247c47b2ecc6366cbf1feb4891bea2155b51b79f19d5816","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4bb5a3bdba42b8e0a71a330e760b71aa7394ac502cfd48b1de1f3eda2cca6243","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8c1db5e073dc0a7f73fff3c54d39406ff439c7bdc3328ecbf55f53c637d07268","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"54435b771a16dd10648b156f1024642a793311554eb5286360fb77a703fc5386","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"daaf9fc168bce14c4bce46781fb351bbecfd08585995df349f3493e2d7b1b194","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0xA2d177687edcFcd2F1a80b517D9F3c46926d7FC7.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1749000000","locking_period":300,"voting_power":"8745000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"8745000000000000000000000000"}]},{"voter_id":"a6771ea19a0ef3cdbe4dd2b4c78825b43fff8102f8bf212a636a8f53b921ffcc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3d0708206647a5cd587710c7a63cd14c5eb01e14fc008d5cf68c7346bdebe1f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"203388940","locking_period":300,"voting_power":"1016944700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"458586400000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"558358300000000000000000000"}]},{"voter_id":"84abb13bb0ff8cf06c7dfa6c34d932bc7657dba900dc8ebef62e0b9706e7f986","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d320fb5b63688bc7474fed76c4828f73ca562daf4a7498951beae886ec9c3327","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"54826bd1ceb4c72a334f6fdf4cff19cb0c712c4a92ffb5cbffd7a8ff18a01edc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b46fcc01bb836354c4e919d45b01ab3361fcbd813cbb267c9fd0315ad8d129df","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c106ead3f018ee7d89874fdb8d0fa4abb48dfef27f6fd9fbdead0d32e7357f72","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"11eb159bdf4a6af2bba7de1c296de6fc4b42fe08cd480fbe241200388c236f98","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a4cc777e00c8d8651712d1efb70c5ed9fa11ca55fad63a3d53719c503b442f53","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"152b702780039983a80bf5ae0377ac048974d1168dd60f99633eddb607a874cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"98abbe7698fbb7ff95925debb9c441b103ddee49619b5732d139eb008ca6c2d3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"be4f59f8dabafb4c4b366c69538beab7880b176caf420c81fc2e054b4041aa82","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"f94a62226496fe75519d1122e74b5f35864afdc434f09fcf32368fea759b462d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6eac14dfcf7a73c3dab5221f692c74d8b24ae734dd4ce3bf5402d5f3c95e5b6a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"20fe170002ec97b1259a2db0a3615c83e9e34cab2b72ff66b9478a939f6fa3b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"709542d81f7e2f0071e71f5204f61b84b4553493848b71126a06bb991f2fd796","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"319d08cd2e6548ea0dc1ebbabe1887ed6a1fc633f3c02a1f1c90bc67e9b0e710","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ee89d38032daf1946243355676f7f8ec4193220b1e0686091433cd14be6a13f7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5df9df24ced21814c3f7a9a136c66f06e6d3f582f594e10ad11426c9a6596118","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8252858c2ccd4356f2e619631fadc5f5d98b730874c63350e9c481a58ceb53f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"dcc33483161f442c409b50b84660b8116ba9cd21b1e8d4694ac5d1b4491940f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"56d2aeba87c686ad37097f1de4871f76699c2cd293b05b8bf844c2e2bf695fb4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"88e7a1911449255e38fcb03746d589d2b1c2f67225ee5005ac92065a9b30dc2c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c32b56e7ecd9d8abd937824157cdb82920de7120ce472e7b2e403820886f1fa5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"p_maksymilian.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"yourdad.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"450000000000","locking_period":300,"voting_power":"2250000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2250000000000000000000000000000"}]},{"voter_id":"pablo158.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"50000502800","locking_period":300,"voting_power":"250002514000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"31000000","locking_period":300,"voting_power":"155000000000000000000000000","unlocking_started_at":1725148829614,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"250002514000000000000000000000"}]},{"voter_id":"gastar.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"270825909200","locking_period":180,"voting_power":"812477727600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"812477727600000000000000000000"}]},{"voter_id":"f2ec587f5ee2c7fcfa255e41b2e6f2f67c996e9e4c3552f4f61f273039499e27","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"604b24c000b2c1ab3e89ba373cb45413a35f911d7f4064b9b965aa30ec6c3307","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"043248a41c0248dfd6559064e7d1df1336a85ff5576fb33b064f3c1c5d0092ac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"55658976300","locking_period":225,"voting_power":"208721161125000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"208721161125000000000000000000","vote_positions":[]},{"voter_id":"0a66750b293fb20dd177700333717798007ad7e267819a22dd4eff151dd940f9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"blazing.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0x8b88064DB1595E37fdEd18102dFf6933A8b6EE90.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"56000000","locking_period":300,"voting_power":"280000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"280000000000000000000000000","vote_positions":[]},{"voter_id":"7bf0c76a85ec08feb38270492a0b056abb60cd407cd4ba1a4a2d554657d8d47a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"31c032af32864627db6ff2892e29bcc3881ed9a39ee9d92c4bfbc0f92cd9e588","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"b2b0bc9ebda197b4528db733c35829ef06c2c348e69145d1d93a68016cd68edd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"876bad3522a08c12b6fb8c87f9dd1ff47497847a7dbe06136e98fd81b7607cdb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"65a5ef266ef7c7afbacac621204efa5c0cc45bc2adf1b806731248f401595356","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"6cb55a3167c07eb7efca2fbf6831dcc45ac735c1961a7c0ca97f351a7bb591ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"0c17bb6326e97bc7cdc106a80b62b999aafd393d8428b00dc6b524d2b13006ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":285,"voting_power":"19000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[]},{"voter_id":"3e8187bbb403b6e3d736edb55eac2224c0f841a27d847482425d0dcd344d2201","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":165,"voting_power":"8250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"5000000","locking_period":300,"voting_power":"25000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13250000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"0x5A79FC812149748e5E4AC30A36177c27b1327Fe2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1007000000","locking_period":150,"voting_power":"2517500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2517500000000000000000000000","vote_positions":[]},{"voter_id":"07c2d2e3a3870e1b474a36df06f81da56f6721c5b9d5062fe81597380e9a664a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"5e5657a757bb41d7cf7e3513124b02eea8d256649cff6f02311f4dfe5bab19ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000190","locking_period":45,"voting_power":"1500142500000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2000000","locking_period":45,"voting_power":"1500000000000000000000000","unlocking_started_at":1727823434852,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"1000142500000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"500000000000000000000000"}]},{"voter_id":"10db3f7f05dc88700f822671ed0f8a9942ad4656f30cfe98c1efa696a80c85fb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"66b93b85f11c34ffbed818ec41ac7e06104a197b78e8ddfc75ba206658811aad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1681989f595a819ee42ce70d4bbf48d03f58ada6a56eeb6b2c1f24f0f3e998a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a0f39a11339d4623b61dac6e400ca59b4d52caf493caa4cf80a0cbda6f3048af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"fd5885a5df244625a6b929a376d92d3926607697bfd85150665c8aaa5d800a1a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"394a4892b4b4cabc53c1adc3b8f95719340998de22871b30ddced6781ef3bfa1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c39e4f5c58cf64cb3cb8223de94392d297c9db63ba129f905d0d9aaa77727f8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"863de74a71ccfe5c27aef97cba791ef2eb034bc63c6fe10304b2eb5775c7f945","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"2ef3b4abcd52922633f5e262b23daf46c3c6fba22b8f211f2ab99451de77b6d8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"88deac4579cb7e475e8e71bea176164e822623996d40d170844cd8d348afca45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1728060364540,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"3d455049327a89b0b0899d886460c7a83e9d97958675b7ee084e68e2dfba3496","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"6fd94977253928c232bf97391813e8be73bf2b19b40e19666aa73c4b960e7abf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"2af49f0840744bbe06799c5f6f486e581fb5f29d63957207be1d17b7bc359857","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b6da2a6c2a45cac4ee04ada08b2420fc0882bb31a9a7c45cfbdef4754ef75cf2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"cfa89fb3ccf6ec703dc0952fce8b7871d784ab26ccb004689df47bfc3759d757","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"eb5015c8ef417da1af8e12e1060785a04013516dc6f46cf59c0692e2316daea2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a71508ec3db015df53c8fec0db941937e0004767a1b2dd2ba88b752bd0e58330","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1fe374089c2af5490b2298baa8957bfd878f4532c5d32b52b0b88bfd408e5878","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"677472908e6bf3684c2c02305636b2843fca477f3d50b76d0e40efb059f673e8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"58a221e69d7e376a82b7c1ef1861d8274efa8b57005362645b431cbd12b7daae","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a7bdecc94e99b2cabed92ee9d427fe33c302de22068a374b01a2947a12fb8762","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f79e3688dbcc3a3165232cd9702213287c08473da0dacab9b5fa246fbf897e76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"801f584e59ec74a98ac0166632e9d021329f483bd4ad627dc7c46fe03088cce8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"61a924afbfc3a863a9023af4feeaef0f0adfd92033b0d0f38fbacc79157262f9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7e8cd1a470ff2806661ea084a7f92a7b0942f7a122772c4e87f60fe988691bf8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"12a7ec72e6e83ecefcb14432bd4e10584885477a44d3b2d4168978e8dd98f42a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e75cd2459b758dbe5838c0f3516bb095ed11544506c7c8b9cb0127c9a4b64b4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cc739bd26bbf6ab90ce3fb9d41e8af1e85297fae6bb6cf0c052b3007387bcb33","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e22dbab1fea3e1ead4ac65c06009194f0b317dbf7c71a5beef5af0aeb863ffd9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b6e52ddb6a6fdc3114331a6b40ecaed92cbcbda6a678f70a2ae03268f33f4051","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"88f5b2cc701e6193bdc4e279f59de48ffaf0ad9f41e47f0b439d2b1fef101dec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"twinstake.poolv1.near","voting_power":"7000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"chain0ps.poolv1.near","voting_power":"7000000000000000000000000"}]},{"voter_id":"cryptotexty.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"incrypted.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"5d52107bee24bc3c1188558a36eb9ace9995e2c7661cef2238c74c7b66ad377e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"1f738d3ccb02936f70e175e079e4b230dc0aaa207ec3731034c273322c482c8f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"086d7c7345fa7656dd5b34ccdbc59e9f28fb8582b4fce9511bc72b8ebc9bc33e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"716ebf23a6beeb2f427de862aa9d68bb0439daebf2ca54d095c603479d02ded0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6ff6c54242179073ada7d208ba3c837fa45235aad79cef65f706e52e2a6c272c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e291f45549fafd0119e0dcffc07c798506f5284291a5ea6763f43940cc705742","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[]},{"voter_id":"0f99f4b0e982905e106d4eab42d90a8934ef1f53d904b335e4eeb4157054bd77","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"81907e7324689c6eec67e816d32dde0a35668ec2b04f991f745893a52d1b6d92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"c900c1c56b632a20451a2851a5057aab6f540d405c4791f85686360a495f8385","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9103370c1ded2e04077ab3fbf0a708b8fee964ad8f83f12a1f52c880d48e3f80","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"15000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"5b04dea42af9b161a0749ef29132117832ae47fc8fedcc2b8b7137bf588526ca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"96d49a1b072f7c79af02b8137e9af2b80c598955180010eab621cdcf5f99ee5f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"15000000000000000000000000"}]},{"voter_id":"c5256542ea6bda0c1be75fbf5ec199ac9650246761eb3c7eacabf82c9b092243","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f67051f40da221d3f6a75ab243845868f49504235568efa3bbd1ac7f0fd6849a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"9287343d8dc623b0f7842fb82ae7e6fc690ab357d8962d604d6ff5e050c044fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"1404d46e1e74c3a20d03380feec9bd751e495a168cbd33686b53276d4f3c695e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0129f067fb619f07c4246425355e69ce28f4fd6e75f530964d5bada7cee0554d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"370ce704f8bfb182314e71341e9b6e452b4bf835021860342ff71b016f1c437a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"swissstar.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"a8717b724a04e70b211c43eb3f47939c172c6a4700056b593490b3232a751798","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"1e27ce857ed9390752de3bfee95f19759abb8659485bfe381ff5ff474e6c75f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b08aaf79df3eaa758f883cb80ebda2a5d02a138e9742e8db62f4ad0f94fee107","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"21fae2adbd00aa6d5767d49809762ccfb6c3817dd51c5d9e756ba50f9a6cf497","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"5dd1b36605c2e66a2406120b9e5cc881fd2e6d1b9e875acaa5bae8f51dfee5e6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":75,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"8cd828ea41a32218e8bcb02b2b2bb6fb3398c71454ec0c1043d0b046761e689f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a7d1ecf514faa181b20c8ceda44585552879453fca844ee214e27f85d711199a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1f32ae051df4c3ee92c0c455dfa7e06864cfd67f48256f06d92b8234f9c06412","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"36788febe85c0e710fc159f7e89dcb3d6f9428f0e6c4c22c66b902bdf74d8fcd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1edd9241c434318001fe7da7d7db216902c2aed93cfd9cec2052bcc031505479","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"42fd84490f61b1f56ff55cecf838af96f845c9b7557d01af8b8964438ed214fc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1725894076521,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"dcdac03cce7f1daba0910a598c1262e3db850b6a5b1dce9050918dec82713af5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"eef3e67b7eb739704c9e69ca4f4846816347ba0956781abc82c4d92e3c6af6de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5877ac057331689da0184690693aed16951b96f51cbbcb73cf299300ce979549","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"6000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"19f6558955d5f5e72c1dcd0c17428939ebaa857be8a4748aa835f66437fa37ff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c924c78471879a3bbf312c4fd14831c34f3066edb79b0da152e0f8d01266bb7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5fb2e4e80796467b929fbfb15bf7c9fe54aa544d660ed95d3cbefa448bc47027","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"b7ba0977a373409ea548cf6b9a34bdfc7d736de65689c3ab8dca04a668994d68","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2e77ab61f7697845e2b526da55ee186d9a0af93dcd76b955be4155b49daca019","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":105,"voting_power":"7000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[]},{"voter_id":"d18a87234bea429ae49a4143ba758d3529e62613569637a769fa5ddea9728bca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e10573fd1f7c442c8f8cf13177eef62993ea34dc731398a14bf1cfaf5baabbe3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"794998389a05be635573b069fa11113a847da0ea15570743e5b6ae2cdd005d07","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"940b79c23a2277a6c1a0b38ecb826ad674c58275adb0d07225ca1d006bae6ed2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"be08aa1d468366014e5f0999b5f952c947f76f470a01fb104f2f067b18d480c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"971b71d69c14b9e5e4df3aebb5329443cfbbf94b24141a95dcd12a668f3ad925","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"036a4501e25eb2751eb42b241de1cf477c7efd1478d077d7b3d6e6ca6476dd61","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ddc32350dfa3606940362e2e65a2827265d02969c0e351871c51c0c3fc0841cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2dd3b187270076f03989389154d03ef114b52ffd87c6d7c4f12493170f571c63","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"b435a74f78ae96d799e6cf7a67aa2146d7b7afeea2bbba75144c16efe9aa2cb1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"7067c05f8d106f645c046e2c70fad16fbf650d361ae1bf9ec025fe5de3140b52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"fc1c6224dccc835deaaaa7f436de293048ff917f49cfbdf7fc168b61a889a80b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"72792332c9650c9712402218535027510079671ee028557ed77bd1d1742fa843","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":240,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"e9294c0783c4ec2598f5133da7be8152ea9c8cdbb48f12c4e381366b72cebb8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0x0E26Ad20DA3ed9530f34f0cE7CAd089099A5b5cB.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3046000000","locking_period":300,"voting_power":"15230000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"15230000000000000000000000000"}]},{"voter_id":"00c6cdee170e38123763838006c396be990515dee717892eeafd1d78ff4609f1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4e6b3efcae07d074d448b075830a09e8245ea606e3ae5008721d8148ea76b1c2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c7a675506432b1aad8273a1bbb39ac98da78abb068b5ddfdc42918a05b93f4bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"05e3b2fa9d1dc7c29c6980e2cf38815c6bcf456bd76ed2398aaa41694293ca85","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3a91a5d6405a139a92e8ab2c600df1bd6ccbd0b9fbe7f75ed99396653169f292","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"799aac39d99dc1a9f6c0208bc1d06c838ed2aca0d4616387dc6e99df79f131b9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0d71f1a9278ef28f622d5e6266c8f6181cbef45f76c2bc02072a7701dcdaaf61","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0c8b607399b0c2c330cfc5e9d4589151a5a092338d102066c4f0942d9a45782a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"14c859966f54eae431fa6dc83c3be5539372f9b2970d5ba88875dad62d52e71e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ff6fa2987f45ca2fb49273dae1d0532647c0b5cec38a62cd0811ab8e2351b943","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"aa7abe81ff463b52e88a682a25b516ed3874f3c12f44ddd12a6744c34574bb92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0829b5ad8f8b242801656a319110fbbd21c18fdbd5476342c6ca54ff68c83bde","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e16cd0d634f877b0f01efb8676bf85f979deea1f789a7203fdac0cb480e408b6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"andrbenz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28476061200","locking_period":270,"voting_power":"128142275400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"128142275400000000000000000000","vote_positions":[]},{"voter_id":"9ed0b94d100f06195b576c5e8da67112d29953269218e4060d41a3798af97100","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1361ad9c02b77a448db162e06a7ee89d9fe75bab9e94bac626ed4431db14e101","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"kryptokriz.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"59776610","locking_period":300,"voting_power":"298883050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"298883050000000000000000000","vote_positions":[]},{"voter_id":"a81bd780c6fda9db96d87e7cbd3ec6e6787dd6dfbd0c40b5fca3fbe2ca7469a0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f1fbf3b685b1d4e1908eaf14ce19ed5f433eb95138834752b7d88b618e9619b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"64000000","locking_period":180,"voting_power":"192000000000000000000000000","unlocking_started_at":1730687301890,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"62c0051153fdc29123c27033db96b359a00235fce8b95ae6d2a90378ed75a6c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1ae33cf82dfc8d29bbe7bb616c65ce3d8fd4b8142d8f2e1472533c8c11fc5d86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d87cbc6d0749755857760871578e9cdc159d92c3772a1028c298b3b0f81e2d9d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"ca216b3e2c398da4b6c79221c097420883c76e434e2e3653dfddc8cab8d9fdb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"99cac8041fe3fa1ad47524aa416e84c0fbf95f47bdadd940afd0fda3debb3a03","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"379009049af0bcb5721f27bff4f1ad261b68979258235e078f03b3aa74b398de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"91f17c18002344c0330382e6a1e00937b6ac49e61181ac18b1d2e57c9979bdfc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"afdcaf49a9cb610082703daca352427ae75c7d43ed80934fc9a9f04c34bb1d11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"db07287a6fafe6f9c8aa7f9645b398c0a39cf432bed7b3bf3c7de8a94b08be97","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"b5b3238925314d194d1566a26a207222a3cdcf317d8cd9ddb8a99533adfe6e57","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":240,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"61d0087874aa6826490c8f85c60c3d7e49438ef3038aff524129bd4cd44efd4d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"9cb1ecae070cf1d83d0c3cd25aee0b854ff77272ac70d596e6a752ef42ea98e7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d746dc8c86033768f956222b19a2a72de997e01699d62402589ea5574f56593e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":1726093284453,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"4f9a31487a01a0f8784a33b640066d8d4156da92c532e68093dd66072da44397","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8eeef53b2a20b60365f3d674587970c71b17f7e9f8f8f11607b46e373edff141","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7ba8a7ceae452436870d704afc0c52b5039f8845eac11787578a13170265eb86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"81dee9ff2783ff8391df1fe52bbe2a9e991a31cb9e54f33ea2ef3ed50682ed3c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"18f9f3dc9641692f92bf6881c6977d775867160e357719718c81f31abfdd0e5c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"06231ff28eac44415ba813bc8017eadd71de48097fe79bd3b0b91297c99d4ee9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1f2503d03142f5248a3edb5294ab7c382ef11a927be9115a8b449e3a8293ce63","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"64159310","locking_period":300,"voting_power":"320796550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"320796550000000000000000000","vote_positions":[]},{"voter_id":"f8c8909679232c5892284275ae41b022f5cdcfca753cd6486eaa7208c58bd676","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":120,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"9313be167ad99a001af9b81ed2485d1972b64d093cea31a921cc99f349fa20fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"bde958893709fe0b8957f28bdc420dc3755f9f97d505bf4944b4ae891b8c8df7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"70e40ed7c95a6dcd3614909d103804937737a8aa468c4b75141ff5dcb71dde6a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1701c668bec7de27894c60678b3643ca7b451c1ea34df65b0d14e27cd9c554e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0267c26e3a65088ca28a326d3a3d38c1e01750cfbcdcacba44b4d356f16d5789","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"028c9fbfecb62e6956d98f9f6cfce636c4113546754385dda1d53bd049ca01d3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b402408e7315a20ee682024b23333e28744d4c0589270a0dca95a04024b8116b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"58af9c4f6a59a435019250e6694b21555eee80803be9dbd25fb7f26097c844be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"54d2db661030396574700c5bbefe3fcb37b33c7ba464b1ec78538aab677193ac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"db6a6ee86376da9b1027b4e4f797643b08f6f163ac1336fd88ca32a56f7ccf66","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7000000","locking_period":300,"voting_power":"35000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"35000000000000000000000000","vote_positions":[]},{"voter_id":"6dfda20779e375d2fa86c4b946759de61bcd147f38cac297c8904ac0b896faac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"abe2cff100136ee3bdb7067e73f01f9652d785083cd94619ca177d0493c53999","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3659cc1b64562db07abc7c8cddf0e16a909620430de3ab18cecbd917d1338798","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"b35327c1958ac20d43c30aef2810d4138c8b6fc7f457f4321166ce1d548c4f84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"82d89f0afac74d5bdb4aeaefc46190afc734bb1477ccf59ebe0b8f209319632f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a5a2e09c728f31f0c4108fd0f2bcfe0b1cb08b2007ebeeaa7043aec4dfc5e01c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b50ebc176b2c3469c1a471aeb95abdebd67da289fb6013f65e7a6fdf11ccbfbd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4dfdeaa39322742a790e25711cb1ae1a2c919eabd1ea97aa102ea55ce63e7130","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f402e8ae9d5b9a11924db2164452f75b3c4d8c11cbf2836c8e523b6d9419b2cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ece40f7ff08812440c1be6b2cb5f89f93efd09069c411699e2781726ceff9e9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"d8bcbfee95d9a5220bea8c39e7963be59260c5ed4f992e58a63c3f45325ae74f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"25736f1f4db48fb3b70f3d252954ad551de4ee15b7afe0ed07928eeb85378463","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":75,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[]},{"voter_id":"b36aad8ecd4d568196cda5b25fa6aded59546f2860d88c2c4411828254aaa148","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7f75a3e118451ceeb60e3fbe69cafde67faa929fecd0658c7a45f3c3a39827fb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9291aa7a7ceb719954a660976188cf0956ade8441bed1c33b0edd6414d4a28ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4219fb6486dc21c3931ea3a04bd5f89ea0282190924a404c911db17b894cfd0f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"f2b9e607ae630b5bdea7a305c6941defde1904303b155247e427ef79614c24c1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2de5cd18bf2babe3b486ea7f16bce876332575b57ee9c0988a6447cef7397fbc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"41234a7447f36f2653505f38a3c92b9de8baf4c128b4b41d0610c28b469ab24d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"563992878d53c3ef12a5f1a72b2bdd122d27377499541d441c3e9769884f200d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"69f5cf0dd02ce3439b937bc32a014d286542aebb85645338c56c866bea1d914d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"904f6b18b908ebf4f6bee39e7cb80b1fe10510fef3106d3f7248763cefd5e511","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"1d6a58651029e4bc3506e7054a0e9731923afaf4ccec52baac0add9f438dc5f0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3494d3f964d58eac9d8c40c9c4e624b632205f8aa3ccab8842041fd09b6bdd71","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fc52036b5cf9c7d2d82015b0035ce0d2a99ce7b51201ad22113bff3b0c291e7c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19600000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"200000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"200000000000000000000000"}]},{"voter_id":"617937c4266a0f2e2585d37d5cd6bfba7cf4c140b08b7e7fdee3b3a8e08d5052","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2509acc08146306be1bcdeaf0341c10f71613d98af714b63a101343b2ac7de36","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"dfc1d9a2fc74440f7a967d511c13e3788e1728ed95b54a4c8f18fa376b749f00","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"73be1f7e83fb1f61d2479ad2347f4e86f16d78c6245d19b774091fd7e43086a1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7357821ec1998d8b21e5e9e0542ef6dc1577933b825f6032d97433584295f330","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2a9de835bbe6103656549fb96bad8389c872c88b272e7c585ea60d112da5790a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"f0837ad29d4a602895bfe6ae3ab909e6e2e98612a4d153df60db7eb7ae0c48aa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d7551a5d68564271f4a351c6bce7fda2ed04b8e4c37741541fefecf15e36a287","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"17b1d503c7c6fae10bc6f4106f2be7883644f88f390c8bc4d128eaf0f7a83666","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"412d18aa88cbc4b8f79039fc6dc1ccdc3122ac9f234a05fcdf802474d54b1417","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3000000000000000000000000"}]},{"voter_id":"3940ded2a79c6b18ad67a37d287eb5abf7a8a2f388a07d1a9e7654398961c77e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"797b0eddee60b4a86a22dd72e009c4577a5dd89ce68dfc44cfb7e0448de7efe3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"a0b12a2f220110c01b8dc0f596f2ed16689ed49b2b05fca2f6481267a2baedd1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"89904f59c5f1652f16d13675e197727e7c4f414e6abee4e4db393b996f6febea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8d7ba69f8e14eab2567604c1fe7299501ce9c95c96afe844e9c7a7e709276c45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ea568132b0de7d2bae73efeb747b96ff717240ba68f13bf9b5538d1387a23a19","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1dbc329c35ea3645a2aff4940932ee552491819a9a12dd91eac6ee6e6eeb4c05","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"99067b6fb9d2991532910c7d2a5b15aac61b4d0094dcfa7b2ac8f0ba1b9bfb8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"3311a8782754811121b198b06e97b6f192513f4b817a80f734423a76e5dc7ac2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ea3a50632c840f23224371c4245fdd40365f1cd92c4a0edcfe1b4205aeeabd6e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"oharanodes.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"ad99afc29ba51470ddacc72a907fcfb3dbc5e852e0912e3ab33811e7228cb315","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"32be94ecb363ef27eac86531145f110342b980ad47b5bab8d8968481210a605b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"31187eb2b9d331ec171bd8c2b077c28e1555c2c7a810db4b534124c859ff59ce","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6eac46d7bf3fdfc277ebaf3c0da229d35ff2ff690c59f9282cb18cee676923c4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"398af1f074deb4b17e59331e26cca95555b9f97a5f945c3d152e1410a2b8d5e9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"94178855619e1db1b60517cbfcb24a8040fa06ef069fde1cf327b94d62fb36fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"3000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"b66eddf0109408e2219843a79615fa7aed85662230c86f3b49e3ce67c38460d1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2e0a4f8bdb97040e87582ac6aa213e7f259207b71ff6520f043194ea52567f64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"25beeb247abbdd57a434c84f20e285d0ac15f8f0f7f536976110db632603a0ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"2719f07291753763a4b6b7050bc31aea692e06dc9b823ae25f11190fe1071324","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"ad320831599369e368e49b53ba57db4dff1bc7982e4cf82a72081aafc304064a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"239a8302778fea0163e819c8ffd67358b723b6d92608923b2c2cdaf0478a949f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9702b78216f13fc8f52076f7b71f1998c90fb7dcefcd9283a5611e76df535386","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":180,"voting_power":"24000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"24000000000000000000000000","vote_positions":[]},{"voter_id":"773e7fc65a5f109f9bee2f5c91ffc585fa85bf9346dd6813492271a90b52aab9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"ed82f442c4e6711741f5f66bb9cac14224f54b73ef645f170987981b6547ef57","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1729740231422,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"8987ec7bf6d8f200769b47c8cc715e971b0eed2a43a33d46b0ebcb05893db35e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6febc4e10c051c4c62c2ba06f413dc7035daccbf43ba50f7cde74b0fdb30d462","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"10af8566c11d18fcdd8014548e78f3fbe4d64a06e60b28c2348fbfe2b586f024","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"94287d6539cc3f7cc989b44061fc801f2d3e7f5adeebf50e857b0a52dab4f353","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5eb1bb60fa568aa0047c0a4a0e7c76afa1bc6adaa3b43be54b60057e918d056e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"near.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"c7eacc8cd7b1aa245fa1239dfbe4ca1fed011b4d1a49fdd2d15f3d03456e57de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"f4a2483ca749d299a962dc524700d9d8e9b02f8abda848c1e3f0162626440a43","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"041d31e3cac68ef16de9193a0ecd05d8f4d80d485d84168cc2e498cd36c848b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"bcd20a73a7674609917cdcd2c8886fc6e8f424244afa00cca5b6f3828859661b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6dc4173077f05aeed2c35296f1ef5ef590b38047c705eb1f14b0c03ac26277f9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"955796c59627b325b9122da80bb91948d3ba1c3cd1877802403e66abeecd7d95","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"c4af9e7e792949015b26c7041ce561a9c2d7eb09eba9e9a38216b5b291eac134","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"38e7d968a3796d6c8bc9657e5675a8fd3e2db880e1e61b3f22901d173c3da834","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":285,"voting_power":"19000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[]},{"voter_id":"dd.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":1726916110572,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"fd212826abfe9407c793a4ba5fb56ddd285c625504d1bbfc7f992dd30b0e20b9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"dc16eb15c0e8cb16ea369b23b22c475c098d3c6730deedb02f26b4fdec2345ae","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":240,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"0353b18618ab836aaf5f1f2f10e2ef20ecf219beaad19b5b5d34e6a1ba63105d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"853055aaeee2da3f8da4cf0e8888c172e7a2d4fed8038fb20ad5e487e005cddb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"d66054c5889551c464d85d6541f1ba7a98fd4059c45e1c05ba88f5f9cc6b9ea8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"657e262f996d4ea1e4ea3fc1672a83909672dce823c68ae98ea08b327d0c158a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5547ed00ab078368ba1230cdf4900fdb243832d323b5f58f1fe9934dc1e8f119","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5e10ad8fd96b8afbb36e8e849799ce5b1b9e2827adfd6979d3cd177f66299dd6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d3f5100f50fe7576fea0f4e65ca69f24b5a5af2a4b620ae7634c0467acf7b4bb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0512fe7a65a2b12709739d2d38304daa2f2f26a0540d6e4cc89a0e2d4d3d5575","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"56e489f527e12b53aa98211fc42af43aa806aaa4a74839a29e96d80a6cc5876c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_01.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_00.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"ce07a07ae93621005b5903a34536c79bedb775388e02f9bbf1b5d690346fada7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c9a7647181fe241342e172bc22de0d8340e23e0b42f005c1ed2dea1251d899f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"331a4de65ff7675d133097d60887d215006b8c22c0cb544eb1366407726e90f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e218a8c3c6309415d0ab3caf8a1d92b860546e5afe5bac9df278a6f818057f9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f17c91f5e7f24364d25bdb925335b28f3c71c60a302499c4c5fa3089241eae36","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8cccff5f1d40951be03ef48bbfa0c0e840429a5ec4a9b47c3ab44fa75968f5f3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4701f0ecd4bed4c3d0da51781c62d51f8d75382b9fe397b30b4516f704395f4b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"43e72e92b841b4d69a6dee7eb246ab0715ac4abdf810426bafc72812b9f9be45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1469e35d59f82078f264d3acb769fd4d0596e9217043908f56231d5876c5a83b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":270,"voting_power":"18000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[]},{"voter_id":"88026ed9612ef760670530936cf719a9c1ed941a9b2037347b3c00d2612f705a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"749b9e47d52511305c3dd7142bfd705ff8d42f7490b10f7ef8964a1f45fd9902","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"53f807d64a574da293b04f7b9c3c9264f70614a5f76ff33357f6d4772121167d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"913081482b6b36bb3dc6720f63b928022520ae73db5d25d48844016c6950bbd3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"52e7fe6b11a8d29e00f4a871829beef6db11eea951403a32f5a120b793a124e8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3ebf8d92a358e1b41116ad43776347a655a9d8359c4b403eb4b8e15fd096d985","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"8ac028f6b61de7b53858f4010fe7cfe0123130307138aa9c58070916227b5204","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c28e58c2dca82bb50dfc2818364fac2f7edb958a9ed7daaabc704c6484506850","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"dd36c2fce06f9729b713377bda681c8a8270c0ad5b627f99acbe5daf0656de64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c2f4f51b093ca7ccf515a6ac178d60ea3ae0841ad67d9936e93240081a9851f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"30e18e86c24939472a1e9f103d92bb0b1fe31bfaf183576cb320b55a89b54213","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"780cab709ac8d76b9a8f0af5a258652a97943451b4fb5acd18d49776cf6c9210","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"6f7f6a1e29da4d60d8bff0fa37548b2f07016f1a0a6c5c24d04025691ebeb808","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"04287931bef54f9d89d10098c1e8c220a6c074ad781e1c5c9f4d5195b0414b34","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"734fa29022827615039e320580bfc53444a14b05f9596b3351b69ffb013e6f05","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f6942662733ab031d375130b65055f8f1931296af99e8aa21aac47bec1450a50","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c807d05db081db6687f39b5a6a192edc2aadb157703c7e3b59478c7fc3d4fc87","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5b1e0fd9e166a4c2c6500657766c02452fe80666f7e82fdb825546f9e5aa7fdc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"fe8a7476bd941adbfdc000101c5cf29fc1da907a4cab0c548d6992935f2be323","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0bb4de25a67d7713b6f824edc74964787c34555ed4ba33dad966e051f6b1a48f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"47d48c9349cbd5aa879d981ff3e240123d8ec787067b96077655a8d7bc8ce9b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"65ee595525f90d2ae03982f73cb6e0fd38351e558244b55278249f40f5d00f89","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1731301368381,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"458c8092d0ce14bd7a4888991f9150ea0e8853f6c04c5b85a797d8f373d629fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"45955a324834403cd876b0f6a616470996bdfb1d452b167792df92e03f5f2cff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"184da3a9f33dbc566a033631982a2f8662987f89edea9bac90752643a99c468e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"403b34ac996cca15aefb1abb82334629d09776d86e86da7566f517c222b03966","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8b41ac0a4e523373cbc18c4881a006c6c38dba6ffd9f3c616a9cd772b27a225d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"aae0f747860877ec2334fc538226f53e043dba796a65ba2e2beb55a8e5c73f74","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"591a42fd26c0db3553a45cfdd10410cdc580b028f8c3dd64c66a358ff2e8a9dd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"bec075061d6d3730f635592e6b1baeeea225fce2e6c282fdfdadc6bc4454725d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"spectrum.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"finoa.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"gfs-ventures.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"06c826b8882bba0ececf2f575ec384dc61cc1c6437057fbb407e5866cd3603f3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c108ee446be7651161f81480822c404338dedb10a5aeca4af42244ad48a9180b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"faa24b31bbd2cd1fbbabb1ca36c8030c5aa5f3eeda652a12d25f788d41ecbba5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"29c778fc450b3372ff3749544992f2c3fe1722c25b2da520264a186a41fb5193","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"63b5128969e38ed740b3281bcc6020525abbf57670720e0766139ad0bc7f1bb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"de75576eb4b185d6f483d13e45da751e699b36761a67f02419a7768794e94645","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8731464b3f621bc32b4d8ef03f577bd892dd907305b249018931fb47f7f908c0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a8b5e76c9b9837392fc596c0984e1821744b2b7845d0da40d96a75c3328fde60","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5ae7d21596f8f2bcc3ae3bbb430be5f438d48638393c7f9b4f4eaa3ccbef6a49","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"5e53e3e4ed08236538800f388019fb280fe63ea25834ca5daa9aed810e5b6ada","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"bd6770f68773ae3370fd987ba5da50a9831c8bdb0930ca3403f9746c823e7e73","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":195,"voting_power":"13000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[]},{"voter_id":"c570c39ef05d4252adba606388596d69f6cb39f74a6c199f5a876595d94d11c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b4cc20115a204e4c8b3fbcf1b7cf4d448c0d8ada108acd9902558e2da77061a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"304a7bfd37ca7f7a8010436c7c0ca7a49f1f71a978fb9f7df1b9d538332eeb32","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1ea52dd01e3490820d2545d5f298e28c18254ffa74b4409dedf3553a483177f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"a55dea66741d2b4ebca4a61bd98c0ca03cbaf0b6473950f2dd7789bb3fa71e99","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9cf61dfb276914d9684ac21367ef56d4ae74f88944ee7ac1851a732043e17201","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"409dcc41a29f72b2a8921bb86d22cce65412540eb8e5cc35740a7365853b2ef9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"01c45530443a814c411801de32274975c0417b13e4957b6b96be9f703022cde2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"fec07cd91a1759492221e878a352517c40ce7773d21067d9e9483fe5963ad80e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"4e3850f68fa93d677287cdef4f1f6b07f2a2c4f99959c154fbd27bf44b8529de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f01e43067c4bbca2cee4b2b1d663f36899773c99cda12570f120440d5697c7d8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4dd90306bf3fe8e715bf90422fe431b246a15511255d30c36c7edc5c8c934443","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"19527eb0a7f723b836cb7b3451a05cc0555683862b301554e23277f2662272b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"fabianjeff.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1403139400","locking_period":300,"voting_power":"7015697000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"7015697000000000000000000000"}]},{"voter_id":"14552b1770952b0e1e526fb0c0f0f6977337d46d39dbb473f787124d4e5e15ee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9867ea08d088f14d907d9ec26fcb92caf903c6b25a77b002917c9ec8fb4200ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"cb6d60ae1d33d0640850c0f789c3a691dd2c78889e45580575ac034b87bdcb3c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"858366ab6be01a9f23f2b0995ef12da31fc0f33bdef636eb3aedab86000b4039","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"78233ec39df97f2cdeea83dc25207a878f2b6010f48eec05c834da03ee6c83af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"1b2534ee7033416ed536d2d1d776408410791742866443f0f8e1ba9a3934b8cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"agrestus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"861429932b08480ca9eae4e6beaf3cee2d0640ed7a8b2955eeb462ad286d33b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"binancestaking.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"a6d26985e398c5740af9ffaeb9e328c793631228da41cc87af38ee0aa99770ab","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0cc6b0ba4718764969c565da59c398caaba6dad4d75b7354a92221080018e1b8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3921d59929d52d0df19f6dad70f82ac4afdc76422d0827e4eeecc06395b14fed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"62cec87ff6444ae66e7ca49a31943feeed94a201164e240725695619698c7870","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"55cb1cd72969dfb6847e29c03b31fddf4d228f1402824ea9c4d1b304737f3441","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d23570731443a539074d6f662afe0f7fe76417a7349ada3bb39de1ab731e1a72","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2d380e02cfd85f8b939ab12a0689965b72f763a0dcc1d759da04795bbb11bb8f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6edf4c0d8d6578ce8c0d3d35db9e6ed5425eb7ebbc2ff0dad7caabebabba7d10","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f0e77c7f794ddd17fa3d82725190763a965e7afa53ed1770f0c8bab345cd41f1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f5115938c6a7b5ba2c7bb48d0170ccd516201093e9c07d9cbf0dfa68a1f153ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"017814f0dbff4c1aded5fdae80ab2637951a7eb22a50cdccf438bc1aa91a300d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"7f86966cbe527917e55e0ff27871797bf2fcebdf8b0fa0f1653df6ef38ecd063","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"dec42051f69e0c1028979c55ad771797bc42781d2426627e43f819e8b0308c8e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2d206c0ee8e3a31b3f8b9c4a990db7b03af6c5c21502773491906c43ce95aef9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"96b77234f3041e8993c3d2a4466586c92a750b341ae296b32d9e7db47e6c0c59","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"651102751a31dd23a9a6db8d957fce4e3646e8e8157e6b7c165668322171caa0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":195,"voting_power":"9750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9750000000000000000000000","vote_positions":[]},{"voter_id":"053d5559f94b48175da9f65867befe2a165846ed5b67abdd1be4ee788294120a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a3b4b04805c4bf45cbbc3620547e231b5463c9776021ce9960ba8182ce4cf568","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"cdd59c02a8d09de7744d6d108c235783fbbf08b3ef3b7f0b99bc44c90f28444e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"yobo_z.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3257705100","locking_period":300,"voting_power":"16288525500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16288525500000000000000000000","vote_positions":[]},{"voter_id":"5ab140d2221672ff1f6c90d948d13a6e22dfc232b97fbe5fbc9f2ba0b6a44540","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"495253cc729e92aea99325e09ae1380e80e6b43ef3cb4f692f2018c867dfb80e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"58b8ee8a8e5b628e6b48d4d99a49307bf42cc6ff837832dcce8460650675cb5a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d874d8c8d5477f3dd4a81f7410665afe8f90c9b6e18facf7bc26a07a4953e8b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5649ec01a1d0855cf4ca5ea37b46c0aed2ed0171b3a832e73c8fa380b32e8e9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3677d5af605867f06f3f88ead4613530bb534d1f2cb1ba2e79d1a4692dd190b6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0999e054d23231fa8075c8068e28ff735383f8954f6b20b757d9ec0b3d6e160e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"359aea3d930ae6f9bb436a29e8dc6751c86804057f8a6da7d7b65b4b45a20163","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"5d54e98cfe17cad4bd5835510001281b8e68017fd5d0528cb5ac5054b0bd8a28","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"f8f8ee707de2dd41f5459ca9245147eda3435086aa6609d3bcacca7afda32aca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"40f33559d2bf5a34dc46f0a176586f9a6156d945532e46a71ba7710134526569","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"satoukibijusu.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3167889220","locking_period":300,"voting_power":"15839446100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"15839446100000000000000000000"}]},{"voter_id":"pozzer.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4158992350","locking_period":300,"voting_power":"20794961750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20794961700000000000000000000"}]},{"voter_id":"helmangom.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000","locking_period":300,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"500000000000000000000000000"}]},{"voter_id":"yolima.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":90,"voting_power":"750000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"15000000","locking_period":300,"voting_power":"75000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"825000000000000000000000000"}]},{"voter_id":"70c1b68d0e63229955c10cc1521fe85aa69ce74ff0ed81146e46b14525d0a723","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"61cffaf2ede2960f2f5c42725a5c3ee1b9d8c4c8f19b92190011822597f2286c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c2de8b99013c31050b274d77f6d99e7c8a78718cdad69a5561b2e5f3f4b359ec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3a8c4a25c26b16aa179db0f523403e864d13fcb6fe8f454afc67c3a860b9f5b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"711ae8e59518ae9eb84d8323c08b8510b6a901a557a99b8f366d98c68e95d825","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"6d0cca462c8288d2194eebdea56b441eb0427fa1b9087aec091ae69cb943208b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5417b243f5e47c8edf6e3bb14739fc2901282755bfceed9f4d30a01e3630afdd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1ec72cdae4c26124560640ccce558082715bf44f3eaf9e002e6dbe0bad84f6d1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6b1245ca8a8ff39743e8013d6785831a30a11f715bb6fd9f158ddc52d26f88be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3750000","locking_period":300,"voting_power":"18750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"18750000000000000000000000"}]},{"voter_id":"wap1.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46493835500","locking_period":300,"voting_power":"232469177500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"232469177500000000000000000000"}]},{"voter_id":"f8059e22c67be82dc3c189f1a9ccfcdfdb157ee9fb8e798600947dc43b1abf05","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"87fb3e0878bdfa092084e000957fdd55501be0cf683c22b7809aaf6678c44105","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"ed342b0e0718e71354f1f5c9bf364e0a4ba01c3d924079cca3443f468f66d874","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"criptopepememes.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000100310","locking_period":300,"voting_power":"5000501550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000501500000000000000000000"}]},{"voter_id":"7982bf4b7fd2a75fba7c7ae7ef682791d5102e493158b26491a2119f4a893bdc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8918ba96cd3ea1dc9d97a4f5669b02b9522847354f707ca7660e3da5c8829ad7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f8af656dee213507fb14779067870c95d9ffd5a57b3ef9378fa13b85a81b7611","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"80164fde65fa9b7e3a9309cb7e5fdb40a94d1de0811f0cc1cb30f29458564adc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"fd1b6774df49284bfc712042c99608ef010bfc59ed0617772c41d11c33e9127e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"64759931d3006a008af90de61e4d4cb3d95468b69ae01be63c03fa9d2d166163","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2b65b54b408be1fbb32cc70fc7a0ebc4726f1cdcc0150feee4095ea5097cf561","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"dfd830d6b35aee5415c8ae5436cd10b95deace970141434fb64bbbfa4fc7e3a2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7d5f49ae25465761b64a53ff8efbac94269c3068273ed66fab5eab987762d84b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6bdaa49f43e589e6915c1ce64aff8678a79cbe105abe4954486355c466352a04","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ab953a494fccb3a5996cfef4cd1535c424db5eedf3d1c34c8e31194f276431f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"72723a1ce8858eb25ff1db5513cc45fa768423886865202d5feebddffa8b78fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"38b26fa06a6f586156893d52e5440a46a7503f9fcb80c8e1bac05766ec7cfaff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"0xDC068F2B765e7B73d6BD182E50e1e4AE511733C7.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3323000000","locking_period":300,"voting_power":"16615000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"16615000000000000000000000000"}]},{"voter_id":"581267e1c29b956b5f370839e6faf48b6948049289f9b303bdd7bc92ca0a37f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a3e4d1f9359a08670c8adf05ec48c30c5fea4913f393fc1f4f5400e219ed50a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ee1efab27573279b59c076e841b1cd36cebffbef0cd7f3315b5b4a536b670e60","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b95824696418c4b1000df8eb0aa74206081083d7c76ede207b071639b972268c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"17b4a973d3bce2153eba3ebe078c6d7945c4a9f31f9075ebd97c11cee91d409d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"everstake.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"dc6d50a234b525b8315fa3d360385870eb1fc0be2da7b8535910406a061998fc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"6115106cd5873fe67c7895537c9f14be393d1d17f476188ca2eae861f7985646","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fea113a1e2bc2753df1fc8cba18593f1817a6b3e81065e5ffaef8cb63e73eae9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"nearkok2024.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"62796342850","locking_period":300,"voting_power":"313981714250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"183233068700000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"centurion.poolv1.near","voting_power":"85754581800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"snsmln.poolv1.near","voting_power":"44994063700000000000000000000"}]},{"voter_id":"65d8efce60a4a428ab91587c51ad4b5868c25393b8fe4cbd8c5292a16bcf800a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"2168377db8401dab21b3a25f364aaba7370bae972adb42e7b8074b8d905edb76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"92e1c92cfe613c4e6aa789ab2175a4d5da1d939cc3c96cb8b6386850c1ed77a5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"phuongwallet.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"159558600","locking_period":90,"voting_power":"239337900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"239337900000000000000000000","vote_positions":[]},{"voter_id":"12cdca6113ef23cb19c5a16820571fd6a670614b283b31c15e3cfaa9798f530c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e07bdd07a19febaea5a02d5ed96baa5120541f919da6c9cf0472bd286ba765aa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4a8586f03eec496d98ca4e34acc6139ba54bb605df1844e5fbdea2401e9d6519","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"38615dda77d0ca19be35037f59a104595268620606d54d5011259cfdda0c3e52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1a616190fe849bba5e123abade2c07997fa99be76a9b0b6171def3758aad8ee4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2336854890","locking_period":300,"voting_power":"11684274450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"11684274400000000000000000000"}]},{"voter_id":"b95b75066eb4ded1e0326b37100b4934b5a53fcd1bc579b5c6313e4118c13ffa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"66f84b2665be8d6eaad47d66f1403c7c49a82ac899348a0da1b452c219c313d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"d0f4e50101fbed91426ff5d9484d70910699c2e6f833614e56d8f6da1564dee6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"7b81ec482bb6ae18b43b279185601b6dc2614a22b4fa3882adc68e643a9298cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a759603b2906b888f02011a6e1744ce28bc9a062a9759bc3eed6240619fb11c2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ae46cba8067ea098a7fb02275c47c4e2d971106f62cbe80f3a8d508b7f01c4d1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8ab933f3c34638b6a2512b4c72ed3ecd6b953ecbcff08381c0734d47345a06ed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ff868a3da78561e1ba668e61e9a48eb3dbe8b59e0283ce4115701220faae00e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"a3fe35fe92cf48fe2b0835ae79d79f270cbb08e6d596566e00c8e7cc64d1305c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"gilbert_crypto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"85063e3b8ab7f540f67345178b105f665cbe78c764f2e3d942edf2f7b93a53a8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"tequechongo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"209705250600","locking_period":300,"voting_power":"1048526253000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"798526253000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"250000000000000000000000000000"}]},{"voter_id":"8d050b050cce9dc4d563e0aec29b1bfbd8a33daaa9c1825a414dd127c44125be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":165,"voting_power":"11000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"11000000000000000000000000"}]},{"voter_id":"b39b52a0bb1cce31a2e9e0979c9ca830b0bc9d647493e1effd0119eadcd063f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5944fb29764ce2015652b71a9f227f07c50aac0fd2c040072fc483b120e7dc96","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"366779cadb5a3f677d173fc19f83e269949e4b20c2b4f76c22ea5a2bd22bd8c2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5a362dd0389e5dbffb124b117b5207381ee4cb0b3a25a87ddaed99497b7282cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4e2c34d0bcfe1a22208c433663181d681ef5b523dabc58f15deb737714d7d2e1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f581e13e4754154cb65d55bc8888a926eabd87b72bf1b477df60e339d5332d22","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"520e4fe5920cb92ca5a558828f311872b500171af37e86939d1c8b6c0064f23a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f512fd82959f4dc6309c6ec55ee37b89acd8f9b8f1927a64639000a09c5de94a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"338794f543e468cbe878510513f4f053b804d2c7f16cf3bba9915c125aca7865","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"b77848f86f850f0bb3410db647c866a763dbcb3e212b36f3ce983b9cfca8f7bf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"7500e6f197175d63fe6bb329320610613cd69c73f2265e0ec6d7d111c24bd3f1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"53645f23fa05451377f090fd5be0c86378e6bd7c38ba4278cb0559dd3de83bf2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"stasiey.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2562800","locking_period":270,"voting_power":"11532600000000000000000000","unlocking_started_at":1727845268380,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"5f55c67ec742f4219712f384e0ad2179a89375b1cb71facb9b30423ba71beeb0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"127688ebf35cba72ca93c098ff5669d3b21519447934635ebde46ed932cf9390","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c8546fd655a069d04da742ddb13dcacdfdfbc8dd1adca65c20933b5046646abd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1727954002434,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"428b9d2985f68362ea50c5140a62cfda4adb33072a4d33a192c0e921e719b3da","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"844353620","locking_period":210,"voting_power":"2955237670000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"955237670000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"1000000000000000000000000000"}]},{"voter_id":"28b67c5f1937b06eccbc9f54ff91bb2f39707e1799fcaf64473869cbf3062b91","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"885dc03ba460a7fb2b4cb2619d296e5e623897672a0ba19ba3336c15f8156576","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2bd36f58a7076d4af94d2d6b25e725aef22c5a772b541de04969217383c62d74","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"f3f597e0b900d50e3bae762c3b39fee23db58c1b83e8533618864e2c98d1eda6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e8bc0624baeefeae53d8da80510ef456ef3a347322fa094e612de773b4088925","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a19a1ea737f9f5287ab0ca8c8f96e979391af729acb181c222606f1bc5fbeed0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a971627532339ab09c7b456b773e61897ffa9de2238add850ba4596348654cca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"6cb050cfc1f21e73bfe45be82ec69b30b155fa5e4ad28c2255baafa08abfba73","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c9b78867b091ea43a1524f5981a38c0b9e1ccf65aa81f13f76674d25fa2efdb5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":195,"voting_power":"13000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[]},{"voter_id":"696b648d0abd1845b61ba7441f9eeef16c6dc7f4dd948623d7f586b152a4c816","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"649f75c5dcab29445dbdf052e1e9e276947a1ed2bbce75d961c9c1053f5121eb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f787759cd01431376c94e896a39c742d08c49d28d36c996d9fdc5f28fceeb8bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"098f722a8fefba636a5dc9c6af0982c1bee04be46d651ac54fb6995d6095dde1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"972fc0566781e8bfeec11e7daedb13d31e8bc2c75b3f347c944d1aea223b9cd7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"793eedf5b42464a1693684ccd1185d126817cc01a7b8a7d9bef3cf56862a3bd2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fujiya.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"913000000","locking_period":300,"voting_power":"4565000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"4565000000000000000000000000"}]},{"voter_id":"58acbf4b7f7b926ac436ff8e77abf0fa75b1882d493ad8dff823c2d82b7cf5d8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"27ac9fdffb06b1aa2fca9d6b54cddbcd6b42c2d64fe79da764f1d99019bbfc97","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"956ea0eb5453c89a5d3577c8a38772038429508dcf3292ca00335159ab9a4e75","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ccc4f122ff4c8a855946e9ab031afaa6ada9842ee6341a5e756256c9148478f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"5d0475232d88f6137d90fbafe0de8e5f310c4099eb0871e5b3bfa36a11bbf152","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"350e470d0672079dc30e00d58fa4df938be955e8cd96c1abc4792e8a44a066c7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0x43fFbF682b61d1A8d7c3235eb724775c5d8479d1.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"919000000","locking_period":45,"voting_power":"689250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"689250000000000000000000000","vote_positions":[]},{"voter_id":"1fc26af171760834cf7745f9f7ee4f87d2c9953154dd433f576d7fa376d4f9b9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2bde567dd1f9a173090913aaf637b79d4f6b20825e9f22adb218b4da3280d2be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"19df94eafd51c901eeb482d7842c9c6eabf760982b9180a61f23b4deae693f9d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"16fa3136a231d82be6dc4151e1b2bf5af69faede0bab9c3db89d8a61d6f3dcd1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":180,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"7bd462cf8ce8cebe5d0207b3c8877f7a5f081b44ae66869a912b6c69c2df9e31","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"03a03b9c00c17f0fad492f3c9706e18d05cf02f3170fbd695d1cea903b370976","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e1d93d9d35a6685b89a8d490a4e44760bee71a8a011e2f76985fbe466462ee6f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f30497b3d806c7f657168bb8ec33a4076e74d20a8dee09892bc2c51e9d29aa08","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"11439a0196c4ebfa7a619a525cf6d4396f2db3b80d58f5fba7b1b67d578c30f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8047bbbd226db06e2c013691f0510252ad056d18e48e879641de313ea0ea54f3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b303dbe825076daceae57e9ad34c638366a92955f13515411f38987968a2d0c0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"publicai.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"de9ea4ed7e3c0c0c7bd94df110c3582e88bc66a7548c9af3650e4e17f97f7f86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"edf4dfe7a1b674ce90f11d5ee6ba4caf84fe5f06d2c39bfc8262ea4d59c20c26","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"590972a01161f9e9e83306a89a04018316abd177c120d09cdcd4f0e490e15d47","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c12ac397fc7b2b2790cbf98ebcac2199fdefdd17a370d845e9d6976f9ffd17cd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"134172a1fd4542da0b6775f3438b7c9f4f1d2c349653ccc50d066aaf91e0f47f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"fc19ba0b33ce4e550446c1865168b243daaf28b213e550f276094b8507a4a90d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a798ecc53a312c5e8d7dd3110712c403002c16651a9938868da9a0d25978b783","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"dcfb2aee193fb82e8d835584cfcda72b93a2b888d730ee6fc30bdf4db0b67a32","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"d412062f84c4b6f62d8636285957cf15092950cf2a64086c26f092036a234a81","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"56262872280b0d3b7b9e876a5541c0165985819cd875d780115c128433b09e64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f43f055f8f8ab4a19f1115b8f964a2714529833d464d67f25efe6c91dbd253d0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0ea5efd8dbdb1bb1164301ce7e765a3508376f0c981b0e1b4c6e31bc6f7fbb17","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"fc67fd4fc8fb0ed2f23d7c3e2652b1c05eb735d260ec1c8da825f26bac75a6e2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6ecb5bd9ff89776792cf5b67481c0bf2024da338eb3f26f8d0e51d90b29fe122","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2e7b32b069d7c2033765d124bb66d81b4536a9343b5313d7589b3c54fb8a5f64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"54a3ba1946cef80f13f61a16985a83b37b0fa741f266e375bb31ec1622004708","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"7af7571bb8430a7a7eb36f683880a8ddc1c787a5bd6001138d16224e1f679cad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7403e27a12a2bad8a0b8b20ec58036969995dc12e4ed58746214244c323b9824","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"aef93c341b2c011c0d66c02c1be71944bf30b72bf95f7feb433365e7808a7184","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"577351b6875fe01cd21bae7a801005b9e0903e27980b2ca72674e39f96a3322b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4d429d6f231ede9140dd4036e583fba8382aa5c33b10ee0dae858b8223806cca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2c9091ab5cbe39e1b2fcb8d2d53ba835a4e3affc991876075d831dd5cda08c63","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a86e489578f1eeeb01280be825e050b3b146c6490ba294e2b7ed33bae64ef85f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"b95097c242562f7f559ba6258f702de58115f03476de58f39e7ba0b82457d3dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"60215f937d7d1f3e8e64d9426887618d7dbb0b74b4039eb419f18e62aa4d0f73","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4eb8feca6650593dcbcc6d207ebdc79e1a2c5d7ca198ca10234b7d6b619adad9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8a9a0d6251301f67ff14dc3f975aaeb60cbcb1b65c2af57faacbc9b02056876f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"da9f16ec072ff6784975bd95355de433b474e35df547ad45bec8f893fb1af18c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7201fa1ec8d85fdc3cee2a97f753008f0c16090e7a6d7e548153c18ec18886a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e37642588eba06ca8d815ff29d634261ea0a2e000765a0f300c1313ca8f588f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"9e689570c168c03ce867dda3ee4ca178f118613cedf24cfb37e9742d3683f170","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"30788ba6602013dc4ad4ccbc89249f7033885592a45c86ca9ba90ab6c615a8e1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"86e341955db3c5b0c257a3adc6559868f727eb7337687003957e77e9f50460dd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c45eee5daa577588ba1823b5e50f7f6d3e6e0b89aaf1e906e08915c02ed5a9fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"9e1f0e08609015ba44a98bc2bc65a36a54bbfe572f0f82af28b1c699565ce179","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"387dcaff91a38330ae4478e58f7a16aa7b1541a3a511f05312ba7d24213b4b85","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"12783c0f8b89ba5191493a504567ff911f6ed214bdb78b0938350aeb9a360790","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b96b35dbd9a9ee85c2a3253fe950c7caf1ae0354751497b4ffc237a060763f41","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"steepzip7808.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"715231890","locking_period":300,"voting_power":"3576159450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3576159450000000000000000000","vote_positions":[]},{"voter_id":"7bb1c442ba8cf0eda38ea75f906e64cd23faf7a553b6ca398496b83efed4a912","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6712c9215a8d4d1785eb14dcf4263fc75c83c8272fb2fb0a1cec299d5cb88dfb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f309723c8edcce5c9a5f37ff13fba9f5e94df4098e20de6e5569a70a95362561","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"a16c12180a05d927bb632d1e866ae9b44e94e306110e4ada342a53aef0a0c4b8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7d69aec229766e146f142cabf984851f7b9a245f789af16c42f19bbfbb71dfa6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ee341bf8a845be7d320cbeff65338f98705110440e96aa77bafb76fe0d231d47","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c369077e2d67dc2fa5c9ab5850f998d612b865b900d292d585381e5eb9121d88","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":165,"voting_power":"11000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[]},{"voter_id":"c592448235200dceba29176badb491ca62e88f370a9ec81211901bd911acc650","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2da8de65e1a884e8a986afadb161b3c7578cff28c1456480e6b2aa6ef18786f7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d4b5f44ba1bdf0914ca08b6b3d51276925094bc64002232f973c2658a202a8fb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d8bdd8fad8744afebe4afde79423e46893d8f07bdb008b859e5423cb36c9ecc8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"96ed22be26b1f9160d5c354897717c48f3c124d71fb530cd8a39ac1f70a9509a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"afe8f79a4a01796e071075c4c166f95310493a87ab61794e01851262ea31339f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"999539e3892eab4feba02402ae4a7da7a74f1cc4521bc64361b55f27b68177b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1da415707b8efd23d2493b054c94ed60bd412de3bddbb25527904c273586e76f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"560ce982b8e570acf9904713081dfe4800350ee79443a5e4d2a748d06a77d0af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8084bc7b451627f6220836f288ab664268fbcc35a539304707fb614e53d071f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"60cf5f0a460867ac792a7956d2140be363e8a9aa8d1c4ee4e9dd1f8b4a474a01","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bf9b93aad5038bcf14e8c33144e03dec59306fd7c83d4ce5e4fc8b298079dc4d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"84efea18873941aa281771c327d2c56eb5b83ed45a37c242d9616a59ad006295","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b8d00262ed76fe8acae642dff2e747798bca7b2b21613f00989bf6f3c82c1d4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"89d6ad5e9d4f47fe272394f69d569fbc871695f06bcea749c5dd93f5c4e99298","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"eda649651fe662a9c3a3aca337e3fe29ac0855888f75ce1d83e29b94090dff37","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"263b38e1a3f0c8b958f81f7c869f21c814d39293655eb7b3523480f7e1979bac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d5ece77666570b023363ee977931da238d40f1729ead49fe2ebe67cfe58cc65d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"137c7735965909ff7e2eb54bad7496c1420691feb0b5782c8db9bc178472d344","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"74b26d6048231e35574221aec27c89199d44901da7abdd1ef1dce2c2b413c6be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7cd7853c28b29a8fd0087eaf77356eb7e42db747fd3712b3a596b33ff46eb330","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"b6bfc38230727bc1b44bec6be0978e361302cce2f46afa952f3f24513064113c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0768c53bed6e9ed8d39474cad73c9e0377423ca89ba52b1c56f6e72366ead8d6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ef7a75707bd043127d67c5c81a27ea75771ccac20d421c668a7ff42cbc274e0d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1b78269e86dd496396a8cdb03b4adbc977c8f0e6241635d3206ab8257e837c1f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"49fc2c8a18e1e483993d68e4f19d5732cacf0121afe2beabbc4bee05c6c6f755","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fdf3ce5381eca8a4da41fd3a1cd9ed6ce2b08e20cb13a2e55308cdc52b087333","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"958425ab8d1d060f38ca1aec679044a9a486512befeb2803338829ffe54d2272","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f1ca2ef54cf44b53825ac826a7f5e76c15073aa85c06913c0700618fdc1c66a0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"824218459378b259d14765d23d541e9fa74cc68db8177e76b17574e2f996fec3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":105,"voting_power":"7000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[]},{"voter_id":"18ab3f394757e517f81c79f4b07291817e4d37e1569b1288eb20ecba17ccd953","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0d68edaa380d3b43caf5e9889f01f3185ecb40f8d9ee21c5ccea8b0ff1576f00","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bd69bfdda3dac26df4f063103276ac43c6b1cc5262905c7d971ff18f9fe6f6b2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":180,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"752fa01aa18a8db4b86d41730a664be99cefb6532c5dd64371b87e0bf9d63e5d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"fa02e7b2668a034d0381660f5d94fab0d69c62882765e75a560cd31fa74f8624","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c536fa5f98adfb984f927f9b0f77a0d52416ec38f013368283829bf19d28c48b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"62cdf59509ba9b47e163660636d8defcbde16f7cd2d1b621dd48e1269c876f76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8e0530baf3dcf3684771c3d2c07bc0a378d4aa7860d8b0c13b7bdb86d7540537","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"eb465aab58bc08ce7d3a5388c4eb0b3e575b7f5ae9cad03776d89aa3304d1e86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7f2701b349f86aae7672eeb5558a5bcd45c84429c60e53d978fc79d71dd770de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"53ea2c64d401639431eeac02c871722e8d3001ec9e1fd5a47547c8eb8ecb5f6a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"260159bbcef49f178564505cbdf3fa5d9616d541ca7d58c87c63f8f8a7f6fbf0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f981efa9a7e9bc342b60ff6567474203a422c7b84f7a9c80c76ee7ea30651b8b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b6b4573659b5ff320383a04f8e8658b66ceb358f4c2161da60ad2883bda3e601","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2186c821d615fd467182614ebd73354044947c30d3e953902b60f564fb596887","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"804b08c7d09414a38b3b57a41c63782cfb5ab8267b5e523d1e650ac981a3cfde","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2e2860e67f0d62e07467c6bdd346397624c135f05105066bb987992d05feed41","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"6000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearfans.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"4fb13f56bdb0966dde2483e6f46e375f82b9b33a9a758a603d5f41fdd53d862e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"15000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"cd4042d4b3c086415bc74de8b80efcf2be3e8e185b166b68cff11fa5ba005a70","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6a867970fd940fb241ee09337c8c6d261807b1dca49606098e038577e6d8d105","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"30fbd01de34e31bfd43fda0495e3cfe4d8e45840f28ec544b78532713d299eb6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"20a35f57280953e3642d3ecfc7d6f7df85db592028f1e529dedc3725bc82c5e7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"78d2fd202aede7660c902916971b77be2683f7669ef7004aadb7721189ebdbf4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9f1f64b479ba05bc9277eb2076facd1b15a00fb5ad6161a04f1d8c1e68badb97","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":285,"voting_power":"9500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9500000000000000000000000","vote_positions":[]},{"voter_id":"2cb802d5ba0a2622f5ccb8502419c4e1456529fb3c028a5918b530964aaa6d1a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"99f79d2684ce6a26812e74402f152d0c95cd84428973b99566f071bc91c35d0d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"031034bcc3bc3cb014d866ae702cadc0a7b6537a221491de7c4c5d57c5eec4b8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"949581373d9ec756de56b014799fdddc783c3647b9be19a62cd6f3c448100c3a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"8fba25e4d0f7876f16a9c38ef5394baeeb732158b4d25ee94087bdf3235b1a35","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1890c27bdfcae32cab497f36a863995a2bb54b3b60845245de07959989944c90","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f5e0720c9206ed39b9d20d97bdda945e577903f17ce2ea31533ca7aeb9c19fc5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1f06662965ca0e4403af827d12511023a2e10b7dd1dc504dd6e88ece694b1cae","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"publicai.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"1e3b8ad70d718aa9deab36e80c150ab76b1629987b6c3bf485099b7d0fd427ab","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a6d165bc9aa16995c8db9cac737567a36f76a66e0556a79b66d4f168d1775a65","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4875e1580dbf1102c5bba00ee2eb1312893917caacf2c3c9fcc2bd419fac522e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8f8915ee8749a1aa0e5864c398dea18854d15af608260d4a3bee6a75277e1a23","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ef9c686b82fc905ca02723c22ac76009661a6c64f394609895c2078a113d0227","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"56d8d6e362c65a8ccd5e1e4737cc424f08a9454a1a1db50e9d53df2efaffc5ca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9ab864312f4790f2d00a78560a62852f506e14bafd3fa35e1fb8f25b86fb79bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"46301a73c8570bf44d92497faf975ccf6c7e0c17feb655f85f06dac7efd7aba5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"5a97f35372f2da01383980937440d6ab1e1d0b64fee3186136416de95fc445dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"7023cb33e8f263f61db0fb3ccc1f6607585fb26ff7c36e1054375d567e112040","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"11000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"9000000000000000000000000"}]},{"voter_id":"ecdbbfe1e1373ab6d6908cc356d5959f2d7e96e40a1b0f9947d861603a432000","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"94de4b725023249e5e3eb9a24f41d1c1544eb7fe4e726f45d71e47ccfef20d3b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"3abac43efa7e3ec50a5b0f8c92d64b05775a37399f4027b5360a818a97705d84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"13580b4121d74838ca47d393700a4bdb73d6f18c76865552f2ba941c7aa14114","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"37c865a80639dab5cbe9092c1227909a48a97484a4ec5b9192ee690c70b3a8e5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"87a0f5f7f486d36421ee75be87e4afa880e4e07609c71aecc314a8bbc964bd83","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0b39b39be12209feb533025924e4e2b3ad03236529355be22895617e7d9643e6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"eeb62a3aa614b980537d3fe702ff8ff2b576914bded4daf5c340a3bcd1ebd113","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9ee6c7c94aaf752638b179c41b7e564cdebabb4f9d884c300cc3c4ed4251e8b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5e23625d9fb328d395e34307f31566637e7851ae3820e05e4d7be96f02787b58","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b7b68d23b5d14aef2bdd7f823e91ad0ebaf9f8157fe90d219f91350b452eb93b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5114c59696a0dad197bfff09130c75f0a6d8cbd8cdde8a6830a55f771c39d2c9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"3c9136a912b1bf7a579a9041d7ccd57e12bf568c1541a5992dbacb12e8880d7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"d61dfa4a90c507e7a8c5b2d991b4c5d0fe899dd2b34f4aba779769bafd89e521","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"34a1d4fd633517bc2fc4dbb28b44029fa569601a44f06e7ef6e21bb11f44b69f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fdf1f8ba05276e4ca35e183f568d41fca0a6d4742edc6cebfaeda6a6741004e4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"581c9713324e185bb5e089d78c50bbce945ed5e94f50efc9510a07bf0dec92a5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"e2459c14ba6e77e8281e78b8238c733b6cae669ad5c092192da661eb124af123","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"ab8c4dbfc5319f9b55c545d16f72874720779b13a030c2c96c6e3f04f876ed1f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4bc6c6e7277f3e0c7b959fc1c471e611f643618630e6b3d6f8df4955e22a6ff3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0da2b63c2fe5cdaf30785b82fb978aca1f514d9db27eb530132639d29f64e2c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fef5f6439e828315de5556a5a0408d9400a9c96b5c66b90552ecf02b51392d84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"9045ca0cb82c39b15b17695fc3581a7e57bf5e3769911c6160bdd3f2cb7f9fe2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"35a6d521fa6db19b65310c9b60f7b914bb71ba6dc05a8ecb0e27ff38140704b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"51071377511e5865511fe3046dc4915b0297672ede791ad30b6a2cf7bc3e6102","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":120,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"94138351780d90d6b187b398fec24d7535d9262fc1edde2512e46c5856bd4620","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"fa7cfb6e948a7d200b705be5aa210c4f6edb321b7c40277256809b9ad4afd5bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0a8ced2c030aaf89deff67cb9171026240c9383897ace6bd23d5a796d2de1c3a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2d8fd5f7bba3be0a401c5861667b084761f34de4f5ded9c46711eab184cdf47b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"25f57c3a25a706fbdfa7264329b5e13313829022adc517c05da849872b86561a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1c07371ab41c25d020b1be540b708a0c343217ed2b414e3821c1b455081cb3fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"de0f71396d131cdd51b4213678b39fd578a98d3462ac27c76bb4a2af8d74d77c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c6ea0d083074fc708fdebd97f0e2a50be09ec0d4cfbab958e0cf5fb7490380bc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3382657000","locking_period":150,"voting_power":"8456642500000000000000000000","unlocking_started_at":1728737361510,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"fe6568ec104e0b6dd1b2da488675a79e562591b302e3364daaa7eef9e2dd2e98","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bf1cef6dea2de6d4246d4c4d106fc2a5d0de432b559df59e035a117213f40a79","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"33e5927e9b0b9bd5b6d22aea40f133f4fe4ec6ac2d0c2354bd9811886ab173a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"59027400a26e105fb95bacd5ddd00352494cd080d631f05595c4550870367f9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"89504bc30ce010a1b7a2a4d8a5c4efec79ceed68efa5040853928cf4966eb7fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d7fba1b66a6da1f2af3f99fa27c54aabf6efa70128371b4488a39bdf159e07f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"63cc2a23bd4fb0c9fd0802873d6536204962dad14796b4295e9f6982c364a564","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3edeba67f78436e19beffeb44d6a488905cd1df879dd62fc05a4a4e5ebb39562","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6e8ba11c5ff3781a5b0dd8c51765104b8b041522b34fa8ab112dda618af9d4b7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"20d1f4d9b7a25d85f417d936ba78a1f60536d88e809eff23e00dcfa4931fb07b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"77770326000cc3b7971ea022d6454111ede7a8717a65dd9ec961dcd90b896893","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"57fe52ce5e1004ba77ec5e717c04a8a774144ba08b6488f95cae80721a5b95a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7412340cf0eb340eea19cc8d7535bdfe13d07f058d7fd129572a1550368079d2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a8041d4972bb95b130a46628c3d3723d570d15e7447ce953bc32775cce0572a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"035ed71d46ba30594e7885ccea858f67702aa6a41fe6cdc42c3146178df56efc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5af22d303baead509cb1bc62754d9b74645c1c7a6cbe150de27d56243208ed37","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"414aade1d9106e37b238b11dbc2a96c1cacad6a1561f64deafd4c6494afe106c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":285,"voting_power":"19000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[]},{"voter_id":"ac559d22697b15561e132848e8bd77a006bc19cbb63d9bea8de7938d481a6872","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"06641af629600fe2ed817b077cb4a51f7236a2291a76f60eb5f30c471e98d4fb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"6000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"88ceffa65fc65b12d07c9bbdaf6997c4119222262e6dfa51811a3b7110a840b3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16000000","locking_period":300,"voting_power":"80000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"80000000000000000000000000","vote_positions":[]},{"voter_id":"d8b7bc5623e24b15aa33667faa021ac7be40b0fce636fff149e745f03668ee93","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1e6338d7a9e6722c322b75aa58ca739ddc93f84fcbd6faa8b2908537ee661b34","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"621874f42dc8a92b03b96c7215d139a371733b5e112ba81c2095a3d9cb742db4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ab05985f59778516e33410b4e903c10f5900ed8f368a5df3c4bcce09e2c2b4af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9b0f029772601a97ae8911f1986381273406d7539a084ab2a9dd05070cb8970a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":180,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"8345d207889673d2184cb3d43bc454cb1309de9771748feca8660695f09d650c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"7c8df5a614dd03bc7032e5f8cda085bd8fb7949b07f634c7b7d5bd8d29e752c1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2c4dcd85319450df564a5398a4ff540d4b29bbac9d3d685f191f2c1c2609961d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e2893f359d80129b110dcbaf642616415ae69479a598f999c3773795b87babb2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"171b60a93b2b6ec36d23dd4da4eb2db7be12fd9e2a7a9c817f1f57d548236db4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"6c6bb3046b593fcfab01023ecb4f0949e661ef6a9303e6f1feab7b046e67774d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"25c9134f7fdc3beb5327ba1736f036c134dea5b38b932c5152617cd6da0ca8f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"37960c14e445567e14d8d95aa6482f0c83c346d4e470188fc52def85c6d0f40e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4919083710","locking_period":300,"voting_power":"24595418550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14495418550000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5100000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000000000000000000000000000"}]},{"voter_id":"05ba16092ad6e4511830472822fd24054ce78298345a6b3f0d0d81c718f75f3c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a53dd4a9273b537996d27adad820f326d7c92247e73f72e5ad4c950a0b2a5fac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":165,"voting_power":"11000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"3cd7e097c91412aa03ced89ce8999bccef522f8d3c7dad9708be10a1db75fb14","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ae9993c3f4220ae580f93996814590c5870beb55b8f20c826ca3a01fe94cc665","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0fffe694b17604c525344045af2325da740b67f3e4dfcd102b8b284df74aeeb6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"551744580","locking_period":300,"voting_power":"2758722900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"2758722900000000000000000000"}]},{"voter_id":"3f2bec6ff023a1e8af5dfe4475ec1e4565bbf03a4202e846d22c806869d038f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7cc0696ae18072b77576bb98ce21b2379cbc1786d090abc80074087f4799f1d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1d736861fb23a12aa8bf291bf61dfb4025e18290be64dd2dfae7d17f1ded25ed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"bb7d6b1b86a99158217e0acb447f7aed871a347b2eb9221b35d181238687d525","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"650b182ad2a34abfe873c85d87bb48aadac471088d4f04cf9e0ee113a2830689","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"2394aa987d720b6c6d7a550827bee05f7ba85bd637bc398ed3953161ba4e48e0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"16cc9c22353175639bb477993e20bae2a8d44f10ab8b7cc8f665fc4f1591b5c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6d033ef4039162e42bcd7cad32394062302871d61fdbfa66eddd4a7f8f1f676b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b409b131f7f52e47c8385a155d76f121cfdabfff228160d7e661234d9bea7d3d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c5aad27fec4519a12ba439b16edb6cd19a10b2bcc7b08ef439fe21594582ff6f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"184645ab40ba5fa18b77067d400aa8490f1bf882ce0afc1b7cd14d1eda6e73c6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"5f01c5eb8fe66b99991a9563ba36d94ec41f61a95c0877bd2302aaf60b55dd55","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"efcc44df4ca7f33fe34c2d46b6b2cfa5c5c36f0d3c57f3641c76436e8165aa86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6a92e3b7373bcc54cf305416ebb8cd0f48ec0e7179ffee8128a11ec93d0548be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"9c5e7f96bb77be519a14ec554a61186eaceac2ad3ad10c5f1252edad5f978b26","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d51eadcef6e8fb8ed68a6957bc63a30cbb333b42c18feda1091a5949909b44ba","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"da79b5075aaae16925b7015687d9eaffe75b463254f779b5f9b4b066e479aea2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d4d240926943f46a61233aef6de9d2b04113f564a4aa71b0fa1ee9a2ddd0b6e4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5e164fbe9eba8b3840728deb3dbc21d182cd9222185664ef6b131f5cbc23a7a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"6fc798485e717774ca4190104d5b2d8436816db202893e5705ee20122840a646","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7ac59c6aae125d1099cb4a7532654dfd5820d3308d838019e869fda570d96c3e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"528034cd9a2d62d025a5a598abc77f0ed56ac42175690557649fdd04861bf960","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3970c9eab77e1fc90680cb655a723b8e74f72182aea98ab128c174be97dd0900","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"108888faf531d77fccef1dbface1598f5a8612930b8ee30d73f8e6750f8285d1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f4d9b9891a8a150815f0f4bb34820a104f95b4f0c7cb5cf5b4baf91272522f23","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"01a51432d2841f2ae7d47f9c3341422358147ad854f7ce9fe80d92adabd9d28b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e59d08b13cbc0026c3f0679bc531eebea9676feffdd1e18d17a8b8bdbc0a1717","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"378f71d7f63cdabf2bc15008797421cca742027a51a903b18d13c8d08e622669","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"nigelgrif.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"11844140000","locking_period":300,"voting_power":"59220700000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"59220700000000000000000000000","vote_positions":[]},{"voter_id":"29d628055d4bf4e1d4dedcbfcec782413396ae39647965a4a3fa6a4920f5063a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"560242f95b95efa493310a4cf4817a01de68dc7255bf6a5dc1c888a64908ddc4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"c1aecc4eff6a258e393fe225bee9440fff222b5964b9c03389f414211bf7923a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"88564614e6efc575a5cd4933eeed5829fd3724a9d315736b12ee9df4bc3e2155","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"184e7bb7c10a86a7053682efa687617f91cc5e61bf140d0f2099bce6587134fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"d309b4ca65180b2d16c832d93189717e2324da29b866cdb4a884fc2244c8c4f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b3e2069deb7eb9e5153b1d4646cae13aee9d58aa1bd224f9ff232fa78b1f00cd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f6a41f0d45f63a1ab91cb68edc872a59b399fbe3abc211c567a3b67d312952c2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c2043019d508016033dd677dc09ae75e158d6a99a1370949296a4597762e1884","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"219a80486953e2c7b106d34d71ef6e727a69e85464cc8e4c80395777317a4ae7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":165,"voting_power":"11000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[]},{"voter_id":"0xfA8856FD322c1EFee9268aFE1161B66D55fE000c.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"981000000","locking_period":45,"voting_power":"735750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"735750000000000000000000000","vote_positions":[]},{"voter_id":"e708da32a799ae3d1c4c96e5430d367ea1cb0ba3e8ce333e0c712ec230964c7c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"370621690","locking_period":30,"voting_power":"185310845000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"185310845000000000000000000","vote_positions":[]},{"voter_id":"f18c2a44526e39ed4a0b15e3a89cdc0e7a1f72c5d450fd296ab805b775164f7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":195,"voting_power":"13000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"13000000000000000000000000"}]},{"voter_id":"3152f891de86d40d96afdbab8fd744335f87846184cc1ff80ec5182b6e5f91cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"64927b85224261392645eed5e16d343ab702a0ccf672900fa45705ccc7fb53bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"5dc1b7be64c2ac012ea431949927d7b48c5b3fefa0ee4f2348fdc2fc54435596","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5ee1992e99dc4ceb732beb6d2aed958d5c855c4576a21483714cce314333322a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"134b23882c7fe059244a893c686e950d6d3af6cfc8426cf9adca8bd4c59e3958","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"mjt69.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3670430779900","locking_period":300,"voting_power":"18352153899500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"6000000","locking_period":300,"voting_power":"30000000000000000000000000","unlocking_started_at":1730837665884,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"18352153899500000000000000000000"}]},{"voter_id":"3bcae1f4bcad5fec7fb44a94eeff3906be58d5e7785fe9fb000d8c2ae30b4867","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"daddf0a3ff32bdd992b6f90acd1e076d44de96eae317a2492d7a854952783912","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"7fdc9ec27bf1d33019d874549a74a76475c9f91af4748a3ff7f93d2107465398","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"909155dadd03ed49a0e66b374cd2a6bba837cc78de148e301bfb157e80638238","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"55065500","locking_period":270,"voting_power":"247794750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"247794700000000000000000000"}]},{"voter_id":"8d81a5be73da5bb81bb3972bd4ba62e9f3f6a46ec90e9b01c37f942e4e49f837","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"302ccccca97fbbfe0a697b79ac4e4d5c133b2070af796815ef8feae1446614e7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"9122724b26d2a1a75e3a4bf34d6f731fdabaaeb9ec475f8e929bbba7087356bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":75,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[]},{"voter_id":"577a44eb1445093838f070248c81e7f04bf601c8e6aa5aec674e35ae94599fe1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2f7aea4394b33e1c9618f872683e09359304444896780550a761e515bad2e545","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2dc84c98ae6539130b6fbbbbab28d65bac75d036f0c659d53ed64a0b56af9aed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6966534938.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1554242310","locking_period":300,"voting_power":"7771211550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7771211550000000000000000000","vote_positions":[]},{"voter_id":"91aa7ee5bf80249914b3eecfffaa9ac5267fb637e0436c9e2016442efc2d9f6e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"cd8c1ae490113790271e545682861bec0e2fd678a9cf683a1b84637d5ea958b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"72e33ae6636584d80229fe602cee4a84e75bf94c465933673c967ab585422925","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":300,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"8bfc4bf88edbcfb31bf166a2bc6b8a1c380cd4c1910fb83ac9a11d0db96b4e3a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1f4d03d56411be001311befd5bcfdaba2ea86a8a2e6f19d458db45e506dda4db","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a2b16784d1c8857f1b105f278aa47d1849bad39a3b420135da6fdb8398667279","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fc6f5994a75fb16551d77e38327deb088cd393ac2f9bbd1810e7a25a8c620bc5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12458050","locking_period":300,"voting_power":"62290250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"208522290","locking_period":180,"voting_power":"625566870000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"62290200000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"625566900000000000000000000"}]},{"voter_id":"53e9e63b63e056388ab46cd368719bbe92cf75426bd82d847740e0dbb238de7e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6f2e93d7468ed83f9ad12c73070058a240880cd6abf8e7cc832c477d19f729dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9c2ac31195864e97f16efa47a80bec780ecf5c80113a3d0f048208ddb45c6982","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1653ffc74b5114af4fa7dc65b5dcc3593572237b538f79a634e9d50167b17a4f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"95cc99e6c34be962b799a4c57147ec021b291d0dde4c4eba7395fca3a224312c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"486f2b50286a9a9ceec8c117b8a4f4257abf54eb6227c7372b97193e6b57ee82","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"66b50516e9b81a6bcefdcc77b7252e9efbe75a7ecbe5547b12cc32a2d2b2340b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"da6a0b642ec2cc46616d614beeba0619d279d613f7483cda5817262663e16684","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"883e6dfbdb97b9fb819c77bf08e49939d4e8ad8077a412a007dbf14b09db8ad0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"18d80f3cba5e9cdb39f36be8b82d9f5b329c766a4a1d08ea3c139a044c8b9f76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"5d562b8c5f219bc004a4589f401bbcdeea1ca49f774f82b01c11db80acc74fb5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"32773ef3354c91de4210999243c8a3959945c5aabe5883462d34136d3687c5d2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"fe9ac63e88231099c3109bda7efe172400c4a26d05a5184d1fa55bfb023dfe08","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2fb66201ca05646d7ab673879494c6b41f6edf9bbfb815673ca6178450cd0109","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"46c0713da081f4b5ab7342957c4e8839cf0ea844b30bcd2e503aa41ef35498cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"147991100","locking_period":165,"voting_power":"406975525000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1975525000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"400000000000000000000000000"}]},{"voter_id":"enilsom.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1725019780","locking_period":180,"voting_power":"5175059340000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"08investinwomen_runbybisontrails.poolv1.near","voting_power":"1753818300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakeseeker.poolv1.near","voting_power":"1421241000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"2000000000000000000000000000"}]},{"voter_id":"220e66eb1fb826c83392fb45ca200fd5ae880923d3d6a946b4a07a41c335ec15","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5386b447cc3f36ba9a527af864fd408791869d9e31240b6e689969980403b8d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8f130f1775efdfd281068bc34b265eaa8fe4d5f3467549f0649b71a6aa559f9f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":270,"voting_power":"18000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[]},{"voter_id":"932fe9224879bcc29d24259ed85f3ee1054c09f6125ca4b22092fa3fac8ce4cb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e1f695bc9386ef9aac1458b2ef77ebb707c12d16bebdafe76aac4b147cb035c1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"aca2e0f014d3c569ac0a42e2b756895698f79bc913304789f977ca4b1536bc45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"319735156850","locking_period":300,"voting_power":"1598675784250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1598675784250000000000000000000","vote_positions":[]},{"voter_id":"90e6e2155af17b1ce2b657725c45e073d0ef4fc3c7732ec807a1f984358b3f30","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f0d414f84a5e73061c3a13957acd21a31482204828612ac7efcfa7a4ea2ef874","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e40d87f3438933b047637ed4496d2aa3260278b8151fa94facab221870719c27","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5687f214ba1fde7c53f3fd3425a8cac6d262d9a5348f17b1fd35f1868939c5de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"53d78a5bd2aef5e1eed24f4650f34e56950e447a807442df2def1710fed7fee2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"kenniii.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"156234490","locking_period":300,"voting_power":"781172450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"781172400000000000000000000"}]},{"voter_id":"a27764aa0c4e1d958c8aa4daf47e82f660978534f272f7f7880633fd7a1da55c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":195,"voting_power":"6500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6500000000000000000000000","vote_positions":[]},{"voter_id":"269d1203a58d059e0de5dd8239471e5dad46395161090783c0ea8a0fee506a6f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d6393612a2ea1a363c2f2576941158fd3d76043c6fe295dc5e2ccb44c44c6b88","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4cb0952702011c95172829519f932a380d1403ce527d168a10a81649b511caca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8cc6f47501992c8a7b6a95bffbb8c77f63abc53998855136082003fdd97f62a1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cf391844586e97208c649fc529a5ca577b92fb4a704de2713c6113cabf9a7fad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"be18b977d8b955f25b891bd48a15148f1487d2d854b63e3115b0385235be77b7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"rawkilt7666.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"65378236800","locking_period":270,"voting_power":"294202065600000000000000000000","unlocking_started_at":1730202185556,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"1997bdb4cc313efbc55bea1914f9198c744fb7648ebe442aa1638afdc8d72b72","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"drocacoin.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4615380820","locking_period":300,"voting_power":"23076904100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"23076904100000000000000000000"}]},{"voter_id":"brightport7464.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1613566460","locking_period":300,"voting_power":"8067832300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"8067832300000000000000000000"}]},{"voter_id":"a8b1b21c2831afb89191bc164055d3d4e1794a62e88f2bbee3bb0c57d134d435","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5ebb32c260ffef7481fcd1b2f1366e53ca89ad0d4509079af2b6644384276c2a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1e4a9edbd3e540e98e556ec8155c329f0614d7dd36019553ed98ca9b16504492","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b703b2824c63ce4608b4296edd99c5cf4453f6263efcf83941961fba18649daa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5879789e575b68a1473e603b1fdd3b6677e294f9ff47166370455eeeee44ccbc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"acda0f5435d49bac335cfa2d05562d5d47aae55ce752056f6f59a8899587e98d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4fed5e6fccd878ce58f5f9d1667eb2b373f2e3bf14138d3d157e65fe762a532d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lavenderfive.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"everstake.poolv1.near","voting_power":"14000000000000000000000000"}]},{"voter_id":"6293d69adfd8894703a6cf9e80909fd15ef09cb8b24bf1826a329e27d0596d00","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a85dd91380a1b4600f7acb1746d9c1a20289e14457fd5b30d1788690ac4ba978","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"28421d4193047c854916881379f8d449a5cf4f761d0cddadd7f7724a1d8c089b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f79a4b8cca5dd5257f4eb4537b6de3668b318470784c936786b42e7e1a8e0513","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4af7f238d0d22b44423e3d5886e6aaf28ebe76434c5a2b1871a22b4d3be5fb7c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cesarminoru.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28683910","locking_period":180,"voting_power":"86051730000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"86051730000000000000000000","vote_positions":[]},{"voter_id":"5b1bc6c9e87ae7ae1848f1c2d027a8d8b8ada0def01fba2d43fdf50971c70867","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"4961f61c155ed31e8f3b4b4e8566cf0030dc7fc6f984f805345551fdafe4f8a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"b2812b94059352e0ca4ce3f43e929abe82a7d7451c80fcc1821a29a229a963e7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9c35b806b1a6239c6e8fdfc0aa3330253aab1d18a1ae847ae8fb9fdde426cef1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"21974e7bb61a3b60e16fd97cdeeedb57f282901eb89e0cb13f7aaf8b2435348b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"canvithien11.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"277413650","locking_period":120,"voting_power":"554827300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"150394140","locking_period":135,"voting_power":"338386815000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"893214115000000000000000000","vote_positions":[]},{"voter_id":"ff1989020b32bd12221fddd5c4390c24825c10d292594ca68dc8b268affca3ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a4c96786a0f47deca5588e93039e60e1c4384aa3cc7aa1c3c33c506ea354bb01","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9945933c2a0449a33c7000b23f25662e42b6cf198985db54caf75fcfff679d29","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ac861cd64e8e0e9ec5d2b0fcce6c7805b635bd5c2ed23517f5fe330b236ad3a7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ebb3e4d66c8df11e917886ba3e9158387282750b5e0ed341be88276ab904cca2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d2373b2e6a5738007d57e233082ab47042fe47c70df0c8c421d3c99a7ed5b666","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9dc15b9c447653923e7f08570adefcefef774ef6c94a12b7013c6f8d5c434f0d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e718f141c6b4ce9d6f033f9512df3a9e59c4d1afed0d20e150de58b6c59a8966","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"a53049b9a78da7b5b355e580523301fd4d75f2fcb963d53e6901bdea7bf5f301","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"gustavo_paulo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"46513950","locking_period":300,"voting_power":"232569750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"244569700000000000000000000"}]},{"voter_id":"3990bf7e9e4dfe600cec1bdcf2334c169a4885923aee77b7b86d44c16aacd415","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"thepool.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1166667000000","locking_period":300,"voting_power":"5833335000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"5833335000000000000000000000000"}]},{"voter_id":"c186d04bd5c9669566e982f67830b1449a3d7097265090f6cd0b8588ac48207b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"criptodru.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1624186620","locking_period":300,"voting_power":"8120933100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"8120933100000000000000000000"}]},{"voter_id":"omolayom.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000000","vote_positions":[]},{"voter_id":"vitallibrary6200.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2933126390","locking_period":300,"voting_power":"14665631950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"14665631900000000000000000000"}]},{"voter_id":"b667deeff327766f4d87495b3bc42ac775bab6d9b4fd9e148c6f7bcb3867ab87","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5000000000","locking_period":120,"voting_power":"10000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"10000000000000000000000000000"}]},{"voter_id":"85ee65913d3c1b7204775d6e7478de90973b8f606e0193446df5467210d7849a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"628717730","locking_period":300,"voting_power":"3143588650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3143588600000000000000000000"}]},{"voter_id":"nearstreetbets.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40000000000","locking_period":300,"voting_power":"200000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptoblossom.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"50000000000000000000000000000"}]},{"voter_id":"vvr.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"53675901290","locking_period":300,"voting_power":"268379506450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"40709080700000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"227670425700000000000000000000"}]},{"voter_id":"pedrov42.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"591926520","locking_period":300,"voting_power":"2959632600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2959632600000000000000000000"}]},{"voter_id":"ab9036e2eefd2084daf95ee1647f7ba1dcf5f61367d86c96dc63907b766568e9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"101492900","locking_period":180,"voting_power":"304478700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"304478700000000000000000000","vote_positions":[]},{"voter_id":"nuevanear.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40921210","locking_period":60,"voting_power":"40921210000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"30000000","locking_period":30,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"23000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"32921200000000000000000000"}]},{"voter_id":"p0k.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"53099448500","locking_period":300,"voting_power":"265497242500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sicmundus.poolv1.near","voting_power":"265497242500000000000000000000"}]},{"voter_id":"djonecln.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"antokcombine.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"975580000","locking_period":300,"voting_power":"4877900000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"4877900000000000000000000000"}]},{"voter_id":"luisnet11.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"spectrum.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"3380805fd17c737fba9948ee8dbfba8a4de77c1897ae9810f4da098dc6cb9721","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"near-africa.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000000","vote_positions":[]},{"voter_id":"juniorfigueiredo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":30,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"oliver24.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"soundglove7665.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stardust.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"elliotbraem.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3071971400","locking_period":300,"voting_power":"15359857000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15359857000000000000000000000","vote_positions":[]}] \ No newline at end of file +[{"voter_id":"lucascasp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1447929400","locking_period":300,"voting_power":"7239647000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"7239647000000000000000000000"}]},{"voter_id":"voodooking.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2942789600","locking_period":300,"voting_power":"14713948000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"14713948000000000000000000000"}]},{"voter_id":"kuncho.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000","locking_period":30,"voting_power":"500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"128492199000","locking_period":45,"voting_power":"96369149250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"4153676790","locking_period":300,"voting_power":"20768383950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"96369000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20769033200000000000000000000"}]},{"voter_id":"silkking.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"250000000000","locking_period":240,"voting_power":"1000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"3500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"79200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"917300000000000000000000000000"}]},{"voter_id":"1ce2567f7f49cb34cea72179223ec7fc4ba91077da3e01364d0c729dfbe26467","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7053707400","locking_period":300,"voting_power":"35268537000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"35268537000000000000000000000"}]},{"voter_id":"proxima-centauri.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"177979300210","locking_period":300,"voting_power":"889896501050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"889896501000000000000000000000"}]},{"voter_id":"blazepassion.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000000","locking_period":300,"voting_power":"50000000000000000000000000000","unlocking_started_at":1718642103735,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"alpha-centauri.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"191594836990","locking_period":300,"voting_power":"957974184950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"957974184900000000000000000000"}]},{"voter_id":"0cbf1c4a3da03524dfd9486cf13d404e5f02eb173218d2a512d6991c057fddd3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20064518690","locking_period":300,"voting_power":"100322593450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"100322593400000000000000000000"}]},{"voter_id":"dogon.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"260851958600","locking_period":300,"voting_power":"1304259793000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1304259793000000000000000000000"}]},{"voter_id":"alan777.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"286666722000","locking_period":300,"voting_power":"1433333610000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"100000000","locking_period":150,"voting_power":"250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"10000000","locking_period":180,"voting_power":"30000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"250000000000","locking_period":240,"voting_power":"1000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"2433613610000000000000000000000"}]},{"voter_id":"manzanal.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"594801155900","locking_period":300,"voting_power":"2974005779500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"15000000000","locking_period":300,"voting_power":"75000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"1500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"1541005779500000000000000000000"}]},{"voter_id":"brianmai.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8005634731000","locking_period":240,"voting_power":"32022538924000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"chain0ps.poolv1.near","voting_power":"32022538924000000000000000000000"}]},{"voter_id":"raulb.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"86710598600","locking_period":300,"voting_power":"433552993000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"433552993000000000000000000000"}]},{"voter_id":"platoboss.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5512744200","locking_period":90,"voting_power":"8269116300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10769116300000000000000000000","vote_positions":[]},{"voter_id":"cryptogarik.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7822408800","locking_period":300,"voting_power":"39112044000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"cryptogarik.poolv1.near","voting_power":"39112044000000000000000000000"}]},{"voter_id":"zaphire.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"102709473200","locking_period":300,"voting_power":"513547366000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"swissstar.poolv1.near","voting_power":"513547366000000000000000000000"}]},{"voter_id":"f27af9b6ebfa3896bdcaa20514370f12c849f0e9ead55623fb82c11430b0c2ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20000000","locking_period":30,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dexagon.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"58f54b94dae828ebbafeccebfba61c5ff4773a4f1c1b077d6628a64c1a0af742","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"287889851090","locking_period":300,"voting_power":"1439449255450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"59598407900","locking_period":30,"voting_power":"29799203950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"swissstar.poolv1.near","voting_power":"1469248459400000000000000000000"}]},{"voter_id":"6b16e9e129ce06246c42a33f0d225e3d4735c2de26701f21de28276970e601c0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"232967556300","locking_period":90,"voting_power":"349451334450000000000000000000","unlocking_started_at":1728034878253,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"herimax.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"15850679740","locking_period":300,"voting_power":"79253398700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"79253398700000000000000000000"}]},{"voter_id":"0234hoangnd78439.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"735830030","locking_period":300,"voting_power":"3679150150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"3679150100000000000000000000"}]},{"voter_id":"readylayerone.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1345707734600","locking_period":300,"voting_power":"6728538673000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"6728538673000000000000000000000"}]},{"voter_id":"duongka.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3880961200","locking_period":120,"voting_power":"7761922400000000000000000000","unlocking_started_at":1733562339384,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"c245be41fb5aed65ab30e7b4e1a178147e1e131dc95b1f2206b2f53a7596f093","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"69908270","locking_period":300,"voting_power":"349541350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"349541350000000000000000000","vote_positions":[]},{"voter_id":"themicrowave.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"37095762100","locking_period":300,"voting_power":"185478810500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"3736000000","locking_period":300,"voting_power":"18680000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"204158810500000000000000000000"}]},{"voter_id":"e914e4c91d80a845c79bb134d54fafb01bab9357fa44c3b4c38dffb3f0e5cad1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"34123968100","locking_period":300,"voting_power":"170619840500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"175892000","locking_period":90,"voting_power":"263838000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1529750100","locking_period":165,"voting_power":"4206812775000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"717246800","locking_period":120,"voting_power":"1434493600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"75000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"76524984800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"100000000000000000000000000000"}]},{"voter_id":"branermusso.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6364090500","locking_period":300,"voting_power":"31820452500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10095459200","locking_period":300,"voting_power":"50477296000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"82297748500000000000000000000"}]},{"voter_id":"79148e72172096387294d56b0f2f9b366591610254a21e444a479d6adcf38dd3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"137063146500","locking_period":90,"voting_power":"205594719750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"205594719750000000000000000000","vote_positions":[]},{"voter_id":"gilberth.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"102367750","locking_period":300,"voting_power":"511838750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"511838700000000000000000000"}]},{"voter_id":"0beca730ba634786c7e330fb7c71f8510b51a445522ca728fc30f7c722eb3a14","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"15020424000","locking_period":30,"voting_power":"7510212000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7510212000000000000000000000","vote_positions":[]},{"voter_id":"hannah17.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"23554732500","locking_period":300,"voting_power":"117773662500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"117773662500000000000000000000"}]},{"voter_id":"blaze.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"24895319700","locking_period":90,"voting_power":"37342979550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"openshards.poolv1.near","voting_power":"37342979500000000000000000000"}]},{"voter_id":"do0k13.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"508091860340","locking_period":300,"voting_power":"2540459301700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"2540459301700000000000000000000"}]},{"voter_id":"akdude.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"80865985600","locking_period":300,"voting_power":"404329928000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"60000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"61298970000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"20216500000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"262814458000000000000000000000"}]},{"voter_id":"levoslov.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1126852800","locking_period":300,"voting_power":"5634264000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5634264000000000000000000000","vote_positions":[]},{"voter_id":"aiml.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2984500","locking_period":240,"voting_power":"11938000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10529862400","locking_period":300,"voting_power":"52649312000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"52661250000000000000000000000"}]},{"voter_id":"ramgor.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"38088003700","locking_period":300,"voting_power":"190440018500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"190440018500000000000000000000"}]},{"voter_id":"zzoo-2718.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"68988508600","locking_period":180,"voting_power":"206965525800000000000000000000","unlocking_started_at":1720364226345,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"68686063500","locking_period":165,"voting_power":"188886674625000000000000000000","unlocking_started_at":1720364264076,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"ame9986.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2360529000","locking_period":300,"voting_power":"11802645000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6802645000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"openbitlab.poolv1.near","voting_power":"5000000000000000000000000000"}]},{"voter_id":"abrahamrivv.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46917695800","locking_period":300,"voting_power":"234588479000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"234588479000000000000000000000"}]},{"voter_id":"cacossio.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"754989658520","locking_period":210,"voting_power":"2642463804820000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"201150593900","locking_period":195,"voting_power":"653739430175000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"74821994195000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"221381240800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"121|mpDAO Grants #8 - August 2024-meximalist.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"118|mpDAO Grants #8 - August 2024-marcelorefi.near","voting_power":"500000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"117|mpDAO Grants #8 - August 2024-bagrera.near","voting_power":"250000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"250000000000000000000000000000"}]},{"voter_id":"e87a0f4bbdbc251ca4531be261f7c6821facc2ad076da96a9572acca2049dfe6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"129575448900","locking_period":135,"voting_power":"291544760025000000000000000000","unlocking_started_at":1714994026232,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"skinn.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"76645554700","locking_period":300,"voting_power":"383227773500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"383227773500000000000000000000"}]},{"voter_id":"a2d1b583a0743a892255b9a07eb064d2656589df60ebeffbbcb7f4d32883662c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"172c0cab8eec92d69ba16db4e8ea5bf69149fcc3844008327bcc2c84fa203508","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"9076056900","locking_period":300,"voting_power":"45380284500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"45380284500000000000000000000"}]},{"voter_id":"border_less.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"52897311300","locking_period":300,"voting_power":"264486556500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"264486556500000000000000000000"}]},{"voter_id":"0xf48cAb01aE5b5f3A546f33B7f416E7E203237749.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"790000000","locking_period":120,"voting_power":"1580000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"80000000","locking_period":300,"voting_power":"400000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1977000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"109|mpDAO Grants #7 - June 2024-near-africa.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"mp-cc.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20633333330000","locking_period":225,"voting_power":"77374999987500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"28125000000000","locking_period":90,"voting_power":"42187500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"55030000000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sharpdarts.poolv1.near","voting_power":"2000000987500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"galaxydigital.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"rekt.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staketab.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.pool.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"openshards.poolv1.near","voting_power":"3000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"moonlet.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"3000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fresh.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"grassets.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stablelab.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"tritonone.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"3562499000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"senseinode.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakesstone.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"kiln-1.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dsrvlabs-staking.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"prophet.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearvietnamhub.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"08investinwomen_runbybisontrails.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"gfs-ventures.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"a41.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"bee1stake.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"blockdaemon.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"50000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"150000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"134|mpDAO Grants #8 - August 2024-marcrypted.near","voting_power":"560000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"1500000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"400000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"128|mpDAO Grants #8 - August 2024-aleiranova.near","voting_power":"1250000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"225000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"137|mpDAO Grants #8 - August 2024-criptolaplata.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"335000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"142|mpDAO Grants #8 - August 2024-refibogota.near","voting_power":"200000000000000000000000000000"}]},{"voter_id":"mbbevilacqua_caffepoesia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"536000000","locking_period":300,"voting_power":"2680000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"1729000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"931000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"2be59bfa20787b81b8ffa9a6a6c45714020e2e157dbc1c5636287ca91ebc1aee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"yaneisia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"901208400","locking_period":300,"voting_power":"4506042000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"120245800","locking_period":300,"voting_power":"601229000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"335994910","locking_period":300,"voting_power":"1679974550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"cryptoblossom.poolv1.near","voting_power":"6787245500000000000000000000"}]},{"voter_id":"8901ad3c104f92dba61c1624b74285a481fdc93670f7e307d27512bcf6f83e4b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3751efa63c92312d2324dc19f26c6ec15c869b72d39ef5c852e69067dca585da","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"775000000","locking_period":90,"voting_power":"1162500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"8963000000","locking_period":300,"voting_power":"44815000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"15977500000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"14000000000000000000000000"}]},{"voter_id":"travbori.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"36093446400","locking_period":90,"voting_power":"54140169600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1791481030","locking_period":300,"voting_power":"8957405150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"63097574700000000000000000000"}]},{"voter_id":"shadowmonarch.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"32406418600","locking_period":300,"voting_power":"162032093000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"162032093000000000000000000000"}]},{"voter_id":"luciotato.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17740840948500","locking_period":90,"voting_power":"26611261422750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"100000000000","locking_period":180,"voting_power":"300000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"40000000000000","locking_period":30,"voting_power":"20000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"35000000000000","locking_period":60,"voting_power":"35000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"64986261422750000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"550000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"2180000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearstar.poolv1.near","voting_power":"1850000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"2980000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"openshards.poolv1.near","voting_power":"1485000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"deutschetelekom.poolv1.near","voting_power":"850000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"480000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"800000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearkoreahub.poolv1.near","voting_power":"480000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"grassets.poolv1.near","voting_power":"300000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"senseinode.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nttdata_dti.poolv1.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"50000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"101|mpDAO Grants #7 - June 2024-criptolaplata.near","voting_power":"1050000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"85|mpDAO Grants #7 - June 2024-fpsgames.near","voting_power":"350000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"94|mpDAO Grants #7 - June 2024-cryptopr3neur.near","voting_power":"665000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"95|mpDAO Grants #7 - June 2024-edfernandezg.near","voting_power":"45000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"111|mpDAO Grants #7 - June 2024-c0d0c8b3bfcbc17347002733be3e2d17df833d2931c425eaa40bd7e73a4509e3","voting_power":"40000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"102|mpDAO Grants #7 - June 2024-fr-grant.near","voting_power":"400000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"86|mpDAO Grants #7 - June 2024-la-incubadora.near","voting_power":"650000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"20000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"600000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"121|mpDAO Grants #8 - August 2024-meximalist.near","voting_power":"300000000000000000000000000000"}]},{"voter_id":"brunomazzaggio.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"251428084300","locking_period":300,"voting_power":"1257140421500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"1257140421500000000000000000000"}]},{"voter_id":"c3001cd00f76dd7d4e60d4bf6c8b392fa2e724f7367b3b92279f467262f0724e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e515b46dc1c974ca39c5c622d869f7bc73e31d2c995354715a1520e18290fbac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12355013000","locking_period":120,"voting_power":"24710026000000000000000000000","unlocking_started_at":1726266396655,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"biolmoon.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10254082300","locking_period":195,"voting_power":"33325767475000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"75000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"33325767400000000000000000000"}]},{"voter_id":"mex-holding.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"281424231200","locking_period":300,"voting_power":"1407121156000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"1407121156000000000000000000000"}]},{"voter_id":"rosaglez_art.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"2500000000000000000000000000"}]},{"voter_id":"adisa81.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1035323359600","locking_period":300,"voting_power":"5176616798000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"2pilot.poolv1.near","voting_power":"5176616798000000000000000000000"}]},{"voter_id":"natto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28715520000","locking_period":90,"voting_power":"43073280000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"770000000","locking_period":30,"voting_power":"385000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"21729000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"21729280000000000000000000000"}]},{"voter_id":"daorecords.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7059590900","locking_period":90,"voting_power":"10589386350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"5289386300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"5300000000000000000000000000"}]},{"voter_id":"alejandro.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28257645190","locking_period":300,"voting_power":"141288225950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"85598400500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"55689825400000000000000000000"}]},{"voter_id":"jarednotjerry.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"209997339990","locking_period":300,"voting_power":"1049986699950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1049986699900000000000000000000"}]},{"voter_id":"wapzp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20033551200","locking_period":300,"voting_power":"100167756000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"100167756000000000000000000000"}]},{"voter_id":"marrown.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27459200900","locking_period":300,"voting_power":"137296004500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"63818410000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.pool.near","voting_power":"73477590000000000000000000000"}]},{"voter_id":"lukelee.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1306488420000","locking_period":300,"voting_power":"6532442100000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"6532442100000000000000000000000"}]},{"voter_id":"anmatig.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"266031800","locking_period":300,"voting_power":"1330159000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"3330000000","locking_period":30,"voting_power":"1665000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1000000","locking_period":225,"voting_power":"3750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2998909000000000000000000000"}]},{"voter_id":"neonong.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"325787524100","locking_period":300,"voting_power":"1628937620500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking-power.poolv1.near","voting_power":"1628937620500000000000000000000"}]},{"voter_id":"luismanac.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"33953313100","locking_period":300,"voting_power":"169766565500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"interstakeone.poolv1.near","voting_power":"69766000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lscmval.poolv1.near","voting_power":"50000565500000000000000000000"}]},{"voter_id":"max_one.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10627243500","locking_period":300,"voting_power":"53136217500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2327239200","locking_period":300,"voting_power":"11636196000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"424000000","locking_period":300,"voting_power":"2120000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"449000000","locking_period":300,"voting_power":"2245000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"4445212500","locking_period":300,"voting_power":"22226062500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"91363476000000000000000000000"}]},{"voter_id":"sanam1027.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1206000000","locking_period":30,"voting_power":"603000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"353000000","locking_period":90,"voting_power":"529500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"2000000","locking_period":60,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"2491108600","locking_period":45,"voting_power":"1868331450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"402831450000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"atomic-nodes.poolv1.near","voting_power":"1000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"400000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"100000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"50000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"800000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"65|mpDAO Grants #7 - June 2024-auroraafrica.near","voting_power":"50000000000000000000000000"}]},{"voter_id":"7b3735e6e4891b67d6949b37007000f1006c9bc50f7b2ffbc7591b84f4a41d11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"173653480600","locking_period":120,"voting_power":"347306961200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"infiniteloop.poolv1.near","voting_power":"347306961200000000000000000000"}]},{"voter_id":"serik.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2625018100","locking_period":90,"voting_power":"3937527150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3937527150000000000000000000","vote_positions":[]},{"voter_id":"roago.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"223179655390","locking_period":300,"voting_power":"1115898276950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"715898276900000000000000000000"}]},{"voter_id":"3228f4ec8437ab4271b99b7ce0c5d6a82ac28fe7be40ecce1f0920b12eaba915","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16452372830","locking_period":300,"voting_power":"82261864150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1000000","locking_period":300,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1000000","locking_period":135,"voting_power":"2250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"20760795000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"27402183200000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"34106135900000000000000000000"}]},{"voter_id":"6490723760b963cabd9b82382d0faf0f1cc693dc7a9b8bdfd6595dde53bb6559","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"35936943900","locking_period":300,"voting_power":"179684719500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"79684719500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"50000000000000000000000000000"}]},{"voter_id":"stingray.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stingray.poolv1.near","voting_power":"5000000000000000000000000000"}]},{"voter_id":"011050b84e018322932173e1309b8496af96f7792d29a48cf82eaf7d12c24850","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3470000000000","locking_period":300,"voting_power":"17350000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"17350000000000000000000000000000"}]},{"voter_id":"bdaf.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1269125990000","locking_period":270,"voting_power":"5711066955000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"178571430000","locking_period":225,"voting_power":"669642862500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1130952370000","locking_period":225,"voting_power":"4241071387500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"250000000000","locking_period":225,"voting_power":"937500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"861884035070","locking_period":210,"voting_power":"3016594122745000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":5,"amount":"788881924520","locking_period":180,"voting_power":"2366645773560000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":6,"amount":"796553508940","locking_period":300,"voting_power":"3982767544700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":7,"amount":"761904750000","locking_period":120,"voting_power":"1523809500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":8,"amount":"314121000000","locking_period":165,"voting_power":"863832750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lavenderfive.poolv1.near","voting_power":"9892331030500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"polkachu.poolv1.near","voting_power":"9212744787800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"1559281205000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"1784741122700000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"863832750000000000000000000000"}]},{"voter_id":"marior.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"65158439600","locking_period":300,"voting_power":"325792198000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"30719428500","locking_period":300,"voting_power":"153597142500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"479389340500000000000000000000"}]},{"voter_id":"davletuner.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10495055000","locking_period":120,"voting_power":"20990110000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"3228186500","locking_period":165,"voting_power":"8877512875000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"75176860000","locking_period":300,"voting_power":"375884300000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"75000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"405751922800000000000000000000"}]},{"voter_id":"phandinhtuan.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"123230054400","locking_period":300,"voting_power":"616150272000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"616150272000000000000000000000"}]},{"voter_id":"sivakalathi008.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sharpdarts.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"welldonestake.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"700000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptotribe.poolv1.near","voting_power":"600000000000000000000000000"}]},{"voter_id":"xuanduy86.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"23121411500","locking_period":300,"voting_power":"115607057500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"26799658700","locking_period":300,"voting_power":"133998293500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"742000000","locking_period":300,"voting_power":"3710000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"25463122400","locking_period":300,"voting_power":"127315612000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"380630963000000000000000000000"}]},{"voter_id":"fdea0a6bcd48da25f783feb9b66627698cb00a7b34f064eb3ff59e3367b625de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"22676096130","locking_period":300,"voting_power":"113380480650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"meria-staking.poolv1.near","voting_power":"113380480600000000000000000000"}]},{"voter_id":"b71nw.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7416352400","locking_period":180,"voting_power":"22249057200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"22249057200000000000000000000","vote_positions":[]},{"voter_id":"sivakalathi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"98276654600","locking_period":300,"voting_power":"491383273000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"trust-nodes.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"4ire.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pinrocks.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearfans.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptotribe.poolv1.near","voting_power":"30968291100000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"buildlinks.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"encipher.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"iamcryptobro.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dsrvlabs-staking.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"ni.poolv1.near","voting_power":"16000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"genesislab.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"106|mpDAO Grants #7 - June 2024-ludium.near","voting_power":"2414981900000000000000000000"},{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"2000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"144|mpDAO Grants #8 - August 2024-cryptopr3neur.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"136|mpDAO Grants #8 - August 2024-bstt.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"126|mpDAO Grants #8 - August 2024-5e85fef96bd903eec08d1f59fabdc524d0ac13f44c7a1e08e43de9a01a62a11c","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"128|mpDAO Grants #8 - August 2024-aleiranova.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"137|mpDAO Grants #8 - August 2024-criptolaplata.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"133|mpDAO Grants #8 - August 2024-ffbc3084a4d75ed3c28a7b6c26f44a4ffb43dc3959365d80d70a9660913cbacf","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"117|mpDAO Grants #8 - August 2024-bagrera.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"125|mpDAO Grants #8 - August 2024-xiiw.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"118|mpDAO Grants #8 - August 2024-marcelorefi.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"3000000000000000000000000000"}]},{"voter_id":"15870c8972a9fe6cdb7dfc2df835740108e8674cc170a091cd0ece0b9e4f6cfa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000886200","locking_period":270,"voting_power":"4503987900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"183000000","locking_period":180,"voting_power":"549000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"17349290","locking_period":300,"voting_power":"86746450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"86746450000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"5052987900000000000000000000"}]},{"voter_id":"mansaet.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"193545300200","locking_period":300,"voting_power":"967726501000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"967726501000000000000000000000"}]},{"voter_id":"pathrock.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17006278400","locking_period":90,"voting_power":"25509417600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pathrocknetwork.poolv1.near","voting_power":"25509417600000000000000000000"}]},{"voter_id":"19f7dbc58065da8a3cc636987d646b3844517fef9533b3d86bef4f3e10a194bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"823081344000","locking_period":195,"voting_power":"2675014368000000000000000000000","unlocking_started_at":1726751184719,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"112890669800","locking_period":300,"voting_power":"564453349000000000000000000000","unlocking_started_at":1723052296755,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"39043526300","locking_period":210,"voting_power":"136652342050000000000000000000","unlocking_started_at":1725348921894,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":3,"amount":"29988017300","locking_period":270,"voting_power":"134946077850000000000000000000","unlocking_started_at":1725348888783,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"syrbuss.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"418161013630","locking_period":300,"voting_power":"2090805068150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"1917190819800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"173614248300000000000000000000"}]},{"voter_id":"0rz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"502237362700","locking_period":300,"voting_power":"2511186813500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"2511186813500000000000000000000"}]},{"voter_id":"salpaslan.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"35730495600","locking_period":105,"voting_power":"62528367300000000000000000000","unlocking_started_at":1731416381037,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"sv404771.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"169793351100","locking_period":120,"voting_power":"339586702200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"339586702200000000000000000000"}]},{"voter_id":"6d6ce3d63ba86cce16407b328e9d6b92421f7265be64f3efc9ef32f542176e91","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5781710500","locking_period":300,"voting_power":"28908552500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"here.poolv1.near","voting_power":"28908552500000000000000000000"}]},{"voter_id":"leandrogavidia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1165606140","locking_period":300,"voting_power":"5828030700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5828030700000000000000000000","vote_positions":[]},{"voter_id":"59583199fc8d2b7207ad71f5213b3e7c917cbf07579d0e63fe1d7cebd9a2cd00","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"109173817500","locking_period":300,"voting_power":"545869087500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"40827000000","locking_period":180,"voting_power":"122481000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"30000000000","locking_period":60,"voting_power":"30000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"20000000000","locking_period":30,"voting_power":"10000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"50000000000","locking_period":120,"voting_power":"100000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"87500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"808350000000000000000000000000"}]},{"voter_id":"curmello.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7000016900","locking_period":300,"voting_power":"35000084500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"136|mpDAO Grants #8 - August 2024-bstt.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"5000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"5000084500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000000000000000000000000000"}]},{"voter_id":"e0ace3957f5b8a6b2c1903c6ec4c16a69e5c76b231eb1bf259e219ccfc3c63db","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16314629900","locking_period":90,"voting_power":"24471944850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"24471944850000000000000000000","vote_positions":[]},{"voter_id":"0f030b64cedb845906e84bf92076f09218de9a636e0758eab90a7aadb75048b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7272730000","locking_period":165,"voting_power":"20000007500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"}]},{"voter_id":"lematecat.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"32768913580","locking_period":300,"voting_power":"163844567900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"163844567900000000000000000000"}]},{"voter_id":"v3ry.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12303870000","locking_period":270,"voting_power":"55367415000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"211767800000","locking_period":300,"voting_power":"1058839000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"47000000000","locking_period":195,"voting_power":"152750000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"130000000000","locking_period":285,"voting_power":"617500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"15274000000","locking_period":180,"voting_power":"45822000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"350000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"180000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"330000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"160000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"chorusone.poolv1.near","voting_power":"420000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"everstake.poolv1.near","voting_power":"290000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"100278415000000000000000000000"}]},{"voter_id":"21mbulls.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"104597025760","locking_period":300,"voting_power":"522985128800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"finoa.poolv1.near","voting_power":"489071981000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"33913147800000000000000000000"}]},{"voter_id":"crypturion.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"38975358730","locking_period":300,"voting_power":"194876793650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"35000000","locking_period":300,"voting_power":"175000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"129932415500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"65119378100000000000000000000"}]},{"voter_id":"jepian.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"814504100","locking_period":90,"voting_power":"1221756150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"78|mpDAO Grants #7 - June 2024-irvingzcc.near","voting_power":"1221756100000000000000000000"}]},{"voter_id":"rodrigos.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"199000000","locking_period":300,"voting_power":"995000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"995000000000000000000000000"}]},{"voter_id":"3b19d8cefe1b7f4aff0745f0751d616d58b8553b35e6bb821edd301a6ecf2d4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"aleh.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"126501701000","locking_period":90,"voting_power":"189752551500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2049687100","locking_period":300,"voting_power":"10248435500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"33333000000","locking_period":90,"voting_power":"49999500000000000000000000000","unlocking_started_at":1727743009244,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":3,"amount":"71419000000","locking_period":90,"voting_power":"107128500000000000000000000000","unlocking_started_at":1728741366553,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"987000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"50000000000000000000000000000"}]},{"voter_id":"e2f0fa9548b641ffc7f4eac2e19987a3d0b3c502f2e3ad7127c4c4d046b7afad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7000000","locking_period":90,"voting_power":"10500000000000000000000000","unlocking_started_at":1720378414463,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"lovali.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1276699700","locking_period":300,"voting_power":"6383498500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lovali.poolv1.near","voting_power":"6383498500000000000000000000"}]},{"voter_id":"dc431cbf756718c9babdeb6839a11f6b2eb29ce48bad6b250cf4db7eee5efb0b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3340921200","locking_period":300,"voting_power":"16704606000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"85|mpDAO Grants #7 - June 2024-fpsgames.near","voting_power":"10000606000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"64|mpDAO Grants #7 - June 2024-plugrel.near","voting_power":"6704000000000000000000000000"}]},{"voter_id":"jojokeko.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8776537700","locking_period":300,"voting_power":"43882688500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"43882688500000000000000000000"}]},{"voter_id":"snowfa11.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"75000000000","locking_period":180,"voting_power":"225000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"76738000000","locking_period":300,"voting_power":"383690000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"135000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"135000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"138690000000000000000000000000"}]},{"voter_id":"kiskesis.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1575422300","locking_period":300,"voting_power":"7877111500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"7877111500000000000000000000"}]},{"voter_id":"nearukraineguild.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5915000000","locking_period":300,"voting_power":"29575000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"29575000000000000000000000000"}]},{"voter_id":"9c57aa38fecf83600922b69af9c8944742c3e45bcc1138ac534ac8de74ca7753","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"29184000000","locking_period":300,"voting_power":"145920000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"25000000000","locking_period":180,"voting_power":"75000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"110110000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"110810000000000000000000000000"}]},{"voter_id":"quickwaycrypto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8711373700","locking_period":90,"voting_power":"13067060550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"13067060500000000000000000000"}]},{"voter_id":"pamm.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000","locking_period":180,"voting_power":"300000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"3559000000","locking_period":105,"voting_power":"6228250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"3992306400","locking_period":300,"voting_power":"19961532000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"20000000","locking_period":60,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"1176771600","locking_period":90,"voting_power":"1765157400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"4ire.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"3000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"5274939400000000000000000000"}]},{"voter_id":"murasinvest.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"297271477000","locking_period":270,"voting_power":"1337721646500000000000000000000","unlocking_started_at":1733413844596,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"cesar667.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":90,"voting_power":"4500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"100000000","locking_period":300,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"504500000000000000000000000"}]},{"voter_id":"luckycat.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714829463538,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"5270925700","locking_period":60,"voting_power":"5270925700000000000000000000","unlocking_started_at":1714829615315,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"showme355.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"24801635800","locking_period":90,"voting_power":"37202453700000000000000000000","unlocking_started_at":1714831418807,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"894000000","locking_period":60,"voting_power":"894000000000000000000000000","unlocking_started_at":1714829193720,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"georgemarlow.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000485500","locking_period":300,"voting_power":"500002427500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"604496000000","locking_period":300,"voting_power":"3022480000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"3522482427500000000000000000000"}]},{"voter_id":"uzis.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714829889706,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"45000000","locking_period":60,"voting_power":"45000000000000000000000000","unlocking_started_at":1714829865130,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"konwey.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"135604870900","locking_period":300,"voting_power":"678024354500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"24354500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"678000000000000000000000000000"}]},{"voter_id":"showme.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830101467,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"42000000","locking_period":60,"voting_power":"42000000000000000000000000","unlocking_started_at":1714830119925,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"wwe.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830256184,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"31000000","locking_period":60,"voting_power":"31000000000000000000000000","unlocking_started_at":1714830284122,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"showtime.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830364816,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"34000000","locking_period":60,"voting_power":"34000000000000000000000000","unlocking_started_at":1714830384498,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"okamoto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46000000","locking_period":60,"voting_power":"46000000000000000000000000","unlocking_started_at":1714830512197,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"691593700","locking_period":105,"voting_power":"1210288975000000000000000000","unlocking_started_at":1714830535796,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"usaa.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830649423,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"38000000","locking_period":60,"voting_power":"38000000000000000000000000","unlocking_started_at":1714830678784,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"7andi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"691593700","locking_period":90,"voting_power":"1037390550000000000000000000","unlocking_started_at":1714830948897,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"jbs.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"711593700","locking_period":90,"voting_power":"1067390550000000000000000000","unlocking_started_at":1714830862740,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"earnestetim.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"507375800","locking_period":300,"voting_power":"2536879000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"109|mpDAO Grants #7 - June 2024-near-africa.near","voting_power":"2536879000000000000000000000"}]},{"voter_id":"vitalpointai.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18080140400","locking_period":30,"voting_power":"9040070200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"epic.poolv1.near","voting_power":"9040070200000000000000000000"}]},{"voter_id":"dcabrera.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"94029815300","locking_period":300,"voting_power":"470149076500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"220149076500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"250000000000000000000000000000"}]},{"voter_id":"71de6add6efe99c57bb02bcacc4be6efaaba9d409403372144d2292db920df39","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"458664942800","locking_period":30,"voting_power":"229332471400000000000000000000","unlocking_started_at":1714723375781,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"albing.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5840570980","locking_period":300,"voting_power":"29202854900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"82912090","locking_period":300,"voting_power":"414560450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"4971818300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"1500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"3000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptogarik.poolv1.near","voting_power":"10481839500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"9663757500000000000000000000"}]},{"voter_id":"ronyle.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13650000000","locking_period":300,"voting_power":"68250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"68250000000000000000000000000","vote_positions":[]},{"voter_id":"5687c659be1c73efa9cc4f5f90e466b39ec929d4c0ba5871a50df092015f49de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"115729500","locking_period":30,"voting_power":"57864750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"57864750000000000000000000","vote_positions":[]},{"voter_id":"imiroslav.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"15000000000","locking_period":120,"voting_power":"30000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4803812600","locking_period":180,"voting_power":"14411437800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"polkachu.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"24411437800000000000000000000"}]},{"voter_id":"sivakalathi006.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100672746900","locking_period":300,"voting_power":"503363734500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ruziev.poolv1.near","voting_power":"12354614200000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sharpdarts.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"d1.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"ni.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pinrocks.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptotribe.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearfans.poolv1.near","voting_power":"18000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"buildnear.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearvietnamhub.poolv1.near","voting_power":"10009120300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"10000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"iamcryptobro.poolv1.near","voting_power":"30000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dsrvlabs-staking.poolv1.near","voting_power":"20000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"89|mpDAO Grants #7 - June 2024-trovelabs.near","voting_power":"2000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"10000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"136|mpDAO Grants #8 - August 2024-bstt.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"128|mpDAO Grants #8 - August 2024-aleiranova.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"133|mpDAO Grants #8 - August 2024-ffbc3084a4d75ed3c28a7b6c26f44a4ffb43dc3959365d80d70a9660913cbacf","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"4000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"118|mpDAO Grants #8 - August 2024-marcelorefi.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"129|mpDAO Grants #8 - August 2024-0xmitz.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"144|mpDAO Grants #8 - August 2024-cryptopr3neur.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"125|mpDAO Grants #8 - August 2024-xiiw.near","voting_power":"3000000000000000000000000000"}]},{"voter_id":"ilya.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"176896422200","locking_period":165,"voting_power":"486465161050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000050000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"323792844400000000000000000000"},{"votable_address":"initiatives","votable_object_id":"96|mpDAO Grants #7 - June 2024-dapplooker.near","voting_power":"30000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"118|mpDAO Grants #8 - August 2024-marcelorefi.near","voting_power":"10000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"108|mpDAO Grants #7 - June 2024-553a5330546aa39850aa0e972bc1bec1c7018575ba141fa6f0a379cf28d46d16","voting_power":"40000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"72672316600000000000000000000"}]},{"voter_id":"a.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"232537338800","locking_period":300,"voting_power":"1162686694000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"1162686694000000000000000000000"}]},{"voter_id":"bestlove89.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"333301900","locking_period":90,"voting_power":"499952850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"499952800000000000000000000"}]},{"voter_id":"pahol.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"73524025800","locking_period":300,"voting_power":"367620129000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"367620129000000000000000000000"}]},{"voter_id":"ktxfauzy.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8834144400","locking_period":300,"voting_power":"44170722000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"44170720000000000000000000000"}]},{"voter_id":"mr_free.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"38000000","locking_period":90,"voting_power":"57000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"57000000000000000000000000","vote_positions":[]},{"voter_id":"173ec8e6ef2f0e328753c1be4779bb5467c02f14ea6b9fe1e69221d7bdc93f6e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"andersonr.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"354251600","locking_period":300,"voting_power":"1771258000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1771258000000000000000000000","vote_positions":[]},{"voter_id":"a4d73137b6ed71e647678dafe514f94e31e2312e2c7a507125c3803bb2bcd475","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20000000000","locking_period":30,"voting_power":"10000000000000000000000000000","unlocking_started_at":1728258996216,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"profit.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6463933370850","locking_period":300,"voting_power":"32319666854250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"5000000","locking_period":300,"voting_power":"25000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"oe.poolv1.near","voting_power":"30000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"2319691854200000000000000000000"}]},{"voter_id":"brzk-93444.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5727419100","locking_period":90,"voting_power":"8591128650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3591128650000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"2000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"3000000000000000000000000000"}]},{"voter_id":"djq.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1395950700","locking_period":30,"voting_power":"697975350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5350000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"697970000000000000000000000"}]},{"voter_id":"e_reece.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2394446500","locking_period":300,"voting_power":"11972232500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2232500000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"11970000000000000000000000000"}]},{"voter_id":"ollie.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"701794250000","locking_period":300,"voting_power":"3508971250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3508971250000000000000000000000"}]},{"voter_id":"transitmoi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2414219500","locking_period":300,"voting_power":"12071097500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12071097500000000000000000000"}]},{"voter_id":"chloe.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"138719132830","locking_period":300,"voting_power":"693595664150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"25037114700","locking_period":300,"voting_power":"125185573500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"369090973400000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"422245870000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"monresearch.poolv1.near","voting_power":"27444394200000000000000000000"}]},{"voter_id":"expenses-meta-pool.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"324785000","locking_period":180,"voting_power":"974355000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"974355000000000000000000000","vote_positions":[]},{"voter_id":"prinwattana.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5153400","locking_period":90,"voting_power":"7730100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7730100000000000000000000","vote_positions":[]},{"voter_id":"fliareccha.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1163826721450","locking_period":300,"voting_power":"5819133607250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"3497000500000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"501119148000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"iamcryptobro.poolv1.near","voting_power":"319000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pathrocknetwork.poolv1.near","voting_power":"502013959200000000000000000000"}]},{"voter_id":"gryptoguru.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"197872100","locking_period":300,"voting_power":"989360500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"300000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"689360500000000000000000000"}]},{"voter_id":"kriptocilginlari.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"45366000","locking_period":300,"voting_power":"226830000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"226830000000000000000000000","vote_positions":[]},{"voter_id":"0x0299b2c88809F5Cd83425c8ea8FffF332a1E6e04.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"liusisi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1500624300","locking_period":90,"voting_power":"2250936450000000000000000000","unlocking_started_at":1717551396822,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"secure.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"794870926300","locking_period":300,"voting_power":"3974354631500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"3974354631500000000000000000000"}]},{"voter_id":"centurion.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"244203961600","locking_period":300,"voting_power":"1221019808000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"1221019808000000000000000000000"}]},{"voter_id":"jaysonkr.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"418517018400","locking_period":300,"voting_power":"2092585092000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"2092585092000000000000000000000"}]},{"voter_id":"serxy.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000000","locking_period":60,"voting_power":"100000000000000000000000000000","unlocking_started_at":1730635967602,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"100000000000","locking_period":270,"voting_power":"450000000000000000000000000000","unlocking_started_at":1730635990484,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0x7bF9a18915D1d40CfDa5ADaa39028F62DC7192E0.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"awsomzs.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18544446700","locking_period":300,"voting_power":"92722233500000000000000000000","unlocking_started_at":1715627876104,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0x15f4dE244107DFd83039927becB74934969087C2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"165283000000","locking_period":180,"voting_power":"495849000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"121|mpDAO Grants #8 - August 2024-meximalist.near","voting_power":"495849000000000000000000000000"}]},{"voter_id":"bochenn.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40869363000","locking_period":300,"voting_power":"204346815000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"197465173500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6881641500000000000000000000"}]},{"voter_id":"cryptonum.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10333242300","locking_period":300,"voting_power":"51666211500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakely_io.poolv1.near","voting_power":"15000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"16666211500000000000000000000"}]},{"voter_id":"chiennguyen1985.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"144355687100","locking_period":300,"voting_power":"721778435500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"68849750000","locking_period":300,"voting_power":"344248750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"928327685500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"137699500000000000000000000000"}]},{"voter_id":"pacek.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"242576650800","locking_period":210,"voting_power":"849018277800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"7729000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"720000952400000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"121288325400000000000000000000"}]},{"voter_id":"cunum.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"140598200600","locking_period":300,"voting_power":"702991003000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"cunum.poolv1.near","voting_power":"702991003000000000000000000000"}]},{"voter_id":"chain0ps.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"117241000000","locking_period":300,"voting_power":"586205000000000000000000000000","unlocking_started_at":1729968457538,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"1187500","locking_period":255,"voting_power":"5046875000000000000000000","unlocking_started_at":1717486868740,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"dipsyv.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40000000","locking_period":300,"voting_power":"200000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"200000000000000000000000000"}]},{"voter_id":"3a996e03e7247a0989f546b1b1d02905f53bc8e3fd23920c725d642910f902c8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3632000","locking_period":210,"voting_power":"12712000000000000000000000","unlocking_started_at":1722423551140,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"5000000","locking_period":210,"voting_power":"17500000000000000000000000","unlocking_started_at":1722535624172,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"97140000000","locking_period":195,"voting_power":"315705000000000000000000000000","unlocking_started_at":1725335843062,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":3,"amount":"100000000000","locking_period":270,"voting_power":"450000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"chain0ps.poolv1.near","voting_power":"450000000000000000000000000000"}]},{"voter_id":"impe83.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000000","locking_period":30,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10000000000","locking_period":90,"voting_power":"15000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakely_io.poolv1.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"116|mpDAO Grants #8 - August 2024-plugrel.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"133|mpDAO Grants #8 - August 2024-ffbc3084a4d75ed3c28a7b6c26f44a4ffb43dc3959365d80d70a9660913cbacf","voting_power":"5000000000000000000000000000"}]},{"voter_id":"2d36feb3bc40513b31e852100a2b70cdb143bfade40a393a8c04d4931e10175c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000","locking_period":300,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"500000000000000000000000000"}]},{"voter_id":"luzmargaritas.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"615737007700","locking_period":90,"voting_power":"923605511550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"113535008220","locking_period":300,"voting_power":"567675041100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"1230540434900000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_01.poolv1.near","voting_power":"123631511500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nativo.pool.near","voting_power":"39815992700000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"40000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lavenderfive.poolv1.near","voting_power":"48993613500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"8300000000000000000000000000"}]},{"voter_id":"satojandro.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"461733459070","locking_period":300,"voting_power":"2308667295350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"14638000000","locking_period":90,"voting_power":"21957000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"optimusvalidatornetwork.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lux.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"205663871000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"624960424300000000000000000000"}]},{"voter_id":"lovelysugar.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":30,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"320000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"30000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"69|mpDAO Grants #7 - June 2024-odisea.near","voting_power":"50000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"100000000000000000000000000"}]},{"voter_id":"vinhdao286.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000000","locking_period":270,"voting_power":"36000000000000000000000000000","unlocking_started_at":1714882252568,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"566000000","locking_period":300,"voting_power":"2830000000000000000000000000","unlocking_started_at":1714882266639,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"kangaroojack.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"706830800","locking_period":300,"voting_power":"3534154000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"128000000","locking_period":30,"voting_power":"64000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"89|mpDAO Grants #7 - June 2024-trovelabs.near","voting_power":"3000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"90|mpDAO Grants #7 - June 2024-nekotoken.near","voting_power":"598154000000000000000000000"}]},{"voter_id":"danieldao.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3787360000","locking_period":300,"voting_power":"18936800000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18936800000000000000000000000","vote_positions":[]},{"voter_id":"b9dca1592030dcd114852805d6efaf86b93a51c95613ee184233e9604a5d5924","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"plenum.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16226095800","locking_period":300,"voting_power":"81130479000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"81130479000000000000000000000"}]},{"voter_id":"patvg.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"332890200","locking_period":180,"voting_power":"998670600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"104|mpDAO Grants #7 - June 2024-lematecat.near","voting_power":"998670600000000000000000000"}]},{"voter_id":"ebtgroup.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3333333400000","locking_period":90,"voting_power":"5000000100000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000100000000000000000000000","vote_positions":[]},{"voter_id":"ux1.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"400000000000","locking_period":300,"voting_power":"2000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"2000000000000000000000000000000"}]},{"voter_id":"idomoji.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3549130400","locking_period":300,"voting_power":"17745652000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"17745652000000000000000000000"}]},{"voter_id":"skyempire.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"11100474500","locking_period":300,"voting_power":"55502372500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"15000000","locking_period":210,"voting_power":"52500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"25000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"15000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"15554872500000000000000000000"}]},{"voter_id":"jefe.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6349837300","locking_period":270,"voting_power":"28574267850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"28574267850000000000000000000","vote_positions":[]},{"voter_id":"victorchaos.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"57167811100","locking_period":30,"voting_power":"28583905550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"28583905550000000000000000000","vote_positions":[]},{"voter_id":"0x442d6AF71aD2f51698ef42C3Df8584b78285a7D2.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"028521fdcdb25436d59ca93155651d613b6d0312010fea8f69d1fbebeeb91b15","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"247486800","locking_period":90,"voting_power":"371230200000000000000000000","unlocking_started_at":1730853332528,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"1357971200","locking_period":60,"voting_power":"1357971200000000000000000000","unlocking_started_at":1730853292623,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"854dea7ff905f277012f0f4a1a029494d3ba90352166a89fac32942e6a2d1ef5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1181470160000","locking_period":300,"voting_power":"5907350800000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"polkachu.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"407350800000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"optimusvalidatornetwork.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dexagon.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptogarik.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lavenderfive.poolv1.near","voting_power":"500000000000000000000000000000"}]},{"voter_id":"032890df5f801f63f81175ca1dc53617657dd309b6967814461b344af606942b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"127575925500","locking_period":300,"voting_power":"637879627500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"7225068430","locking_period":120,"voting_power":"14450136860000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"60000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"473450136800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"178879627500000000000000000000"}]},{"voter_id":"938c83fcf90350e4e5ad7f1a802d3db742718e194efbc77cf5a250742f61aeb9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"157101400","locking_period":300,"voting_power":"785507000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"785507000000000000000000000"}]},{"voter_id":"4ebf8d13c98d4dfe90e272a6ca92657ce87478383313e2c3848374b9fcdd0d9c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1585391570","locking_period":300,"voting_power":"7926957850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"6136078000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1790879800000000000000000000"}]},{"voter_id":"103ddbe67a0e8be99b8515cb9ae78bb9e169cda00db8d29a0fa38aa4814a9b9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1600000000","locking_period":300,"voting_power":"8000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2900000000","locking_period":30,"voting_power":"1450000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"1500000000","locking_period":90,"voting_power":"2250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dacmpool.poolv1.near","voting_power":"5700000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000000"}]},{"voter_id":"isk0p4t1x.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1575695300","locking_period":180,"voting_power":"4727085900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4727085900000000000000000000"}]},{"voter_id":"suave.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"42346290500","locking_period":180,"voting_power":"127038871500000000000000000000","unlocking_started_at":1717480136533,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"814000000","locking_period":300,"voting_power":"4070000000000000000000000000","unlocking_started_at":1717480199068,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"1962000000","locking_period":90,"voting_power":"2943000000000000000000000000","unlocking_started_at":1717480213232,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"craftorium.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8682966500","locking_period":120,"voting_power":"17365933000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17365933000000000000000000000","vote_positions":[]},{"voter_id":"wmheng78.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"87927199300","locking_period":120,"voting_power":"175854398600000000000000000000","unlocking_started_at":1731517191195,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"3442041200","locking_period":30,"voting_power":"1721020600000000000000000000","unlocking_started_at":1732458459623,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"bender172319.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"404246500","locking_period":165,"voting_power":"1111677875000000000000000000","unlocking_started_at":1714941537536,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0xmitz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"30530952900","locking_period":300,"voting_power":"152654764500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"152654764500000000000000000000"}]},{"voter_id":"0782f78681fdf8597ea70f16a52b57096e386e192f6c8ee316847808537b06a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"784063850","locking_period":300,"voting_power":"3920319250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"chain0ps.poolv1.near","voting_power":"3920319200000000000000000000"}]},{"voter_id":"331835d03584caa318219412d054010e5433fd19bdc872c954832f9295d89ff2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"trilli.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"61808170000","locking_period":300,"voting_power":"309040850000000000000000000000","unlocking_started_at":1717009373429,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"laurentcora.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"20000000","locking_period":30,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"3923f6035ff12ae3a2b2de19c3a75f8b13e31fe9b6fd5835f0cefdf7a8bddb52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"78736645600","locking_period":90,"voting_power":"118104968400000000000000000000","unlocking_started_at":1732072368058,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0a561cd515b6c7456452202daecb20971ba9ef68284f96485394a7124716f91e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"kingofkingspages.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"98482160","locking_period":300,"voting_power":"492410800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"342824900000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"21596900000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"smart-stake.poolv1.near","voting_power":"30000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"30000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"142|mpDAO Grants #8 - August 2024-refibogota.near","voting_power":"27000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"136|mpDAO Grants #8 - August 2024-bstt.near","voting_power":"40989000000000000000000000"}]},{"voter_id":"cosine.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"44277800","locking_period":90,"voting_power":"66416700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"51600000000","locking_period":30,"voting_power":"25800000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"minion.poolv1.near","voting_power":"25866416700000000000000000000"}]},{"voter_id":"stakeblock.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"84164564800","locking_period":300,"voting_power":"420822824000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"420822824000000000000000000000","vote_positions":[]},{"voter_id":"xuantd06.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"701000000","locking_period":300,"voting_power":"3505000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"9428345600","locking_period":105,"voting_power":"16499604800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"3299000000","locking_period":300,"voting_power":"16495000000000000000000000000","unlocking_started_at":1717902660136,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"4604800000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000000"}]},{"voter_id":"duongnhuice.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1225005600","locking_period":300,"voting_power":"6125028000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6125028000000000000000000000","vote_positions":[]},{"voter_id":"ac15a0f879f76d5fed6b617171656c40f5232730dcfc4b45799136522537f35b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"35894259300","locking_period":90,"voting_power":"53841388950000000000000000000","unlocking_started_at":1729266569561,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"540576900","locking_period":120,"voting_power":"1081153800000000000000000000","unlocking_started_at":1729266188207,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"bf947d9a9ac8934645559f9cd97da96eddf527be21ef7d7e635ee526bfea1d52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18809136700","locking_period":90,"voting_power":"28213705050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"15000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"13213705000000000000000000000"}]},{"voter_id":"acaryln.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":30,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"335000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"69|mpDAO Grants #7 - June 2024-odisea.near","voting_power":"65000000000000000000000000"}]},{"voter_id":"antiscaliger.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"58327500","locking_period":90,"voting_power":"87491250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"87491250000000000000000000","vote_positions":[]},{"voter_id":"pangdao.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4383355100","locking_period":300,"voting_power":"21916775500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"21916775500000000000000000000","vote_positions":[]},{"voter_id":"avtf.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2501484140100","locking_period":300,"voting_power":"12507420700500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12507420700500000000000000000000"}]},{"voter_id":"sandeephulsur.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"206509764000","locking_period":180,"voting_power":"619529292000000000000000000000","unlocking_started_at":1718557112477,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"imagine_nl.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"44178800","locking_period":45,"voting_power":"33134100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"33134100000000000000000000","vote_positions":[]},{"voter_id":"7ab36cef3907536334b06360c8ecad2f68be4205cd422484135ab579b6fadde4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"196954300","locking_period":90,"voting_power":"295431450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"295431450000000000000000000","vote_positions":[]},{"voter_id":"akaiqn113.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"25000000000","locking_period":300,"voting_power":"125000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"31250000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"93750000000000000000000000000"}]},{"voter_id":"4aafb0fa32afa8bb20a3e15c8ac55126f4bf325c476a6575498cc8af11d05d52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2815258700","locking_period":300,"voting_power":"14076293500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"1407629300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"12668664200000000000000000000"}]},{"voter_id":"tzouf.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40000000000","locking_period":300,"voting_power":"200000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"200000000000000000000000000000","vote_positions":[]},{"voter_id":"fernix.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1548460000","locking_period":150,"voting_power":"3871150000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"3871150000000000000000000000"}]},{"voter_id":"baf318138fe9ae6648a1e06f12f6a7ac079d47c6097eb41737fc1f8c9cd3b71c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2137460000","locking_period":300,"voting_power":"10687300000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"88|mpDAO Grants #7 - June 2024-100diasder.near","voting_power":"10687300000000000000000000000"}]},{"voter_id":"rahulgoel.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"919872500","locking_period":300,"voting_power":"4599362500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4599362500000000000000000000","vote_positions":[]},{"voter_id":"viettrungdoan91.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"49647336300","locking_period":135,"voting_power":"111706506675000000000000000000","unlocking_started_at":1715001641799,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"sailinon.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46872089800","locking_period":30,"voting_power":"23436044900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"23436044900000000000000000000","vote_positions":[]},{"voter_id":"venomity.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"218000000","locking_period":300,"voting_power":"1090000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"1090000000000000000000000000"}]},{"voter_id":"tahuutho.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1548460000","locking_period":240,"voting_power":"6193840000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6193840000000000000000000000"}]},{"voter_id":"mxweb3.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"57674139700","locking_period":300,"voting_power":"288370698500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"288370698500000000000000000000"}]},{"voter_id":"magic.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"59192298500","locking_period":300,"voting_power":"295961492500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"295961492500000000000000000000"}]},{"voter_id":"dollarinveste.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7795086000","locking_period":300,"voting_power":"38975430000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"38975430000000000000000000000"}]},{"voter_id":"wap909.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"424500401120","locking_period":300,"voting_power":"2122502005600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"130000000000","locking_period":300,"voting_power":"650000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"10000000000","locking_period":300,"voting_power":"50000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"35500000000","locking_period":300,"voting_power":"177500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"115505000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"minion.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"here.poolv1.near","voting_power":"230000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"63169390100000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"26005000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"144950000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"105|mpDAO Grants #7 - June 2024-marcelorefi.near","voting_power":"200000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"91|mpDAO Grants #7 - June 2024-4d1fe2e38339dc82b68afd1d09bac3068c30f22f09dc693e8ab6610da8e15c04","voting_power":"220372615500000000000000000000"},{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"200000000000000000000000000000"}]},{"voter_id":"3add6ae62eb60d4edadca209e37012780f32c3d3cf31868496b91f1988bf043a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"37137117500","locking_period":90,"voting_power":"55705676250000000000000000000","unlocking_started_at":1716421523901,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"newnearwallet.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"845742145800","locking_period":150,"voting_power":"2114355364500000000000000000000","unlocking_started_at":1732208388683,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"draconnia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1903014900","locking_period":300,"voting_power":"9515074500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"347000000","locking_period":30,"voting_power":"173500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"9688574500000000000000000000"}]},{"voter_id":"544e14070a33ec9a7e5d34556f5ddaa45411e73d394039cc241fa92e2bc5cfb5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2067778200","locking_period":300,"voting_power":"10338891000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"1000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sharpdarts.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"welldonestake.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"builddao.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zkv_staketosupportprivacy.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"jagon.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"trust-nodes.poolv1.near","voting_power":"300000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"4ire.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pinrocks.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"centurion.poolv1.near","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pathrocknetwork.poolv1.near","voting_power":"1667300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"hakuna_matata.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lunanova.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"ni.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptotribe.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"600000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"637223700000000000000000000"}]},{"voter_id":"vvm25.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"57700000","locking_period":300,"voting_power":"288500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"288500000000000000000000000","vote_positions":[]},{"voter_id":"dund.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1204000000","locking_period":30,"voting_power":"602000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"576000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"67|mpDAO Grants #7 - June 2024-baryo01.near","voting_power":"10000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"112|mpDAO Grants #7 - June 2024-hoangthien1109.near","voting_power":"11000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"69|mpDAO Grants #7 - June 2024-odisea.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"9968.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1050000000","locking_period":30,"voting_power":"525000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"500000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"112|mpDAO Grants #7 - June 2024-hoangthien1109.near","voting_power":"15000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"8000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"67|mpDAO Grants #7 - June 2024-baryo01.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"x999.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1010629400","locking_period":45,"voting_power":"757972050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"710972050000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"112|mpDAO Grants #7 - June 2024-hoangthien1109.near","voting_power":"20000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"10000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"68|mpDAO Grants #7 - June 2024-3sblockchain.near","voting_power":"17000000000000000000000000"}]},{"voter_id":"pablopd.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6741186400","locking_period":300,"voting_power":"33705932000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"33705932000000000000000000000"}]},{"voter_id":"marysol.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"48051052800","locking_period":300,"voting_power":"240255264000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"240255264000000000000000000000"}]},{"voter_id":"cryptomoneymaker.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"31652702800","locking_period":30,"voting_power":"15826351400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4794004400","locking_period":60,"voting_power":"4794004400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"523000000","locking_period":300,"voting_power":"2615000000000000000000000000","unlocking_started_at":1715076609230,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20620355800000000000000000000"}]},{"voter_id":"iosg01.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13100001000000","locking_period":30,"voting_power":"6550000500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6550000500000000000000000000000","vote_positions":[]},{"voter_id":"towanda.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"25500000","locking_period":45,"voting_power":"19125000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1439316500","locking_period":60,"voting_power":"1439316500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"20000000","locking_period":90,"voting_power":"30000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"9500000","locking_period":30,"voting_power":"4750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"1493191500000000000000000000"}]},{"voter_id":"suz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"122153000000","locking_period":90,"voting_power":"183229500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"183229500000000000000000000000"}]},{"voter_id":"nikhil.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5076282200","locking_period":300,"voting_power":"25381411000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"25381411000000000000000000000","vote_positions":[]},{"voter_id":"metapool20.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"101876349400","locking_period":300,"voting_power":"509381747000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"509381747000000000000000000000"}]},{"voter_id":"usainbluntmx.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4967206710","locking_period":300,"voting_power":"24836033550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4836033550000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"20000000000000000000000000000"}]},{"voter_id":"psd.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"800000000000","locking_period":30,"voting_power":"400000000000000000000000000000","unlocking_started_at":1718077736393,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"meximalist.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2248074700","locking_period":300,"voting_power":"11240373500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"11240373500000000000000000000"}]},{"voter_id":"xvii.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"416786600","locking_period":300,"voting_power":"2083933000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"17000000","locking_period":30,"voting_power":"8500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2092433000000000000000000000","vote_positions":[]},{"voter_id":"norelyz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1100000000","locking_period":300,"voting_power":"5500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"191000000","locking_period":90,"voting_power":"286500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5786500000000000000000000000"}]},{"voter_id":"smjunior.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2914829100","locking_period":300,"voting_power":"14574145500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"200000000","locking_period":300,"voting_power":"1000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"10574145500000000000000000000"}]},{"voter_id":"rondell.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"302214342100","locking_period":90,"voting_power":"453321513150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"26138245800","locking_period":105,"voting_power":"45741930150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"499063443300000000000000000000","vote_positions":[]},{"voter_id":"paderuu.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"201000000","locking_period":300,"voting_power":"1005000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"114|mpDAO Grants #7 - June 2024-aksanova33.near","voting_power":"1005000000000000000000000000"}]},{"voter_id":"hos5018.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3163661200","locking_period":300,"voting_power":"15818306000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"15818306000000000000000000000"}]},{"voter_id":"nearmultiverse.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"36833578700","locking_period":300,"voting_power":"184167893500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"419077000","locking_period":300,"voting_power":"2095385000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"186263278500000000000000000000"}]},{"voter_id":"0xAcaa2077b89799b81F390d9cf015Be8d38108A36.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6834000000","locking_period":300,"voting_power":"34170000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11615000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2pstaking.poolv1.near","voting_power":"10555000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"12000000000000000000000000000"}]},{"voter_id":"agusin.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"658762400","locking_period":300,"voting_power":"3293812000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"3293812000000000000000000000"}]},{"voter_id":"3828a8400589b9faa74d53169ee33bad7a2ed3b68bfb01e8c855454391e7b7a7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"42155844100","locking_period":90,"voting_power":"63233766150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"63233766150000000000000000000","vote_positions":[]},{"voter_id":"okpok.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"132894668500","locking_period":300,"voting_power":"664473342500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakeseeker.poolv1.near","voting_power":"664473342500000000000000000000"}]},{"voter_id":"bjirken.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"437000000","locking_period":30,"voting_power":"218500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"200000000","locking_period":300,"voting_power":"1000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"535000000","locking_period":90,"voting_power":"802500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2021000000000000000000000000","vote_positions":[]},{"voter_id":"bjorken.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"62169000000","locking_period":270,"voting_power":"279760500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"20000000000","locking_period":135,"voting_power":"45000000000000000000000000000","unlocking_started_at":1725956697284,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"279760500000000000000000000000"}]},{"voter_id":"tiberiu16.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3760603900","locking_period":300,"voting_power":"18803019500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"18803019500000000000000000000"}]},{"voter_id":"samwangg.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3186786600","locking_period":75,"voting_power":"3983483250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3983483250000000000000000000","vote_positions":[]},{"voter_id":"sam_wang.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17500000000","locking_period":75,"voting_power":"21875000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"21875000000000000000000000000","vote_positions":[]},{"voter_id":"flyingcumbancha.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4686891800","locking_period":300,"voting_power":"23434459000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"23434459000000000000000000000"}]},{"voter_id":"izcc.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16211176100","locking_period":300,"voting_power":"81055880500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"115|mpDAO Grants #8 - August 2024-irvingzcc.near","voting_power":"81055880500000000000000000000"}]},{"voter_id":"e474b668ea4731cf923bc78733f14c849b2d2bb78657af863a84dbc670fe746c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"669000000","locking_period":300,"voting_power":"3345000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"3345000000000000000000000000"}]},{"voter_id":"fersoria.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"117000000","locking_period":180,"voting_power":"351000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1000000000","locking_period":180,"voting_power":"3000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"77|mpDAO Grants #7 - June 2024-1724cbdb130e5b0f9bd750f666f4fe00581489fcc294dca4ed055c90c76808fc","voting_power":"3351000000000000000000000000"}]},{"voter_id":"zahrah.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"261000000","locking_period":300,"voting_power":"1305000000000000000000000000","unlocking_started_at":1714795035865,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"e8669b64d048c0b7627a49250cd08407f9020c4ec252350838c29956c53f4a92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"11525635800","locking_period":300,"voting_power":"57628179000000000000000000000","unlocking_started_at":1716310161875,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"jack1265.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7489666000","locking_period":60,"voting_power":"7489666000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7489666000000000000000000000","vote_positions":[]},{"voter_id":"cryptoreumd.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"574343300","locking_period":300,"voting_power":"2871716500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"2871716500000000000000000000"}]},{"voter_id":"c8a73319812a4e49a3d18f097cc5b00c105d724c5451ec5e38c6146c5561c800","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"419828200","locking_period":90,"voting_power":"629742300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"333171200","locking_period":210,"voting_power":"1166099200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1795841500000000000000000000"}]},{"voter_id":"wdz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3624088600","locking_period":300,"voting_power":"18120443000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18120443000000000000000000000","vote_positions":[]},{"voter_id":"imok.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1496000000","locking_period":90,"voting_power":"2244000000000000000000000000","unlocking_started_at":1717345585368,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"car_parts.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6569600000","locking_period":90,"voting_power":"9854400000000000000000000000","unlocking_started_at":1714991687794,"is_unlocked":true,"is_unlocking":false,"is_locked":false},{"index":1,"amount":"2296509600","locking_period":300,"voting_power":"11482548000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"1732548000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"9750000000000000000000000000"}]},{"voter_id":"mullthebull.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27830480000","locking_period":240,"voting_power":"111321920000000000000000000000","unlocking_started_at":1728571162160,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"1000000000","locking_period":30,"voting_power":"500000000000000000000000000","unlocking_started_at":1728571173050,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"flokielon.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"93669107700","locking_period":300,"voting_power":"468345538500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"1545538500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"466800000000000000000000000000"}]},{"voter_id":"condog.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28830480000","locking_period":285,"voting_power":"136944780000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"136944780000000000000000000000"}]},{"voter_id":"lastryano.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27027920000","locking_period":285,"voting_power":"128382620000000000000000000000","unlocking_started_at":1733280809798,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"zardoz90.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"24554920000","locking_period":285,"voting_power":"116635870000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"883000000","locking_period":135,"voting_power":"1986750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"118622620000000000000000000000"}]},{"voter_id":"farpost.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"43719400","locking_period":300,"voting_power":"218597000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"218597000000000000000000000","vote_positions":[]},{"voter_id":"cuongdcdev.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3944328600","locking_period":300,"voting_power":"19721643000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"721000000","locking_period":30,"voting_power":"360500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"buildnear.poolv1.near","voting_power":"20082143000000000000000000000"}]},{"voter_id":"nostory.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"800000","locking_period":60,"voting_power":"800000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"734556000","locking_period":300,"voting_power":"3672780000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"1000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"1000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"1000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"673580000000000000000000000"}]},{"voter_id":"309872076a518c9c96935b959d13e2f23546113bc6ea41941e5562b65cbf7085","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"669000000","locking_period":165,"voting_power":"1839750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1839750000000000000000000000","vote_positions":[]},{"voter_id":"0x56D994c08CdC5c9189B4d6863129e5F195C6E8F2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"15034000000","locking_period":120,"voting_power":"30068000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"30068000000000000000000000000"}]},{"voter_id":"0x16E44d8F1BEc8fb6c6770ABE8E8EF199B7723c9a.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27095000000","locking_period":300,"voting_power":"135475000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"135475000000000000000000000000"}]},{"voter_id":"0xA4243e13bD28fa9e0e67704E381cb98d06240C93.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"30000000000","locking_period":300,"voting_power":"150000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"here.poolv1.near","voting_power":"150000000000000000000000000000"}]},{"voter_id":"0x22B052CbBf01cfE2EC5A8424a7d361f376aEaEb0.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"50032000000","locking_period":300,"voting_power":"250160000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"250160000000000000000000000000"}]},{"voter_id":"0xEA3e3612175a894362f3062785adC133D9b98f10.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"285827000000","locking_period":300,"voting_power":"1429135000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"929135000000000000000000000000"}]},{"voter_id":"c47093dc9f12d71135b4940a0744944b7936b0d4458bc1feab0eafb4d3d1249d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":270,"voting_power":"18000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[]},{"voter_id":"0x2929f6FEDD4897236fB97A059aaA1fe057adeB0e.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"30767000000","locking_period":300,"voting_power":"153835000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"153835000000000000000000000000"}]},{"voter_id":"0xE3Ecd1907caa757A4a5C9DA32d7AA34005681C1e.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16251000000","locking_period":300,"voting_power":"81255000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"81255000000000000000000000000"}]},{"voter_id":"0xf5597b4094058efe44fe61C8f2844823Fc566e3c.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"650000000","locking_period":300,"voting_power":"3250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"3250000000000000000000000000"}]},{"voter_id":"0x1430b0DF64C0126c6184D2f28b1cF505f39147b2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"19921000000","locking_period":300,"voting_power":"99605000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"99605000000000000000000000000"}]},{"voter_id":"0x1fb751200fda2aBa12BA2C254c3c02d23E06dB8D.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1508000000","locking_period":300,"voting_power":"7540000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7540000000000000000000000000","vote_positions":[]},{"voter_id":"0xC479d61faFAd7374af8eE1902d3797E8cCAb0522.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6460000000","locking_period":300,"voting_power":"32300000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearuaguild.poolv1.near","voting_power":"32300000000000000000000000000"}]},{"voter_id":"5d7e273e59ce62b306698ce7fb9a8b885264be2ce90980254c85b361d9d8d71b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"invoices.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"246428600500","locking_period":300,"voting_power":"1232143002500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stardust.poolv1.near","voting_power":"1232143002500000000000000000000"}]},{"voter_id":"pat.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6823431700","locking_period":300,"voting_power":"34117158500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"oharanodes.poolv1.near","voting_power":"34117158500000000000000000000"}]},{"voter_id":"seanvan1411.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"31670100000","locking_period":225,"voting_power":"118762875000000000000000000000","unlocking_started_at":1725956985812,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"50000000000","locking_period":225,"voting_power":"187500000000000000000000000000","unlocking_started_at":1725956931014,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"2349000000","locking_period":285,"voting_power":"11157750000000000000000000000","unlocking_started_at":1725957070629,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"number9dream.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12029171300","locking_period":150,"voting_power":"30072928250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"30072928200000000000000000000"}]},{"voter_id":"e5fb81d59683c942333b8f420ab667dcc33beeea5227c19350ca0c1c6b7439dd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1089218300","locking_period":300,"voting_power":"5446091500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"75|mpDAO Grants #7 - June 2024-abrahamrivv.near","voting_power":"2700000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"2746091500000000000000000000"}]},{"voter_id":"ihyshan.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10055324400","locking_period":300,"voting_power":"50276622000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"651000000","locking_period":90,"voting_power":"976500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"51253122000000000000000000000"}]},{"voter_id":"0x32292056970DCe4B9528d71642F765f14E0F1042.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1315000000","locking_period":300,"voting_power":"6575000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"6575000000000000000000000000"}]},{"voter_id":"0x8989964863cf4dbaedFFbb4F43Abcf54DA5F5f1b.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1410209000000","locking_period":300,"voting_power":"7051045000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"7051045000000000000000000000000"}]},{"voter_id":"0x1256E7992564AB22e332532472c916Bd8D1e1Ca7.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1735000000","locking_period":120,"voting_power":"3470000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3470000000000000000000000000","vote_positions":[]},{"voter_id":"ivanovic.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"48900983500","locking_period":300,"voting_power":"244504917500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"244504917500000000000000000000"}]},{"voter_id":"developer.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1040497000000","locking_period":90,"voting_power":"1560745500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"650100000000","locking_period":165,"voting_power":"1787775000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"90000000000","locking_period":300,"voting_power":"450000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":3,"amount":"10000000000","locking_period":120,"voting_power":"20000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":4,"amount":"1000783000000","locking_period":105,"voting_power":"1751370250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2534890750000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"950000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearstar.poolv1.near","voting_power":"1900000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"101|mpDAO Grants #7 - June 2024-criptolaplata.near","voting_power":"150000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"87|mpDAO Grants #7 - June 2024-97c131e898ddae0f081783a3d43dfef5434e098728fed3276eeece5ab5a7a1c6","voting_power":"35000000000000000000000000000"}]},{"voter_id":"e7bfa469cc055cb12f4d90e551e5cb128fda19400da59768b0963cde89fc774d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"natcrypto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"848677601500","locking_period":150,"voting_power":"2121694003750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"47498000000","locking_period":120,"voting_power":"94996000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"16441398980","locking_period":210,"voting_power":"57544896430000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"57544896480000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dexagon.poolv1.near","voting_power":"222000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"rekt.poolv1.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"101|mpDAO Grants #7 - June 2024-criptolaplata.near","voting_power":"350000003700000000000000000000"},{"votable_address":"initiatives","votable_object_id":"79|mpDAO Grants #7 - June 2024-478f0f81f81824a1db9f1c434b73e2c800e2992069a2d0484d050a86406a4402","voting_power":"95000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"81|mpDAO Grants #7 - June 2024-herdaomexico.near","voting_power":"150000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"94|mpDAO Grants #7 - June 2024-cryptopr3neur.near","voting_power":"190000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"120000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"107|mpDAO Grants #7 - June 2024-ae1ddf13d147c6afdb5e0d53274ea31a10f12206bebfa082a231df5a6cfa82d9","voting_power":"220000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"102|mpDAO Grants #7 - June 2024-fr-grant.near","voting_power":"400000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"108|mpDAO Grants #7 - June 2024-553a5330546aa39850aa0e972bc1bec1c7018575ba141fa6f0a379cf28d46d16","voting_power":"169690000000000000000000000000"}]},{"voter_id":"0xCF6d2f6E2839ac50f49a76c1DA88f82F62d29B74.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1200000000","locking_period":300,"voting_power":"6000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000000","vote_positions":[]},{"voter_id":"eventosmetapool.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000","locking_period":300,"voting_power":"50000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"50000000000000000000000000"}]},{"voter_id":"davidmo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17578467300","locking_period":90,"voting_power":"26367700950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"26367700900000000000000000000"}]},{"voter_id":"0x36D93bAC8ffcc3af98cCf3A5A1b2588df5508256.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1418000000","locking_period":105,"voting_power":"2481500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2481500000000000000000000000","vote_positions":[]},{"voter_id":"smile143.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":90,"voting_power":"1500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"1500000000000000000000000000"}]},{"voter_id":"0x7AbAEb445D6c0036B386c1Ccd0c4d58c528AC450.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"0xcc7B864e79fA2eB1Aa43905A74815fac8Dd99fc7.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18472000000","locking_period":300,"voting_power":"92360000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"92360000000000000000000000000"}]},{"voter_id":"0xFFaCD7e55e79fABeD8E6096A5ab325AE38A5faC8.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"75937000000","locking_period":300,"voting_power":"379685000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"379685000000000000000000000000"}]},{"voter_id":"0x26B0Bb09478A8423b53b2854dEfFb8A61A889725.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"102861000000","locking_period":300,"voting_power":"514305000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"514305000000000000000000000000"}]},{"voter_id":"forklift.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2702185900","locking_period":300,"voting_power":"13510929500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"8815929500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"mpdao.poolv1.near","voting_power":"4695000000000000000000000000"}]},{"voter_id":"zzw.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"27062790900","locking_period":300,"voting_power":"135313954500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"128913954500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"6400000000000000000000000000"}]},{"voter_id":"nonothenono.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7002268300","locking_period":30,"voting_power":"3501134150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"50000000","locking_period":90,"voting_power":"75000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"3576134100000000000000000000"}]},{"voter_id":"0xEa36e8E31df3eC5dC2350589697dDF346Bc58E24.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1265000000","locking_period":300,"voting_power":"6325000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6325000000000000000000000000","vote_positions":[]},{"voter_id":"0x092cfF73c77a9de794D25b0088DeD0e430733dbb.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8309000000","locking_period":300,"voting_power":"41545000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"77|mpDAO Grants #7 - June 2024-1724cbdb130e5b0f9bd750f666f4fe00581489fcc294dca4ed055c90c76808fc","voting_power":"41545000000000000000000000000"}]},{"voter_id":"alansicairos.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"22853653620","locking_period":300,"voting_power":"114268268100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"114268268100000000000000000000"}]},{"voter_id":"mathieutetreault.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5981299200","locking_period":180,"voting_power":"17943897600000000000000000000","unlocking_started_at":1717697591586,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0x4246FF0452B95780122E84bdFfe5Ba9371E1d7a2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"488000000","locking_period":300,"voting_power":"2440000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"121|mpDAO Grants #8 - August 2024-meximalist.near","voting_power":"1000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"1440000000000000000000000000"}]},{"voter_id":"edfernandezg.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13261924800","locking_period":180,"voting_power":"39785774400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"39785774400000000000000000000"}]},{"voter_id":"0xE351625D4E07D185880fdcc3a8FA6E2558d2F470.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16000000000","locking_period":300,"voting_power":"80000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"p2p-org.poolv1.near","voting_power":"80000000000000000000000000000"}]},{"voter_id":"14152d8789b96ce70fbe4b105194eeb85052b06a00c839b8b7de70928ab865fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"66667603300","locking_period":90,"voting_power":"100001404950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1798000000","locking_period":90,"voting_power":"2697000000000000000000000000","unlocking_started_at":1733365535829,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":2,"amount":"42209000000","locking_period":90,"voting_power":"63313500000000000000000000000","unlocking_started_at":1733365600765,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"1404950000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"100000000000000000000000000000"}]},{"voter_id":"vinh1024.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13958814100","locking_period":45,"voting_power":"10469110575000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10469110575000000000000000000","vote_positions":[]},{"voter_id":"haytham.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"35854400","locking_period":90,"voting_power":"53781600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"53781600000000000000000000","vote_positions":[]},{"voter_id":"0xF7b188d84192e23049dA2E659ddC2d79D7C85e1d.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"550000000","locking_period":300,"voting_power":"2750000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"2750000000000000000000000000"}]},{"voter_id":"a9f49c2f4c066b83c3e71d5ca3ff097c7c6a09707386298da42b52c5927a25bc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"11010780500","locking_period":90,"voting_power":"16516170750000000000000000000","unlocking_started_at":1733685503490,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"1000000000","locking_period":90,"voting_power":"1500000000000000000000000000","unlocking_started_at":1733685461120,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"test_near.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000","locking_period":30,"voting_power":"500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10759382620","locking_period":300,"voting_power":"53796913100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"25000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"28797413100000000000000000000"}]},{"voter_id":"oulrij.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"369566900","locking_period":90,"voting_power":"554350350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"554350350000000000000000000","vote_positions":[]},{"voter_id":"a79cee9b44db1224c8a1f8b3819309d1ce1df8832aee8bf99ba5848269eaa2de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28746963100","locking_period":90,"voting_power":"43120444650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"43120444650000000000000000000","vote_positions":[]},{"voter_id":"marek505.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46639121900","locking_period":30,"voting_power":"23319560950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"23319560950000000000000000000","vote_positions":[]},{"voter_id":"f627b3e2e72da7b2dd98fafa91ed7b56b658142e1e4c0a33d8147dcc081de775","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"jasp83.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"33829927500","locking_period":30,"voting_power":"16914963750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"134|mpDAO Grants #8 - August 2024-marcrypted.near","voting_power":"5000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"135|mpDAO Grants #8 - August 2024-nohbekwoman.near","voting_power":"3450000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"133|mpDAO Grants #8 - August 2024-ffbc3084a4d75ed3c28a7b6c26f44a4ffb43dc3959365d80d70a9660913cbacf","voting_power":"3464963700000000000000000000"}]},{"voter_id":"724d3919c74c7d07b1be7359acea2ba45f244fa2da476fa4988cbc1b9338f967","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5fd80e617970059ecc99bc0b6542eacc7da5288361918dc31a637918b937921d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"179119020600","locking_period":90,"voting_power":"268678530900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"268678530900000000000000000000"}]},{"voter_id":"155ab93ae7dba33a94552113da59100609c32c29df1faa39fdffcacf51ace7ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"nickharrington.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1674280100","locking_period":300,"voting_power":"8371400500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8371400500000000000000000000","vote_positions":[]},{"voter_id":"khoacoi.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1198231100","locking_period":105,"voting_power":"2096904425000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"299557825000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"1797346600000000000000000000"}]},{"voter_id":"rockpath.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"282264232200","locking_period":90,"voting_power":"423396348300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pathrocknetwork.poolv1.near","voting_power":"423396348300000000000000000000"}]},{"voter_id":"300525dbba750911263e4f330c25aa340463745b94246b866b714fa0f8ef867c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"margarine-escapist-agonizing-epilogue.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"47161820000","locking_period":180,"voting_power":"141485460000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"176410527700","locking_period":300,"voting_power":"882052638500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1023538098500000000000000000000","vote_positions":[]},{"voter_id":"170721a8d15577b6c359de7132195102f9a89c0362244038e382197d40e4011a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0xc8B19839ae371bd541F20B15c3A3CB82BFB6A6C6.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1450000000","locking_period":300,"voting_power":"7250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7250000000000000000000000000","vote_positions":[]},{"voter_id":"pedrompg311.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10791900","locking_period":60,"voting_power":"10791900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"liver.pool.near","voting_power":"2000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"111|mpDAO Grants #7 - June 2024-c0d0c8b3bfcbc17347002733be3e2d17df833d2931c425eaa40bd7e73a4509e3","voting_power":"8791900000000000000000000"}]},{"voter_id":"0x3f6b5Df4a6fDAE2b58e3F5aA7037E68E1Eac9188.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1500000000","locking_period":300,"voting_power":"7500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"7500000000000000000000000000"}]},{"voter_id":"8fc648574b6aafaf9a966fb173827a3efcf3fc2b56bef501910246df43dcaa50","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"26695179500","locking_period":30,"voting_power":"13347589750000000000000000000","unlocking_started_at":1715956794887,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"daddyarbitrage.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5920373200","locking_period":300,"voting_power":"29601866000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"29601866000000000000000000000"}]},{"voter_id":"vandal.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6931309200","locking_period":90,"voting_power":"10396963800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"10396963800000000000000000000"}]},{"voter_id":"vandao.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10964420300","locking_period":90,"voting_power":"16446630450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"5023709430","locking_period":300,"voting_power":"25118547150000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"25118547200000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"16446630400000000000000000000"}]},{"voter_id":"9896.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"403071612900","locking_period":90,"voting_power":"604607419350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"604607419300000000000000000000"}]},{"voter_id":"zkjandro.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"658410920110","locking_period":300,"voting_power":"3292054600550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"2602594905000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"182938975100000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"centurion.poolv1.near","voting_power":"506520720400000000000000000000"}]},{"voter_id":"decollation.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"299392600","locking_period":300,"voting_power":"1496963000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1496963000000000000000000000","vote_positions":[]},{"voter_id":"zuenti.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"239648900","locking_period":300,"voting_power":"1198244500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1198244500000000000000000000"}]},{"voter_id":"kentnearvn.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000000","vote_positions":[]},{"voter_id":"orangejoe.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8705295200","locking_period":300,"voting_power":"43526476000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"43526470000000000000000000000"}]},{"voter_id":"dcc81d49b62bf89e1a07de111c32aa89fb4b6859e8bd47fa73052df9e3599244","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1884816698900","locking_period":30,"voting_power":"942408349450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"942408349450000000000000000000","vote_positions":[]},{"voter_id":"0x86874cD0aa311E56593CD045F35dbc038057D614.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2486000000","locking_period":30,"voting_power":"1243000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"243000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"1000000000000000000000000000"}]},{"voter_id":"augie.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"90932694400","locking_period":300,"voting_power":"454663472000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"454663472000000000000000000000"}]},{"voter_id":"0xA67Dd142A1395b23607f47C1b262C973f6258E7E.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"jablko.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1852644700","locking_period":300,"voting_power":"9263223500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4705289400000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"4557934100000000000000000000"}]},{"voter_id":"apitchdeck.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10152269900","locking_period":300,"voting_power":"50761349500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50761349500000000000000000000","vote_positions":[]},{"voter_id":"chloethedev.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"89282812370","locking_period":300,"voting_power":"446414061850000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"164292100","locking_period":300,"voting_power":"821460500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"443228017600000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"4007504700000000000000000000"}]},{"voter_id":"kaymens.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"81900000","locking_period":300,"voting_power":"409500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"409500000000000000000000000"}]},{"voter_id":"4a6f65.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"53694286400","locking_period":300,"voting_power":"268471432000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"268471432000000000000000000000"}]},{"voter_id":"adeusa.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"224293500","locking_period":300,"voting_power":"1121467500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"1121467500000000000000000000"}]},{"voter_id":"a5c354d15df4256bc5bcc1996965a7bdc60db7f6cbdd342e16583f62b63bc0ae","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":1727996056671,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"a0394de7c6bdb3fa4ae09a25853e467f3fe5223b8f4917a5ff223195018ede14","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"140234200","locking_period":90,"voting_power":"210351300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"restake.poolv1.near","voting_power":"20350000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"190001300000000000000000000"}]},{"voter_id":"7baced7c2334e6e975e2ea6965af15fdae7deba588594049692392f985287eb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"kaion.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"400000000","locking_period":90,"voting_power":"600000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2586055900","locking_period":300,"voting_power":"12930279500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"13530279500000000000000000000"}]},{"voter_id":"donsober.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1299019500","locking_period":120,"voting_power":"2598039000000000000000000000","unlocking_started_at":1716481383787,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"8fe8e86771824d3880fe18798f56baf5c24640850ebdc142ba2b9760cd5718cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"47285461100","locking_period":150,"voting_power":"118213652750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"118213652700000000000000000000"}]},{"voter_id":"0x1968145D78FE9799871522a067cbc161CE5603a8.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"swissstar.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000000","vote_positions":[]},{"voter_id":"0x5650F30f719e6E9C0D00b2fdd20C7ba8649337C0.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"14881000000","locking_period":300,"voting_power":"74405000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"74405000000000000000000000000"}]},{"voter_id":"jared.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"138363492500","locking_period":300,"voting_power":"691817462500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"691817462500000000000000000000"}]},{"voter_id":"kaboochis.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"meteor.poolv1.near","voting_power":"5000000000000000000000000000"}]},{"voter_id":"0xC3F836EC06A2202af23e59997A613CA0722F35d1.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"osc.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5432906700","locking_period":300,"voting_power":"27164533500000000000000000000","unlocking_started_at":1716842243888,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"47e7f13cbaf178c66d2c12d93593513a0f406188c40e72977f9b1a4deed15958","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"43049110","locking_period":300,"voting_power":"215245550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"215245500000000000000000000"}]},{"voter_id":"cozybird.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"301008270300","locking_period":300,"voting_power":"1505041351500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1505041351500000000000000000000"}]},{"voter_id":"walapril.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"821421300","locking_period":300,"voting_power":"4107106500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"2344580500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"1762526000000000000000000000"}]},{"voter_id":"profit2.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"601830443400","locking_period":300,"voting_power":"3009152217000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"oe.poolv1.near","voting_power":"3009152217000000000000000000000"}]},{"voter_id":"4083b50cb907509eba10b56a41300f719d341175cfb92c819bd07a4fe3efddb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1034519658100","locking_period":300,"voting_power":"5172598290500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearkoreahub.poolv1.near","voting_power":"5172598290500000000000000000000"}]},{"voter_id":"bianca.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2556353500","locking_period":300,"voting_power":"12781767500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1448605500","locking_period":30,"voting_power":"724302750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"13506070200000000000000000000"}]},{"voter_id":"huktodev.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7164006000","locking_period":240,"voting_power":"28656024000000000000000000000","unlocking_started_at":1722119926238,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"nedlar7.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"300090000","locking_period":90,"voting_power":"450135000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"450135000000000000000000000"}]},{"voter_id":"phars.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1295718700","locking_period":75,"voting_power":"1619648375000000000000000000","unlocking_started_at":1732333528450,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0x773c24F007Ef074CacD382be18e5857785837D4a.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"201000000","locking_period":300,"voting_power":"1005000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"1005000000000000000000000000"}]},{"voter_id":"arttrader.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46585365400","locking_period":120,"voting_power":"93170730800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"93170730800000000000000000000","vote_positions":[]},{"voter_id":"0x12f5ecd00fe3851f4d7eB2bf77E72E3299f2C868.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"0xb5621490079D534D737B6B8837947FB376D5F629.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"0x263779E7338DEf14C431D6301F97Cc846D69C099.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"0x7844317568728BdC9475E939553A2A0446D19E2e.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"dosisweb3.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18578826400","locking_period":120,"voting_power":"37157652800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"128|mpDAO Grants #8 - August 2024-aleiranova.near","voting_power":"37157652800000000000000000000"}]},{"voter_id":"0x6DEe4a729430078CD281988Ac76292cf3B27e2c0.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"692000000","locking_period":300,"voting_power":"3460000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3460000000000000000000000000","vote_positions":[]},{"voter_id":"0x13DF946B95ee60922257af5811423ea9dc353F36.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1250000000","locking_period":300,"voting_power":"6250000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"6250000000000000000000000000"}]},{"voter_id":"0x93857Ad03a9Ab1226D909FFa49BcA37e70e4D32d.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7959000000","locking_period":300,"voting_power":"39795000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"32835000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"86|mpDAO Grants #7 - June 2024-la-incubadora.near","voting_power":"6301000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"70|mpDAO Grants #7 - June 2024-a5732a8195200f4203a633cea3dc154b696ed628d2ed4a322f214849f2247307","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"77|mpDAO Grants #7 - June 2024-1724cbdb130e5b0f9bd750f666f4fe00581489fcc294dca4ed055c90c76808fc","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"75|mpDAO Grants #7 - June 2024-abrahamrivv.near","voting_power":"300000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"79|mpDAO Grants #7 - June 2024-478f0f81f81824a1db9f1c434b73e2c800e2992069a2d0484d050a86406a4402","voting_power":"50000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"69000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"67|mpDAO Grants #7 - June 2024-baryo01.near","voting_power":"40000000000000000000000000"}]},{"voter_id":"21819.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"103204600","locking_period":300,"voting_power":"516023000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"516023000000000000000000000"}]},{"voter_id":"marmaj.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"106206400","locking_period":300,"voting_power":"531032000000000000000000000","unlocking_started_at":1733344985728,"is_unlocked":false,"is_unlocking":true,"is_locked":false},{"index":1,"amount":"17000000","locking_period":90,"voting_power":"25500000000000000000000000","unlocking_started_at":1733344938613,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"manyoil.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"74151250700","locking_period":300,"voting_power":"370756253500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"370756253500000000000000000000"}]},{"voter_id":"tensors.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2942857100000","locking_period":300,"voting_power":"14714285500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"9714285500000000000000000000000"}]},{"voter_id":"0xAa3F1faAa3a93Bb5799A8464b14F0FE427fC231f.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"21281000000","locking_period":300,"voting_power":"106405000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"106405000000000000000000000000"}]},{"voter_id":"nftgol.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"200000000","locking_period":195,"voting_power":"650000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"23000000","locking_period":300,"voting_power":"115000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"465000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"70|mpDAO Grants #7 - June 2024-a5732a8195200f4203a633cea3dc154b696ed628d2ed4a322f214849f2247307","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"78|mpDAO Grants #7 - June 2024-irvingzcc.near","voting_power":"100000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"73|mpDAO Grants #7 - June 2024-mrcoingdl.near","voting_power":"100000000000000000000000000"}]},{"voter_id":"happypenguin.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"261734447600","locking_period":300,"voting_power":"1308672238000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1308672238000000000000000000000"}]},{"voter_id":"7ed4c34d7903e97120fc95415f4c68b61d4594ed6108bbacd73eaaae96eccb62","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000","locking_period":300,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"480000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"10000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"114|mpDAO Grants #7 - June 2024-aksanova33.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"9656a3cd3da0d65563dce927c9c184808a7c9d7d077d199c999ebe65ef526af9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"667000000","locking_period":90,"voting_power":"1000500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"633000000","locking_period":90,"voting_power":"949500000000000000000000000","unlocking_started_at":1726063035006,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"500000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"114|mpDAO Grants #7 - June 2024-aksanova33.near","voting_power":"200000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"112|mpDAO Grants #7 - June 2024-hoangthien1109.near","voting_power":"200000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"200000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"200000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"66|mpDAO Grants #7 - June 2024-figitalclub.near","voting_power":"200000000000000000000000000"}]},{"voter_id":"5d0680d19b8b51114209be84e5545049fd39055736a470e69edc8b1b45530758","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1152654900","locking_period":300,"voting_power":"5763274500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5760774500000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"110|mpDAO Grants #7 - June 2024-a0824e3043025dbc552529f9fefcde06b5c43c0ebad5d595d1f3343153932b88","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"80|mpDAO Grants #7 - June 2024-coineasydao.near","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"500000000000000000000000"}]},{"voter_id":"0x18869e75ADdB335FeFDD5aaE02d98326EABCC6d9.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"24296000000","locking_period":300,"voting_power":"121480000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"121480000000000000000000000000"}]},{"voter_id":"0xCdDE67a6E1666a1d56BaB73DCed9bEA132895Ba9.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1390000000","locking_period":120,"voting_power":"2780000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1159000000","locking_period":300,"voting_power":"5795000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"8575000000000000000000000000"}]},{"voter_id":"67fe33da5d926910ec480377ea76d119efb39486e779db0735cdaaba11ec862b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":225,"voting_power":"11250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9750000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"88|mpDAO Grants #7 - June 2024-100diasder.near","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"69|mpDAO Grants #7 - June 2024-odisea.near","voting_power":"500000000000000000000000"}]},{"voter_id":"37e9df77934c86417c090e62bcc174730061cb3fc1fa2eadfad96709ebc1c7ac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9200000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"84|mpDAO Grants #7 - June 2024-613d92d562c428aa478083dc0be04321ef76d48d836610f63cf9948122d4b1cd","voting_power":"300000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"500000000000000000000000"}]},{"voter_id":"f3e4b457a4f0dc8b7130f347b90370f2da12174c92c8e2c2ff72d3d9a07e202f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9400000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"82|mpDAO Grants #7 - June 2024-2f267735d0046f372f1cb6cb6f6c6201bd66b0c3cdf03a4f5ad9fc3877000cbc","voting_power":"500000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"97|mpDAO Grants #7 - June 2024-solowcripto.near","voting_power":"100000000000000000000000"}]},{"voter_id":"yamadori.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"42637421300","locking_period":300,"voting_power":"213187106500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"100100106500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"113087000000000000000000000000"}]},{"voter_id":"rogservin.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"669000000","locking_period":300,"voting_power":"3345000000000000000000000000","unlocking_started_at":1718692127142,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"0xEe1140ab647Bf0bD4Aa9603376Ed95a969243096.evmp.near","balance_in_contract":"0","locking_positions":[],"voting_power":"0","vote_positions":[]},{"voter_id":"happypenguin1.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"245260656500","locking_period":300,"voting_power":"1226303282500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1226303282500000000000000000000"}]},{"voter_id":"cybersecurity_rok.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40235154800","locking_period":300,"voting_power":"201175774000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"201175774000000000000000000000"}]},{"voter_id":"0x55b8e2c4AE5951D1A8e77d0E513a6E598Ee0bE86.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"23323000000","locking_period":300,"voting_power":"116615000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"epic.poolv1.near","voting_power":"116615000000000000000000000000"}]},{"voter_id":"venomity268.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"458730000","locking_period":300,"voting_power":"2293650000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"2293650000000000000000000000"}]},{"voter_id":"0x837667C7C5cB49609ed5BFA5d3B2f1bbae81d8bA.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2263000000","locking_period":120,"voting_power":"4526000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2742000000","locking_period":300,"voting_power":"13710000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"18236000000000000000000000000"}]},{"voter_id":"takeshi7.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2229447900","locking_period":300,"voting_power":"11147239500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"11147239500000000000000000000"}]},{"voter_id":"bradrussell.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8870168900","locking_period":90,"voting_power":"13305253350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"67|mpDAO Grants #7 - June 2024-baryo01.near","voting_power":"13305253300000000000000000000"}]},{"voter_id":"0xD2F5090E322E1Ef6f4F0699Bbaf5b68992eAa330.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1173000000","locking_period":30,"voting_power":"586500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"893000000","locking_period":300,"voting_power":"4465000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"68|mpDAO Grants #7 - June 2024-3sblockchain.near","voting_power":"5051500000000000000000000000"}]},{"voter_id":"0x21F713f686fcF7b9ab4D2f144eCe611A97b15f41.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6551000000","locking_period":300,"voting_power":"32755000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"65|mpDAO Grants #7 - June 2024-auroraafrica.near","voting_power":"32755000000000000000000000000"}]},{"voter_id":"happypenguin2.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100824695000","locking_period":300,"voting_power":"504123475000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"504123475000000000000000000000"}]},{"voter_id":"0xF42Cd128cDc69bC8d1e7eacC413342064Ce07121.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1200000000","locking_period":300,"voting_power":"6000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"68|mpDAO Grants #7 - June 2024-3sblockchain.near","voting_power":"6000000000000000000000000000"}]},{"voter_id":"0xAa6f8999a3675d755d471b0c8Be8735453abE7C1.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1200000000","locking_period":300,"voting_power":"6000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"68|mpDAO Grants #7 - June 2024-3sblockchain.near","voting_power":"6000000000000000000000000000"}]},{"voter_id":"fionng.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000","locking_period":90,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"0xfC86655494C7B2A9bDFC014B6819A8Ed6D31fc76.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5114000000","locking_period":300,"voting_power":"25570000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"25570000000000000000000000000"}]},{"voter_id":"funkycarioca.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"125461937590","locking_period":300,"voting_power":"627309687950000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"580505508500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"46804179400000000000000000000"}]},{"voter_id":"0xe668da86f28f94F36aEb1a21F1f5fBDC681233E5.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1570000000","locking_period":300,"voting_power":"7850000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"850000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"106|mpDAO Grants #7 - June 2024-ludium.near","voting_power":"2000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"105|mpDAO Grants #7 - June 2024-marcelorefi.near","voting_power":"2000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"99|mpDAO Grants #7 - June 2024-venomity.near","voting_power":"1000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"2000000000000000000000000000"}]},{"voter_id":"0xB1ea931b49e6671f2CdC67e5704B763a244Dee2d.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"324000000","locking_period":120,"voting_power":"648000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"250000000","locking_period":210,"voting_power":"875000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"1523000000000000000000000000"}]},{"voter_id":"15eb7941392955eb2b0a28fc9bdafcd94c8d7526b140005dbaac8be965a1d204","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1911315010","locking_period":300,"voting_power":"9556575050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"450000050000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"polkachu.poolv1.near","voting_power":"5756042500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"3350532500000000000000000000"}]},{"voter_id":"ledelegated.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000000000","locking_period":120,"voting_power":"4000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"210000000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"76|mpDAO Grants #7 - June 2024-latambuilder.near","voting_power":"1200000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"108|mpDAO Grants #7 - June 2024-553a5330546aa39850aa0e972bc1bec1c7018575ba141fa6f0a379cf28d46d16","voting_power":"700000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"77|mpDAO Grants #7 - June 2024-1724cbdb130e5b0f9bd750f666f4fe00581489fcc294dca4ed055c90c76808fc","voting_power":"350000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"94|mpDAO Grants #7 - June 2024-cryptopr3neur.near","voting_power":"300000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"92|mpDAO Grants #7 - June 2024-musicaw3.near","voting_power":"80000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"95|mpDAO Grants #7 - June 2024-edfernandezg.near","voting_power":"120000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"71|mpDAO Grants #7 - June 2024-anarchychains.near","voting_power":"100000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"98|mpDAO Grants #7 - June 2024-fdea0a6bcd48da25f783feb9b66627698cb00a7b34f064eb3ff59e3367b625de","voting_power":"420000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"83|mpDAO Grants #7 - June 2024-733770575b5ba41692fe56bfeadb98cf0f46492dfa608027ea6e0cc8cdf832be","voting_power":"450000000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"85|mpDAO Grants #7 - June 2024-fpsgames.near","voting_power":"70000000000000000000000000000"}]},{"voter_id":"hannahh.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"237185100","locking_period":300,"voting_power":"1185925500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"127|mpDAO Grants #8 - August 2024-venomity.near","voting_power":"1185925500000000000000000000"}]},{"voter_id":"0xC1a7E732413D919333d6C9c42b02E29deD5c84E6.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"541000000","locking_period":300,"voting_power":"2705000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"94|mpDAO Grants #7 - June 2024-cryptopr3neur.near","voting_power":"2705000000000000000000000000"}]},{"voter_id":"lele.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2465311826400","locking_period":90,"voting_power":"3697967739600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3697967739600000000000000000000","vote_positions":[]},{"voter_id":"0x5504E7AeA0cf0582f7db6fD9C810f3810425a6c2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2438000000","locking_period":300,"voting_power":"12190000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nacioncrypto-parceros.poolv1.near","voting_power":"6000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"6190000000000000000000000000"}]},{"voter_id":"solari.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"250000000000","locking_period":300,"voting_power":"1250000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"idtcn4.poolv1.near","voting_power":"1250000000000000000000000000000"}]},{"voter_id":"ea4ddb1c9a3e1a65438b5cd1069f8b9d324601d9f80cc207fd21aee433a129d4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"200000000000","locking_period":300,"voting_power":"1000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_01.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearvietnamhub.poolv1.near","voting_power":"200000000000000000000000000000"}]},{"voter_id":"misfitsmedia.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1750782461340","locking_period":300,"voting_power":"8753912306700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"2500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"dexagon.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"308218130100000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"coinpayu.poolv1.near","voting_power":"2000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakely_io.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"shurik.poolv1.near","voting_power":"144011912400000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"oharanodes.poolv1.near","voting_power":"500000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"spectrum.poolv1.near","voting_power":"589967769000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"711714495200000000000000000000"}]},{"voter_id":"68cd5e5436d2013eb95aeadf595d889a0ae79ea00a86996d64a3ca8f397275da","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":195,"voting_power":"13000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[]},{"voter_id":"0x34a9E0fc6dBBd4144dFEB3b6895900f519436E06.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"624000000","locking_period":120,"voting_power":"1248000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"451000000","locking_period":210,"voting_power":"1578500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"2826500000000000000000000000"}]},{"voter_id":"fedb6b1f672872f7d1aab1ea9dd4a6c1b7781c9f23645ded83307acd071e2a7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"0xCeFF651357EAC55845e3F33a5623e23C5Df8BB47.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000000","locking_period":300,"voting_power":"2500000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"1000000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1500000000000000000000000000000"}]},{"voter_id":"0x0dA891f618e773f3b765bcE938c8DB1c55D2A588.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"90782000000","locking_period":300,"voting_power":"453910000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"453910000000000000000000000000"}]},{"voter_id":"cquang.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"18429890800","locking_period":60,"voting_power":"18429890800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"1720462600","locking_period":90,"voting_power":"2580693900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"21010584700000000000000000000","vote_positions":[]},{"voter_id":"0xEd88424737639e3c891F3f2357E82f33676d98C1.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12315000000","locking_period":300,"voting_power":"61575000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"61575000000000000000000000000"}]},{"voter_id":"27904ea9d2b34fd4cc2a7397b0331ee42526fcb368dd34e2e6a9498e3d8b20cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"30811635900","locking_period":285,"voting_power":"146355270525000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"146355270525000000000000000000","vote_positions":[]},{"voter_id":"ac426054401e2565ad5083b43a1f832e02701e86a83a586d57581bef4ac9a67e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"0x8c7b8474dC370703Ac10D95d91e8f4D26ce41797.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000000","locking_period":300,"voting_power":"500000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"200000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"iamcryptobro.poolv1.near","voting_power":"200000000000000000000000000000"}]},{"voter_id":"palomaetienne.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"667879810","locking_period":120,"voting_power":"1335759620000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"1335759600000000000000000000"}]},{"voter_id":"meta-pulgas.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"174540033280","locking_period":300,"voting_power":"872700166400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"10843520810","locking_period":300,"voting_power":"54217604050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"926917770400000000000000000000"}]},{"voter_id":"kenrou-it.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"55008403300","locking_period":300,"voting_power":"275042016500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"275042016500000000000000000000"}]},{"voter_id":"345329086d811eb171962ecfcfca5e95752bc5d21d1fbb90f6a2242130d8aa1c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1931646300","locking_period":120,"voting_power":"3863292600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"3863292600000000000000000000"}]},{"voter_id":"vinc.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"75590282400","locking_period":300,"voting_power":"377951412000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"377951412000000000000000000000"}]},{"voter_id":"mizu.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":285,"voting_power":"9500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"43927100","locking_period":300,"voting_power":"219635500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"229135500000000000000000000","vote_positions":[]},{"voter_id":"babami.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"305751800","locking_period":60,"voting_power":"305751800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"731186000","locking_period":90,"voting_power":"1096779000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1402530800000000000000000000","vote_positions":[]},{"voter_id":"lucasdeprueba.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1121063700","locking_period":300,"voting_power":"5605318500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"5605318500000000000000000000"}]},{"voter_id":"hongeda.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"17000000000","locking_period":300,"voting_power":"85000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"85000000000000000000000000000"}]},{"voter_id":"gridmanservo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"88963850000","locking_period":120,"voting_power":"177927700000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"177927700000000000000000000000"}]},{"voter_id":"0x71610bC1aCF75d39fCf962076D97bE17cd7105Ac.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1938000000","locking_period":300,"voting_power":"9690000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9690000000000000000000000000","vote_positions":[]},{"voter_id":"millyrodriguez.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"446738100","locking_period":120,"voting_power":"893476200000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"353551270","locking_period":300,"voting_power":"1767756350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"2661232500000000000000000000"}]},{"voter_id":"0xfc10B4f288Dfcc996EB819021934536095a7Ce90.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"725000000","locking_period":300,"voting_power":"3625000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"3625000000000000000000000000"}]},{"voter_id":"mp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"105798270600","locking_period":180,"voting_power":"317394811800000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4831000000","locking_period":90,"voting_power":"7246500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"profit.poolv1.near","voting_power":"150000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alpha-stake.poolv1.near","voting_power":"100000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"avado.poolv1.near","voting_power":"74631311800000000000000000000"}]},{"voter_id":"bef43bd27b338103ac85d2b0387fe2412306b9800877e66331e3095f36bb5b77","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"151929000000","locking_period":120,"voting_power":"303858000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"303858000000000000000000000000","vote_positions":[]},{"voter_id":"0x137c265E744687bdBC897FF288d2F94C42b62387.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5658000000","locking_period":300,"voting_power":"28290000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"28290000000000000000000000000"}]},{"voter_id":"c11e452e9d223ada5937b4e867a992fbff3dbc8113a3bef712ff1f84e707d052","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"23000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"cristian_nacional.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"41000000","locking_period":30,"voting_power":"20500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":2,"amount":"50000000","locking_period":90,"voting_power":"75000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2595500000000000000000000000"}]},{"voter_id":"juanes.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"48000000","locking_period":60,"voting_power":"48000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2548000000000000000000000000"}]},{"voter_id":"blntmain.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3043797600","locking_period":90,"voting_power":"4565696400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"210000000","locking_period":30,"voting_power":"105000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"200000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"blntmain.poolv1.near","voting_power":"4470696400000000000000000000"}]},{"voter_id":"angie_pao.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":300,"voting_power":"2500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"41000000","locking_period":30,"voting_power":"20500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2520500000000000000000000000"}]},{"voter_id":"0xa2d237ecF8F29689Bf86d72f97beBe9EF85220A8.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"34643000000","locking_period":300,"voting_power":"173215000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"173215000000000000000000000000"}]},{"voter_id":"3e4cee760d5da969b8ec3f4fb9eb8f49a0b116a23ba6b34ee58ed5fd2825c063","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"589000000","locking_period":300,"voting_power":"2945000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"920000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"2000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"25000000000000000000000000"}]},{"voter_id":"0xA802EedBa8C8C9959cC7cb47CA536a06D485DcfB.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1506000000","locking_period":300,"voting_power":"7530000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"7530000000000000000000000000"}]},{"voter_id":"0x1497848AAF8F20d7F4e32501FF3D240e388Ba235.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"91065000000","locking_period":300,"voting_power":"455325000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"142070000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"313255000000000000000000000000"}]},{"voter_id":"27d9ecdd62122688ad5f957b75b7e49d534a09990ef4cc51a792bff893df5732","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2869ed8b3674730dd29cdf01fdf29005c85d8adce6b7cbd8b958f49de445a6b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6e57fb53931fe3b39af8c654587c1336a9a1e421edb6198c829ed61102ff125a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"5439daea621f0c5a5f845d19e779463934590b4f41b683f1f03e4ea03222006f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"20000000000000000000000000"}]},{"voter_id":"40a6d21456433e6c270130d72437460019245779582d477794f1bdd57ecb6a2c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"34000000","locking_period":300,"voting_power":"170000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"119|mpDAO Grants #8 - August 2024-wuipod.near","voting_power":"5000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"50000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"50000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"50000000000000000000000000"}]},{"voter_id":"080e6108d06914a004453c4f849897417cd87579688d316ac53e3c46a3a0197b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"551a3a5188be41e7c27af497a6b786f6af33ba43c58e9106a68fe99134f65fca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"dc046584136b3be4de731441a95c2a602c25cf006f37b783e340b916a597994b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"79f4a7a847a95acb0ee11959852bb76d30d650b878c96a136688e8aecd1b83d5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bf80de337d4ab1660da7b7f4b843df1380e166fb923d175a18d372263fd09280","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ce231ad89a500d2641cc4b71ae6400a4a9522bf263b3c11900a26073cc126f29","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"4f085a61ce00d684798f1e83d0e438e9f1be98622b33f1db512566c554268663","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"871d7c902ab362e6c777a3fe7b702b0077b174a404c13d6e551a47b0acf570a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6ec6bc9dc928b72b4363fb7cc5c3abe92f810a3b612dcc457d13ee696a78897c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"3000000000000000000000000"}]},{"voter_id":"ccd9f3cfad712e6f5fddc0a7c8935be20083824a62f780a49a1802f8d8ca0358","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4e283f659382e0611553695153424adb66e6222e0ee5a024ed98e8962a20900b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"465d7752c059302ce8eb9ec94c0141d650e072ad20fa6eeb304841b1d3693a0e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"8d89586d5043e09131c2536b0ea2624e6be60e501d31042d77810e3736cbfb2f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9b62c473cf563f33409cd8de6c95a7aa50e36fab9ee3ae665c06acb4e3d3ed41","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"82c358635147b474d097f8c8af8f88d1ec07fa7fc300ca461004e0869ccad68f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"145f614111e81b7e43cd133599d63cd5b2559a15b435de66fb14cd266945a29f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"724d617b38099343899ce0e774ca65a8161d1df6d64ab41c689f618a881c7c2e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"37488f54cd9af0e9688ce56e13f026197437402ba0038cd5c929d1f2f1e15573","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"27c783b47b0155afceec13739ff7f48c25fa052d94dac9d29905a81c97641187","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c9e350fd02b7f51b3789fc142896ba8f59732ea7c4303e3f5ad38377f9b042c1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"ddb1700afe3bd55f34f6daf7b60e3c17946eab8e6d165ef72342c8fcb83ed48c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"acc95270fb0aca46d0e43a8e8b67d8542aa07a9ae4c960e0e22142a58db7039a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":240,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"727175ee48957b652c4eb9e1f3386e5ae4b1e04fb90a920922741364216336b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3aa31f123dbcd4623c2242403d92da66549f137cf36076ae846604483d914f70","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"509eb55b76a82eac23605481aa402bc9203d6a44c378a13bde10b6639b43a50e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d04e08aac303494787086c069d5610cb473d1f89085a4fe801378fed9fde9837","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"56e80825d408c2c9dda78bf1f02be638678b65d672752e8c9716d2f40520d983","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d6254335ff5aebd172073d28a1328373f2a55c8cd7e3b1e6a4db00cfceeeab43","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7b5b78c071c8f8381752135e3aab8f7db9d000224e6bfba092a0d6a4265f99ff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"15c3596cf7391ccc414095e6c6275fd1df3b21b705d8c9b3ea9d86a2d74df757","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"45eaf934e0497dfdf044426aba4ea2a71698747054b0026705eae32ae08fba82","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"30e156790e33afc94d79e5a011a86fe321df75b290cebd41128d9475ea3a798e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c4a6a8fbc679ba0178931f874f6c8889a15293715e62302adc82a5056fe56c52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4df8cdf0c996c60267782f1ea4f715b4457a0e255b64444b7667c5802e3a15e4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9ad0acfa09155485e83b7ca2fdff74778a5213f3a1fc458ac124006730df5288","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4c96376bd93c70a029006ad678dbd0bd23c9581c0826b50f49e18355de6a4624","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d8db0f4398c25971a15ed897df6952ac56ef7f1696e171b10f0a74b3abd52c63","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f0e6a55316073c638b8fba4768aa10a44569be63afa4bad08f7900a98d1cefe7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"600f83be2d72903dcba587f44b9c23055ce944d028a7a2bb1b3477347ba1f9b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"28000000","locking_period":180,"voting_power":"84000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"92000000000000000000000000","vote_positions":[]},{"voter_id":"b9a6051bf04d420d3bead6eae864f9625738035440ef98d0416d24fce4b031c7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7dd3535ece7b1272c91009f1b64c146cca76c2a33d2bb45850ad6acd3cae641a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d150624872f14dc33334051fe65560eeb3bc29649dfcda1b4378b0ef2e5bd858","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"45ddf8c502c4d473262e48e15edca49764fe287ee7608f837adb9ac4451e6d54","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3d910f506564edec55243439dd320e1cccc9d974c8bbae1c3f84c1a624b4d0e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"d60246859abb16dfdfffa0f9a8853e151aaec35aaef63f6a5e779e2816f4ca02","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"40fb70571de5befb4288d1349e748bd4f66673eda7ebb2414edb0f08b420d048","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4cd3bd3730d2170fe8a08d8bcd8a8d65caa6e70d4dd9c21fac3140c0ceaae2e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"28f019d94ea494bc9b6fb6f80e1fc9b9e1f64cd3f01a818dcc110df215c12c51","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"df6af62cde824f1107570bc89c29e9ea77ed7cdc90946851e79c5cb97d319bd3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[]},{"voter_id":"b3c6d1641621bdf7d6c5dfb5ee79b6d4a7db01e0e3e00b51a0a8196b593e128c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cdb63c30bcfe7f8639be1f4450b54fb3c93dcc94d6fea4fac2a817c19beb6e9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"d58324802e2d1b14768c23cd97ee96b9d07f1998c949d7086b2d0bdbfa2f2044","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"126d31434377f67c1d9239c4ee08a9f4096b528a32d07fd3392c62ce57668347","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1e61b6b2f78fe3183e1d1e8c84653911175e1e4c7e513317522318595d3bbd41","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"879685fac06351252a970f57a7e97f080af01157a48c615b62773a58dedc8f21","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"21bb7e4a279588b15caf9f8f2c6830b0977d05b7f7eed520d9db4823ca8d75ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"51de4a543301f6a463d38254610d240d4250ef4839539247ed5a84dd4df6dda6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bf5ac95ee86e9cb3bb3ec2cdce2f4ee6960d0e04a768644127ed308d62093f53","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"adcaa3c13e241dfeadb60395804319a8eac4c8e1fc7a2f10af7044b56ae958a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[]},{"voter_id":"fdf274f98c681f001c70e3c7b1cc07c20b30540a45df4a494b8a570522bc0051","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"3d2c4a4b8008e32675af08c2291eb71ad0152368d0e9ac0388568590e488605e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a72465717e9a01592dba7417cf3286cf6ef5fe6f566e4b7618c9a325e832f87d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6de9ad273eb4cb144933aa96adfa2d13d2972f1f28149ebe8e576315314d33fc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4d247b55e9c962cc0a91e176cdc203cd29793fdb3ed9929d35c94dea04c46846","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"94b993f2e816c9df4498513dd1ad0c0d5b4faba22450a048508a90ed5b322033","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"635dcf64c3f388cc28f201f0638a1fd4902ff06b9d38a373fcbfe8d31876c073","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"69f814b94f50e0103dbe6188143f92ed677249129e2909081bdf9d350171ee92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9ac5f9a4826d44cab50a4445c1c5661e53354dc5c9bcc2ace728ec8bbb9fb0bf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"0628b0e0b88939a9f0dd7d8090ead5a44444249e1a02fbb21c9ee48bcc7ab589","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1deeef63c1a662bb80dd272e9b1592441177abcca0a014b98a59be9c3267f65e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"61e62c1d6a197fc777aa86cdbd7900090573bdb164a896220d676fe893de62f0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fb721bbd98b3314881c3221790ecb609b9fc9e0b03bcc3b9397bc661f7998d0a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"784c13604c4ee56b6f04b3ec81191669afb4feffb37addd54c0533999288a966","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5a7e4cd1c0807e267673c87c8c1aafa838238e081e5bc4fb3eb74b07157cbd48","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"032822c2df35abd5d64267ac3646fa5cbac384931ea8eb910d765c3de5f4ee68","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"64265e3de75158f61b9ef55c4706cfe915bb0e2f93612a28f68fca4ea3dbadda","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":210,"voting_power":"7000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[]},{"voter_id":"0aea9dae0b824a5b1ddffc82a085525e272b27604d7a23e86273638cb8bccaa9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"25ed418ec4f404b648014a7a67132185819133130e342a20270ea0bcb38423d0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"566333ac4be558d078caae7e9f03c229769ef622d526f64d0329288cf5ed3e64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5e615ab58113cb49ce825e9e8b03970612e7989d85241e03ebf176a8031dd03c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"29dc126b65387ce46a7c310c66e54de928eca68d5a5105df7e2407484e89ac21","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"fe3bd303add191acffeb5c9e68c6180bb73ef457011486327c40fbc210e1bd44","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"12a7d19c45d66f8ff9ce283472113db8cfba5b02c3d355f1bf99e0b9a9d3ae12","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bf4ed5f03e7250a191193e90ac855f2cd10d6cd6a4252ff85ca9b25a65e886a7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e3119c5646d65b71b93c2eefd0ee7e9c04ec4703230394f7cbd5180dd87bfe05","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"31e6e8ec45ba36b296f72527326b8bdc3dc4cb5e599f79aaef259c6157015510","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e09601a6d8a9aa54d386d828decea23618326f3f8d03e9c74777525d36b4dda9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c8a0f58e6cdd59193251586eef3516144b6ca5b6d65145d3ef51637b6fd3a7bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"41a6dbc79d3e999b6dd61446e7d6b960d8b91567d63144834bf5774cd755d14c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"64bc5f956a96ea17b4ad8afedecba9da9afda7e15e21911edd87a01208cf17cb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a41f794e9a12a4c7bb20e50f0e4a90a8f8954b005153e9d7f0155759c1c556e5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f87f4c6759572dc6791bc40f32f43d92be0b2e11898b0351fe489db89a21d941","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"17365cf3621fc7f67a1309721c23090f61b3a56055b25bb3ea46eacd5edf115f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d3d94792f5c9725ec20ef667ed79b6dc1d19375ebe4d8088b4934feee207c2ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d103904336958830b76f2f0be437c2d4012fdf9b7f7c0a820403fbe0b2df118a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":300,"voting_power":"40000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40000000000000000000000000","vote_positions":[]},{"voter_id":"b52167c20ba7e35decd8cb9bf835af742076f3388a1df1dbda2ef4f65d91bb7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"fadda6343ec00cb11386628439d387f14233b54e5bd6340e3b2b06d264899ec5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bf6b208fb2b64889932936f61be74f08d24d42ece2ba00183f3d76f10239b98b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c6348568406a803ef2a0cb907bc4d82f40e02d331718b0e7f56e0f7028010056","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6b4bde35f494dbe6b8b6592ed9347fefc612b354e0602ea18dc1a4b54fcda75e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6fc39b6b6d644b3eff8d85a9ba5f5cb299b52536687a362e04312f04a7ab0ee4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12000000","locking_period":300,"voting_power":"60000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"60000000000000000000000000","vote_positions":[]},{"voter_id":"bddd78c0d1c07bdb7b19af336de2dfeaae85989ccbaecf3b921fad2703ac0be7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"50108439a98338304ce9636d91076eebbc2c985edd8619afd37c34eede2d3655","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"821924554de471723ce40bcaa6cba15ade3457af1d7ed400ca4a087822fb8415","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"ffe787370c5814bfb5c8a4e8182ef6274c08a1c5a161f6db5fa5373af629d105","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3e51d1513014c4f048ba22f71c941155d63012062c10927e4d23418acb972fc6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9d1dceae30161bd8abb3003ebdc2a823f8a6d48f8f9992a914272be21303d6cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6424f1549815b2d8998adadf36298cfd9767521bd5041bafb23c8ba47af240dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ad7237013330c862c5f80b6dc35a1e8bd010a6e8c286af8f23373cd81cd52ad6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8d90c3359d7556f72ebb7947251370dbc77a2a09945baf7e1c1319520267b808","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1cdde58faf7ec2e95e218bfb5c595bdde431f86071703b36a5e281e6ffe0259a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5035c53bc6be0c600ecdcaf7b62dc6a94fa3d9aae167c2a66d40eec4e2044b24","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8acef5812594f20a9036819abfd6223e355addb393b0643788e046db02e973cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"13360750","locking_period":120,"voting_power":"26721500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"16721500000000000000000000"}]},{"voter_id":"8a7fc02d17e2be9903ebb4b399dafa0867e4b9577b76ee07cac347b291a1f037","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b07568896b9db361abb53fe69ed590d1150c1bf73af14ba5bf7e6c202c6a647b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b510ab77eb2855b4990bd717c81f4c33e09ad7b88a6c8bcf9b9c12107ff681f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"74b8e0a54db38de6119c53e9e69221cd6d1d0396d3753298f35274e7425f88e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d4e22847e7b0de6ffd12cea613c78dfb185e9b806cbe014b3535cedf93bf6b6d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4c5bad1a24ca50f884c33d95d167e4660e9df4d101c6bb8cf92f909ce91ae055","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a189bdbee8efcf4b24faf7e9a942e1de8b8a4cfcc87abf48bb9d20004a24231f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"342817a5eb29a5d21773b90702d8b2f91571fde1d81439248e62527f0ec45f11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cc84c5ae29a5235bb61a2901726b7577c32cf6852134e4756a52f279dd83f5b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":300,"voting_power":"40000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40000000000000000000000000","vote_positions":[]},{"voter_id":"25ddcc13fba3e88679f74a5b17ff996e7ec03ad7545f74bdd1636b2bfa453010","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"854c054401631858556be5083fcccf159d48edd8636cb3b79ba52c6057acadb3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1aacbea312d4197b0113e669a7b69075e6d03562402e7f7a504eda02740cbb56","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b0432ae83eaca897ef4bbdd0bfdef1d85dc663320eeba22650e2dd6f4826ea64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3fbc1a6f724071613889480d9b9ac30ea5c834f94c99195164b6cabf2b45843c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8ff8b942169412f9e77f9285b7987c6529c06680231488e254dc6c6324454468","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"241da5c7cdb1e6e1827a71b832d1c20f039e66a759152df2c7b7680a3965b7c7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"dc79e82f7b1f6a2d72ee59db296d0f2e71607a842f06f74bb2df7f6d2190a47a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"29725629aa22082c6bd7381f27c60bb5a5cde8256813f205455e5cbe417bbacc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c02f72947575dd33b24706f80a66e727361e805180b4a24de26db3c207b71437","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7f7c40fd9cd0ba99a934f6c6e0bddee27fb2f2719a3c81b6a1c892f6bd5f8244","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2228749d4a327c1b4ce63b4d2d1d900b2bad5e778858df27f3bcb46468b16914","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ace8c16bc981fdb2f9e8717ea84afb896b30cb33591e11aad48728fc512a7dd5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5c2262ab1caaa245a74e6eff6e0c7fe9dc11be9ffa83953df15983964a511d85","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1711058907148abafc17d6b2992c739029368bd60932e278f340eb8eafbf2d39","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cc4668f6804c5eab5cd0fbcda10f12eb2e7d42611e6f5bf4d8b211b7bc549710","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8dd96458ee039a98fc1bbc9dd1318c52d84fd0dda34af7955367dd4ac889a61c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2715d97fc5bd0357834e083bed8b734f55d38655df4267ccc39f088da4837c7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f36fb70ebe8b6ea903d31f27c740bd55efcba15f8514968d98b479ed408fd2e6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"126|mpDAO Grants #8 - August 2024-5e85fef96bd903eec08d1f59fabdc524d0ac13f44c7a1e08e43de9a01a62a11c","voting_power":"5000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"10000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"140|mpDAO Grants #8 - August 2024-latambuilder.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"709a48e088e2fce23feb548f488ed76c03d7e6fa20aaed60d866f4ec855b1f9b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"eb562c458208824d6931a32326e2d8aa02317aa44f3e6d352346d51ad172ae23","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"797d81ab560875d044dd525113c326482fff507ad3ef5c4cd88038841152a8a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b8f64970d4ad1dcb0a1b055841669e13d6c1f3415b0e97c92c0639142e45b7b6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"de8696cd0eac0f7d0967acef1a01bc8448f5c10e02a19143a6eff2c070f2e164","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b941bf312c36f1cab1f9772988683a0be8c2f71d793f0bad5267a09fb33a96a8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"992a128740698ea70168fa1c28e1697ff08c30705d8a03866ad9e18c9a42f2fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c5b42afda74918ccabccabcded6730385401035af8aca25946c036a1fbe40680","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"137|mpDAO Grants #8 - August 2024-criptolaplata.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"383e360624305faa2979807b6209d1c9720ae26ecabf23a97b989377516c3e92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6e5bf40489b5db158fcea08ed48b69d5e0f0671507c8cdf40864529d940bd422","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"838cb5c7a7e6a974fb7103275ffb5a6b365d9693e7d2ca079f4dcf4911d1dcfd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[]},{"voter_id":"95b69d7289a749ecf14bd875ed7b19b162aded7c928c2ad0e1ba575ac9bb2130","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b82bc37572d5c4df564aec9df527d5a77834f896efc349ef6ad53c073b4e2993","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e55e71330f336344cc8d335ab945bb8e7af0106e44128226f5218ea3a6a8f69b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a43d454b3e82939a66ded3afa2549fb9654ecac0bc7f82b21557aa52c162191e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a63976644520283c719398ae6c91d00ae1606d2327ad351dbe982fe9fdb0ad7a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"574acb8fc04b1c3ab8ef36f9ead301e43101aa9d7da813bf6b9bad88c1e3f345","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"76581563d230f311acdca2d86f66d2cde14c4f73f9ea7660f8e0512a115b0902","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8b0ebb3093af316bc9af21fa51e9701f662942b44723118260de7bbcf1a27fbb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1f4522ec761f02eb239eba4010fae0973d3f51f806aa625688323b6fbba68d76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"06c40f955f3a998c83036d60892c51b8005779455bf669ff811786cb51f4ba4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"pardaox.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"73691fa9beb8985f59e1f34c7f071f9eb4a2949a3631a2e4506042be629f9120","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d7fa30ab2d1c683f7305d7089ca68ceb47ba88efaf10e0669adc7b2b4c7bf1bf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ba827e34f20b3688d91bd7214c21f7f54544d3e822a5d6a2848e8e0d0db523b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"01e03e5d247fd1d5fec6126619c09ed61c95189cc9d96d43c061296e71f855e6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0ce15fbdc5e027780eab5616ee28e4054d47e02f82e7e64b8b14d59a5d39f256","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3ddc62d20361b09a3cb05ff8ecf98892f7c4463fd2e45effe9ee98e42dfc4ca5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":300,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"96556b0e7d0995242dc09d9131906e3477ff134de62d9d98ca92a5b4abdfea78","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0x55f8832e0012508a4e96dfE4FE0Ebd643a0EaDf0.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"551000000","locking_period":120,"voting_power":"1102000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"123|mpDAO Grants #8 - August 2024-palomaetienne.near","voting_power":"1102000000000000000000000000"}]},{"voter_id":"3633caacd5cbbbb750c21223879cfff43f38393d99afb12cec8c02d08858ac66","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"2126bdcdca813671f169f5d2f097f33943a9fb4fa4f623fd1ef121a742ee58a0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0e4d0d7b95f032275189a57bd986e5efa627c7df6ec218f3535fe2432419a3d5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"1fcddb18fe0a004a8d28c6f5e0bf852c21d38e12247ac1cab12bfbbb92e88ab9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a62ee42e81bc7d418c42c51f6e960261521c329247fd79d5eb924598011841fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"70c5332b463ac242414a50569d5497a90a3ec0aa6ea631880cb7ddd0cd0f2b0a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1c77b7bf771188383374724d3da5c76970a2a8264f2d57ecd07fa6e062c98361","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"1e3a0a7b1ac2a3dd8039f15ff082687ebdb6655be9fa9c73232da7db898c6ae0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a376a2a5c4d3c3f48c63b7f5b026bc49ef052ca6983f581caa9a5ae79c1958af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"374dfec531a1274099b31c8c0e95b97764fd74ba8547ddd25ec7fe2e248eedfc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f2c7eac39ea0e049d95489ecca8537a6d9a9250f9bcfa94b1ab1e46e3b3fe78a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"043cc3d656976dc43255fe05a3f096a3f5dcad9aebc505722a61dd34b09145c9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"b6c4a9ea99037c5016352f9e38d631b1c71fb7e7f85f68ee7b3795392027a8ca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"44004e9a37d73f76f4438b2d81551d5d84a5cee477152599f77ccc75a1ffe71a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":120,"voting_power":"16000000000000000000000000","unlocking_started_at":1730684117890,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"6d591903a7eb29a274db75bc836ed7238905564f2f159f037903fd7f4994b64d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"23af65399d4308b87bf7d025d0d754953981a3768262aaeefa4fc8599004655c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7c1f396dc92b40c9f147aba123f9dec892280d0c9c89de8aa85863150d637db7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a53abdc78a549af399d29c11b4d10f40e370c61ba57452e6e7e105e8d34074b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0e59b727b425fbd59713976d554c7a4b61e9dbc87a9258fc2dab03fa977f07de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"421af233e4cce7391ff3717a29865831bc7783b7c475c75db193e95b7cc77a11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"da6515ecacac46fff7ce58aeed698a716f5d2a77bfb6e01e378a5fe2a280645b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"0543ba6076365770e1fc112628b940339b7d0093e955b9226b022b5cf2061214","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"10ce1430d68b7d0734e6be6e98e6a7e0d77f4377a2881d04c47626f10165aafc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3d990fd769cede27d9997be214879a709264e83011e81263839ffac64cb48077","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cec1dc05651fc81257087923d5500aeb4fcf73bafc3196cc0a7b08ee8e89d9c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fc448953e31f51dc0830bc616f2870a2e8a8d1a30712b271d80eb1de83183029","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"518a04e19c9880ae8a8d23cf377eae98c4851841aac17381bb6e1bd05e681839","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8332370664fd8ab7553209000b17d0cfcf09f5c496c28c849e5fae0b356725c9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f02609877a26b5a6330a2425c723cc543fb6d1877a9787ab28c456df63428227","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0x87242d1A0C2A35260A500524903e2F7fD6DdBA8c.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"14200000000","locking_period":300,"voting_power":"71000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"124|mpDAO Grants #8 - August 2024-3abf2c0f4d5b0d69dbc36cce64266b8ac1d06d4ba6b61664a2ac43fc809ac2a2","voting_power":"71000000000000000000000000000"}]},{"voter_id":"a754373922d2facc53564d06d9c3ec90c6bfe65d3d0274a48b83160ead333cdc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bb8df3e665063dc3d45e04eaebf021fa44872d6932664c2711e77f361ea7e057","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e52e3366a5a3896cb50fdfc959eaaf2f4447fc65eaaa3e422d75a2d1e422b39b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"eda933687deca3faa0f7dc0b0f5abeaab0735863cdad0ced8505f40585c02a2c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5a5c1df8434f9d2efc1ce2f1ca954e80d19973c312751fd9408ddc7c974a99df","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e4f7e99949c97212d1147293d3ccdcd60007bb3b85b0a998dac8c1301996f8ec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4e61247339ec607616c8414642f48776ed371d1c0cca79cfe30caa3d1ae976e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c19653aaff75cc876d71dacd9265a439168fcaf88e028056a22745b1d34bb111","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"be1d942eb5906688e80ff883f6b8dab81691ecc82b86eaa1ddcdc8adc90d302e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d2edcc1a3e301374d81298e68bdbcf6a6ebfca2d9581c43c36bbb6a70c79bd11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bb7817cf789b1caf0d6b1316606e48b65b01dc64bf24cf68dcdc78a30a413791","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"773e24f843824e90b43e9bc5de9e3fca913c6f0854f1b5295e9b547f5416b836","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4fefc08752464cc32a8ba5b3a908f3ffbac02e71ab0393f499535d3c77ad89bb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c0af5ced052e574333cb1c8b75d2a4c5aa6cd3433012eb1871c74f34432276ce","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2be302dfaa09f5e7f20ff681d3ac30d594fc2f6fc7643a865d9c7087b71c9888","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e754183eb049b8af3659f4bc1cc57633e5a58affbba13ff8416095a714012480","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"5304e92a7d44c49ec6132f52a99d715af8a9af6b87293bfa686d20363804071d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"515e9eb55b737d743431d7f13b42b7ef97b4df3defd1a009f4e7aa1fcce40ca5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"58bb3e20e84e518d96276cdda6fdccac28797e265985c133dad3bcc1d32ad55a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b6c45bd64e530f369ff152e7274f05faa97626f9747e711cb264acff2dfce145","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"db490978dedc9591c5564de517d9016b7ec26d0b1e32598d112b3aed5d3c5d44","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"975a9a87e364f4c65795df909bfcca13a9001c511b1e23e66d73429e10b1af20","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1ca99bead19333c375e999bfbed7954e819e4b106c3020cb53f475cbde25f7de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"9b2603375f93a971f7058a12793802e626237bb9886ac8b090ebffb538b14e45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b1ae289e35121155c7d86de6e470795c9cb72f0c92ba1fcd845ef5959b76b636","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4a67d87de0d5ee202d8de5a0c47a2f970c28611d1952897941dac7396089cbf1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1ad70dd704941ad7ff7b0cba80f5ba38ad51fcb24ae86746c68a794560cf3ac1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"455673ba2b0f868c4929b35fcd0ffc43d9c385a506a93f1a4ca1ad85ffeed5c9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e41658db79aa59b0a68dea644337b87c3f0f415323b68e21e75c22b41aa94d1d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fbf5f11886b30c7748ef7e186edfa33e9d003cfd9f490568c6fb0f6448feb5b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"143|mpDAO Grants #8 - August 2024-coineasydao.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"eae77cf00f20f48c3222465f2657f4155b158bcff7ef1d4d2a4a59daae75ac10","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7b60e6eda63bb71517423f58677a9a0e30035644e8f6dd1347737bf6f089b816","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"0x9cCBbC021888Ad9F572F44681401172BeCcA76A4.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"36245000000","locking_period":120,"voting_power":"72490000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"72490000000000000000000000000","vote_positions":[]},{"voter_id":"6c6b881fa3991bfc090d07779d7af49071273ae1e0fb2b1274daf47994da4e8a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7dde0a27720b6490887c301e6cf4861cff61ec3a7a7460fae79473311c260e32","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cb1271c1b72c8e2d9e160d5d6e71b85749236da7433e120534ac1dcd11635c67","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d8c01019cfb4f94a4622a8e64c73445a5ffded6f0714aad1719894ec994b2a4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9134a58890b1749e417076929bb7aa95d8efd6ee8e8cc55069cd1782a8664130","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8d38a492a20136fad38b04e0f48cdb8a9873b7d174bd3a416db17905e91616d7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f0ec367be4c59c05f36862a736ffa8f2e1f7805851d56f547964de2fe8ab4cbb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"17d1e322e42abf9b49246b4e54f7fa387fe687f2aeea6fa74b1ca00cfe5d2464","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5bdca8fd83adc36b70d884688cf9dab89600f2e035925d51b3ea53a604dc7af6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"efdfecee9b258dcc2eb0ab156bd8c79926cf7db9a005b9548174f86481190e52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"20803c47d004df56673a08767e71a67977ccf66d01692ee9c9ede101456ee4c8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"4bc615441e3789fdaf2135e0ca6d6a1cad473e4f21492585494c0539a43d4b4c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"20f7e9405de349d839bd0c6c3322311c73eacf7054d9d8e54936878cd39f8b8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a8cf0344672f720c39ce905438977cf267efefc500a099b50fb7f1b6dde61684","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a94ed3d0e1e831cf125d9f5b8cdbf2ebfdff86e2c44ea63252ca1184b10c06c4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"aa3b8f24a65a7f6a06cae02b13c41f86066408af5b44c80adcc1e1c9ad089f4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"979aa7167db0b18d6a7121f2d64f0fae18041f47b0ae5992072b67542e6b7983","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bb8fdcd3a83a5ac151644faa98b40de05fd3736356f8e95c03c9262938c2df4b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"80472814c00e4c1cec25e4291907b42b0cf2e322c42e87a3bb30d921fec64b15","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cf24343ee9e23ebe2df6b9cc689071bfe3d6dcbc30444ec328b9743e3d111eed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"ec4560a9c20168089603407a0a1d43bf26f9f19f7f58434b40e5b91383cc42c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5fb73daaa28e215656c48f377e6bcf93325b3fe46381ed30784fe7fef676b1bf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"96691dd13f1680b71816e8e262641d435c82448197b619fc0e768bf92e100d79","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7f86464b01ee932eb8e07a6f92883e3b140121cffc4e63b4a579116f2649af2f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7ff81419e0ed0c08404b0ba6d9bd693a0c6af1524a33a4127f58fbf1d0db7943","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"669c9d8008e2ebf1ff07f73fc29d54654621089298a0f3ddc9b08868cb371d87","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f4b2594fb1cf64f2be6ce9e45cce847a09a8d4ea33609847de67018a19d6c23d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"358268f3de6da1258c6b9d82a09045cf6614da2c663330456df5a6f912997027","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"d0a2ed46a6b71f995ee0bc8eb24623f3f8c5b1b5ea0d9c32cf81fde09b802dbd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"0dbf3d872eb1f003da63139b7fcc9443493e2c3163186e3e626cc236c1ac1ca5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"79cad1aea61e8f4fbe575059ce60a71e093734063df4a3c073a453d9185ffe3c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"44bf54e4b457b65d27c38b0ffb53aa0ad649fd1fff2d18f4554651c0c4ff27de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"55d1d4e702b67edbf0ef197752231644b7ad849e78103fc554fb62528e28c900","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"91d3fd762c2e09e33ef11e812551b194e2c8f6ce37a9120ab1a8d5666885f64d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"33986323569c11499fdfffb13a0c53bc37174e2768e3ba98010ddedd113f2fc4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":300,"voting_power":"40000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40000000000000000000000000","vote_positions":[]},{"voter_id":"9c18bd8c0b09e6f80d0872ff6486ebb4ca4a78d1abe5a0b1d38f7abe3e8469fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"2ab37ab9c9bb48add2ff5ec2f232594a4010aef29169b396c916f7fb7ebc66b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b2d6c345e762593aee56a61eb484c24b814cea9155abd29da3e637c4ea8c30dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5a5dda72c012837e52f874b793069f457139fdc79cb68a026de58f93ff048f80","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8878773f86c030c087865143188340e90da331ac4b46d11fce473e582f5731c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"12846a9a54abe348f6893dd3f70b47162d38a5ecc76cbf77d0292c230fbc1b5b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"11a3698e7b282b744d26eb731823c20409ed94e129697dc0d8c361016832def4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"6192e5b18cec17f047785684f24fec02b594b428a1f7997da7b6f3257a8a31cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"78c8ad2e2bdf6de315a795344d87ddecd95e2f0ccf9c37eedfc7b6ee4e3055d5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6e9ae68249483db066789560625a9df789c0519ec9d5c36be6fec90832aa29c8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5131996c25ae37b776f3586518066ae5e7fd155c03ba1688377ff9ec63437c8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8be05f82f633b625c74ab1dd1c30fd48d032e27beb88fa87f1a0d9dda6d6e649","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"6646b541a73db187e5bf1f0eb03bcd0646a7fce3806f98dabd333cfde7ab5bcf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"caf2c794d5f4f1769a8a9323807c6ade109da32728d74ffb442b2b13c63cb7c7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"af9e86fb4939dad3eed7fb4462938f91f6328338ff0dece34b0f8d5eb94412db","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"61bb815b7c1e5d2937a4da895a0f9384e25b3e45d3d57683c222b2ea28e258de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a21d62077ceaa7599d11e8a2ac51a458b42ff00822037ea8bbc7623a1aebe50c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6048790c11c87428c132ee7e442294a91200cf08e6e8d9708d8227225314910e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3107fc720c0db450d94191df5706d6c9120044317653afb224170c01bf6f8ba5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"16e5109bc4cd76f332b86f44f64639084318cfabc08ccbc97dc87fb84d01fbb6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cfeb6d9bd318bbd6aea2d6279ed0d68a92c0bdd210a307275666121196e64324","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1cacffa12e96e8a6b8087dd5e665b08990538a4d5df300b30a70cf9524bb3ac6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"2936133c84f0c6cb7fe4a3d0099c3e9f2764936098b4cdaf478d9c8e5dd7e1ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"aa42ff1fc4c261045c9620532571e6428c9e63466c887569262cb509b6f97b02","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"139|mpDAO Grants #8 - August 2024-farmersleague.near","voting_power":"1000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"1000000000000000000000000"}]},{"voter_id":"07ecb2f7b9ea6c46ad0f1dac4a59c601ef2a024ff644fbd003f6e51b3ae84966","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"e07591162a404fbd6a0295669d2cbf2f94c4a3211eba1ca45a660c2575afe2a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"f6ddfa48d3e9d1307bee36aa33bb25ae61b772571dad90f9a089d0bd3a414360","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"268d56dde9508b3cfd525763ae40a634bba0e3cf58b92a2257ad9541b95a9cee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"92f435efe5cc03013714e2647afa8ad5044da3e62aef4d129171e30c4d835891","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":75,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[]},{"voter_id":"778109628ff45b88b8adca44f1534de6a7643ea3e95c54d29644750d427e9fa3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b6be88ce9708a743d073d3571ee08fa61fbbd33d84b184ce64c7d5bcc855d358","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8c8905aaf07c9ce9ed683f6a30128fdf2aa0688ae1b8836efebf49419d8b6949","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b32fb6fc7767f6e463645a7f96250b68cb6f01cea21e15d36e3b82a02b3f3ef1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a050001d022f25200ebb416b683cfa8c4e2b3b1432819758e8b43006e2b043dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5c12991cc12f19b2089e13beb6b43a03153abce39d9643bbd81089faeec69466","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3ca3a3770bf71111b61151fc8200a7867a9377cea73c552e3e4e4ae1364d518c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4f98c08af540f76c5a5e833ea73e4229829c3bb4f0f5dc04e43fda59c008f3ee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"39a6ff0cb51e454ba62ad44c44d6dcb03a93ad6eb5ed098dd09ff56c63f45552","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10000000","locking_period":120,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"5000000000000000000000000"}]},{"voter_id":"e5a08b8a611477602027fd8e70ea91321be5881d0de5549f4d06785da0d2c377","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f20f70e7ac9614f75eaf008bf88e14fcde36582aa54816f7de7fa60b890946de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"fb7c04ac2724f64957f4a700f94524955de56fabc1fd0b68323bd08f6aa7cc82","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"27ac911aabbce0844d426372dd5df761b3141a28d393a3a260d5c4d14043bb49","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"747156aec3a7985a93a65a45a9ba62fae52932854951838c982ba52dff86d390","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"556b27a404e10628bc3af098c6bf66d2413b972f7b422eaa2e7a648ed2482d9f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"db32917cca6785ff274a44c38214c6b193266348320571c53e033eb762a48e7a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"379877a63becc5ddbdd82b8f8dfa0b037cf70a708fe679977495e66aedafaad1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0211d26e71677fcdf1d5b30eca7f6a3a0c31ee6f159f2624ca2d7f03fb11459a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"080070b516a6fade0e96e369989d96aad40a0f24c3d5523fbc1d6d5ffff840f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"234b3a0336bf7e99c6b7af8341da3fa15d1925efea131a1e26c8e652e72959b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"977a7fdf4eb8cccb0d902dec1200b9d494ca4d29d7cdbebf8092eb7f1097d103","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"81bc19b1cebfd21359fedf226eca3a91e6a85dc7f5f1cb2e7f497ef0bc9da8bc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"29b355959d1ef5b3c2138bbdc7894800195092c91be06bb02da533a022cb6dde","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d40ffab2f4a449ad010114ed81768458c83c592381cb16b5b0282802aad29d06","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1db3e8c64a5cc8737f6320d199b61b44215f76bd8e4089559e9553d4eaeeafcb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"59576ef3e20c6c3b554bbafbb1519f181c9a999b3308247449b83a7df68864be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"611b89b28a3e150476dd47b4124998df9ddc97e148999ac7cd282839200818ed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"78efa2b0c35ce04ddeb90805c614588bcd267e49e034dcfc1945d57901d17764","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"02a306a560688888c650385e60d13610109f47cf8120b8ae3e4ce4fe645cada7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"28674c93aff46aafec7eccceb3b8ae0b5aeeaf24404314bbc9dcb8080a857f7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9c810841538ec569e6662d08e33881d3931b4ee327f7db6bcb9608308195d3b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"fvear.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"845806810","locking_period":300,"voting_power":"4229034050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2366198150000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"145|mpDAO Grants #8 - August 2024-d145ede2238bd103475bb8b173a2a48420d0a9de14917f0259d71cdfae77071c","voting_power":"76210900000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"365000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_00.poolv1.near","voting_power":"365000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"n0ok.poolv1.near","voting_power":"365000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"28000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"663625000000000000000000000"}]},{"voter_id":"968e4fb2fbd7a917fe611d0b238a72cfa438f2a1ef8eaf56d25e090abba5db45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4c550bea99297bd787489e1f731a19cf07cfeaa204801743f50418fe17838761","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"153cef0ad7f63df11217436b57f53f2f0402c068a52b66c2afe2890c4e1f1d8a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"1c60486b897a47e320bd87a734e71ab15acb786ebbcde3b24ac4944fbfc0d721","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e9d9d20a4d163fa8171186044a5928e9d691b91928f56d01a84477bcd5da85c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3aea087c2064b6106e5d4de20822994617076102176608981acc1c02b161d07c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"be06431e2053a0e1b556e394ef435a34e6a4e0927dc7fc6ff94e019ee9ab6840","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1bfb8eba15f02092bf9f60be2feffe8fdca0c5846fb8ca1afff40baf8a795fd0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16000000","locking_period":60,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"16000000000000000000000000"}]},{"voter_id":"04b4f734c6ca812b1de152f0820dc89c3e15c924b61434289441ab80ddaabeb6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1abab1dc88dbdc9ae81a3a2120cdccb299b35b9613cc1c3ecae49776aa61d235","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b46c33e740e216d1ca19c21866d9b65d56f4569d983215debd2e139b6ec684ec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"20e1695a53629313ec5d6b704e15381870a8c214557cbb3b8c5638ecbd055c49","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"eabf4545d6c7c2b2e1784f5c903a933cdafec44c328777caa7c62cb4ee5704f7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7ecba0546aa291bee1e488b8d8c0c7e97cbfc5b5003abc83de172d30aaa43a68","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3a1012c4b875ca3a928930fb30f81b98230651b2cce98b301c92a31e9cf7c219","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"23b8ad47a89a7e2bba27e8f8f120ed8f21242a7db7927f24cd32767ea5727010","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0c6bfdaa39018ee5628c8646648fa8f52da17b986ebbf25bcb0cb15258b383a8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cd0af57fd8b3d2ec4634d525d7d0652e7f445c02180d9e1c06c3a254e9805ec5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"988db74ac3e3b1e2b72ea5f9e0582029478d0207c17c0331935caba5a3ee72e8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"99fb84c8452dbc39f6d97857056362a53eed0f013ac37a97a8fa22a58213610c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"141d296201665fe1747b54df738ca06a301cd00b72c64169a04dcb705f275f67","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"aba3457fcc299e6598d06d158b16b06387d5d1faa6ee0a09f177ae66fdc792a1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"bd021d197c99f677e94cad2023e1dac554d7a9006df9b34cd970ae14ed6a0033","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5b4144f92912150fe37a77f2a289886e83ba835e1119dcb4154f7621f7c3ad79","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2a4945ac7a9776e606777efbe9f72664972bb5d7f4fe05ef500425a6f5a5e8cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"0d35ec724f873a0d65cd8e57007554bc89a055db33c1521881d6c0971f245abe","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2839e62b4dda8357de530da8615752ef2eae0c59ff5e9038e05264e72ccb3f60","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"20cc1aa754ed9fc403e86d10b2d73670f6ea73bc9acb325b97f71f5f84b1d330","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"add5195e2c922d21d6502dbfec0d618eaaabe272890c399ed4728b2e16feda13","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"aimery.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"81837703700","locking_period":300,"voting_power":"409188518500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"409188518500000000000000000000"}]},{"voter_id":"a30e630c9e462c3beade144ac47f0b45c733b848ab2c62585e428126ba486a4c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"218f4ccd583da5ce4cdda560852c26903502e60d4f6c10064109935ad41c3bea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"98a9ded118d6777f98d5babdb4f3fd67c98220aa6cb5d5e7e3fbecb904f540a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":285,"voting_power":"9500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9500000000000000000000000","vote_positions":[]},{"voter_id":"bbb9c20fe7481a2efc4db7027faed285adf993d8bde8caa8aaa127a59e2ddd7b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8ca3bd3de47b83d129ff38dc26a832bb4e027aa461a153734c6602d9fae09cef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"366180291e47db59ba84be4af67d2e03346778de2a32f6a867529fd271ece335","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"8c7f514bb54f5e022b2261ea399ba744c8727ae9bd7efb1fce95f18ef95ccbf9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b16a0fa8197ac396e38269129cf017be5f341684c255f8e87de67ca2b3725dcf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c0da3cba6ef59fc85b915981a8c2c38fb30cbcba8301b3157a5ba222ebd5e43f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8a26a6887c73ae1508eb2e7509e910a79aceca89c6136014cb730957ea5cbd7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"48fe90cf3ebcfbbcad415b6cabf8e3f9c291e7190e245b633dae2892bf7c4130","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"0f128555cb1271f815ae30af3b2f532640d360ef296f4fd3b7d47a8c228b50aa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"55e3c9bf100bb334b47172fd74a4f478eef9de7cbaf8565a44ee501a483cd822","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4fc8b84fb7e0d7b0836d49b09f9358a40d8dbca07ad4df8e70eea2cdd5252cf6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5ff3346a5a17b687beed2e6f08a3105fc81b3b5a4fc49da4a22861ed41c9b9d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"ec66eee5d0faa13f5aebfa48dae3750218ae745e881b04bfb67dbe983063fa16","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"0cf7fc15d35da02960c2443910832df3dde20feffb97c6cbb30bd7fc3fb2663b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"c42b6562743f5366f9a635ebdcda0bf99e864cb06bc1d88d220b29192e19d71c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"694cc6ed362b437b9288e2115a561e8755e098e638761ee4e5064420f2c0f4b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3946051d6ca5fb7284bf84c22566ab870bef80b41aeee58f2bc04f2c7b7e4886","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f38dbffabe264f9c7de5a25e9c6be72f0095010eeae1af6fdcc4cf9563fb8d5f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"048d834d145df1ea099bd19f00cae600237de148c086feae635b89cf9a7ea3f3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8820980971235ef2ad09c1223e50544510ae3550b5b4d86a208f78703cb63ff8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"343dc87aefe6c6178d94ecd6e06fab476ba9b3a3c0a34c59c1dccda126fac8cd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"28e30f6e7b6e311459aceb56873a62d1e04d0cf1ae8224b1b7aeabbfef6cfa5e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"2f172066f80b20617a293dc3abf2aebeaed2d31e64762e0fe216fbf011cc85fc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9a8adbb0b85fb9c68d5fa68d68d91016d3ac0c81061f46e98843f6233e3d287d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1cd1c03f7b6f8bf6b1a0e10757b7a66ee78ab4529020301a675db35aa83b28cd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c767b3ed24f4c72981a67c32c65ccf95e062a000f0f7046fce5629bc734b5763","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c420eefcac8e2f2e1bc4c134cd3da1e3f907f8baa35e2c0e38f2d9de802c96d5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6d26c0c644d5dd341fd080d99e927cd5224418d481b3a01619709c856672d2c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"336954a8db5b830e45ddd8f97d0fbab53307b15052e1d0a3c87580c1d67af4d2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a8cbfe144d53b0177b40169bbf99846519ab6048c993fc68e9af9e91e4e52dd4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bitju.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40000000000","locking_period":300,"voting_power":"200000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"200000000000000000000000000000","vote_positions":[]},{"voter_id":"5b388c6c0e5af7ea9b01ade25aa942759b27b3e9b279ad4977502d09aeab9165","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c6663fafb7a518edcf63c0b5512fe25841385701d6578ff15c263e6becb5817c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":285,"voting_power":"19000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[]},{"voter_id":"69f8b8969d75657e8cfa363dc97a35c656d614c9006a6b1449d7af19f613eb8a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"92b46b9b10895d44f8a559e145868b00bb0ce027ce390d5f2e4889fc677ed245","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2f19df2453be0ab186530b7b00c176e9083e89eb811d150d4ae6dbaaccd8e8a5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f1b3805a63b7f5a2e7b234b51a7cb96ef30eb075895ff1f7947226c1f036618b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a5f2e552bde4b802eaf7e8972b936ce97ee3a20e57452bb815f5fde179bba304","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"256c4adb705b28bc1b73931faa884eaf527f02e6d9f82dffd0b53f98d4a9139b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e9ef5dadacbe86c996bb2cb1c9b72cbe7b4f90be63dee07644b8d6f03966c800","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"af0a172be54657d9a41f2614b1c5411270de5ddfc5c27d4b400c1cca1044d52d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5bc6316b653d670c80c324acb5969987d1d79c3959a043dcab28e5b93e8755aa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0x8972Aebd5DBa64DF1E0321daA4914687e4E1e252.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8137000000","locking_period":300,"voting_power":"40685000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"132|mpDAO Grants #8 - August 2024-ramgor.near","voting_power":"40685000000000000000000000000"}]},{"voter_id":"a8141d13043454265ee7627936a1add21f7e66573bef308ab1a61c0945d66a9d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"9bee101fca2a9b72a49a8a631f31d4836a024d755e387f3ac36005c7db4b9742","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d6e29896e822a1ca617c2d8aba47c1a422ac94a992e0ce662bc952dd32c25893","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5974613f0cd93fcf396ea4f22d833f0373c75e706a4d422f633202de7e4bfa84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"14fad723702ebe71e528d07bb53c9fe5600d4387dac9ef7834dacd1349807359","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":300,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"5b54be3cf01244e0a028a62a50ba80e907cc2d553caea959bb91386946c4e117","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a377b09035d59f9dfc99c62a2857faa6d07ac33cb8ff5e5cc9d39d52295c0898","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"54032c64a147f7e46a4b321f19fbb4f9c9e612e7a96cdf750d5fa24282f75551","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6d1cac58fc1dd96120ded7fb0f873d1f639381b496e3b28c95b58e4a40101dbe","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dacmpool.poolv1.near","voting_power":"14000000000000000000000000"}]},{"voter_id":"52e3611537ddb25f53ac8cc9f7f73ea6fbb7668d368ed3b929913f54605a7461","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"715d77863f9f1f8e6bfd9dafa164dab8ca78df3fcca90cc858b1ae2e0764e20f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d3f4462cae39f6f6ff030dcfe155eaf6f336ac0249946de7c761e3b2582ad486","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"632b699c6ba3d9fdacabb203bea70e61470cc5fdf8e11b85ec3a0f03a1289ff1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"75ce3c238d9c8e9981bc047b387b0f418dc5aa71cc32a30a10df0b5ed52e90a1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c80d3dbf4a8d934ecf66a525c2769e95e55e4bae575cf7bdceabcea77a869b3f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"41cc33183eadc1a3dae72f083e44bb2e3249700210325ccd0f92323a933715a2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7b41a44ec99cd83e006d52799a01ec2efed57b96cce388c00826479f1ce32d11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"adb186f05024d8c5614e8fb99a21937b5ef2053646aab2825e3b3f68242759e9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"0xF71e509Bd6d79Ba5f5d58A94e995392751949b33.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"538000000","locking_period":300,"voting_power":"2690000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"2690000000000000000000000000"}]},{"voter_id":"7860e6d1a0e49df2b5650b95e81d6c4b3adeddbf3540c61c594277544b9d9c09","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"be607f3dd37e7c084fea48e4e3f54d4653fe704881628061ac9c3db1218e3b66","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8af72d04c28d2c91f984aa9f4285728ef775963fcfac5832d94fc1e64b41e016","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a374c862d2902185c977d3691d669110342efc35e91f39baa7cef4e8b66db6be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"502832de71252cf6cf727bbb1438d10d918e0aee05ff90fa40fda6468df6757b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"773a801799a1274339f3229f4bd3883a7e261f4424330cd198db33cd685b3579","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2038f436ef9570d7a7a03ff4a60c0a4eae453e87ccb1424de3eb46960f5281b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0d32dfbecdd6ca8dc622f39f68c7ab7fbe90c1ba92787a82348234b547dd3d44","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4aa9ccdc769e426fa7df278c66bde305abf0a4b5572925676a756a610f344c7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2fb647129ad58791b8eecca6df2e6803c357336f0c9d888a6659a1f3b2878617","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2c48d0e57d0b0a59d9a497e44a1b837346e00c9e74107a2a33b9e2a42fa0de34","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"370dcaa178b8242d9fb38559babeda641dba3631196a79eccb31a3e51a23d341","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7b9da3fd6a0ceeec24a9fc455e39b08cf137d0ef7474b5c5cf8f2523fc7cf455","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"26c2ed46d1e50444392c5a43eb75497273a8f20decf02e5829048bad3d3fb926","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e431e4b4c01e9cba290ee680004333d4633379355e4db59235d9f618817483b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e54377a28d67afd9efb6daeb0b51fc0f8ae930014f07fdcdac502829be012cb5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fae6c40b8d63a4cc53dec40ebb568315231a43cd9c497a71aa964eceba5dcc8f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"570fd0840ad6ba7b075a39b88a4db335c8ff53aca601f16031d12e9b3a33b9d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"cee75fae578ecc6c43f39738b4f7479e2d1515d8cf5dfa80d45326902ab20a7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"47e9ae9ce20301b91a859cbfa30d87a0a4ca0acedc036be350ce85467d26bc13","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"99ee941cf0726f217e2468bb00596de4709f5c3c1142a2f083c6dbd0d0484399","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6de31832994c462817b5d11456fb7975abc23dd00185e4bdc5ddc209bc567240","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8a49e79ae23c39d28f5411571d8ad2fc6b4e9eb5f69102902a2158042960d052","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"ee542bf6d4cda50ba458f6a7ac037458f54f76e4a6a69a5c70042b2881e7f626","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4b11e4b51e34d3b98bf70576b2f34615aadc7284b8f2602fc81c825876d8f894","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"609fbb460a525cdd54dbc02a2c2ee83337a13624a2550e233b2910ebf3d23dda","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d47890479963e93beddeec274c7e2af5728b25966bd1c480a55b469a29ab6774","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d481fb470c9068c96c8d63846aa3f6a2ff4a4ba7e9e00a89e74cfba52cd08701","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"37765a61d5f6d8b5e18cd0cac8152d5e13a54394f585d8c73c9f67bf4de9892d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"db7cd17d8ea22f47437ec0676c14cd8333614e09b888c2fb542e1a322469409b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"eaf331cc794e6a7dcc7df80bf4ed4d2d8d49f68359b5e0a05d47d1099f840701","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2156236d5579fcbea1f36e16ecd1e236c12952effe4104ac78860a51e1401a4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"aca1e649ce286eac2c96a37c3b815fa5722f029d9acfc8450bb521e68ca7482a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b61f5174bc0e4250aab40c5c80f392ae8f428c175c65fc163a9616fb60862ab7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d7bced7082eab16173fe70bbfb2ddcc3b5e040b759a75c90658334089c1d8f9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"56cf9ab5bf2fb8b587809bb6cbc75a812490bbfb500041db10f84e98fabd4205","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"743cfbcd1c2e69badd884df82cb4f33dfbbb54d42d6cdeab2c34c3833b270c1e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"iriska.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1509978000","locking_period":165,"voting_power":"4152439500000000000000000000","unlocking_started_at":1724422008133,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"57bdb5e1224e10c1ca3248c17cea691fb0f2af381f3205a61cdc267976e2774e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"10afdcbecc7d6799ff4001b88d58a98237bb1d7d6d0cbc6f9dcfd9138a929578","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7397737337822c24256ddea216ba16d3c1539332b3d6cfaf6cdc90410fa80d03","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"2574247693b8fa3954b199bf1032c94d448b7ed399a7788ce4df7cce7bec72f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a9d23cb480a38b5a23c7682e9e245a3d120f1e39050a002adb9d2f82d5c6dad2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"3721500c3dfd668f9e88270274b361868e4bbfe96bf8de63f0a4d04bfabca434","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f97b119641e1c57386247cb58a91d9562872a01b94aba9efa40b3d1e98f080ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1cafed7e66f4468277c66f24e29cf9332ae32aabdeaa906ee64214c537376e57","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"05aaa7e1174bde3c69d7be67000d6f9f0b2fbe9e5ef91f7b299af73aab15f793","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"67e59e26611aad2b7adc9abde443170cc6656f09f0c22f6cf0c58a57e6852642","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"119|mpDAO Grants #8 - August 2024-wuipod.near","voting_power":"2000000000000000000000000"},{"votable_address":"initiatives","votable_object_id":"131|mpDAO Grants #8 - August 2024-dollarinveste.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"c683730ce3e1c18dfff909f2d7cf98da0a560a4b2c954d937fd0550de3b9a429","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"abd4d6b1463b65d06fc31993c6ec4caec4956fddb2e55f3e636dd21b37833d0c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"4ae6de511d7f09aa0f21ba48fe193532f4ed96761ab4e2db5abdea536c7f767b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3cf07a62452f1efb6dbcd84c12497ab2ef1e14f73f579496bc4c877c114e4cca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a40fe9b4cae23bd42b0740c74145ac5a2074a2c7e6f11367458309cff80d50c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"47770f6bece15d9dab89881bdf5ec7f690cf93162a0de582633ec266535313ca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"f6b01c746da45122f9e15e53ce751be04224b85f6e1dc356ca99b6a5cf42267a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"832c0a931a9ece9318a843ed36d01698a5cc25e28901acda8eea64ca80e90c4c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0352a3f372f076cbcfe8e7feb3fa0e12b97c9ecbc7fee305a5fca2c35cb15aa4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"382725c5b68a51c29ad53b809b76b89e75506d344cd3f5d2dced433f05331afc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"464c7388db9c7e7f13bb8ffdf2d0b19cfc851b7043613170ff74be4362d46827","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12000000","locking_period":120,"voting_power":"24000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"initiatives","votable_object_id":"138|mpDAO Grants #8 - August 2024-fairnode4031.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"9d4ba8fa1fb2a9f0751e3eafc43e297fe2f02bd02a73530c2110d3c8defb2e58","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"43b1e08662df227161148d9ef496cff7272f4734a1814a69c20152bb06ab5a32","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"initiatives","votable_object_id":"122|mpDAO Grants #8 - August 2024-289e3d40eaaf7df42a14f468ad0ab0db5c1fe5b8a9c5141ae6ca31734481f46b","voting_power":"5000000000000000000000000"}]},{"voter_id":"1c16087c43f61a40873c673c20df1b30037d2f85bb63785e6ad3a73886383ffa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5798ea934692c8534f18a660efb5d631528ae9bf389dacde28f549391a2f7be7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"331b381bf154e8635c1094a8c36aab95481977d6cc466deffb3a162b0f39d367","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6412c3d502b574775039981ff044aee67d0bf12aaa0a51a7c2f9c45e8e1c8e64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fa71ae0e0943dedb7e152e61b92f1a52da3ec9334bf20aa53bab15e5b34b2be0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1edd67c6bf71c4f2fa845b775c7c7d5b1d90460169bdc414fc1818b5a9d12864","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f14611cbbaf993b982b2abc2a007b0cfc959b38ab33f75806c964aac94aa5cc5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"503357b2fca78b45effb090beb6c739ff1e0c4c6843a85be537cbc819fd7ad9b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bdc429f74fad89db2c7399d5cf5743134ca43024b6cf5cdd97d2aa019d55f201","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b6acc91ea27d18db1cb3d435ccbb9bc7d649cc41190ddd7f792b2b3cbae1c0f7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"e3e7978f4558f3496d2e955a5b69e91c3a7a8b47ed01e39a10d2bd3230785a26","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b1abe99021d300cda54c7002d8d1974e9e10d54507a5be1962b72871ef4d8cff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":120,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"74a541779df6025d9547e1234e0882234dec3ffbceb7bf4949430cc4bdd99e04","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5a74d3a12445735a328c7f3900a0b0c4c936e16bf2c2aa0790c112c7c0fdcc5b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"cccd57915ab1db1e2d57ce15bb8418e96e19eb77839dd3e839addfcab33305db","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"293177693d0471d225c99cf389693661023ac1b91267f67db618653262b96ff0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"384382ab388324e31c352e6bb75e86eb6a572c629bb774a0e44d6fee44ea5bf2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"64d39c354a9123e95d1e910a0f3ecda8b7c76832684d45a8738417dcf77e031d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"79256c94ca2bf7f044a9bc1ea261bfe791c7c2c15465781833769ae814509005","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"06f1130cbd4e1bc753f92bfcb911dde4903f7343190319ee9f74d0f726b07356","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"554247e1b4f1ae9ffa4fe5d5461fd1a5fb95a39ef40f9d5a79fe882fdf0db6e4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"654385e2daa3abe92030477089afd42d8282bc158b92298a53cb889278cb1319","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0d230d4d080e1129173e2882e9bc7eb81fd74120ac72821508f1fe49c3e307b2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7bb51238af71276fb11e438d11a3a8b2abf64af961012f543c16c2ad72448c4f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"144219d1230ab15c79965c94523b03dd0daed3fe7391590491b9b1f72debb34e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bf53c02015ee30aa29e758e9a39aff0748d1a339fc9eb8013d35740b86f3073b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"ca6651088f5c9df3fadac5ccbf66539dc4c2443ccffa275a698e25f2043755ee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":120,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"340cef1b18f3055485cbf8089af43ab9a17515aab3899f246f18c69f298366ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"af30fd317806b5509e9d46c88bf591ec9153012a0362b203b65cb2fdfb8ec5a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f26a01b5add812aeea6cfd858368da6bc91cbadea42b0a9da890ddad1aaae842","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e4dbeda7c7eeeb286d1de196ba1ccb7210dc3afb0079ffdcb7ee4e62d58f188b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a40e95af191420c80c6e8de486c3afc747bfa4b45eaaed12d80bfca7fc52c6a0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"f0f4dffd7a8ee7bca6a2767ff67d3f98727d95d7a9ac964228b4647c4a600f21","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"ef980e018c4a2cf396f7e2cb91e19ab63c7b44f01c4543982d94367e7db536da","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0989636324af45adc99bbe569c0aa23d99a1d02d45003435ad64f78da17b30d6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"071df7e320872934e07730567c0a3970a0fd66e1c4758594687890c17e24f16b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"20a575e5451c6338748efbe652fc48bd4361a836c7ce7214a5b23c7f8f498f4c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"944645dee4e875525ca0f7d9b9722f83d586c5188c54452309c29be832113212","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c00d210b3daa1081187c88259b1f0471c924ca0dfa3f0ee15c54c43258e6358c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"eb46a36c2bcd5dc0a23386118409dcfd7a60b9e11e2291f89d18ec25abca7968","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6d7db62592dfc8505377d26a58caba944cac21a23a5f8e2d4f16eca3fb4409f1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"eb5c7c9b5c0d63050d975a5437ca78c31a3c7d6e90e77d133681429917cd2bec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c5febb2767883864ff8f2a745edbd1848ae86fe7dc6bf5aded178e600c7b5900","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"82cd78de0a45695719fe9826d12a1935360f31d9166ee4c66d400b82d4ba2bbf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"729987990","locking_period":120,"voting_power":"1459975980000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"154525410","locking_period":180,"voting_power":"463576230000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"1923552200000000000000000000"}]},{"voter_id":"2a57f1fca1a177973201e3ffed1e09884add68e07ac9df182c3c94af377fb684","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b93ef028a5bf4f1eb25e2f451fdadb23d5650f7c90b5bb835dabc5dd01026da6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b23a9e7a99a5d53797b2fe085d2c41b05054745a3bdb8a24b68f85ed580a5a52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0xB2736156370F078c0242Caab55E83844DDA53DE1.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"10122000000","locking_period":120,"voting_power":"20244000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20244000000000000000000000000","vote_positions":[]},{"voter_id":"f408d5daa540dae1158e697d1fe31eca80b938d07db47a1de852a08332b65d93","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"34fbe29e924d8fa7228f6b7bab35ae0ebcfc3b174829028e573bda46dc4fa199","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12000000","locking_period":300,"voting_power":"60000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"60000000000000000000000000","vote_positions":[]},{"voter_id":"ad56d38ed67ce016eb2d4865faa4e9dd496b2ab459ddf3a08a3a611dc4fd9c5a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7b784e43a91d6756c091f5a3974dacd23b5703e8c0b66794a14c725d47a01104","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5df049b1d953a5fa9b931bea84866420d5da4b4da5e1b9b1ccca235aa8ca5fb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c383a2d14579cb90ade09603a56934f7a20b31d108054e01bbdc1d43396e2427","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5870b96b1feef73ca79970c99da18e686b55660b7ba6dfeece7ba58542389c84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"30f54129c99bebd808722860cf1b679e5f99577525e51f241300696fb87edc7a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"78dc524b3f3dbb3bed995bf98212bb825901b9a365788dd678176893b3e44a62","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"026e7baaaa100dff5ca1bacb6f6918920df407bc61df03f7732f5d72102582ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"69d91026e644c7d5fb8ff5909805e554b0383142fc2cc626523b5837de0e2781","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"7893fe2015c6e28f430f57c0050dd8d39a62343206e684501ffe7142fd472bc3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fec14113e1ba2b64e2afd3969ecdbfa8d066a6966b7b76ebe3cee959bd295a5b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"60e2fdab2508c7b41c9a9e1391668da9088143cee3cadff6d2a5992e669afa27","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"32d44416cf554d8699ec0ececc28476e10a858bc35f54805a5ad1f2bf0e29e24","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"276e924dfbeeb6ea6247c47b2ecc6366cbf1feb4891bea2155b51b79f19d5816","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4bb5a3bdba42b8e0a71a330e760b71aa7394ac502cfd48b1de1f3eda2cca6243","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"8c1db5e073dc0a7f73fff3c54d39406ff439c7bdc3328ecbf55f53c637d07268","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"54435b771a16dd10648b156f1024642a793311554eb5286360fb77a703fc5386","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"daaf9fc168bce14c4bce46781fb351bbecfd08585995df349f3493e2d7b1b194","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0xA2d177687edcFcd2F1a80b517D9F3c46926d7FC7.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1749000000","locking_period":300,"voting_power":"8745000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"8745000000000000000000000000"}]},{"voter_id":"a6771ea19a0ef3cdbe4dd2b4c78825b43fff8102f8bf212a636a8f53b921ffcc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3d0708206647a5cd587710c7a63cd14c5eb01e14fc008d5cf68c7346bdebe1f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"339662530","locking_period":300,"voting_power":"1698312650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"558358300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"1139954300000000000000000000"}]},{"voter_id":"84abb13bb0ff8cf06c7dfa6c34d932bc7657dba900dc8ebef62e0b9706e7f986","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d320fb5b63688bc7474fed76c4828f73ca562daf4a7498951beae886ec9c3327","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"54826bd1ceb4c72a334f6fdf4cff19cb0c712c4a92ffb5cbffd7a8ff18a01edc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"b46fcc01bb836354c4e919d45b01ab3361fcbd813cbb267c9fd0315ad8d129df","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c106ead3f018ee7d89874fdb8d0fa4abb48dfef27f6fd9fbdead0d32e7357f72","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"11eb159bdf4a6af2bba7de1c296de6fc4b42fe08cd480fbe241200388c236f98","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a4cc777e00c8d8651712d1efb70c5ed9fa11ca55fad63a3d53719c503b442f53","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"152b702780039983a80bf5ae0377ac048974d1168dd60f99633eddb607a874cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"98abbe7698fbb7ff95925debb9c441b103ddee49619b5732d139eb008ca6c2d3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"be4f59f8dabafb4c4b366c69538beab7880b176caf420c81fc2e054b4041aa82","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"f94a62226496fe75519d1122e74b5f35864afdc434f09fcf32368fea759b462d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6eac14dfcf7a73c3dab5221f692c74d8b24ae734dd4ce3bf5402d5f3c95e5b6a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"20fe170002ec97b1259a2db0a3615c83e9e34cab2b72ff66b9478a939f6fa3b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"709542d81f7e2f0071e71f5204f61b84b4553493848b71126a06bb991f2fd796","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"319d08cd2e6548ea0dc1ebbabe1887ed6a1fc633f3c02a1f1c90bc67e9b0e710","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ee89d38032daf1946243355676f7f8ec4193220b1e0686091433cd14be6a13f7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5df9df24ced21814c3f7a9a136c66f06e6d3f582f594e10ad11426c9a6596118","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8252858c2ccd4356f2e619631fadc5f5d98b730874c63350e9c481a58ceb53f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"dcc33483161f442c409b50b84660b8116ba9cd21b1e8d4694ac5d1b4491940f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"56d2aeba87c686ad37097f1de4871f76699c2cd293b05b8bf844c2e2bf695fb4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"88e7a1911449255e38fcb03746d589d2b1c2f67225ee5005ac92065a9b30dc2c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c32b56e7ecd9d8abd937824157cdb82920de7120ce472e7b2e403820886f1fa5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"p_maksymilian.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"yourdad.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1800000000000","locking_period":300,"voting_power":"9000000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2250000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"6750000000000000000000000000000"}]},{"voter_id":"pablo158.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"200000502800","locking_period":300,"voting_power":"1000002514000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"31000000","locking_period":300,"voting_power":"155000000000000000000000000","unlocking_started_at":1725148829614,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"250002514000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"750000000000000000000000000000"}]},{"voter_id":"gastar.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"270825909200","locking_period":180,"voting_power":"812477727600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"812477727600000000000000000000"}]},{"voter_id":"f2ec587f5ee2c7fcfa255e41b2e6f2f67c996e9e4c3552f4f61f273039499e27","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"604b24c000b2c1ab3e89ba373cb45413a35f911d7f4064b9b965aa30ec6c3307","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"043248a41c0248dfd6559064e7d1df1336a85ff5576fb33b064f3c1c5d0092ac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"55658976300","locking_period":225,"voting_power":"208721161125000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"208721161125000000000000000000","vote_positions":[]},{"voter_id":"0a66750b293fb20dd177700333717798007ad7e267819a22dd4eff151dd940f9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"blazing.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0x8b88064DB1595E37fdEd18102dFf6933A8b6EE90.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"56000000","locking_period":300,"voting_power":"280000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"280000000000000000000000000","vote_positions":[]},{"voter_id":"7bf0c76a85ec08feb38270492a0b056abb60cd407cd4ba1a4a2d554657d8d47a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"31c032af32864627db6ff2892e29bcc3881ed9a39ee9d92c4bfbc0f92cd9e588","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"b2b0bc9ebda197b4528db733c35829ef06c2c348e69145d1d93a68016cd68edd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"876bad3522a08c12b6fb8c87f9dd1ff47497847a7dbe06136e98fd81b7607cdb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"65a5ef266ef7c7afbacac621204efa5c0cc45bc2adf1b806731248f401595356","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"6cb55a3167c07eb7efca2fbf6831dcc45ac735c1961a7c0ca97f351a7bb591ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"0c17bb6326e97bc7cdc106a80b62b999aafd393d8428b00dc6b524d2b13006ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":285,"voting_power":"19000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[]},{"voter_id":"3e8187bbb403b6e3d736edb55eac2224c0f841a27d847482425d0dcd344d2201","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":165,"voting_power":"8250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"5000000","locking_period":300,"voting_power":"25000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13250000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"0x5A79FC812149748e5E4AC30A36177c27b1327Fe2.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1007000000","locking_period":150,"voting_power":"2517500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2517500000000000000000000000","vote_positions":[]},{"voter_id":"07c2d2e3a3870e1b474a36df06f81da56f6721c5b9d5062fe81597380e9a664a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"5e5657a757bb41d7cf7e3513124b02eea8d256649cff6f02311f4dfe5bab19ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000190","locking_period":45,"voting_power":"1500142500000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"2000000","locking_period":45,"voting_power":"1500000000000000000000000","unlocking_started_at":1727823434852,"is_unlocked":true,"is_unlocking":false,"is_locked":false}],"voting_power":"1000142500000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"500000000000000000000000"}]},{"voter_id":"10db3f7f05dc88700f822671ed0f8a9942ad4656f30cfe98c1efa696a80c85fb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"66b93b85f11c34ffbed818ec41ac7e06104a197b78e8ddfc75ba206658811aad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1681989f595a819ee42ce70d4bbf48d03f58ada6a56eeb6b2c1f24f0f3e998a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a0f39a11339d4623b61dac6e400ca59b4d52caf493caa4cf80a0cbda6f3048af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"fd5885a5df244625a6b929a376d92d3926607697bfd85150665c8aaa5d800a1a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"394a4892b4b4cabc53c1adc3b8f95719340998de22871b30ddced6781ef3bfa1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c39e4f5c58cf64cb3cb8223de94392d297c9db63ba129f905d0d9aaa77727f8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"863de74a71ccfe5c27aef97cba791ef2eb034bc63c6fe10304b2eb5775c7f945","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"2ef3b4abcd52922633f5e262b23daf46c3c6fba22b8f211f2ab99451de77b6d8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"88deac4579cb7e475e8e71bea176164e822623996d40d170844cd8d348afca45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1728060364540,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"3d455049327a89b0b0899d886460c7a83e9d97958675b7ee084e68e2dfba3496","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"6fd94977253928c232bf97391813e8be73bf2b19b40e19666aa73c4b960e7abf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"2af49f0840744bbe06799c5f6f486e581fb5f29d63957207be1d17b7bc359857","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b6da2a6c2a45cac4ee04ada08b2420fc0882bb31a9a7c45cfbdef4754ef75cf2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"cfa89fb3ccf6ec703dc0952fce8b7871d784ab26ccb004689df47bfc3759d757","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"eb5015c8ef417da1af8e12e1060785a04013516dc6f46cf59c0692e2316daea2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a71508ec3db015df53c8fec0db941937e0004767a1b2dd2ba88b752bd0e58330","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1fe374089c2af5490b2298baa8957bfd878f4532c5d32b52b0b88bfd408e5878","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"677472908e6bf3684c2c02305636b2843fca477f3d50b76d0e40efb059f673e8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"58a221e69d7e376a82b7c1ef1861d8274efa8b57005362645b431cbd12b7daae","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a7bdecc94e99b2cabed92ee9d427fe33c302de22068a374b01a2947a12fb8762","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f79e3688dbcc3a3165232cd9702213287c08473da0dacab9b5fa246fbf897e76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"801f584e59ec74a98ac0166632e9d021329f483bd4ad627dc7c46fe03088cce8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"61a924afbfc3a863a9023af4feeaef0f0adfd92033b0d0f38fbacc79157262f9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7e8cd1a470ff2806661ea084a7f92a7b0942f7a122772c4e87f60fe988691bf8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"12a7ec72e6e83ecefcb14432bd4e10584885477a44d3b2d4168978e8dd98f42a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e75cd2459b758dbe5838c0f3516bb095ed11544506c7c8b9cb0127c9a4b64b4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cc739bd26bbf6ab90ce3fb9d41e8af1e85297fae6bb6cf0c052b3007387bcb33","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e22dbab1fea3e1ead4ac65c06009194f0b317dbf7c71a5beef5af0aeb863ffd9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b6e52ddb6a6fdc3114331a6b40ecaed92cbcbda6a678f70a2ae03268f33f4051","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"88f5b2cc701e6193bdc4e279f59de48ffaf0ad9f41e47f0b439d2b1fef101dec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"twinstake.poolv1.near","voting_power":"7000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"chain0ps.poolv1.near","voting_power":"7000000000000000000000000"}]},{"voter_id":"cryptotexty.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"incrypted.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"5d52107bee24bc3c1188558a36eb9ace9995e2c7661cef2238c74c7b66ad377e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"1f738d3ccb02936f70e175e079e4b230dc0aaa207ec3731034c273322c482c8f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"086d7c7345fa7656dd5b34ccdbc59e9f28fb8582b4fce9511bc72b8ebc9bc33e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"716ebf23a6beeb2f427de862aa9d68bb0439daebf2ca54d095c603479d02ded0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6ff6c54242179073ada7d208ba3c837fa45235aad79cef65f706e52e2a6c272c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e291f45549fafd0119e0dcffc07c798506f5284291a5ea6763f43940cc705742","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":30,"voting_power":"1000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1000000000000000000000000","vote_positions":[]},{"voter_id":"0f99f4b0e982905e106d4eab42d90a8934ef1f53d904b335e4eeb4157054bd77","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"81907e7324689c6eec67e816d32dde0a35668ec2b04f991f745893a52d1b6d92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"c900c1c56b632a20451a2851a5057aab6f540d405c4791f85686360a495f8385","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9103370c1ded2e04077ab3fbf0a708b8fee964ad8f83f12a1f52c880d48e3f80","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"15000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"5b04dea42af9b161a0749ef29132117832ae47fc8fedcc2b8b7137bf588526ca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"96d49a1b072f7c79af02b8137e9af2b80c598955180010eab621cdcf5f99ee5f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"15000000000000000000000000"}]},{"voter_id":"c5256542ea6bda0c1be75fbf5ec199ac9650246761eb3c7eacabf82c9b092243","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f67051f40da221d3f6a75ab243845868f49504235568efa3bbd1ac7f0fd6849a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":120,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"9287343d8dc623b0f7842fb82ae7e6fc690ab357d8962d604d6ff5e050c044fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"1404d46e1e74c3a20d03380feec9bd751e495a168cbd33686b53276d4f3c695e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0129f067fb619f07c4246425355e69ce28f4fd6e75f530964d5bada7cee0554d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"370ce704f8bfb182314e71341e9b6e452b4bf835021860342ff71b016f1c437a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"swissstar.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"a8717b724a04e70b211c43eb3f47939c172c6a4700056b593490b3232a751798","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"1e27ce857ed9390752de3bfee95f19759abb8659485bfe381ff5ff474e6c75f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b08aaf79df3eaa758f883cb80ebda2a5d02a138e9742e8db62f4ad0f94fee107","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"21fae2adbd00aa6d5767d49809762ccfb6c3817dd51c5d9e756ba50f9a6cf497","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"5dd1b36605c2e66a2406120b9e5cc881fd2e6d1b9e875acaa5bae8f51dfee5e6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":75,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"8cd828ea41a32218e8bcb02b2b2bb6fb3398c71454ec0c1043d0b046761e689f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a7d1ecf514faa181b20c8ceda44585552879453fca844ee214e27f85d711199a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1f32ae051df4c3ee92c0c455dfa7e06864cfd67f48256f06d92b8234f9c06412","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"36788febe85c0e710fc159f7e89dcb3d6f9428f0e6c4c22c66b902bdf74d8fcd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1edd9241c434318001fe7da7d7db216902c2aed93cfd9cec2052bcc031505479","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"42fd84490f61b1f56ff55cecf838af96f845c9b7557d01af8b8964438ed214fc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1725894076521,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"dcdac03cce7f1daba0910a598c1262e3db850b6a5b1dce9050918dec82713af5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"eef3e67b7eb739704c9e69ca4f4846816347ba0956781abc82c4d92e3c6af6de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5877ac057331689da0184690693aed16951b96f51cbbcb73cf299300ce979549","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"6000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"19f6558955d5f5e72c1dcd0c17428939ebaa857be8a4748aa835f66437fa37ff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c924c78471879a3bbf312c4fd14831c34f3066edb79b0da152e0f8d01266bb7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5fb2e4e80796467b929fbfb15bf7c9fe54aa544d660ed95d3cbefa448bc47027","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"b7ba0977a373409ea548cf6b9a34bdfc7d736de65689c3ab8dca04a668994d68","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2e77ab61f7697845e2b526da55ee186d9a0af93dcd76b955be4155b49daca019","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":105,"voting_power":"7000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[]},{"voter_id":"d18a87234bea429ae49a4143ba758d3529e62613569637a769fa5ddea9728bca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e10573fd1f7c442c8f8cf13177eef62993ea34dc731398a14bf1cfaf5baabbe3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"794998389a05be635573b069fa11113a847da0ea15570743e5b6ae2cdd005d07","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"940b79c23a2277a6c1a0b38ecb826ad674c58275adb0d07225ca1d006bae6ed2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"be08aa1d468366014e5f0999b5f952c947f76f470a01fb104f2f067b18d480c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"971b71d69c14b9e5e4df3aebb5329443cfbbf94b24141a95dcd12a668f3ad925","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"036a4501e25eb2751eb42b241de1cf477c7efd1478d077d7b3d6e6ca6476dd61","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ddc32350dfa3606940362e2e65a2827265d02969c0e351871c51c0c3fc0841cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2dd3b187270076f03989389154d03ef114b52ffd87c6d7c4f12493170f571c63","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"b435a74f78ae96d799e6cf7a67aa2146d7b7afeea2bbba75144c16efe9aa2cb1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"7067c05f8d106f645c046e2c70fad16fbf650d361ae1bf9ec025fe5de3140b52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"fc1c6224dccc835deaaaa7f436de293048ff917f49cfbdf7fc168b61a889a80b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"72792332c9650c9712402218535027510079671ee028557ed77bd1d1742fa843","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":240,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"e9294c0783c4ec2598f5133da7be8152ea9c8cdbb48f12c4e381366b72cebb8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0x0E26Ad20DA3ed9530f34f0cE7CAd089099A5b5cB.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3046000000","locking_period":300,"voting_power":"15230000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"15230000000000000000000000000"}]},{"voter_id":"00c6cdee170e38123763838006c396be990515dee717892eeafd1d78ff4609f1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4e6b3efcae07d074d448b075830a09e8245ea606e3ae5008721d8148ea76b1c2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c7a675506432b1aad8273a1bbb39ac98da78abb068b5ddfdc42918a05b93f4bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"05e3b2fa9d1dc7c29c6980e2cf38815c6bcf456bd76ed2398aaa41694293ca85","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3a91a5d6405a139a92e8ab2c600df1bd6ccbd0b9fbe7f75ed99396653169f292","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"799aac39d99dc1a9f6c0208bc1d06c838ed2aca0d4616387dc6e99df79f131b9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0d71f1a9278ef28f622d5e6266c8f6181cbef45f76c2bc02072a7701dcdaaf61","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0c8b607399b0c2c330cfc5e9d4589151a5a092338d102066c4f0942d9a45782a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"14c859966f54eae431fa6dc83c3be5539372f9b2970d5ba88875dad62d52e71e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ff6fa2987f45ca2fb49273dae1d0532647c0b5cec38a62cd0811ab8e2351b943","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"aa7abe81ff463b52e88a682a25b516ed3874f3c12f44ddd12a6744c34574bb92","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0829b5ad8f8b242801656a319110fbbd21c18fdbd5476342c6ca54ff68c83bde","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e16cd0d634f877b0f01efb8676bf85f979deea1f789a7203fdac0cb480e408b6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"andrbenz.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28476061200","locking_period":270,"voting_power":"128142275400000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"128142275400000000000000000000","vote_positions":[]},{"voter_id":"9ed0b94d100f06195b576c5e8da67112d29953269218e4060d41a3798af97100","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1361ad9c02b77a448db162e06a7ee89d9fe75bab9e94bac626ed4431db14e101","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"kryptokriz.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"59776610","locking_period":300,"voting_power":"298883050000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"298883050000000000000000000","vote_positions":[]},{"voter_id":"a81bd780c6fda9db96d87e7cbd3ec6e6787dd6dfbd0c40b5fca3fbe2ca7469a0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f1fbf3b685b1d4e1908eaf14ce19ed5f433eb95138834752b7d88b618e9619b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"64000000","locking_period":180,"voting_power":"192000000000000000000000000","unlocking_started_at":1730687301890,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"62c0051153fdc29123c27033db96b359a00235fce8b95ae6d2a90378ed75a6c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1ae33cf82dfc8d29bbe7bb616c65ce3d8fd4b8142d8f2e1472533c8c11fc5d86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d87cbc6d0749755857760871578e9cdc159d92c3772a1028c298b3b0f81e2d9d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"ca216b3e2c398da4b6c79221c097420883c76e434e2e3653dfddc8cab8d9fdb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"99cac8041fe3fa1ad47524aa416e84c0fbf95f47bdadd940afd0fda3debb3a03","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"379009049af0bcb5721f27bff4f1ad261b68979258235e078f03b3aa74b398de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"91f17c18002344c0330382e6a1e00937b6ac49e61181ac18b1d2e57c9979bdfc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"afdcaf49a9cb610082703daca352427ae75c7d43ed80934fc9a9f04c34bb1d11","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"db07287a6fafe6f9c8aa7f9645b398c0a39cf432bed7b3bf3c7de8a94b08be97","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"b5b3238925314d194d1566a26a207222a3cdcf317d8cd9ddb8a99533adfe6e57","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":240,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"61d0087874aa6826490c8f85c60c3d7e49438ef3038aff524129bd4cd44efd4d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"9cb1ecae070cf1d83d0c3cd25aee0b854ff77272ac70d596e6a752ef42ea98e7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d746dc8c86033768f956222b19a2a72de997e01699d62402589ea5574f56593e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":1726093284453,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"4f9a31487a01a0f8784a33b640066d8d4156da92c532e68093dd66072da44397","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8eeef53b2a20b60365f3d674587970c71b17f7e9f8f8f11607b46e373edff141","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7ba8a7ceae452436870d704afc0c52b5039f8845eac11787578a13170265eb86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"81dee9ff2783ff8391df1fe52bbe2a9e991a31cb9e54f33ea2ef3ed50682ed3c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"18f9f3dc9641692f92bf6881c6977d775867160e357719718c81f31abfdd0e5c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"06231ff28eac44415ba813bc8017eadd71de48097fe79bd3b0b91297c99d4ee9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1f2503d03142f5248a3edb5294ab7c382ef11a927be9115a8b449e3a8293ce63","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"64159310","locking_period":300,"voting_power":"320796550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"320796550000000000000000000","vote_positions":[]},{"voter_id":"f8c8909679232c5892284275ae41b022f5cdcfca753cd6486eaa7208c58bd676","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":120,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"9313be167ad99a001af9b81ed2485d1972b64d093cea31a921cc99f349fa20fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"bde958893709fe0b8957f28bdc420dc3755f9f97d505bf4944b4ae891b8c8df7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"70e40ed7c95a6dcd3614909d103804937737a8aa468c4b75141ff5dcb71dde6a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1701c668bec7de27894c60678b3643ca7b451c1ea34df65b0d14e27cd9c554e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0267c26e3a65088ca28a326d3a3d38c1e01750cfbcdcacba44b4d356f16d5789","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"028c9fbfecb62e6956d98f9f6cfce636c4113546754385dda1d53bd049ca01d3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b402408e7315a20ee682024b23333e28744d4c0589270a0dca95a04024b8116b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"58af9c4f6a59a435019250e6694b21555eee80803be9dbd25fb7f26097c844be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"54d2db661030396574700c5bbefe3fcb37b33c7ba464b1ec78538aab677193ac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"db6a6ee86376da9b1027b4e4f797643b08f6f163ac1336fd88ca32a56f7ccf66","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"7000000","locking_period":300,"voting_power":"35000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"35000000000000000000000000","vote_positions":[]},{"voter_id":"6dfda20779e375d2fa86c4b946759de61bcd147f38cac297c8904ac0b896faac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"abe2cff100136ee3bdb7067e73f01f9652d785083cd94619ca177d0493c53999","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3659cc1b64562db07abc7c8cddf0e16a909620430de3ab18cecbd917d1338798","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"b35327c1958ac20d43c30aef2810d4138c8b6fc7f457f4321166ce1d548c4f84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"82d89f0afac74d5bdb4aeaefc46190afc734bb1477ccf59ebe0b8f209319632f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a5a2e09c728f31f0c4108fd0f2bcfe0b1cb08b2007ebeeaa7043aec4dfc5e01c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b50ebc176b2c3469c1a471aeb95abdebd67da289fb6013f65e7a6fdf11ccbfbd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4dfdeaa39322742a790e25711cb1ae1a2c919eabd1ea97aa102ea55ce63e7130","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f402e8ae9d5b9a11924db2164452f75b3c4d8c11cbf2836c8e523b6d9419b2cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ece40f7ff08812440c1be6b2cb5f89f93efd09069c411699e2781726ceff9e9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"d8bcbfee95d9a5220bea8c39e7963be59260c5ed4f992e58a63c3f45325ae74f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"25736f1f4db48fb3b70f3d252954ad551de4ee15b7afe0ed07928eeb85378463","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":75,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[]},{"voter_id":"b36aad8ecd4d568196cda5b25fa6aded59546f2860d88c2c4411828254aaa148","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7f75a3e118451ceeb60e3fbe69cafde67faa929fecd0658c7a45f3c3a39827fb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9291aa7a7ceb719954a660976188cf0956ade8441bed1c33b0edd6414d4a28ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4219fb6486dc21c3931ea3a04bd5f89ea0282190924a404c911db17b894cfd0f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"f2b9e607ae630b5bdea7a305c6941defde1904303b155247e427ef79614c24c1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2de5cd18bf2babe3b486ea7f16bce876332575b57ee9c0988a6447cef7397fbc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"41234a7447f36f2653505f38a3c92b9de8baf4c128b4b41d0610c28b469ab24d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"563992878d53c3ef12a5f1a72b2bdd122d27377499541d441c3e9769884f200d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"69f5cf0dd02ce3439b937bc32a014d286542aebb85645338c56c866bea1d914d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"904f6b18b908ebf4f6bee39e7cb80b1fe10510fef3106d3f7248763cefd5e511","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"1d6a58651029e4bc3506e7054a0e9731923afaf4ccec52baac0add9f438dc5f0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3494d3f964d58eac9d8c40c9c4e624b632205f8aa3ccab8842041fd09b6bdd71","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fc52036b5cf9c7d2d82015b0035ce0d2a99ce7b51201ad22113bff3b0c291e7c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19600000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"200000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"200000000000000000000000"}]},{"voter_id":"617937c4266a0f2e2585d37d5cd6bfba7cf4c140b08b7e7fdee3b3a8e08d5052","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2509acc08146306be1bcdeaf0341c10f71613d98af714b63a101343b2ac7de36","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"dfc1d9a2fc74440f7a967d511c13e3788e1728ed95b54a4c8f18fa376b749f00","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"73be1f7e83fb1f61d2479ad2347f4e86f16d78c6245d19b774091fd7e43086a1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7357821ec1998d8b21e5e9e0542ef6dc1577933b825f6032d97433584295f330","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2a9de835bbe6103656549fb96bad8389c872c88b272e7c585ea60d112da5790a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"f0837ad29d4a602895bfe6ae3ab909e6e2e98612a4d153df60db7eb7ae0c48aa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d7551a5d68564271f4a351c6bce7fda2ed04b8e4c37741541fefecf15e36a287","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"17b1d503c7c6fae10bc6f4106f2be7883644f88f390c8bc4d128eaf0f7a83666","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"412d18aa88cbc4b8f79039fc6dc1ccdc3122ac9f234a05fcdf802474d54b1417","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3000000000000000000000000"}]},{"voter_id":"3940ded2a79c6b18ad67a37d287eb5abf7a8a2f388a07d1a9e7654398961c77e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"797b0eddee60b4a86a22dd72e009c4577a5dd89ce68dfc44cfb7e0448de7efe3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"a0b12a2f220110c01b8dc0f596f2ed16689ed49b2b05fca2f6481267a2baedd1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"89904f59c5f1652f16d13675e197727e7c4f414e6abee4e4db393b996f6febea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8d7ba69f8e14eab2567604c1fe7299501ce9c95c96afe844e9c7a7e709276c45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ea568132b0de7d2bae73efeb747b96ff717240ba68f13bf9b5538d1387a23a19","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1dbc329c35ea3645a2aff4940932ee552491819a9a12dd91eac6ee6e6eeb4c05","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"99067b6fb9d2991532910c7d2a5b15aac61b4d0094dcfa7b2ac8f0ba1b9bfb8c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"3311a8782754811121b198b06e97b6f192513f4b817a80f734423a76e5dc7ac2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ea3a50632c840f23224371c4245fdd40365f1cd92c4a0edcfe1b4205aeeabd6e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"oharanodes.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"ad99afc29ba51470ddacc72a907fcfb3dbc5e852e0912e3ab33811e7228cb315","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"32be94ecb363ef27eac86531145f110342b980ad47b5bab8d8968481210a605b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"31187eb2b9d331ec171bd8c2b077c28e1555c2c7a810db4b534124c859ff59ce","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6eac46d7bf3fdfc277ebaf3c0da229d35ff2ff690c59f9282cb18cee676923c4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"398af1f074deb4b17e59331e26cca95555b9f97a5f945c3d152e1410a2b8d5e9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"94178855619e1db1b60517cbfcb24a8040fa06ef069fde1cf327b94d62fb36fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"3000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"b66eddf0109408e2219843a79615fa7aed85662230c86f3b49e3ce67c38460d1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2e0a4f8bdb97040e87582ac6aa213e7f259207b71ff6520f043194ea52567f64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"25beeb247abbdd57a434c84f20e285d0ac15f8f0f7f536976110db632603a0ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"2719f07291753763a4b6b7050bc31aea692e06dc9b823ae25f11190fe1071324","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"ad320831599369e368e49b53ba57db4dff1bc7982e4cf82a72081aafc304064a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"239a8302778fea0163e819c8ffd67358b723b6d92608923b2c2cdaf0478a949f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9702b78216f13fc8f52076f7b71f1998c90fb7dcefcd9283a5611e76df535386","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":180,"voting_power":"24000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"24000000000000000000000000","vote_positions":[]},{"voter_id":"773e7fc65a5f109f9bee2f5c91ffc585fa85bf9346dd6813492271a90b52aab9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"ed82f442c4e6711741f5f66bb9cac14224f54b73ef645f170987981b6547ef57","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1729740231422,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"8987ec7bf6d8f200769b47c8cc715e971b0eed2a43a33d46b0ebcb05893db35e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6febc4e10c051c4c62c2ba06f413dc7035daccbf43ba50f7cde74b0fdb30d462","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"10af8566c11d18fcdd8014548e78f3fbe4d64a06e60b28c2348fbfe2b586f024","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"94287d6539cc3f7cc989b44061fc801f2d3e7f5adeebf50e857b0a52dab4f353","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5eb1bb60fa568aa0047c0a4a0e7c76afa1bc6adaa3b43be54b60057e918d056e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"near.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"c7eacc8cd7b1aa245fa1239dfbe4ca1fed011b4d1a49fdd2d15f3d03456e57de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"f4a2483ca749d299a962dc524700d9d8e9b02f8abda848c1e3f0162626440a43","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"041d31e3cac68ef16de9193a0ecd05d8f4d80d485d84168cc2e498cd36c848b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"bcd20a73a7674609917cdcd2c8886fc6e8f424244afa00cca5b6f3828859661b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6dc4173077f05aeed2c35296f1ef5ef590b38047c705eb1f14b0c03ac26277f9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"955796c59627b325b9122da80bb91948d3ba1c3cd1877802403e66abeecd7d95","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"c4af9e7e792949015b26c7041ce561a9c2d7eb09eba9e9a38216b5b291eac134","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"38e7d968a3796d6c8bc9657e5675a8fd3e2db880e1e61b3f22901d173c3da834","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":285,"voting_power":"19000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[]},{"voter_id":"dd.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":1726916110572,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"fd212826abfe9407c793a4ba5fb56ddd285c625504d1bbfc7f992dd30b0e20b9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"dc16eb15c0e8cb16ea369b23b22c475c098d3c6730deedb02f26b4fdec2345ae","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":240,"voting_power":"16000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16000000000000000000000000","vote_positions":[]},{"voter_id":"0353b18618ab836aaf5f1f2f10e2ef20ecf219beaad19b5b5d34e6a1ba63105d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"853055aaeee2da3f8da4cf0e8888c172e7a2d4fed8038fb20ad5e487e005cddb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"d66054c5889551c464d85d6541f1ba7a98fd4059c45e1c05ba88f5f9cc6b9ea8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"657e262f996d4ea1e4ea3fc1672a83909672dce823c68ae98ea08b327d0c158a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5547ed00ab078368ba1230cdf4900fdb243832d323b5f58f1fe9934dc1e8f119","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5e10ad8fd96b8afbb36e8e849799ce5b1b9e2827adfd6979d3cd177f66299dd6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d3f5100f50fe7576fea0f4e65ca69f24b5a5af2a4b620ae7634c0467acf7b4bb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0512fe7a65a2b12709739d2d38304daa2f2f26a0540d6e4cc89a0e2d4d3d5575","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"56e489f527e12b53aa98211fc42af43aa806aaa4a74839a29e96d80a6cc5876c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_01.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"consensus_finoa_00.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"ce07a07ae93621005b5903a34536c79bedb775388e02f9bbf1b5d690346fada7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c9a7647181fe241342e172bc22de0d8340e23e0b42f005c1ed2dea1251d899f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"331a4de65ff7675d133097d60887d215006b8c22c0cb544eb1366407726e90f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e218a8c3c6309415d0ab3caf8a1d92b860546e5afe5bac9df278a6f818057f9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f17c91f5e7f24364d25bdb925335b28f3c71c60a302499c4c5fa3089241eae36","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8cccff5f1d40951be03ef48bbfa0c0e840429a5ec4a9b47c3ab44fa75968f5f3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4701f0ecd4bed4c3d0da51781c62d51f8d75382b9fe397b30b4516f704395f4b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"43e72e92b841b4d69a6dee7eb246ab0715ac4abdf810426bafc72812b9f9be45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1469e35d59f82078f264d3acb769fd4d0596e9217043908f56231d5876c5a83b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":270,"voting_power":"18000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[]},{"voter_id":"88026ed9612ef760670530936cf719a9c1ed941a9b2037347b3c00d2612f705a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"749b9e47d52511305c3dd7142bfd705ff8d42f7490b10f7ef8964a1f45fd9902","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"53f807d64a574da293b04f7b9c3c9264f70614a5f76ff33357f6d4772121167d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"913081482b6b36bb3dc6720f63b928022520ae73db5d25d48844016c6950bbd3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"52e7fe6b11a8d29e00f4a871829beef6db11eea951403a32f5a120b793a124e8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3ebf8d92a358e1b41116ad43776347a655a9d8359c4b403eb4b8e15fd096d985","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"8ac028f6b61de7b53858f4010fe7cfe0123130307138aa9c58070916227b5204","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c28e58c2dca82bb50dfc2818364fac2f7edb958a9ed7daaabc704c6484506850","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"dd36c2fce06f9729b713377bda681c8a8270c0ad5b627f99acbe5daf0656de64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c2f4f51b093ca7ccf515a6ac178d60ea3ae0841ad67d9936e93240081a9851f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"30e18e86c24939472a1e9f103d92bb0b1fe31bfaf183576cb320b55a89b54213","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"780cab709ac8d76b9a8f0af5a258652a97943451b4fb5acd18d49776cf6c9210","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"6f7f6a1e29da4d60d8bff0fa37548b2f07016f1a0a6c5c24d04025691ebeb808","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"04287931bef54f9d89d10098c1e8c220a6c074ad781e1c5c9f4d5195b0414b34","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"734fa29022827615039e320580bfc53444a14b05f9596b3351b69ffb013e6f05","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f6942662733ab031d375130b65055f8f1931296af99e8aa21aac47bec1450a50","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c807d05db081db6687f39b5a6a192edc2aadb157703c7e3b59478c7fc3d4fc87","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5b1e0fd9e166a4c2c6500657766c02452fe80666f7e82fdb825546f9e5aa7fdc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"fe8a7476bd941adbfdc000101c5cf29fc1da907a4cab0c548d6992935f2be323","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0bb4de25a67d7713b6f824edc74964787c34555ed4ba33dad966e051f6b1a48f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"47d48c9349cbd5aa879d981ff3e240123d8ec787067b96077655a8d7bc8ce9b1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"65ee595525f90d2ae03982f73cb6e0fd38351e558244b55278249f40f5d00f89","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1731301368381,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"458c8092d0ce14bd7a4888991f9150ea0e8853f6c04c5b85a797d8f373d629fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"45955a324834403cd876b0f6a616470996bdfb1d452b167792df92e03f5f2cff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"184da3a9f33dbc566a033631982a2f8662987f89edea9bac90752643a99c468e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"403b34ac996cca15aefb1abb82334629d09776d86e86da7566f517c222b03966","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8b41ac0a4e523373cbc18c4881a006c6c38dba6ffd9f3c616a9cd772b27a225d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"aae0f747860877ec2334fc538226f53e043dba796a65ba2e2beb55a8e5c73f74","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"591a42fd26c0db3553a45cfdd10410cdc580b028f8c3dd64c66a358ff2e8a9dd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"bec075061d6d3730f635592e6b1baeeea225fce2e6c282fdfdadc6bc4454725d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"spectrum.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"finoa.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"gfs-ventures.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"06c826b8882bba0ececf2f575ec384dc61cc1c6437057fbb407e5866cd3603f3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c108ee446be7651161f81480822c404338dedb10a5aeca4af42244ad48a9180b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"faa24b31bbd2cd1fbbabb1ca36c8030c5aa5f3eeda652a12d25f788d41ecbba5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"29c778fc450b3372ff3749544992f2c3fe1722c25b2da520264a186a41fb5193","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"63b5128969e38ed740b3281bcc6020525abbf57670720e0766139ad0bc7f1bb8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"de75576eb4b185d6f483d13e45da751e699b36761a67f02419a7768794e94645","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8731464b3f621bc32b4d8ef03f577bd892dd907305b249018931fb47f7f908c0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a8b5e76c9b9837392fc596c0984e1821744b2b7845d0da40d96a75c3328fde60","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5ae7d21596f8f2bcc3ae3bbb430be5f438d48638393c7f9b4f4eaa3ccbef6a49","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"5e53e3e4ed08236538800f388019fb280fe63ea25834ca5daa9aed810e5b6ada","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"bd6770f68773ae3370fd987ba5da50a9831c8bdb0930ca3403f9746c823e7e73","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":195,"voting_power":"13000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[]},{"voter_id":"c570c39ef05d4252adba606388596d69f6cb39f74a6c199f5a876595d94d11c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b4cc20115a204e4c8b3fbcf1b7cf4d448c0d8ada108acd9902558e2da77061a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"304a7bfd37ca7f7a8010436c7c0ca7a49f1f71a978fb9f7df1b9d538332eeb32","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1ea52dd01e3490820d2545d5f298e28c18254ffa74b4409dedf3553a483177f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"a55dea66741d2b4ebca4a61bd98c0ca03cbaf0b6473950f2dd7789bb3fa71e99","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9cf61dfb276914d9684ac21367ef56d4ae74f88944ee7ac1851a732043e17201","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"409dcc41a29f72b2a8921bb86d22cce65412540eb8e5cc35740a7365853b2ef9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"01c45530443a814c411801de32274975c0417b13e4957b6b96be9f703022cde2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"fec07cd91a1759492221e878a352517c40ce7773d21067d9e9483fe5963ad80e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"4e3850f68fa93d677287cdef4f1f6b07f2a2c4f99959c154fbd27bf44b8529de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f01e43067c4bbca2cee4b2b1d663f36899773c99cda12570f120440d5697c7d8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4dd90306bf3fe8e715bf90422fe431b246a15511255d30c36c7edc5c8c934443","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"19527eb0a7f723b836cb7b3451a05cc0555683862b301554e23277f2662272b5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"fabianjeff.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1403139400","locking_period":300,"voting_power":"7015697000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"7015697000000000000000000000"}]},{"voter_id":"14552b1770952b0e1e526fb0c0f0f6977337d46d39dbb473f787124d4e5e15ee","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9867ea08d088f14d907d9ec26fcb92caf903c6b25a77b002917c9ec8fb4200ef","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"cb6d60ae1d33d0640850c0f789c3a691dd2c78889e45580575ac034b87bdcb3c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"858366ab6be01a9f23f2b0995ef12da31fc0f33bdef636eb3aedab86000b4039","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"78233ec39df97f2cdeea83dc25207a878f2b6010f48eec05c834da03ee6c83af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"1b2534ee7033416ed536d2d1d776408410791742866443f0f8e1ba9a3934b8cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"agrestus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"861429932b08480ca9eae4e6beaf3cee2d0640ed7a8b2955eeb462ad286d33b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"binancestaking.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"a6d26985e398c5740af9ffaeb9e328c793631228da41cc87af38ee0aa99770ab","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0cc6b0ba4718764969c565da59c398caaba6dad4d75b7354a92221080018e1b8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3921d59929d52d0df19f6dad70f82ac4afdc76422d0827e4eeecc06395b14fed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"62cec87ff6444ae66e7ca49a31943feeed94a201164e240725695619698c7870","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"55cb1cd72969dfb6847e29c03b31fddf4d228f1402824ea9c4d1b304737f3441","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d23570731443a539074d6f662afe0f7fe76417a7349ada3bb39de1ab731e1a72","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2d380e02cfd85f8b939ab12a0689965b72f763a0dcc1d759da04795bbb11bb8f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6edf4c0d8d6578ce8c0d3d35db9e6ed5425eb7ebbc2ff0dad7caabebabba7d10","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f0e77c7f794ddd17fa3d82725190763a965e7afa53ed1770f0c8bab345cd41f1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f5115938c6a7b5ba2c7bb48d0170ccd516201093e9c07d9cbf0dfa68a1f153ad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"017814f0dbff4c1aded5fdae80ab2637951a7eb22a50cdccf438bc1aa91a300d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"7f86966cbe527917e55e0ff27871797bf2fcebdf8b0fa0f1653df6ef38ecd063","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"dec42051f69e0c1028979c55ad771797bc42781d2426627e43f819e8b0308c8e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2d206c0ee8e3a31b3f8b9c4a990db7b03af6c5c21502773491906c43ce95aef9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"96b77234f3041e8993c3d2a4466586c92a750b341ae296b32d9e7db47e6c0c59","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"651102751a31dd23a9a6db8d957fce4e3646e8e8157e6b7c165668322171caa0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":195,"voting_power":"9750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9750000000000000000000000","vote_positions":[]},{"voter_id":"053d5559f94b48175da9f65867befe2a165846ed5b67abdd1be4ee788294120a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a3b4b04805c4bf45cbbc3620547e231b5463c9776021ce9960ba8182ce4cf568","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"cdd59c02a8d09de7744d6d108c235783fbbf08b3ef3b7f0b99bc44c90f28444e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"yobo_z.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3257705100","locking_period":300,"voting_power":"16288525500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"16288525500000000000000000000","vote_positions":[]},{"voter_id":"5ab140d2221672ff1f6c90d948d13a6e22dfc232b97fbe5fbc9f2ba0b6a44540","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"495253cc729e92aea99325e09ae1380e80e6b43ef3cb4f692f2018c867dfb80e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"58b8ee8a8e5b628e6b48d4d99a49307bf42cc6ff837832dcce8460650675cb5a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d874d8c8d5477f3dd4a81f7410665afe8f90c9b6e18facf7bc26a07a4953e8b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5649ec01a1d0855cf4ca5ea37b46c0aed2ed0171b3a832e73c8fa380b32e8e9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3677d5af605867f06f3f88ead4613530bb534d1f2cb1ba2e79d1a4692dd190b6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0999e054d23231fa8075c8068e28ff735383f8954f6b20b757d9ec0b3d6e160e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"359aea3d930ae6f9bb436a29e8dc6751c86804057f8a6da7d7b65b4b45a20163","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"5d54e98cfe17cad4bd5835510001281b8e68017fd5d0528cb5ac5054b0bd8a28","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"f8f8ee707de2dd41f5459ca9245147eda3435086aa6609d3bcacca7afda32aca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"40f33559d2bf5a34dc46f0a176586f9a6156d945532e46a71ba7710134526569","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"satoukibijusu.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3949596470","locking_period":300,"voting_power":"19747982350000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3908536250000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"15839446100000000000000000000"}]},{"voter_id":"pozzer.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4158992350","locking_period":300,"voting_power":"20794961750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20794961700000000000000000000"}]},{"voter_id":"helmangom.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"100000000","locking_period":300,"voting_power":"500000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"500000000000000000000000000"}]},{"voter_id":"yolima.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"500000000","locking_period":90,"voting_power":"750000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"15000000","locking_period":300,"voting_power":"75000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"825000000000000000000000000"}]},{"voter_id":"70c1b68d0e63229955c10cc1521fe85aa69ce74ff0ed81146e46b14525d0a723","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"61cffaf2ede2960f2f5c42725a5c3ee1b9d8c4c8f19b92190011822597f2286c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c2de8b99013c31050b274d77f6d99e7c8a78718cdad69a5561b2e5f3f4b359ec","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3a8c4a25c26b16aa179db0f523403e864d13fcb6fe8f454afc67c3a860b9f5b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"711ae8e59518ae9eb84d8323c08b8510b6a901a557a99b8f366d98c68e95d825","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"6d0cca462c8288d2194eebdea56b441eb0427fa1b9087aec091ae69cb943208b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5417b243f5e47c8edf6e3bb14739fc2901282755bfceed9f4d30a01e3630afdd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1ec72cdae4c26124560640ccce558082715bf44f3eaf9e002e6dbe0bad84f6d1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"6b1245ca8a8ff39743e8013d6785831a30a11f715bb6fd9f158ddc52d26f88be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3750000","locking_period":300,"voting_power":"18750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"18750000000000000000000000"}]},{"voter_id":"wap1.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"46493835500","locking_period":300,"voting_power":"232469177500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"232469177500000000000000000000"}]},{"voter_id":"f8059e22c67be82dc3c189f1a9ccfcdfdb157ee9fb8e798600947dc43b1abf05","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"87fb3e0878bdfa092084e000957fdd55501be0cf683c22b7809aaf6678c44105","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"ed342b0e0718e71354f1f5c9bf364e0a4ba01c3d924079cca3443f468f66d874","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"criptopepememes.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000100310","locking_period":300,"voting_power":"5000501550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000501500000000000000000000"}]},{"voter_id":"7982bf4b7fd2a75fba7c7ae7ef682791d5102e493158b26491a2119f4a893bdc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8918ba96cd3ea1dc9d97a4f5669b02b9522847354f707ca7660e3da5c8829ad7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f8af656dee213507fb14779067870c95d9ffd5a57b3ef9378fa13b85a81b7611","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"80164fde65fa9b7e3a9309cb7e5fdb40a94d1de0811f0cc1cb30f29458564adc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"fd1b6774df49284bfc712042c99608ef010bfc59ed0617772c41d11c33e9127e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"64759931d3006a008af90de61e4d4cb3d95468b69ae01be63c03fa9d2d166163","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2b65b54b408be1fbb32cc70fc7a0ebc4726f1cdcc0150feee4095ea5097cf561","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"dfd830d6b35aee5415c8ae5436cd10b95deace970141434fb64bbbfa4fc7e3a2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7d5f49ae25465761b64a53ff8efbac94269c3068273ed66fab5eab987762d84b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6bdaa49f43e589e6915c1ce64aff8678a79cbe105abe4954486355c466352a04","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ab953a494fccb3a5996cfef4cd1535c424db5eedf3d1c34c8e31194f276431f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"72723a1ce8858eb25ff1db5513cc45fa768423886865202d5feebddffa8b78fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"38b26fa06a6f586156893d52e5440a46a7503f9fcb80c8e1bac05766ec7cfaff","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"0xDC068F2B765e7B73d6BD182E50e1e4AE511733C7.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3323000000","locking_period":300,"voting_power":"16615000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"16615000000000000000000000000"}]},{"voter_id":"581267e1c29b956b5f370839e6faf48b6948049289f9b303bdd7bc92ca0a37f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a3e4d1f9359a08670c8adf05ec48c30c5fea4913f393fc1f4f5400e219ed50a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ee1efab27573279b59c076e841b1cd36cebffbef0cd7f3315b5b4a536b670e60","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b95824696418c4b1000df8eb0aa74206081083d7c76ede207b071639b972268c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"17b4a973d3bce2153eba3ebe078c6d7945c4a9f31f9075ebd97c11cee91d409d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"everstake.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"dc6d50a234b525b8315fa3d360385870eb1fc0be2da7b8535910406a061998fc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"6115106cd5873fe67c7895537c9f14be393d1d17f476188ca2eae861f7985646","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fea113a1e2bc2753df1fc8cba18593f1817a6b3e81065e5ffaef8cb63e73eae9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"nearkok2024.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"78188512100","locking_period":300,"voting_power":"390942560500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"243793232500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"centurion.poolv1.near","voting_power":"85754581800000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"snsmln.poolv1.near","voting_power":"61394746200000000000000000000"}]},{"voter_id":"65d8efce60a4a428ab91587c51ad4b5868c25393b8fe4cbd8c5292a16bcf800a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"2168377db8401dab21b3a25f364aaba7370bae972adb42e7b8074b8d905edb76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":45,"voting_power":"3000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"3000000000000000000000000","vote_positions":[]},{"voter_id":"92e1c92cfe613c4e6aa789ab2175a4d5da1d939cc3c96cb8b6386850c1ed77a5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"phuongwallet.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"159558600","locking_period":90,"voting_power":"239337900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"239337900000000000000000000","vote_positions":[]},{"voter_id":"12cdca6113ef23cb19c5a16820571fd6a670614b283b31c15e3cfaa9798f530c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e07bdd07a19febaea5a02d5ed96baa5120541f919da6c9cf0472bd286ba765aa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4a8586f03eec496d98ca4e34acc6139ba54bb605df1844e5fbdea2401e9d6519","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"38615dda77d0ca19be35037f59a104595268620606d54d5011259cfdda0c3e52","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1a616190fe849bba5e123abade2c07997fa99be76a9b0b6171def3758aad8ee4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6336854890","locking_period":300,"voting_power":"31684274450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"31684274400000000000000000000"}]},{"voter_id":"b95b75066eb4ded1e0326b37100b4934b5a53fcd1bc579b5c6313e4118c13ffa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"66f84b2665be8d6eaad47d66f1403c7c49a82ac899348a0da1b452c219c313d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"8000000000000000000000000"}]},{"voter_id":"d0f4e50101fbed91426ff5d9484d70910699c2e6f833614e56d8f6da1564dee6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"7b81ec482bb6ae18b43b279185601b6dc2614a22b4fa3882adc68e643a9298cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a759603b2906b888f02011a6e1744ce28bc9a062a9759bc3eed6240619fb11c2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ae46cba8067ea098a7fb02275c47c4e2d971106f62cbe80f3a8d508b7f01c4d1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8ab933f3c34638b6a2512b4c72ed3ecd6b953ecbcff08381c0734d47345a06ed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ff868a3da78561e1ba668e61e9a48eb3dbe8b59e0283ce4115701220faae00e3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"a3fe35fe92cf48fe2b0835ae79d79f270cbb08e6d596566e00c8e7cc64d1305c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"gilbert_crypto.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"85063e3b8ab7f540f67345178b105f665cbe78c764f2e3d942edf2f7b93a53a8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"tequechongo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"355018965200","locking_period":300,"voting_power":"1775094826000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sigmapool.poolv1.near","voting_power":"798526253000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"fialka.poolv1.near","voting_power":"250000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"spectrum.poolv1.near","voting_power":"69181954500000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"657386618500000000000000000000"}]},{"voter_id":"8d050b050cce9dc4d563e0aec29b1bfbd8a33daaa9c1825a414dd127c44125be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":165,"voting_power":"11000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"11000000000000000000000000"}]},{"voter_id":"b39b52a0bb1cce31a2e9e0979c9ca830b0bc9d647493e1effd0119eadcd063f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5944fb29764ce2015652b71a9f227f07c50aac0fd2c040072fc483b120e7dc96","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"366779cadb5a3f677d173fc19f83e269949e4b20c2b4f76c22ea5a2bd22bd8c2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5a362dd0389e5dbffb124b117b5207381ee4cb0b3a25a87ddaed99497b7282cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4e2c34d0bcfe1a22208c433663181d681ef5b523dabc58f15deb737714d7d2e1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f581e13e4754154cb65d55bc8888a926eabd87b72bf1b477df60e339d5332d22","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"520e4fe5920cb92ca5a558828f311872b500171af37e86939d1c8b6c0064f23a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f512fd82959f4dc6309c6ec55ee37b89acd8f9b8f1927a64639000a09c5de94a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"338794f543e468cbe878510513f4f053b804d2c7f16cf3bba9915c125aca7865","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"b77848f86f850f0bb3410db647c866a763dbcb3e212b36f3ce983b9cfca8f7bf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"7500e6f197175d63fe6bb329320610613cd69c73f2265e0ec6d7d111c24bd3f1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"53645f23fa05451377f090fd5be0c86378e6bd7c38ba4278cb0559dd3de83bf2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"stasiey.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2562800","locking_period":270,"voting_power":"11532600000000000000000000","unlocking_started_at":1727845268380,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"5f55c67ec742f4219712f384e0ad2179a89375b1cb71facb9b30423ba71beeb0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"127688ebf35cba72ca93c098ff5669d3b21519447934635ebde46ed932cf9390","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c8546fd655a069d04da742ddb13dcacdfdfbc8dd1adca65c20933b5046646abd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":1727954002434,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"428b9d2985f68362ea50c5140a62cfda4adb33072a4d33a192c0e921e719b3da","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"844353620","locking_period":210,"voting_power":"2955237670000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"955237670000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"500000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"1000000000000000000000000000"}]},{"voter_id":"28b67c5f1937b06eccbc9f54ff91bb2f39707e1799fcaf64473869cbf3062b91","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"885dc03ba460a7fb2b4cb2619d296e5e623897672a0ba19ba3336c15f8156576","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2bd36f58a7076d4af94d2d6b25e725aef22c5a772b541de04969217383c62d74","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"f3f597e0b900d50e3bae762c3b39fee23db58c1b83e8533618864e2c98d1eda6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e8bc0624baeefeae53d8da80510ef456ef3a347322fa094e612de773b4088925","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a19a1ea737f9f5287ab0ca8c8f96e979391af729acb181c222606f1bc5fbeed0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a971627532339ab09c7b456b773e61897ffa9de2238add850ba4596348654cca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"6cb050cfc1f21e73bfe45be82ec69b30b155fa5e4ad28c2255baafa08abfba73","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c9b78867b091ea43a1524f5981a38c0b9e1ccf65aa81f13f76674d25fa2efdb5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":195,"voting_power":"13000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[]},{"voter_id":"696b648d0abd1845b61ba7441f9eeef16c6dc7f4dd948623d7f586b152a4c816","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"649f75c5dcab29445dbdf052e1e9e276947a1ed2bbce75d961c9c1053f5121eb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f787759cd01431376c94e896a39c742d08c49d28d36c996d9fdc5f28fceeb8bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"098f722a8fefba636a5dc9c6af0982c1bee04be46d651ac54fb6995d6095dde1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"972fc0566781e8bfeec11e7daedb13d31e8bc2c75b3f347c944d1aea223b9cd7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"793eedf5b42464a1693684ccd1185d126817cc01a7b8a7d9bef3cf56862a3bd2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fujiya.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"913000000","locking_period":300,"voting_power":"4565000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"4565000000000000000000000000"}]},{"voter_id":"58acbf4b7f7b926ac436ff8e77abf0fa75b1882d493ad8dff823c2d82b7cf5d8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"27ac9fdffb06b1aa2fca9d6b54cddbcd6b42c2d64fe79da764f1d99019bbfc97","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"956ea0eb5453c89a5d3577c8a38772038429508dcf3292ca00335159ab9a4e75","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ccc4f122ff4c8a855946e9ab031afaa6ada9842ee6341a5e756256c9148478f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alumlabs.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"5d0475232d88f6137d90fbafe0de8e5f310c4099eb0871e5b3bfa36a11bbf152","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"350e470d0672079dc30e00d58fa4df938be955e8cd96c1abc4792e8a44a066c7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0x43fFbF682b61d1A8d7c3235eb724775c5d8479d1.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"919000000","locking_period":45,"voting_power":"689250000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"689250000000000000000000000","vote_positions":[]},{"voter_id":"1fc26af171760834cf7745f9f7ee4f87d2c9953154dd433f576d7fa376d4f9b9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2bde567dd1f9a173090913aaf637b79d4f6b20825e9f22adb218b4da3280d2be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"19df94eafd51c901eeb482d7842c9c6eabf760982b9180a61f23b4deae693f9d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"16fa3136a231d82be6dc4151e1b2bf5af69faede0bab9c3db89d8a61d6f3dcd1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":180,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"7bd462cf8ce8cebe5d0207b3c8877f7a5f081b44ae66869a912b6c69c2df9e31","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"03a03b9c00c17f0fad492f3c9706e18d05cf02f3170fbd695d1cea903b370976","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"e1d93d9d35a6685b89a8d490a4e44760bee71a8a011e2f76985fbe466462ee6f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f30497b3d806c7f657168bb8ec33a4076e74d20a8dee09892bc2c51e9d29aa08","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"11439a0196c4ebfa7a619a525cf6d4396f2db3b80d58f5fba7b1b67d578c30f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8047bbbd226db06e2c013691f0510252ad056d18e48e879641de313ea0ea54f3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b303dbe825076daceae57e9ad34c638366a92955f13515411f38987968a2d0c0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"publicai.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"de9ea4ed7e3c0c0c7bd94df110c3582e88bc66a7548c9af3650e4e17f97f7f86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"edf4dfe7a1b674ce90f11d5ee6ba4caf84fe5f06d2c39bfc8262ea4d59c20c26","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"590972a01161f9e9e83306a89a04018316abd177c120d09cdcd4f0e490e15d47","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c12ac397fc7b2b2790cbf98ebcac2199fdefdd17a370d845e9d6976f9ffd17cd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"134172a1fd4542da0b6775f3438b7c9f4f1d2c349653ccc50d066aaf91e0f47f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"fc19ba0b33ce4e550446c1865168b243daaf28b213e550f276094b8507a4a90d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a798ecc53a312c5e8d7dd3110712c403002c16651a9938868da9a0d25978b783","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"dcfb2aee193fb82e8d835584cfcda72b93a2b888d730ee6fc30bdf4db0b67a32","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"d412062f84c4b6f62d8636285957cf15092950cf2a64086c26f092036a234a81","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"56262872280b0d3b7b9e876a5541c0165985819cd875d780115c128433b09e64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f43f055f8f8ab4a19f1115b8f964a2714529833d464d67f25efe6c91dbd253d0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0ea5efd8dbdb1bb1164301ce7e765a3508376f0c981b0e1b4c6e31bc6f7fbb17","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"fc67fd4fc8fb0ed2f23d7c3e2652b1c05eb735d260ec1c8da825f26bac75a6e2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6ecb5bd9ff89776792cf5b67481c0bf2024da338eb3f26f8d0e51d90b29fe122","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2e7b32b069d7c2033765d124bb66d81b4536a9343b5313d7589b3c54fb8a5f64","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"54a3ba1946cef80f13f61a16985a83b37b0fa741f266e375bb31ec1622004708","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"7af7571bb8430a7a7eb36f683880a8ddc1c787a5bd6001138d16224e1f679cad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7403e27a12a2bad8a0b8b20ec58036969995dc12e4ed58746214244c323b9824","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"aef93c341b2c011c0d66c02c1be71944bf30b72bf95f7feb433365e7808a7184","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"577351b6875fe01cd21bae7a801005b9e0903e27980b2ca72674e39f96a3322b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4d429d6f231ede9140dd4036e583fba8382aa5c33b10ee0dae858b8223806cca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2c9091ab5cbe39e1b2fcb8d2d53ba835a4e3affc991876075d831dd5cda08c63","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a86e489578f1eeeb01280be825e050b3b146c6490ba294e2b7ed33bae64ef85f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"b95097c242562f7f559ba6258f702de58115f03476de58f39e7ba0b82457d3dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"60215f937d7d1f3e8e64d9426887618d7dbb0b74b4039eb419f18e62aa4d0f73","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4eb8feca6650593dcbcc6d207ebdc79e1a2c5d7ca198ca10234b7d6b619adad9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"8a9a0d6251301f67ff14dc3f975aaeb60cbcb1b65c2af57faacbc9b02056876f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"da9f16ec072ff6784975bd95355de433b474e35df547ad45bec8f893fb1af18c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7201fa1ec8d85fdc3cee2a97f753008f0c16090e7a6d7e548153c18ec18886a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e37642588eba06ca8d815ff29d634261ea0a2e000765a0f300c1313ca8f588f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stardust.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"9e689570c168c03ce867dda3ee4ca178f118613cedf24cfb37e9742d3683f170","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"30788ba6602013dc4ad4ccbc89249f7033885592a45c86ca9ba90ab6c615a8e1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"86e341955db3c5b0c257a3adc6559868f727eb7337687003957e77e9f50460dd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c45eee5daa577588ba1823b5e50f7f6d3e6e0b89aaf1e906e08915c02ed5a9fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"13000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"9e1f0e08609015ba44a98bc2bc65a36a54bbfe572f0f82af28b1c699565ce179","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"387dcaff91a38330ae4478e58f7a16aa7b1541a3a511f05312ba7d24213b4b85","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"12783c0f8b89ba5191493a504567ff911f6ed214bdb78b0938350aeb9a360790","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b96b35dbd9a9ee85c2a3253fe950c7caf1ae0354751497b4ffc237a060763f41","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"steepzip7808.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"715231890","locking_period":300,"voting_power":"3576159450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3576159400000000000000000000"}]},{"voter_id":"7bb1c442ba8cf0eda38ea75f906e64cd23faf7a553b6ca398496b83efed4a912","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6712c9215a8d4d1785eb14dcf4263fc75c83c8272fb2fb0a1cec299d5cb88dfb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f309723c8edcce5c9a5f37ff13fba9f5e94df4098e20de6e5569a70a95362561","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"a16c12180a05d927bb632d1e866ae9b44e94e306110e4ada342a53aef0a0c4b8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7d69aec229766e146f142cabf984851f7b9a245f789af16c42f19bbfbb71dfa6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ee341bf8a845be7d320cbeff65338f98705110440e96aa77bafb76fe0d231d47","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c369077e2d67dc2fa5c9ab5850f998d612b865b900d292d585381e5eb9121d88","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":165,"voting_power":"11000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[]},{"voter_id":"c592448235200dceba29176badb491ca62e88f370a9ec81211901bd911acc650","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2da8de65e1a884e8a986afadb161b3c7578cff28c1456480e6b2aa6ef18786f7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d4b5f44ba1bdf0914ca08b6b3d51276925094bc64002232f973c2658a202a8fb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d8bdd8fad8744afebe4afde79423e46893d8f07bdb008b859e5423cb36c9ecc8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"96ed22be26b1f9160d5c354897717c48f3c124d71fb530cd8a39ac1f70a9509a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"afe8f79a4a01796e071075c4c166f95310493a87ab61794e01851262ea31339f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"999539e3892eab4feba02402ae4a7da7a74f1cc4521bc64361b55f27b68177b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1da415707b8efd23d2493b054c94ed60bd412de3bddbb25527904c273586e76f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"560ce982b8e570acf9904713081dfe4800350ee79443a5e4d2a748d06a77d0af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8084bc7b451627f6220836f288ab664268fbcc35a539304707fb614e53d071f4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"60cf5f0a460867ac792a7956d2140be363e8a9aa8d1c4ee4e9dd1f8b4a474a01","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"bf9b93aad5038bcf14e8c33144e03dec59306fd7c83d4ce5e4fc8b298079dc4d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"84efea18873941aa281771c327d2c56eb5b83ed45a37c242d9616a59ad006295","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b8d00262ed76fe8acae642dff2e747798bca7b2b21613f00989bf6f3c82c1d4e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"89d6ad5e9d4f47fe272394f69d569fbc871695f06bcea749c5dd93f5c4e99298","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"eda649651fe662a9c3a3aca337e3fe29ac0855888f75ce1d83e29b94090dff37","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"263b38e1a3f0c8b958f81f7c869f21c814d39293655eb7b3523480f7e1979bac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d5ece77666570b023363ee977931da238d40f1729ead49fe2ebe67cfe58cc65d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"137c7735965909ff7e2eb54bad7496c1420691feb0b5782c8db9bc178472d344","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"74b26d6048231e35574221aec27c89199d44901da7abdd1ef1dce2c2b413c6be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7cd7853c28b29a8fd0087eaf77356eb7e42db747fd3712b3a596b33ff46eb330","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"b6bfc38230727bc1b44bec6be0978e361302cce2f46afa952f3f24513064113c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0768c53bed6e9ed8d39474cad73c9e0377423ca89ba52b1c56f6e72366ead8d6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ef7a75707bd043127d67c5c81a27ea75771ccac20d421c668a7ff42cbc274e0d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1b78269e86dd496396a8cdb03b4adbc977c8f0e6241635d3206ab8257e837c1f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"49fc2c8a18e1e483993d68e4f19d5732cacf0121afe2beabbc4bee05c6c6f755","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fdf3ce5381eca8a4da41fd3a1cd9ed6ce2b08e20cb13a2e55308cdc52b087333","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"958425ab8d1d060f38ca1aec679044a9a486512befeb2803338829ffe54d2272","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f1ca2ef54cf44b53825ac826a7f5e76c15073aa85c06913c0700618fdc1c66a0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"824218459378b259d14765d23d541e9fa74cc68db8177e76b17574e2f996fec3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":105,"voting_power":"7000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7000000000000000000000000","vote_positions":[]},{"voter_id":"18ab3f394757e517f81c79f4b07291817e4d37e1569b1288eb20ecba17ccd953","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0d68edaa380d3b43caf5e9889f01f3185ecb40f8d9ee21c5ccea8b0ff1576f00","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bd69bfdda3dac26df4f063103276ac43c6b1cc5262905c7d971ff18f9fe6f6b2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":180,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"752fa01aa18a8db4b86d41730a664be99cefb6532c5dd64371b87e0bf9d63e5d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"fa02e7b2668a034d0381660f5d94fab0d69c62882765e75a560cd31fa74f8624","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"c536fa5f98adfb984f927f9b0f77a0d52416ec38f013368283829bf19d28c48b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"62cdf59509ba9b47e163660636d8defcbde16f7cd2d1b621dd48e1269c876f76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8e0530baf3dcf3684771c3d2c07bc0a378d4aa7860d8b0c13b7bdb86d7540537","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"eb465aab58bc08ce7d3a5388c4eb0b3e575b7f5ae9cad03776d89aa3304d1e86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7f2701b349f86aae7672eeb5558a5bcd45c84429c60e53d978fc79d71dd770de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"53ea2c64d401639431eeac02c871722e8d3001ec9e1fd5a47547c8eb8ecb5f6a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"260159bbcef49f178564505cbdf3fa5d9616d541ca7d58c87c63f8f8a7f6fbf0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f981efa9a7e9bc342b60ff6567474203a422c7b84f7a9c80c76ee7ea30651b8b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b6b4573659b5ff320383a04f8e8658b66ceb358f4c2161da60ad2883bda3e601","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2186c821d615fd467182614ebd73354044947c30d3e953902b60f564fb596887","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"804b08c7d09414a38b3b57a41c63782cfb5ab8267b5e523d1e650ac981a3cfde","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2e2860e67f0d62e07467c6bdd346397624c135f05105066bb987992d05feed41","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"6000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"nearfans.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"4fb13f56bdb0966dde2483e6f46e375f82b9b33a9a758a603d5f41fdd53d862e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"15000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"cd4042d4b3c086415bc74de8b80efcf2be3e8e185b166b68cff11fa5ba005a70","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6a867970fd940fb241ee09337c8c6d261807b1dca49606098e038577e6d8d105","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"30fbd01de34e31bfd43fda0495e3cfe4d8e45840f28ec544b78532713d299eb6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"20a35f57280953e3642d3ecfc7d6f7df85db592028f1e529dedc3725bc82c5e7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"78d2fd202aede7660c902916971b77be2683f7669ef7004aadb7721189ebdbf4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9f1f64b479ba05bc9277eb2076facd1b15a00fb5ad6161a04f1d8c1e68badb97","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":285,"voting_power":"9500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9500000000000000000000000","vote_positions":[]},{"voter_id":"2cb802d5ba0a2622f5ccb8502419c4e1456529fb3c028a5918b530964aaa6d1a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"99f79d2684ce6a26812e74402f152d0c95cd84428973b99566f071bc91c35d0d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"031034bcc3bc3cb014d866ae702cadc0a7b6537a221491de7c4c5d57c5eec4b8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"949581373d9ec756de56b014799fdddc783c3647b9be19a62cd6f3c448100c3a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"8fba25e4d0f7876f16a9c38ef5394baeeb732158b4d25ee94087bdf3235b1a35","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1890c27bdfcae32cab497f36a863995a2bb54b3b60845245de07959989944c90","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f5e0720c9206ed39b9d20d97bdda945e577903f17ce2ea31533ca7aeb9c19fc5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1f06662965ca0e4403af827d12511023a2e10b7dd1dc504dd6e88ece694b1cae","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"publicai.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"1e3b8ad70d718aa9deab36e80c150ab76b1629987b6c3bf485099b7d0fd427ab","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a6d165bc9aa16995c8db9cac737567a36f76a66e0556a79b66d4f168d1775a65","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4875e1580dbf1102c5bba00ee2eb1312893917caacf2c3c9fcc2bd419fac522e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8f8915ee8749a1aa0e5864c398dea18854d15af608260d4a3bee6a75277e1a23","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ef9c686b82fc905ca02723c22ac76009661a6c64f394609895c2078a113d0227","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"56d8d6e362c65a8ccd5e1e4737cc424f08a9454a1a1db50e9d53df2efaffc5ca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9ab864312f4790f2d00a78560a62852f506e14bafd3fa35e1fb8f25b86fb79bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"46301a73c8570bf44d92497faf975ccf6c7e0c17feb655f85f06dac7efd7aba5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"6000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"5a97f35372f2da01383980937440d6ab1e1d0b64fee3186136416de95fc445dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"10000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"7023cb33e8f263f61db0fb3ccc1f6607585fb26ff7c36e1054375d567e112040","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"11000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"9000000000000000000000000"}]},{"voter_id":"ecdbbfe1e1373ab6d6908cc356d5959f2d7e96e40a1b0f9947d861603a432000","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"94de4b725023249e5e3eb9a24f41d1c1544eb7fe4e726f45d71e47ccfef20d3b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"3abac43efa7e3ec50a5b0f8c92d64b05775a37399f4027b5360a818a97705d84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"13580b4121d74838ca47d393700a4bdb73d6f18c76865552f2ba941c7aa14114","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"37c865a80639dab5cbe9092c1227909a48a97484a4ec5b9192ee690c70b3a8e5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"87a0f5f7f486d36421ee75be87e4afa880e4e07609c71aecc314a8bbc964bd83","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0b39b39be12209feb533025924e4e2b3ad03236529355be22895617e7d9643e6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"eeb62a3aa614b980537d3fe702ff8ff2b576914bded4daf5c340a3bcd1ebd113","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9ee6c7c94aaf752638b179c41b7e564cdebabb4f9d884c300cc3c4ed4251e8b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5e23625d9fb328d395e34307f31566637e7851ae3820e05e4d7be96f02787b58","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b7b68d23b5d14aef2bdd7f823e91ad0ebaf9f8157fe90d219f91350b452eb93b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5114c59696a0dad197bfff09130c75f0a6d8cbd8cdde8a6830a55f771c39d2c9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"3c9136a912b1bf7a579a9041d7ccd57e12bf568c1541a5992dbacb12e8880d7f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"d61dfa4a90c507e7a8c5b2d991b4c5d0fe899dd2b34f4aba779769bafd89e521","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"34a1d4fd633517bc2fc4dbb28b44029fa569601a44f06e7ef6e21bb11f44b69f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fdf1f8ba05276e4ca35e183f568d41fca0a6d4742edc6cebfaeda6a6741004e4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"581c9713324e185bb5e089d78c50bbce945ed5e94f50efc9510a07bf0dec92a5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"e2459c14ba6e77e8281e78b8238c733b6cae669ad5c092192da661eb124af123","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"ab8c4dbfc5319f9b55c545d16f72874720779b13a030c2c96c6e3f04f876ed1f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"4bc6c6e7277f3e0c7b959fc1c471e611f643618630e6b3d6f8df4955e22a6ff3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"0da2b63c2fe5cdaf30785b82fb978aca1f514d9db27eb530132639d29f64e2c5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fef5f6439e828315de5556a5a0408d9400a9c96b5c66b90552ecf02b51392d84","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"9045ca0cb82c39b15b17695fc3581a7e57bf5e3769911c6160bdd3f2cb7f9fe2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":135,"voting_power":"9000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"9000000000000000000000000","vote_positions":[]},{"voter_id":"35a6d521fa6db19b65310c9b60f7b914bb71ba6dc05a8ecb0e27ff38140704b0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"lonk_validator.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"51071377511e5865511fe3046dc4915b0297672ede791ad30b6a2cf7bc3e6102","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":120,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"94138351780d90d6b187b398fec24d7535d9262fc1edde2512e46c5856bd4620","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"fa7cfb6e948a7d200b705be5aa210c4f6edb321b7c40277256809b9ad4afd5bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"0a8ced2c030aaf89deff67cb9171026240c9383897ace6bd23d5a796d2de1c3a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2d8fd5f7bba3be0a401c5861667b084761f34de4f5ded9c46711eab184cdf47b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"25f57c3a25a706fbdfa7264329b5e13313829022adc517c05da849872b86561a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1c07371ab41c25d020b1be540b708a0c343217ed2b414e3821c1b455081cb3fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"de0f71396d131cdd51b4213678b39fd578a98d3462ac27c76bb4a2af8d74d77c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c6ea0d083074fc708fdebd97f0e2a50be09ec0d4cfbab958e0cf5fb7490380bc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3382657000","locking_period":150,"voting_power":"8456642500000000000000000000","unlocking_started_at":1728737361510,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"fe6568ec104e0b6dd1b2da488675a79e562591b302e3364daaa7eef9e2dd2e98","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"bf1cef6dea2de6d4246d4c4d106fc2a5d0de432b559df59e035a117213f40a79","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"33e5927e9b0b9bd5b6d22aea40f133f4fe4ec6ac2d0c2354bd9811886ab173a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"59027400a26e105fb95bacd5ddd00352494cd080d631f05595c4550870367f9a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"89504bc30ce010a1b7a2a4d8a5c4efec79ceed68efa5040853928cf4966eb7fa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d7fba1b66a6da1f2af3f99fa27c54aabf6efa70128371b4488a39bdf159e07f8","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"63cc2a23bd4fb0c9fd0802873d6536204962dad14796b4295e9f6982c364a564","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"3edeba67f78436e19beffeb44d6a488905cd1df879dd62fc05a4a4e5ebb39562","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"6e8ba11c5ff3781a5b0dd8c51765104b8b041522b34fa8ab112dda618af9d4b7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"20d1f4d9b7a25d85f417d936ba78a1f60536d88e809eff23e00dcfa4931fb07b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"77770326000cc3b7971ea022d6454111ede7a8717a65dd9ec961dcd90b896893","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"57fe52ce5e1004ba77ec5e717c04a8a774144ba08b6488f95cae80721a5b95a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7412340cf0eb340eea19cc8d7535bdfe13d07f058d7fd129572a1550368079d2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a8041d4972bb95b130a46628c3d3723d570d15e7447ce953bc32775cce0572a9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"035ed71d46ba30594e7885ccea858f67702aa6a41fe6cdc42c3146178df56efc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5af22d303baead509cb1bc62754d9b74645c1c7a6cbe150de27d56243208ed37","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"414aade1d9106e37b238b11dbc2a96c1cacad6a1561f64deafd4c6494afe106c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":285,"voting_power":"19000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"19000000000000000000000000","vote_positions":[]},{"voter_id":"ac559d22697b15561e132848e8bd77a006bc19cbb63d9bea8de7938d481a6872","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"06641af629600fe2ed817b077cb4a51f7236a2291a76f60eb5f30c471e98d4fb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"ursamajor.poolv1.near","voting_power":"6000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"88ceffa65fc65b12d07c9bbdaf6997c4119222262e6dfa51811a3b7110a840b3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"16000000","locking_period":300,"voting_power":"80000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"80000000000000000000000000","vote_positions":[]},{"voter_id":"d8b7bc5623e24b15aa33667faa021ac7be40b0fce636fff149e745f03668ee93","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1e6338d7a9e6722c322b75aa58ca739ddc93f84fcbd6faa8b2908537ee661b34","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"621874f42dc8a92b03b96c7215d139a371733b5e112ba81c2095a3d9cb742db4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"ab05985f59778516e33410b4e903c10f5900ed8f368a5df3c4bcce09e2c2b4af","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9b0f029772601a97ae8911f1986381273406d7539a084ab2a9dd05070cb8970a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":180,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6000000000000000000000000","vote_positions":[]},{"voter_id":"8345d207889673d2184cb3d43bc454cb1309de9771748feca8660695f09d650c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"7c8df5a614dd03bc7032e5f8cda085bd8fb7949b07f634c7b7d5bd8d29e752c1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"2c4dcd85319450df564a5398a4ff540d4b29bbac9d3d685f191f2c1c2609961d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e2893f359d80129b110dcbaf642616415ae69479a598f999c3773795b87babb2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"171b60a93b2b6ec36d23dd4da4eb2db7be12fd9e2a7a9c817f1f57d548236db4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":300,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"6c6bb3046b593fcfab01023ecb4f0949e661ef6a9303e6f1feab7b046e67774d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"25c9134f7fdc3beb5327ba1736f036c134dea5b38b932c5152617cd6da0ca8f5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"37960c14e445567e14d8d95aa6482f0c83c346d4e470188fc52def85c6d0f40e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4919083710","locking_period":300,"voting_power":"24595418550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"5100000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"5000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"delightlabs.pool.near","voting_power":"14495418500000000000000000000"}]},{"voter_id":"05ba16092ad6e4511830472822fd24054ce78298345a6b3f0d0d81c718f75f3c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"a53dd4a9273b537996d27adad820f326d7c92247e73f72e5ad4c950a0b2a5fac","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":165,"voting_power":"11000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"1000000000000000000000000"}]},{"voter_id":"3cd7e097c91412aa03ced89ce8999bccef522f8d3c7dad9708be10a1db75fb14","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ae9993c3f4220ae580f93996814590c5870beb55b8f20c826ca3a01fe94cc665","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"0fffe694b17604c525344045af2325da740b67f3e4dfcd102b8b284df74aeeb6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"551744580","locking_period":300,"voting_power":"2758722900000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"2758722900000000000000000000"}]},{"voter_id":"3f2bec6ff023a1e8af5dfe4475ec1e4565bbf03a4202e846d22c806869d038f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"7cc0696ae18072b77576bb98ce21b2379cbc1786d090abc80074087f4799f1d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1d736861fb23a12aa8bf291bf61dfb4025e18290be64dd2dfae7d17f1ded25ed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":150,"voting_power":"10000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[]},{"voter_id":"bb7d6b1b86a99158217e0acb447f7aed871a347b2eb9221b35d181238687d525","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"650b182ad2a34abfe873c85d87bb48aadac471088d4f04cf9e0ee113a2830689","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"2394aa987d720b6c6d7a550827bee05f7ba85bd637bc398ed3953161ba4e48e0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"16cc9c22353175639bb477993e20bae2a8d44f10ab8b7cc8f665fc4f1591b5c3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6d033ef4039162e42bcd7cad32394062302871d61fdbfa66eddd4a7f8f1f676b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b409b131f7f52e47c8385a155d76f121cfdabfff228160d7e661234d9bea7d3d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"c5aad27fec4519a12ba439b16edb6cd19a10b2bcc7b08ef439fe21594582ff6f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"184645ab40ba5fa18b77067d400aa8490f1bf882ce0afc1b7cd14d1eda6e73c6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":210,"voting_power":"14000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[]},{"voter_id":"5f01c5eb8fe66b99991a9563ba36d94ec41f61a95c0877bd2302aaf60b55dd55","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"efcc44df4ca7f33fe34c2d46b6b2cfa5c5c36f0d3c57f3641c76436e8165aa86","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6a92e3b7373bcc54cf305416ebb8cd0f48ec0e7179ffee8128a11ec93d0548be","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"5000000000000000000000000"}]},{"voter_id":"9c5e7f96bb77be519a14ec554a61186eaceac2ad3ad10c5f1252edad5f978b26","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"d51eadcef6e8fb8ed68a6957bc63a30cbb333b42c18feda1091a5949909b44ba","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"da79b5075aaae16925b7015687d9eaffe75b463254f779b5f9b4b066e479aea2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d4d240926943f46a61233aef6de9d2b04113f564a4aa71b0fa1ee9a2ddd0b6e4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5e164fbe9eba8b3840728deb3dbc21d182cd9222185664ef6b131f5cbc23a7a4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12000000000000000000000000"}]},{"voter_id":"6fc798485e717774ca4190104d5b2d8436816db202893e5705ee20122840a646","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"7ac59c6aae125d1099cb4a7532654dfd5820d3308d838019e869fda570d96c3e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"528034cd9a2d62d025a5a598abc77f0ed56ac42175690557649fdd04861bf960","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"3970c9eab77e1fc90680cb655a723b8e74f72182aea98ab128c174be97dd0900","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"108888faf531d77fccef1dbface1598f5a8612930b8ee30d73f8e6750f8285d1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f4d9b9891a8a150815f0f4bb34820a104f95b4f0c7cb5cf5b4baf91272522f23","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"01a51432d2841f2ae7d47f9c3341422358147ad854f7ce9fe80d92adabd9d28b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e59d08b13cbc0026c3f0679bc531eebea9676feffdd1e18d17a8b8bdbc0a1717","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"378f71d7f63cdabf2bc15008797421cca742027a51a903b18d13c8d08e622669","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"nigelgrif.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"11844140000","locking_period":300,"voting_power":"59220700000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"59220700000000000000000000000","vote_positions":[]},{"voter_id":"29d628055d4bf4e1d4dedcbfcec782413396ae39647965a4a3fa6a4920f5063a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"560242f95b95efa493310a4cf4817a01de68dc7255bf6a5dc1c888a64908ddc4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"c1aecc4eff6a258e393fe225bee9440fff222b5964b9c03389f414211bf7923a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":60,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"4000000000000000000000000","vote_positions":[]},{"voter_id":"88564614e6efc575a5cd4933eeed5829fd3724a9d315736b12ee9df4bc3e2155","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"184e7bb7c10a86a7053682efa687617f91cc5e61bf140d0f2099bce6587134fd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"d309b4ca65180b2d16c832d93189717e2324da29b866cdb4a884fc2244c8c4f6","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"b3e2069deb7eb9e5153b1d4646cae13aee9d58aa1bd224f9ff232fa78b1f00cd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f6a41f0d45f63a1ab91cb68edc872a59b399fbe3abc211c567a3b67d312952c2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"c2043019d508016033dd677dc09ae75e158d6a99a1370949296a4597762e1884","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"219a80486953e2c7b106d34d71ef6e727a69e85464cc8e4c80395777317a4ae7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":165,"voting_power":"11000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"11000000000000000000000000","vote_positions":[]},{"voter_id":"0xfA8856FD322c1EFee9268aFE1161B66D55fE000c.evmp.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"981000000","locking_period":45,"voting_power":"735750000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"735750000000000000000000000","vote_positions":[]},{"voter_id":"e708da32a799ae3d1c4c96e5430d367ea1cb0ba3e8ce333e0c712ec230964c7c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"370621690","locking_period":30,"voting_power":"185310845000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"185310845000000000000000000","vote_positions":[]},{"voter_id":"f18c2a44526e39ed4a0b15e3a89cdc0e7a1f72c5d450fd296ab805b775164f7d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":195,"voting_power":"13000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"13000000000000000000000000"}]},{"voter_id":"3152f891de86d40d96afdbab8fd744335f87846184cc1ff80ec5182b6e5f91cc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"64927b85224261392645eed5e16d343ab702a0ccf672900fa45705ccc7fb53bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"5dc1b7be64c2ac012ea431949927d7b48c5b3fefa0ee4f2348fdc2fc54435596","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":120,"voting_power":"8000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"8000000000000000000000000","vote_positions":[]},{"voter_id":"5ee1992e99dc4ceb732beb6d2aed958d5c855c4576a21483714cce314333322a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"134b23882c7fe059244a893c686e950d6d3af6cfc8426cf9adca8bd4c59e3958","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"mjt69.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3670430779900","locking_period":300,"voting_power":"18352153899500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"6000000","locking_period":300,"voting_power":"30000000000000000000000000","unlocking_started_at":1730837665884,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"dragonfly.poolv1.near","voting_power":"18352153899500000000000000000000"}]},{"voter_id":"3bcae1f4bcad5fec7fb44a94eeff3906be58d5e7785fe9fb000d8c2ae30b4867","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":255,"voting_power":"17000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"17000000000000000000000000","vote_positions":[]},{"voter_id":"daddf0a3ff32bdd992b6f90acd1e076d44de96eae317a2492d7a854952783912","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"7fdc9ec27bf1d33019d874549a74a76475c9f91af4748a3ff7f93d2107465398","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"909155dadd03ed49a0e66b374cd2a6bba837cc78de148e301bfb157e80638238","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"55065500","locking_period":270,"voting_power":"247794750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"mexa-staking.poolv1.near","voting_power":"247794700000000000000000000"}]},{"voter_id":"8d81a5be73da5bb81bb3972bd4ba62e9f3f6a46ec90e9b01c37f942e4e49f837","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"302ccccca97fbbfe0a697b79ac4e4d5c133b2070af796815ef8feae1446614e7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"14000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"readylayerone_staking.poolv1.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"9122724b26d2a1a75e3a4bf34d6f731fdabaaeb9ec475f8e929bbba7087356bd","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":75,"voting_power":"5000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000","vote_positions":[]},{"voter_id":"577a44eb1445093838f070248c81e7f04bf601c8e6aa5aec674e35ae94599fe1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2f7aea4394b33e1c9618f872683e09359304444896780550a761e515bad2e545","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2dc84c98ae6539130b6fbbbbab28d65bac75d036f0c659d53ed64a0b56af9aed","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6966534938.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1554242310","locking_period":300,"voting_power":"7771211550000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"7771211550000000000000000000","vote_positions":[]},{"voter_id":"91aa7ee5bf80249914b3eecfffaa9ac5267fb637e0436c9e2016442efc2d9f6e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"cd8c1ae490113790271e545682861bec0e2fd678a9cf683a1b84637d5ea958b4","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"72e33ae6636584d80229fe602cee4a84e75bf94c465933673c967ab585422925","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3000000","locking_period":300,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"8bfc4bf88edbcfb31bf166a2bc6b8a1c380cd4c1910fb83ac9a11d0db96b4e3a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"1f4d03d56411be001311befd5bcfdaba2ea86a8a2e6f19d458db45e506dda4db","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a2b16784d1c8857f1b105f278aa47d1849bad39a3b420135da6fdb8398667279","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"fc6f5994a75fb16551d77e38327deb088cd393ac2f9bbd1810e7a25a8c620bc5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"12458050","locking_period":300,"voting_power":"62290250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"208522290","locking_period":180,"voting_power":"625566870000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"62290200000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"frensvalidator.poolv1.near","voting_power":"625566900000000000000000000"}]},{"voter_id":"53e9e63b63e056388ab46cd368719bbe92cf75426bd82d847740e0dbb238de7e","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"6f2e93d7468ed83f9ad12c73070058a240880cd6abf8e7cc832c477d19f729dc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9c2ac31195864e97f16efa47a80bec780ecf5c80113a3d0f048208ddb45c6982","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1653ffc74b5114af4fa7dc65b5dcc3593572237b538f79a634e9d50167b17a4f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"95cc99e6c34be962b799a4c57147ec021b291d0dde4c4eba7395fca3a224312c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"486f2b50286a9a9ceec8c117b8a4f4257abf54eb6227c7372b97193e6b57ee82","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"66b50516e9b81a6bcefdcc77b7252e9efbe75a7ecbe5547b12cc32a2d2b2340b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"da6a0b642ec2cc46616d614beeba0619d279d613f7483cda5817262663e16684","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"883e6dfbdb97b9fb819c77bf08e49939d4e8ad8077a412a007dbf14b09db8ad0","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"18d80f3cba5e9cdb39f36be8b82d9f5b329c766a4a1d08ea3c139a044c8b9f76","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":225,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15000000000000000000000000","vote_positions":[]},{"voter_id":"5d562b8c5f219bc004a4589f401bbcdeea1ca49f774f82b01c11db80acc74fb5","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"32773ef3354c91de4210999243c8a3959945c5aabe5883462d34136d3687c5d2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"4000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"4000000000000000000000000"}]},{"voter_id":"fe9ac63e88231099c3109bda7efe172400c4a26d05a5184d1fa55bfb023dfe08","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"2fb66201ca05646d7ab673879494c6b41f6edf9bbfb815673ca6178450cd0109","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"46c0713da081f4b5ab7342957c4e8839cf0ea844b30bcd2e503aa41ef35498cf","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"147991100","locking_period":165,"voting_power":"406975525000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1975525000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"400000000000000000000000000"}]},{"voter_id":"enilsom.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1725019780","locking_period":180,"voting_power":"5175059340000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"40000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"08investinwomen_runbybisontrails.poolv1.near","voting_power":"1753818300000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakeseeker.poolv1.near","voting_power":"1421241000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"2000000000000000000000000000"}]},{"voter_id":"220e66eb1fb826c83392fb45ca200fd5ae880923d3d6a946b4a07a41c335ec15","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5386b447cc3f36ba9a527af864fd408791869d9e31240b6e689969980403b8d9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8f130f1775efdfd281068bc34b265eaa8fe4d5f3467549f0649b71a6aa559f9f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":270,"voting_power":"18000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"18000000000000000000000000","vote_positions":[]},{"voter_id":"932fe9224879bcc29d24259ed85f3ee1054c09f6125ca4b22092fa3fac8ce4cb","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e1f695bc9386ef9aac1458b2ef77ebb707c12d16bebdafe76aac4b147cb035c1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"aca2e0f014d3c569ac0a42e2b756895698f79bc913304789f977ca4b1536bc45","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"319735156850","locking_period":300,"voting_power":"1598675784250000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"1598675784250000000000000000000","vote_positions":[]},{"voter_id":"90e6e2155af17b1ce2b657725c45e073d0ef4fc3c7732ec807a1f984358b3f30","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"f0d414f84a5e73061c3a13957acd21a31482204828612ac7efcfa7a4ea2ef874","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e40d87f3438933b047637ed4496d2aa3260278b8151fa94facab221870719c27","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5687f214ba1fde7c53f3fd3425a8cac6d262d9a5348f17b1fd35f1868939c5de","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"53d78a5bd2aef5e1eed24f4650f34e56950e447a807442df2def1710fed7fee2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"kenniii.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"156234490","locking_period":300,"voting_power":"781172450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"781172400000000000000000000"}]},{"voter_id":"a27764aa0c4e1d958c8aa4daf47e82f660978534f272f7f7880633fd7a1da55c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"2000000","locking_period":195,"voting_power":"6500000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"6500000000000000000000000","vote_positions":[]},{"voter_id":"269d1203a58d059e0de5dd8239471e5dad46395161090783c0ea8a0fee506a6f","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"d6393612a2ea1a363c2f2576941158fd3d76043c6fe295dc5e2ccb44c44c6b88","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4cb0952702011c95172829519f932a380d1403ce527d168a10a81649b511caca","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"8cc6f47501992c8a7b6a95bffbb8c77f63abc53998855136082003fdd97f62a1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cf391844586e97208c649fc529a5ca577b92fb4a704de2713c6113cabf9a7fad","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"be18b977d8b955f25b891bd48a15148f1487d2d854b63e3115b0385235be77b7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"rawkilt7666.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"65378236800","locking_period":270,"voting_power":"294202065600000000000000000000","unlocking_started_at":1730202185556,"is_unlocked":false,"is_unlocking":true,"is_locked":false}],"voting_power":"0","vote_positions":[]},{"voter_id":"1997bdb4cc313efbc55bea1914f9198c744fb7648ebe442aa1638afdc8d72b72","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"drocacoin.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"6112112060","locking_period":300,"voting_power":"30560560300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"30560560300000000000000000000"}]},{"voter_id":"brightport7464.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3121690130","locking_period":300,"voting_power":"15608450650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"15608450600000000000000000000"}]},{"voter_id":"a8b1b21c2831afb89191bc164055d3d4e1794a62e88f2bbee3bb0c57d134d435","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"5ebb32c260ffef7481fcd1b2f1366e53ca89ad0d4509079af2b6644384276c2a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"1e4a9edbd3e540e98e556ec8155c329f0614d7dd36019553ed98ca9b16504492","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"b703b2824c63ce4608b4296edd99c5cf4453f6263efcf83941961fba18649daa","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"5879789e575b68a1473e603b1fdd3b6677e294f9ff47166370455eeeee44ccbc","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"acda0f5435d49bac335cfa2d05562d5d47aae55ce752056f6f59a8899587e98d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4fed5e6fccd878ce58f5f9d1667eb2b373f2e3bf14138d3d157e65fe762a532d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"lavenderfive.poolv1.near","voting_power":"1000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"5000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"everstake.poolv1.near","voting_power":"14000000000000000000000000"}]},{"voter_id":"6293d69adfd8894703a6cf9e80909fd15ef09cb8b24bf1826a329e27d0596d00","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"a85dd91380a1b4600f7acb1746d9c1a20289e14457fd5b30d1788690ac4ba978","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"28421d4193047c854916881379f8d449a5cf4f761d0cddadd7f7724a1d8c089b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"f79a4b8cca5dd5257f4eb4537b6de3668b318470784c936786b42e7e1a8e0513","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"4af7f238d0d22b44423e3d5886e6aaf28ebe76434c5a2b1871a22b4d3be5fb7c","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"cesarminoru.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"28683910","locking_period":180,"voting_power":"86051730000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"86051730000000000000000000","vote_positions":[]},{"voter_id":"5b1bc6c9e87ae7ae1848f1c2d027a8d8b8ada0def01fba2d43fdf50971c70867","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"4961f61c155ed31e8f3b4b4e8566cf0030dc7fc6f984f805345551fdafe4f8a3","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"b2812b94059352e0ca4ce3f43e929abe82a7d7451c80fcc1821a29a229a963e7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9c35b806b1a6239c6e8fdfc0aa3330253aab1d18a1ae847ae8fb9fdde426cef1","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"21974e7bb61a3b60e16fd97cdeeedb57f282901eb89e0cb13f7aaf8b2435348b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"canvithien11.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"277413650","locking_period":120,"voting_power":"554827300000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"150394140","locking_period":135,"voting_power":"338386815000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"893214115000000000000000000","vote_positions":[]},{"voter_id":"ff1989020b32bd12221fddd5c4390c24825c10d292594ca68dc8b268affca3ea","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"a4c96786a0f47deca5588e93039e60e1c4384aa3cc7aa1c3c33c506ea354bb01","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"9945933c2a0449a33c7000b23f25662e42b6cf198985db54caf75fcfff679d29","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ac861cd64e8e0e9ec5d2b0fcce6c7805b635bd5c2ed23517f5fe330b236ad3a7","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"ebb3e4d66c8df11e917886ba3e9158387282750b5e0ed341be88276ab904cca2","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"20000000000000000000000000","vote_positions":[]},{"voter_id":"d2373b2e6a5738007d57e233082ab47042fe47c70df0c8c421d3c99a7ed5b666","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"9dc15b9c447653923e7f08570adefcefef774ef6c94a12b7013c6f8d5c434f0d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"e718f141c6b4ce9d6f033f9512df3a9e59c4d1afed0d20e150de58b6c59a8966","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"a53049b9a78da7b5b355e580523301fd4d75f2fcb963d53e6901bdea7bf5f301","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"12000000000000000000000000","vote_positions":[]},{"voter_id":"gustavo_paulo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"46513950","locking_period":300,"voting_power":"232569750000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"244569700000000000000000000"}]},{"voter_id":"3990bf7e9e4dfe600cec1bdcf2334c169a4885923aee77b7b86d44c16aacd415","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"thepool.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1166667000000","locking_period":300,"voting_power":"5833335000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"nearweek.poolv1.near","voting_power":"5833335000000000000000000000000"}]},{"voter_id":"c186d04bd5c9669566e982f67830b1449a3d7097265090f6cd0b8588ac48207b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"2000000000000000000000000","vote_positions":[]},{"voter_id":"criptodru.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1624186620","locking_period":300,"voting_power":"8120933100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"8120933100000000000000000000"}]},{"voter_id":"omolayom.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000000","vote_positions":[]},{"voter_id":"vitallibrary6200.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3068492500","locking_period":300,"voting_power":"15342462500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecito.poolv1.near","voting_power":"15342462500000000000000000000"}]},{"voter_id":"b667deeff327766f4d87495b3bc42ac775bab6d9b4fd9e148c6f7bcb3867ab87","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"5000000000","locking_period":120,"voting_power":"10000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"alphanodes.poolv1.near","voting_power":"10000000000000000000000000000"}]},{"voter_id":"85ee65913d3c1b7204775d6e7478de90973b8f606e0193446df5467210d7849a","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"628717730","locking_period":300,"voting_power":"3143588650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"3143588600000000000000000000"}]},{"voter_id":"nearstreetbets.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"75000000000","locking_period":300,"voting_power":"375000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"astro-stakers.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"cryptoblossom.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"115000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"namdokmai.poolv1.near","voting_power":"50000000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"60000000000000000000000000000"}]},{"voter_id":"vvr.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"53675901290","locking_period":300,"voting_power":"268379506450000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakin.poolv1.near","voting_power":"40709080700000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"227670425700000000000000000000"}]},{"voter_id":"pedrov42.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"591926520","locking_period":300,"voting_power":"2959632600000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"2959632600000000000000000000"}]},{"voter_id":"ab9036e2eefd2084daf95ee1647f7ba1dcf5f61367d86c96dc63907b766568e9","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"101492900","locking_period":180,"voting_power":"304478700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"304478700000000000000000000","vote_positions":[]},{"voter_id":"nuevanear.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"40921210","locking_period":60,"voting_power":"40921210000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"30000000","locking_period":30,"voting_power":"15000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"23000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"piertwopool.poolv1.near","voting_power":"32921200000000000000000000"}]},{"voter_id":"p0k.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"53099448500","locking_period":300,"voting_power":"265497242500000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"sicmundus.poolv1.near","voting_power":"265497242500000000000000000000"}]},{"voter_id":"djonecln.tg","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":30,"voting_power":"2000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"antokcombine.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"975580000","locking_period":300,"voting_power":"4877900000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"4877900000000000000000000000"}]},{"voter_id":"luisnet11.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"138470000","locking_period":300,"voting_power":"692350000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"692350000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"spectrum.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"3380805fd17c737fba9948ee8dbfba8a4de77c1897ae9810f4da098dc6cb9721","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"142470000","locking_period":300,"voting_power":"712350000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"444300000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"zavodil.poolv1.near","voting_power":"20000000000000000000000000"},{"votable_address":"metastaking.app","votable_object_id":"08investinwomen_runbybisontrails.poolv1.near","voting_power":"248050000000000000000000000"}]},{"voter_id":"near-africa.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"1000000000","locking_period":300,"voting_power":"5000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"5000000000000000000000000000","vote_positions":[]},{"voter_id":"juniorfigueiredo.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"8000000","locking_period":30,"voting_power":"4000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"138470000","locking_period":60,"voting_power":"138470000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"142470000000000000000000000"}]},{"voter_id":"oliver24.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"soundglove7665.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"147397820","locking_period":300,"voting_power":"736989100000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"716989100000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stardust.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"elliotbraem.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3071971400","locking_period":300,"voting_power":"15359857000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15359857000000000000000000000","vote_positions":[]},{"voter_id":"chemtrails.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"33798600","locking_period":300,"voting_power":"168993000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"shitzuapes.pool.near","voting_power":"168993000000000000000000000"}]},{"voter_id":"arely455.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"autostake.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"rennyoel.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"142470000","locking_period":300,"voting_power":"712350000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"692350000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"maximordm.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":90,"voting_power":"6000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true},{"index":1,"amount":"118000000","locking_period":210,"voting_power":"413000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"413000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"staking4all.poolv1.near","voting_power":"6000000000000000000000000"}]},{"voter_id":"fxws.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"336517704200","locking_period":300,"voting_power":"1682588521000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"oe.poolv1.near","voting_power":"1682588521000000000000000000000"}]},{"voter_id":"rafuch00.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":180,"voting_power":"12000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"stakecraft.poolv1.near","voting_power":"2000000000000000000000000"}]},{"voter_id":"2413761283c73caf2e62f375a0ac0cffc09725d354e3c5c75c5726f54f076a0b","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"20000000000000000000000000"}]},{"voter_id":"say_sarg.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"122680000","locking_period":300,"voting_power":"613400000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"613400000000000000000000000"}]},{"voter_id":"holobit.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4000000","locking_period":300,"voting_power":"20000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"10000000000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"allnodes.poolv1.near","voting_power":"10000000000000000000000000"}]},{"voter_id":"478f0f81f81824a1db9f1c434b73e2c800e2992069a2d0484d050a86406a4402","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"4865974730","locking_period":150,"voting_power":"12164936825000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"25000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"12164936800000000000000000000"}]},{"voter_id":"bagrera.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"20552177730","locking_period":300,"voting_power":"102760888650000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"50000000000000000000","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"luganodes.pool.near","voting_power":"102760888600000000000000000000"}]},{"voter_id":"233341a07296a79ed5bd7fe548012c1821ad4823277fc6a2a43bb49fa9ce6665","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"14585095940","locking_period":300,"voting_power":"72925479700000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"owa.poolv1.near","voting_power":"72925479700000000000000000000"}]},{"voter_id":"anarchychains.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3500000000","locking_period":120,"voting_power":"7000000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"colossus.poolv1.near","voting_power":"7000000000000000000000000000"}]},{"voter_id":"irambellum.near","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"3058000000","locking_period":300,"voting_power":"15290000000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"15290000000000000000000000000","vote_positions":[]},{"voter_id":"ec2c2274600d00c62371b688cc5b21c46155648b34b32aa3020d6e6c4e18d83d","balance_in_contract":"0","locking_positions":[{"index":0,"amount":"319464000","locking_period":300,"voting_power":"1597320000000000000000000000","unlocking_started_at":null,"is_unlocked":false,"is_unlocking":false,"is_locked":true}],"voting_power":"0","vote_positions":[{"votable_address":"metastaking.app","votable_object_id":"pandateam.poolv1.near","voting_power":"1597320000000000000000000000"}]}] \ No newline at end of file From 23828c8badcd9e40b0185ca0a1c5bd62751b3216 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 10 Dec 2024 13:19:31 +0100 Subject: [PATCH 332/454] discern similar contract methods --- indexer_app/handler.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/indexer_app/handler.py b/indexer_app/handler.py index 48a2eed..c620820 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -188,10 +188,11 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess parsed_log.get("data")[0], receipt.receipt_id, signer_id, "NEAR" ) if event_name == "vote": - process_vote_event_async = sync_to_async(process_vote_event) - await process_vote_event_async( - parsed_log.get("data")[0], receipt.receipt_id, "NEAR" - ) + if receiver_id == settings.NEAR_GRANTPICKS_CONTRACT_ID: + process_vote_event_async = sync_to_async(process_vote_event) + await process_vote_event_async( + parsed_log.get("data")[0], receipt.receipt_id, "NEAR" + ) if event_name == "payouts_set": create_round_payout_aync = sync_to_async(create_round_payout) await create_round_payout_aync( From 734d659913fbabb83296ff9690a6e43ba9417c83 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 11 Dec 2024 04:49:54 +0100 Subject: [PATCH 333/454] fix project stat --- api/urls.py | 2 +- grantpicks/api.py | 26 +++++++++++--------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/api/urls.py b/api/urls.py index 6d86f37..8cc222e 100644 --- a/api/urls.py +++ b/api/urls.py @@ -149,7 +149,7 @@ name="rounds_applications_api", ), path("v1//projects", AccountProjectListAPI.as_view(), name="user_projects_api"), - path("v1///project-stats", ProjectStatsAPI.as_view(), name="projects_api"), + path("v1//project-stats", ProjectStatsAPI.as_view(), name="projects_stat__api"), path( "v1/mpdao/voter-info", diff --git a/grantpicks/api.py b/grantpicks/api.py index 9356c63..d3c223a 100644 --- a/grantpicks/api.py +++ b/grantpicks/api.py @@ -335,16 +335,11 @@ def get(self, request: Request, *args, **kwargs): class ProjectStatsAPI(APIView): - def dispatch(self, request, *args, **kwargs): - return super(ProjectStatsAPI, self).dispatch(request, *args, **kwargs) - - @method_decorator( cache_page(60 * 5) ) @extend_schema( parameters=[ - OpenApiParameter("project_id", str, OpenApiParameter.PATH), OpenApiParameter("account_id", str, OpenApiParameter.PATH), *pagination_parameters, ], @@ -369,24 +364,25 @@ def dispatch(self, request, *args, **kwargs): } ) def get(self, request: Request, *args, **kwargs): - project_id = kwargs.get("project_id") owner_address = kwargs.get("account_id") - project = Project.objects.get(id=project_id) - total_donations_usd = ( - Donation.objects.all().aggregate(Sum("total_amount_usd"))[ - "total_amount_usd__sum" - ] - or 0 - ) + # project = Project.objects.get(id=project_id) + account = Account.objects.get(id=owner_address) + + # total_donations_usd = ( + # Donation.objects.all().aggregate(Sum("total_amount_usd"))[ + # "total_amount_usd__sum" + # ] + # or 0 + # ) total_fund_received = ( PotPayout.objects.filter(paid_at__isnull=False, recipient_id=owner_address).aggregate( Sum("amount_paid_usd") )["amount_paid_usd__sum"] or 0 ) - rounds = project.rounds_approved_in.count() - total_votes = Vote.objects.filter(pairs__project=project).aggregate(total_votes=Count('id'))['total_votes'] + rounds = account.rounds_approved_in.count() + total_votes = Vote.objects.filter(pairs_voted_project=account).aggregate(total_votes=Count('id'))['total_votes'] return Response( { From 235489a448ba17f8eb5f9641fd68081a172c0074 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 11 Dec 2024 04:55:12 +0100 Subject: [PATCH 334/454] fix filter param --- grantpicks/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grantpicks/api.py b/grantpicks/api.py index d3c223a..45c0219 100644 --- a/grantpicks/api.py +++ b/grantpicks/api.py @@ -382,7 +382,7 @@ def get(self, request: Request, *args, **kwargs): or 0 ) rounds = account.rounds_approved_in.count() - total_votes = Vote.objects.filter(pairs_voted_project=account).aggregate(total_votes=Count('id'))['total_votes'] + total_votes = Vote.objects.filter(pairs__voted_project=account).aggregate(total_votes=Count('id'))['total_votes'] return Response( { From cd7b9c4a9de619658fce5e5894d0cfa1892431a9 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 11 Dec 2024 06:15:06 +0100 Subject: [PATCH 335/454] change stellar testnet contracts --- base/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/settings.py b/base/settings.py index 6c1d643..81d1fe0 100644 --- a/base/settings.py +++ b/base/settings.py @@ -64,8 +64,8 @@ POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") # NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") -STELLAR_CONTRACT_ID = "CBMCMOS3KX2R3AFOG3SZFPOFSX2ASMIC3TWF5FTIQTQSLZ6G3RUBQUM5" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") -STELLAR_PROJECTS_REGISTRY_CONTRACT = "CDOR6NBGSIE6ARJZHRAJAEDN2NG4QS67LSQRMJETVX6GHEJELLVPXWJQ" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_CONTRACT_ID = "CBEW52FEWIFWK4LCSMX5FAAG2MYB36C2RLKS5YEIF5AOJEDMCGYU3ZGB" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_PROJECTS_REGISTRY_CONTRACT = "CBG2JAGBYUAM3YLQFNFR3ZSWVR7XR5BEQHQ6XGUA3HCWVCHWO3RS3YUD" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) From 82c1bf41d3f592319e2bd4bdfc11c9166f40fde6 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 11 Dec 2024 16:30:35 +0100 Subject: [PATCH 336/454] change static pub key due to testnet reset --- indexer_app/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index d4f68d7..19e5ca9 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1424,7 +1424,7 @@ def get_pair_projects(pair_id: int, round_id: int, chain_id: str) -> Dict: contract_id = settings.STELLAR_CONTRACT_ID function_name = "get_pair_by_index" parameters = [stellar_sdk.scval.to_uint128(round_id), stellar_sdk.scval.to_uint32(pair_id)] - public_key = "GDRZ47PQ43TA7GCBW22HHRM6FHN644KF23HFNZ76I46HPNBD5Q7YEYLJ" + public_key = "GAMFYFI7TIAPMLSAWIECFZCN52TR3NUIO74YM7ECBCPM6J743KENH367" # TODO: move to settings acct = server.load_account(public_key) pair_result = server.simulate_transaction( @@ -1463,7 +1463,7 @@ def process_vote_event(event_data, tx_hash, chain_id="stellar"): round_id, vote_data = event_data[0], event_data[1] else: # vote_event_data = event_data['vote'] - round_id, vote_data = event_data.get("round_id", 2), event_data['vote'] + round_id, vote_data = event_data.get("round_id"), event_data['vote'] chain = Chain.objects.get(name=chain_id) round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) From 6fdb8f60fc4130b09bfbe5a5edbf0147a8b4a451 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 11 Dec 2024 19:33:15 +0100 Subject: [PATCH 337/454] add chain to roundds --- grantpicks/serializers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/grantpicks/serializers.py b/grantpicks/serializers.py index 4293d95..3b558e3 100644 --- a/grantpicks/serializers.py +++ b/grantpicks/serializers.py @@ -63,6 +63,7 @@ class Meta: fields = [ "id", # Include the primary key "on_chain_id", + "chain", "factory_contract", "deployed_at", "owner", @@ -105,6 +106,10 @@ class Meta: admins = AccountSerializer(many=True) remaining_dist_by = AccountSerializer() contacts = ProjectContactSerializer(many=True, required=False) + chain = serializers.SerializerMethodField() + + def get_chain(self, obj): + return obj.chain.name class ApplicationReviewSerializer(ModelSerializer): From 31804c4faa6b50085a39d948d45f904da54e6d7d Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 11 Dec 2024 19:46:06 +0100 Subject: [PATCH 338/454] update response sample --- grantpicks/serializers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/grantpicks/serializers.py b/grantpicks/serializers.py index 3b558e3..a611fa3 100644 --- a/grantpicks/serializers.py +++ b/grantpicks/serializers.py @@ -178,6 +178,8 @@ class Meta: SIMPLE_ROUND_EXAMPLE = { "id": 1, "name": "InteractGrant TO Apply V1", + "on_chain_id": 13, + "chain": "stellar", "owner": "GD4I4FXMIKKKVSGVCGNILRFFHDQHITMDW545SCLGEOKGBN6W44AV6367", "contacts": [ { From 819abea7f09c1aa7a6f941c99fd16c6a29bfef21 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 13 Dec 2024 02:50:27 +0100 Subject: [PATCH 339/454] rounds by account: --- api/urls.py | 5 +++++ grantpicks/api.py | 13 ++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/api/urls.py b/api/urls.py index 8cc222e..90638c0 100644 --- a/api/urls.py +++ b/api/urls.py @@ -90,6 +90,11 @@ AccountUpvotedListsAPI.as_view(), name="accounts_api_upvoted_lists", ), + path( + "v1/accounts//rounds", + RoundsListAPI.as_view(), + name="accounts_api_rounds", + ), # donate contract config path( "v1/donate_contract_config", diff --git a/grantpicks/api.py b/grantpicks/api.py index 45c0219..5f5005d 100644 --- a/grantpicks/api.py +++ b/grantpicks/api.py @@ -51,6 +51,7 @@ class RoundsListAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ + OpenApiParameter("account_id", str, OpenApiParameter.PATH, description="get rounds by this account"), OpenApiParameter( name="sort", type=str, @@ -84,7 +85,17 @@ class RoundsListAPI(APIView, CustomSizePageNumberPagination): ) @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): - rounds = Round.objects.all() + account_id = kwargs.get("account_id") + if account_id: + try: + account = Account.objects.get(id=account_id) + rounds = Round.objects.filter(owner=account) + except Account.DoesNotExist: + return Response( + {"message": f"Account with ID {account_id} not found."}, status=404 + ) + else: + rounds = Round.objects.all() chain_param = request.query_params.get("chain") if chain_param: # chain = Chain.objects.get(name=chain_param) From bb1c761578706e9241d22a350950491fe43a5b15 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 13 Dec 2024 15:05:38 +0100 Subject: [PATCH 340/454] celery config to fix memory usage --- base/celery.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/base/celery.py b/base/celery.py index 9ba059d..f3f0b31 100644 --- a/base/celery.py +++ b/base/celery.py @@ -52,6 +52,12 @@ }, } + +app.conf.update( + worker_max_memory_per_child=1500000, # Limit worker memory to ~1.5GB + worker_max_tasks_per_child=100, # Restart worker after 100 tasks +) + app.conf.task_routes = { "indexer_app.tasks.update_account_statistics": {"queue": "beat_tasks"}, "indexer_app.tasks.fetch_usd_prices": {"queue": "beat_tasks"}, From 95edb93a0932467b39db3cc955621f688c432e47 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 13 Dec 2024 16:07:33 +0100 Subject: [PATCH 341/454] move to settings --- base/celery.py | 5 ----- base/settings.py | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/base/celery.py b/base/celery.py index f3f0b31..f8d2d88 100644 --- a/base/celery.py +++ b/base/celery.py @@ -53,11 +53,6 @@ } -app.conf.update( - worker_max_memory_per_child=1500000, # Limit worker memory to ~1.5GB - worker_max_tasks_per_child=100, # Restart worker after 100 tasks -) - app.conf.task_routes = { "indexer_app.tasks.update_account_statistics": {"queue": "beat_tasks"}, "indexer_app.tasks.fetch_usd_prices": {"queue": "beat_tasks"}, diff --git a/base/settings.py b/base/settings.py index 81d1fe0..dba9a2d 100644 --- a/base/settings.py +++ b/base/settings.py @@ -250,6 +250,8 @@ CELERY_BROKER_URL = f"{REDIS_BASE_URL}/0" CELERY_RESULT_BACKEND = f"{REDIS_BASE_URL}/1" +CELERY_MAX_TASKS_PER_CHILD = 100 +CELERY_MAX_MEMORY_PER_CHILD = 1500000 if ENVIRONMENT != "local": From 0c2ba9f4e0be13b1a3812b33efc1d24b061fdacf Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sat, 14 Dec 2024 22:02:53 +0100 Subject: [PATCH 342/454] remove celery conf variables --- base/celery.py | 1 - base/settings.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/base/celery.py b/base/celery.py index f8d2d88..9ba059d 100644 --- a/base/celery.py +++ b/base/celery.py @@ -52,7 +52,6 @@ }, } - app.conf.task_routes = { "indexer_app.tasks.update_account_statistics": {"queue": "beat_tasks"}, "indexer_app.tasks.fetch_usd_prices": {"queue": "beat_tasks"}, diff --git a/base/settings.py b/base/settings.py index dba9a2d..81d1fe0 100644 --- a/base/settings.py +++ b/base/settings.py @@ -250,8 +250,6 @@ CELERY_BROKER_URL = f"{REDIS_BASE_URL}/0" CELERY_RESULT_BACKEND = f"{REDIS_BASE_URL}/1" -CELERY_MAX_TASKS_PER_CHILD = 100 -CELERY_MAX_MEMORY_PER_CHILD = 1500000 if ENVIRONMENT != "local": From ab226ba2b4df3417caf0a3cbc0fc2f5a9d13d108 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 27 Dec 2024 11:59:33 +0100 Subject: [PATCH 343/454] add review_notes to application --- indexer_app/utils.py | 2 +- pots/serializers.py | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 19e5ca9..aaf6eb1 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -406,7 +406,7 @@ async def handle_list_update( logger.info(f"updating list..... {data}") - listObject = await List.objects.filter(on_chain_id=data["id"]).aupdate( + listObject = await List.objects.filter(on_chain_id=data["list_id"]).aupdate( owner_id=data["owner"], default_registration_status=data["default_registration_status"], name=data["name"], diff --git a/pots/serializers.py b/pots/serializers.py index a9f2180..f809f68 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -5,7 +5,7 @@ from base.serializers import TwoDecimalPlacesField from tokens.serializers import SIMPLE_TOKEN_EXAMPLE, TokenSerializer -from .models import Pot, PotApplication, PotFactory, PotPayout +from .models import Pot, PotApplication, PotFactory, PotPayout, PotApplicationReview class PotSerializer(ModelSerializer): @@ -80,7 +80,22 @@ class Meta: whitelisted_deployers = AccountSerializer(many=True) +class ApplicationReviewSerializer(serializers.ModelSerializer): + reviewer = AccountSerializer() + + class Meta: + model = PotApplicationReview + fields = [ + "reviewer", + "notes", + "status", + "reviewed_at", + "tx_hash" + ] + + class PotApplicationSerializer(ModelSerializer): + reviews = ApplicationReviewSerializer(many=True) class Meta: model = PotApplication @@ -93,6 +108,7 @@ class Meta: "submitted_at", "updated_at", "tx_hash", + "reviews", ] pot = PotSerializer() @@ -211,6 +227,14 @@ class PaginatedPotFactoriesResponseSerializer(serializers.Serializer): "results": [SIMPLE_POT_FACTORY_EXAMPLE], } +POT_APPLICATION_REVIEW_EXAMPLE = { + "reviewer": SIMPLE_ACCOUNT_EXAMPLE, + "notes": "Looks good!", + "status": "Approved", + "reviewed_at": "2024-06-05T18:12:39.014Z", + "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", +} + SIMPLE_POT_APPLICATION_EXAMPLE = { "id": 2, @@ -221,6 +245,7 @@ class PaginatedPotFactoriesResponseSerializer(serializers.Serializer): "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", "pot": SIMPLE_POT_EXAMPLE, "applicant": SIMPLE_ACCOUNT_EXAMPLE, + "reviews": [POT_APPLICATION_REVIEW_EXAMPLE], } PAGINATED_POT_APPLICATION_EXAMPLE = { From 76b466bc7e2860fc6556a9b0d243d5fe0a30e695 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 27 Dec 2024 12:36:56 +0100 Subject: [PATCH 344/454] pick reviewer id --- nadabot/serializers.py | 2 +- pots/serializers.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/nadabot/serializers.py b/nadabot/serializers.py index 270a1d4..dc76f63 100644 --- a/nadabot/serializers.py +++ b/nadabot/serializers.py @@ -1,4 +1,4 @@ -from rest_framework.serializers import ModelSerializer, SerializerMethodField +from rest_framework.serializers import ModelSerializer from .models import NadabotRegistry, Provider, Stamp diff --git a/pots/serializers.py b/pots/serializers.py index f809f68..41b5d14 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from rest_framework.serializers import ModelSerializer, SerializerMethodField +from rest_framework.serializers import ModelSerializer from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer from base.serializers import TwoDecimalPlacesField @@ -81,7 +81,7 @@ class Meta: class ApplicationReviewSerializer(serializers.ModelSerializer): - reviewer = AccountSerializer() + reviewer = serializers.SerializerMethodField() class Meta: model = PotApplicationReview @@ -93,6 +93,9 @@ class Meta: "tx_hash" ] + def get_reviewer(self, obj): + return obj.reviewer.id + class PotApplicationSerializer(ModelSerializer): reviews = ApplicationReviewSerializer(many=True) From 8c8ab905c95e6e33af8449559788f8c2b81640d3 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 31 Dec 2024 16:04:12 +0100 Subject: [PATCH 345/454] get voter's staking balance --- indexer_app/utils.py | 4 ++-- pots/serializers.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index aaf6eb1..9dd1e9b 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -404,9 +404,9 @@ async def handle_list_update( ) - logger.info(f"updating list..... {data}") + logger.info(f"updating list from result..... {data}") - listObject = await List.objects.filter(on_chain_id=data["list_id"]).aupdate( + listObject = await List.objects.filter(on_chain_id=data["id"]).aupdate( owner_id=data["owner"], default_registration_status=data["default_registration_status"], name=data["name"], diff --git a/pots/serializers.py b/pots/serializers.py index 41b5d14..55c4837 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -1,6 +1,9 @@ +import requests from rest_framework import serializers from rest_framework.serializers import ModelSerializer +from django.conf import settings + from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer from base.serializers import TwoDecimalPlacesField from tokens.serializers import SIMPLE_TOKEN_EXAMPLE, TokenSerializer @@ -316,6 +319,24 @@ class MpdaoVoterSerializer(serializers.Serializer): locking_positions = LockingPositionSerializer(many=True) vote_positions = VotePositionSerializer(many=True) + staking_token_balance = serializers.SerializerMethodField() + + staking_token_id = serializers.SerializerMethodField() # mpdao is only available on mainnet, + + def get_staking_token_balance(self, obj): + voter_id = obj.get('voter_id') + url = f"https://rpc.web4.near.page/account/meta-pool.near/view/ft_balance_of?account_id={voter_id}" + response = requests.get(url) + if response.status_code == 200: + balance = response.json() + return balance + return "0" + + def get_staking_token_id(self, obj): + return "meta-pool.near" + + + class PaginatedMpdaoVotersResponseSerializer(serializers.Serializer): count = serializers.IntegerField() From 3b70b50dfa1f03be3266dbe6d2440ec157ebfa73 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 31 Dec 2024 18:23:28 +0100 Subject: [PATCH 346/454] merge account data and snapshot data --- pots/api.py | 25 +++++++++++++++++++------ pots/serializers.py | 8 ++++---- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/pots/api.py b/pots/api.py index b4bd46b..3325a3f 100644 --- a/pots/api.py +++ b/pots/api.py @@ -369,20 +369,33 @@ def get(self, request: Request, *args, **kwargs): with open(json_path, 'r') as file: all_voters = json.load(file) - # Find voter by ID - voter = next( + # Find voter by ID in JSON + voter_data = next( (voter for voter in all_voters if voter['voter_id'] == voter_id), None ) - if not voter: + # Check in the database + try: + account = Account.objects.get(id=voter_id) + account_data = AccountSerializer(account).data + except Account.DoesNotExist: + account_data = None + + + if not (voter_data and account_data): return Response( - {"message": f"Voter with ID {voter_id} not found"}, + {"message": f"Voter with ID {voter_id} not found."}, status=404 ) - serializer = MpdaoVoterSerializer(voter) - return Response(serializer.data) + + response_data = { + "account_data": account_data, + "voter_data": MpdaoVoterSerializer(voter_data or {"voter_id": voter_id}).data + } + + return Response(response_data) except FileNotFoundError: return Response( diff --git a/pots/serializers.py b/pots/serializers.py index 55c4837..58c4557 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -314,10 +314,10 @@ class VotePositionSerializer(serializers.Serializer): class MpdaoVoterSerializer(serializers.Serializer): voter_id = serializers.CharField() - balance_in_contract = serializers.CharField() - voting_power = serializers.CharField() - locking_positions = LockingPositionSerializer(many=True) - vote_positions = VotePositionSerializer(many=True) + balance_in_contract = serializers.CharField(allow_null=True) + voting_power = serializers.CharField(allow_null=True) + locking_positions = LockingPositionSerializer(many=True, allow_null=True) + vote_positions = VotePositionSerializer(many=True, allow_null=True) staking_token_balance = serializers.SerializerMethodField() From 51feb33c77916528b591358e66e29b058e00478c Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 31 Dec 2024 19:04:21 +0100 Subject: [PATCH 347/454] fix check logic --- pots/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pots/api.py b/pots/api.py index 3325a3f..5fe0dd3 100644 --- a/pots/api.py +++ b/pots/api.py @@ -383,7 +383,7 @@ def get(self, request: Request, *args, **kwargs): account_data = None - if not (voter_data and account_data): + if not voter_data and not account_data: return Response( {"message": f"Voter with ID {voter_id} not found."}, status=404 From 88c7beaf0ac948b45e95f3b60efd21be4c44c5c2 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 3 Jan 2025 00:10:22 +0100 Subject: [PATCH 348/454] get voters from contract and add caching --- pots/api.py | 161 +++++++++++++++++++++++++++++++++++--------- pots/serializers.py | 37 +++++++--- 2 files changed, 159 insertions(+), 39 deletions(-) diff --git a/pots/api.py b/pots/api.py index 5fe0dd3..9cbeac5 100644 --- a/pots/api.py +++ b/pots/api.py @@ -14,6 +14,9 @@ from rest_framework.response import Response from rest_framework.views import APIView import json +import requests +from django.core.cache import cache +from django.utils.functional import cached_property from accounts.models import Account from accounts.serializers import ( @@ -31,6 +34,7 @@ ) from .models import Pot, PotApplication, PotApplicationStatus, PotFactory from .serializers import ( + PAGINATED_MPDAO_USER_EXAMPLE, PAGINATED_PAYOUT_EXAMPLE, PAGINATED_POT_APPLICATION_EXAMPLE, PAGINATED_POT_EXAMPLE, @@ -346,65 +350,160 @@ def get(self, request: Request, *args, **kwargs): class MpdaoUsers(APIView): + DEFAULT_PAGE_SIZE = 30 @extend_schema( parameters=[ OpenApiParameter("voter_id", str, OpenApiParameter.QUERY, required=False, description="NEAR account ID of the voter"), + OpenApiParameter("page", int, OpenApiParameter.QUERY, required=False, description="Page number (starts from 1)"), + OpenApiParameter("page_size", int, OpenApiParameter.QUERY, required=False, description="Number of items per page (default: 30)"), ], responses={ 200: OpenApiResponse( response=MpdaoVoterSerializer, - description="Returns voter details", + description="Returns voter details or paginated list of all voters", + examples=[ + PAGINATED_MPDAO_USER_EXAMPLE + ] ), 404: OpenApiResponse(description="Voter not found"), 500: OpenApiResponse(description="File read error"), }, ) - @method_decorator(cache_page(60 * 5)) + @method_decorator(cache_page(14400)) def get(self, request: Request, *args, **kwargs): voter_id = request.query_params.get("voter_id") + try: - # Read JSON file - json_path = os.path.join(settings.BASE_DIR, 'pots', 'last-snapshot-AllVoters.json') - with open(json_path, 'r') as file: - all_voters = json.load(file) - - # Find voter by ID in JSON - voter_data = next( - (voter for voter in all_voters if voter['voter_id'] == voter_id), - None + if voter_id: + return self.get_single_voter(voter_id) + else: + return self.get_all_voters(request.query_params) + except FileNotFoundError: + return Response( + {"message": "Voters snapshot file not found"}, + status=500 + ) + except Exception as e: + return Response( + {"message": f"Error processing voter data: {str(e)}"}, + status=500 ) - # Check in the database + def get_all_voters(self, query_params): + """Handle request for all voters with pagination""" + try: + unique_voters = self.get_unique_voters() + try: - account = Account.objects.get(id=voter_id) - account_data = AccountSerializer(account).data - except Account.DoesNotExist: - account_data = None - + page = max(int(query_params.get('page', 1)), 1) + page_size = min(int(query_params.get('page_size', self.DEFAULT_PAGE_SIZE)), 100) + except ValueError: + page = 1 + page_size = self.DEFAULT_PAGE_SIZE + + # Calculate pagination slices + start_idx = (page - 1) * page_size + end_idx = start_idx + page_size - if not voter_data and not account_data: - return Response( - {"message": f"Voter with ID {voter_id} not found."}, - status=404 - ) - + # Get paginated voters + paginated_voters = unique_voters[start_idx:end_idx] - response_data = { + # Get all account data for the page in one query + accounts = self.get_bulk_account_data(paginated_voters) + + voters_data = [] + for voter_id in paginated_voters: + voter_data = self.get_voter_data(voter_id) + account_data = accounts.get(voter_id) + + voters_data.append({ + "voter_id": voter_id, "account_data": account_data, "voter_data": MpdaoVoterSerializer(voter_data or {"voter_id": voter_id}).data - } + }) + + base_url = self.request.build_absolute_uri().split('?')[0] + + next_url = None + if end_idx < len(unique_voters): + next_params = query_params.copy() + next_params['page'] = page + 1 + next_params['page_size'] = page_size + next_url = f"{base_url}?{'&'.join(f'{k}={v}' for k, v in next_params.items())}" + + previous_url = None + if page > 1: + prev_params = query_params.copy() + prev_params['page'] = page - 1 + prev_params['page_size'] = page_size + previous_url = f"{base_url}?{'&'.join(f'{k}={v}' for k, v in prev_params.items())}" + + response_data = { + "count": len(paginated_voters), + "next": next_url, + "previous": previous_url, + "results": voters_data + } return Response(response_data) - except FileNotFoundError: - return Response( - {"message": "Voters snapshot file not found"}, - status=500 - ) except Exception as e: return Response( - {"message": f"Error processing voter data: {str(e)}"}, + {"message": f"Error fetching voters: {str(e)}"}, status=500 ) + + @cached_property + def all_voters(self): + """Cache the JSON file in memory""" + json_path = os.path.join(settings.BASE_DIR, 'pots', 'last-snapshot-AllVoters.json') + with open(json_path, 'r') as file: + return json.load(file) + + def get_unique_voters(self): + """Get and cache the unique voters from RPC""" + cache_key = 'mpdao_unique_voters' + cached_voters = cache.get(cache_key) + + if cached_voters is not None: + return cached_voters + + url = "https://rpc.web4.near.page/account/mpdao.vote.potlock.near/view/get_unique_voters?election_id.json=1" + response = requests.get(url) + + if response.status_code != 200: + raise Exception("Error fetching voters from contract") + + unique_voters = response.json() + cache.set(cache_key, unique_voters, 86400) # Cache for 24 hours + return unique_voters + + def get_bulk_account_data(self, voter_ids): + """Get account data for multiple voters in one query""" + accounts = { + account.id: AccountSerializer(account).data + for account in Account.objects.select_related().filter(id__in=voter_ids) + } + return accounts + + def get_voter_data(self, voter_id): + """Get voter data from cached JSON""" + return next( + (voter for voter in self.all_voters if voter['voter_id'] == voter_id), + None + ) + + def get_single_voter(self, voter_id): + """Handle single voter request""" + voter_data = self.get_voter_data(voter_id) + accounts = self.get_bulk_account_data([voter_id]) + account_data = accounts.get(voter_id) + + response_data = { + "account_data": account_data, + "voter_data": MpdaoVoterSerializer(voter_data or {"voter_id": voter_id}).data + } + + return Response(response_data) \ No newline at end of file diff --git a/pots/serializers.py b/pots/serializers.py index 58c4557..5b3c60f 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -201,6 +201,35 @@ class PaginatedPotsResponseSerializer(serializers.Serializer): previous = serializers.CharField(allow_null=True) results = PotSerializer(many=True) +SIMPLE_MPDAO_VOTER_INFO_EXAMPLE = { + "voter_id": "01f7f1d124232a78b2a2f8e8ac21219d1ecd131ab592c2d9cae505d3c3cd21b6", + "account_data": { + "id": "01f7f1d124232a78b2a2f8e8ac21219d1ecd131ab592c2d9cae505d3c3cd21b6", + "total_donations_in_usd": 0.0, + "total_donations_out_usd": 0.0, + "total_matching_pool_allocations_usd": 0.0, + "donors_count": 0, + "near_social_profile_data": { + "name": "Manny98" + } + }, + "voter_data": { + "voter_id": "01f7f1d124232a78b2a2f8e8ac21219d1ecd131ab592c2d9cae505d3c3cd21b6", + "balance_in_contract": None, + "voting_power": None, + "locking_positions": None, + "vote_positions": None, + "staking_token_balance": "0", + "staking_token_id": "meta-pool.near" + } +} + +PAGINATED_MPDAO_USER_EXAMPLE = { + "count": 30, + "next": "http://127.0.0.1:8000/api/v1/mpdao/voter-info?page=3&page_size=30", + "previous": None, + "results": [SIMPLE_MPDAO_VOTER_INFO_EXAMPLE], +} SIMPLE_POT_FACTORY_EXAMPLE = { "account": "v1.potfactory.potlock.near", @@ -335,11 +364,3 @@ def get_staking_token_balance(self, obj): def get_staking_token_id(self, obj): return "meta-pool.near" - - - -class PaginatedMpdaoVotersResponseSerializer(serializers.Serializer): - count = serializers.IntegerField() - next = serializers.CharField(allow_null=True) - previous = serializers.CharField(allow_null=True) - results = MpdaoVoterSerializer(many=True) From cdd1f7356ce75f92d4df06137dc7efea3f1bca67 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 3 Jan 2025 00:39:32 +0100 Subject: [PATCH 349/454] fix schema param: --- pots/api.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pots/api.py b/pots/api.py index 9cbeac5..83298b1 100644 --- a/pots/api.py +++ b/pots/api.py @@ -361,10 +361,15 @@ class MpdaoUsers(APIView): responses={ 200: OpenApiResponse( response=MpdaoVoterSerializer, - description="Returns voter details or paginated list of all voters", + description="Returns voter details or paginated list of all voters for mpdao round", examples=[ - PAGINATED_MPDAO_USER_EXAMPLE - ] + OpenApiExample( + "mpdao-example", + summary="Simple example", + description="Example response for mpdao voters info", + value=PAGINATED_MPDAO_USER_EXAMPLE, + ), + ], ), 404: OpenApiResponse(description="Voter not found"), 500: OpenApiResponse(description="File read error"), From 2257688f4581dfe32ef722c1f11f9af1e8e78370 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 3 Jan 2025 11:10:27 +0100 Subject: [PATCH 350/454] fix api schema for doc --- pots/api.py | 9 +++++---- pots/serializers.py | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/pots/api.py b/pots/api.py index 83298b1..dcc71d3 100644 --- a/pots/api.py +++ b/pots/api.py @@ -40,6 +40,7 @@ PAGINATED_POT_EXAMPLE, PAGINATED_POT_FACTORY_EXAMPLE, SIMPLE_POT_EXAMPLE, + PaginatedMpdaoUsersSerializer, PaginatedPotApplicationsResponseSerializer, PaginatedPotFactoriesResponseSerializer, PaginatedPotPayoutsResponseSerializer, @@ -48,7 +49,7 @@ PotFactorySerializer, PotPayoutSerializer, PotSerializer, - MpdaoVoterSerializer, + MpdaoSnapshotSerializer, ) @@ -360,7 +361,7 @@ class MpdaoUsers(APIView): ], responses={ 200: OpenApiResponse( - response=MpdaoVoterSerializer, + response=PaginatedMpdaoUsersSerializer, description="Returns voter details or paginated list of all voters for mpdao round", examples=[ OpenApiExample( @@ -425,7 +426,7 @@ def get_all_voters(self, query_params): voters_data.append({ "voter_id": voter_id, "account_data": account_data, - "voter_data": MpdaoVoterSerializer(voter_data or {"voter_id": voter_id}).data + "voter_data": MpdaoSnapshotSerializer(voter_data or {"voter_id": voter_id}).data }) base_url = self.request.build_absolute_uri().split('?')[0] @@ -507,7 +508,7 @@ def get_single_voter(self, voter_id): response_data = { "account_data": account_data, - "voter_data": MpdaoVoterSerializer(voter_data or {"voter_id": voter_id}).data + "voter_data": MpdaoSnapshotSerializer(voter_data or {"voter_id": voter_id}).data } return Response(response_data) diff --git a/pots/serializers.py b/pots/serializers.py index 5b3c60f..a042fa6 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -4,7 +4,7 @@ from django.conf import settings -from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer +from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer, NearSocialProfileDataSerializer from base.serializers import TwoDecimalPlacesField from tokens.serializers import SIMPLE_TOKEN_EXAMPLE, TokenSerializer @@ -341,7 +341,7 @@ class VotePositionSerializer(serializers.Serializer): voting_power = serializers.CharField() -class MpdaoVoterSerializer(serializers.Serializer): +class MpdaoSnapshotSerializer(serializers.Serializer): voter_id = serializers.CharField() balance_in_contract = serializers.CharField(allow_null=True) voting_power = serializers.CharField(allow_null=True) @@ -364,3 +364,15 @@ def get_staking_token_balance(self, obj): def get_staking_token_id(self, obj): return "meta-pool.near" + +class MpdaoVoterItemSerializer(serializers.Serializer): + voter_id = serializers.CharField() + account_data = AccountSerializer(allow_null=True) + voter_data = MpdaoSnapshotSerializer() + +class PaginatedMpdaoUsersSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.URLField(allow_null=True) + previous = serializers.URLField(allow_null=True) + results = MpdaoVoterItemSerializer(many=True) + From 918529577e6cee3d6366b649b2a33eec6f3d831f Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 3 Jan 2025 12:05:27 +0100 Subject: [PATCH 351/454] separate endpoint for list and detail --- api/urls.py | 15 +++-- pots/api.py | 161 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 102 insertions(+), 74 deletions(-) diff --git a/api/urls.py b/api/urls.py index 90638c0..a5391c3 100644 --- a/api/urls.py +++ b/api/urls.py @@ -27,7 +27,8 @@ ListsListAPI, ) from pots.api import ( - MpdaoUsers, + MpdaoVotersListAPI, + MpdaoVoterDetailAPI, PotApplicationsAPI, PotDetailAPI, PotDonationsAPI, @@ -157,9 +158,13 @@ path("v1//project-stats", ProjectStatsAPI.as_view(), name="projects_stat__api"), path( - "v1/mpdao/voter-info", - MpdaoUsers.as_view(), - name="mpdao_api", + "v1/mpdao/voters", + MpdaoVotersListAPI.as_view(), + name="mpdao_voters_list", + ), + path( + "v1/mpdao/voters/", + MpdaoVoterDetailAPI.as_view(), + name="mpdao_voter_detail", ), - ] diff --git a/pots/api.py b/pots/api.py index dcc71d3..8a9f145 100644 --- a/pots/api.py +++ b/pots/api.py @@ -39,7 +39,9 @@ PAGINATED_POT_APPLICATION_EXAMPLE, PAGINATED_POT_EXAMPLE, PAGINATED_POT_FACTORY_EXAMPLE, + SIMPLE_MPDAO_VOTER_INFO_EXAMPLE, SIMPLE_POT_EXAMPLE, + MpdaoVoterItemSerializer, PaginatedMpdaoUsersSerializer, PaginatedPotApplicationsResponseSerializer, PaginatedPotFactoriesResponseSerializer, @@ -350,46 +352,76 @@ def get(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) -class MpdaoUsers(APIView): +class MpdaoVoterMixin: + @cached_property + def all_voters(self): + """Cache the JSON file in memory""" + json_path = os.path.join(settings.BASE_DIR, 'pots', 'last-snapshot-AllVoters.json') + with open(json_path, 'r') as file: + return json.load(file) + + def get_unique_voters(self): + """Get and cache the unique voters from RPC""" + cache_key = 'mpdao_unique_voters' + cached_voters = cache.get(cache_key) + + if cached_voters is not None: + return cached_voters + + url = "https://rpc.web4.near.page/account/mpdao.vote.potlock.near/view/get_unique_voters?election_id.json=1" + response = requests.get(url) + + if response.status_code != 200: + raise Exception("Error fetching voters from contract") + + unique_voters = response.json() + cache.set(cache_key, unique_voters, 86400) + return unique_voters + + def get_bulk_account_data(self, voter_ids): + """Get account data for multiple voters in one query""" + accounts = { + account.id: AccountSerializer(account).data + for account in Account.objects.select_related().filter(id__in=voter_ids) + } + return accounts + + def get_voter_data(self, voter_id): + """Get voter data from cached JSON""" + return next( + (voter for voter in self.all_voters if voter['voter_id'] == voter_id), + None + ) + + +class MpdaoVotersListAPI(MpdaoVoterMixin, APIView): DEFAULT_PAGE_SIZE = 30 @extend_schema( parameters=[ - OpenApiParameter("voter_id", str, OpenApiParameter.QUERY, required=False, description="NEAR account ID of the voter"), OpenApiParameter("page", int, OpenApiParameter.QUERY, required=False, description="Page number (starts from 1)"), OpenApiParameter("page_size", int, OpenApiParameter.QUERY, required=False, description="Number of items per page (default: 30)"), ], responses={ 200: OpenApiResponse( response=PaginatedMpdaoUsersSerializer, - description="Returns voter details or paginated list of all voters for mpdao round", + description="Returns paginated list of all voters for mpdao round", examples=[ OpenApiExample( - "mpdao-example", - summary="Simple example", - description="Example response for mpdao voters info", + "mpdao-list-example", + summary="Paginated voters list", + description="Example response for mpdao voters list", value=PAGINATED_MPDAO_USER_EXAMPLE, ), ], ), - 404: OpenApiResponse(description="Voter not found"), - 500: OpenApiResponse(description="File read error"), + 500: OpenApiResponse(description="Error fetching voters"), }, ) @method_decorator(cache_page(14400)) def get(self, request: Request, *args, **kwargs): - voter_id = request.query_params.get("voter_id") - try: - if voter_id: - return self.get_single_voter(voter_id) - else: - return self.get_all_voters(request.query_params) - except FileNotFoundError: - return Response( - {"message": "Voters snapshot file not found"}, - status=500 - ) + return self.get_all_voters(request.query_params) except Exception as e: return Response( {"message": f"Error processing voter data: {str(e)}"}, @@ -460,56 +492,47 @@ def get_all_voters(self, query_params): status=500 ) - @cached_property - def all_voters(self): - """Cache the JSON file in memory""" - json_path = os.path.join(settings.BASE_DIR, 'pots', 'last-snapshot-AllVoters.json') - with open(json_path, 'r') as file: - return json.load(file) - def get_unique_voters(self): - """Get and cache the unique voters from RPC""" - cache_key = 'mpdao_unique_voters' - cached_voters = cache.get(cache_key) - - if cached_voters is not None: - return cached_voters - - url = "https://rpc.web4.near.page/account/mpdao.vote.potlock.near/view/get_unique_voters?election_id.json=1" - response = requests.get(url) - - if response.status_code != 200: - raise Exception("Error fetching voters from contract") - - unique_voters = response.json() - cache.set(cache_key, unique_voters, 86400) # Cache for 24 hours - return unique_voters - - def get_bulk_account_data(self, voter_ids): - """Get account data for multiple voters in one query""" - accounts = { - account.id: AccountSerializer(account).data - for account in Account.objects.select_related().filter(id__in=voter_ids) - } - return accounts - - def get_voter_data(self, voter_id): - """Get voter data from cached JSON""" - return next( - (voter for voter in self.all_voters if voter['voter_id'] == voter_id), - None - ) - - def get_single_voter(self, voter_id): - """Handle single voter request""" - voter_data = self.get_voter_data(voter_id) - accounts = self.get_bulk_account_data([voter_id]) - account_data = accounts.get(voter_id) +class MpdaoVoterDetailAPI(MpdaoVoterMixin, APIView): + @extend_schema( + parameters=[ + OpenApiParameter("voter_id", str, OpenApiParameter.PATH, required=True, description="account ID of the voter"), + ], + responses={ + 200: OpenApiResponse( + response=MpdaoVoterItemSerializer, + description="Returns details for a specific voter", + examples=[ + OpenApiExample( + "mpdao-voter-example", + summary="Specific voter details", + description="Example response for single voter info", + value=SIMPLE_MPDAO_VOTER_INFO_EXAMPLE + ) + ] + ), + 404: OpenApiResponse(description="Voter not found"), + 500: OpenApiResponse(description="Error fetching voter data"), + }, + ) + @method_decorator(cache_page(14400)) + def get(self, request: Request, voter_id: str, *args, **kwargs): + try: + voter_data = self.get_voter_data(voter_id) + + accounts = self.get_bulk_account_data([voter_id]) + account_data = accounts.get(voter_id) - response_data = { - "account_data": account_data, - "voter_data": MpdaoSnapshotSerializer(voter_data or {"voter_id": voter_id}).data - } - - return Response(response_data) + response_data = { + "voter_id": voter_id, + "account_data": account_data, + "voter_data": MpdaoSnapshotSerializer(voter_data or {"voter_id": voter_id}).data + } + + return Response(response_data) + except Exception as e: + return Response( + {"message": f"Error processing voter data: {str(e)}"}, + status=500 + ) \ No newline at end of file From 23390e32739f7912102605bd2a6d4e9c8816ab30 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sun, 5 Jan 2025 14:47:56 +0100 Subject: [PATCH 352/454] increase max page size --- pots/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pots/api.py b/pots/api.py index 8a9f145..2d90ac7 100644 --- a/pots/api.py +++ b/pots/api.py @@ -435,7 +435,7 @@ def get_all_voters(self, query_params): try: page = max(int(query_params.get('page', 1)), 1) - page_size = min(int(query_params.get('page_size', self.DEFAULT_PAGE_SIZE)), 100) + page_size = min(int(query_params.get('page_size', self.DEFAULT_PAGE_SIZE)), 170) except ValueError: page = 1 page_size = self.DEFAULT_PAGE_SIZE From 3c15d77c6cf16247be0b2ab1faaac64b33e0227a Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 7 Jan 2025 11:15:55 +0100 Subject: [PATCH 353/454] add filter by admin to list --- lists/api.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lists/api.py b/lists/api.py index 04a910b..0a3e86e 100644 --- a/lists/api.py +++ b/lists/api.py @@ -41,6 +41,12 @@ class ListsListAPI(APIView, CustomSizePageNumberPagination): OpenApiParameter.QUERY, description="Filter lists by account", ), + OpenApiParameter( + "admin", + str, + OpenApiParameter.QUERY, + description="Filter lists by admin", + ), *pagination_parameters, ], responses={ @@ -73,6 +79,15 @@ def get(self, request: Request, *args, **kwargs): return Response( {"message": f"Account with ID {account_id} not found."}, status=404 ) + admin_id = request.query_params.get("admin") + if admin_id: + try: + admin = Account.objects.get(id=admin_id) + lists = lists.filter(admins=admin) + except Account.DoesNotExist: + return Response( + {"message": f"Admin with ID {admin_id} not found."}, status=404 + ) results = self.paginate_queryset(lists, request, view=self) serializer = ListSerializer(results, many=True) return self.get_paginated_response(serializer.data) From a2ba5da4d294013058f5b3da156c02b246016b55 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 13 Jan 2025 14:29:07 +0100 Subject: [PATCH 354/454] add is human param --- pots/serializers.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pots/serializers.py b/pots/serializers.py index a042fa6..55a4785 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -347,11 +347,21 @@ class MpdaoSnapshotSerializer(serializers.Serializer): voting_power = serializers.CharField(allow_null=True) locking_positions = LockingPositionSerializer(many=True, allow_null=True) vote_positions = VotePositionSerializer(many=True, allow_null=True) + is_human = serializers.SerializerMethodField() staking_token_balance = serializers.SerializerMethodField() staking_token_id = serializers.SerializerMethodField() # mpdao is only available on mainnet, + def get_is_human(self, obj): + voter_id = obj.get('voter_id') + url = f"https://rpc.web4.near.page/account/v1.nadabot.near/view/is_human?account_id={voter_id}" + response = requests.get(url) + if response.status_code == 200: + is_human = response.json() + return is_human + return False + def get_staking_token_balance(self, obj): voter_id = obj.get('voter_id') url = f"https://rpc.web4.near.page/account/meta-pool.near/view/ft_balance_of?account_id={voter_id}" From 4fe7bbac715559149fd35e93f1bf17d969152663 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 13 Jan 2025 20:04:50 +0100 Subject: [PATCH 355/454] add type --- pots/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pots/serializers.py b/pots/serializers.py index 55a4785..b3b2669 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -353,7 +353,7 @@ class MpdaoSnapshotSerializer(serializers.Serializer): staking_token_id = serializers.SerializerMethodField() # mpdao is only available on mainnet, - def get_is_human(self, obj): + def get_is_human(self, obj) -> bool: voter_id = obj.get('voter_id') url = f"https://rpc.web4.near.page/account/v1.nadabot.near/view/is_human?account_id={voter_id}" response = requests.get(url) From a88c3ed27ad3b0e35587bd33138a77ad17890dae Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sat, 18 Jan 2025 05:21:41 +0100 Subject: [PATCH 356/454] use select and prefetch related --- accounts/api.py | 3 ++- lists/api.py | 26 +++++++++----------------- pots/api.py | 4 ++-- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/accounts/api.py b/accounts/api.py index 8d2bb8b..242ffbc 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -348,7 +348,8 @@ def get(self, request: Request, *args, **kwargs): {"message": f"Account with ID {account_id} not found."}, status=404 ) - donations = Donation.objects.select_related('donor', 'pot', 'recipient', 'referrer', 'chef', 'token').prefetch_related('pot__admins').filter(donor=account) #TODO: this takes more time than just doing a prefetch_related for the fields. + # donations = account.donations.select_related('donor', 'pot', 'recipient', 'referrer', 'chef', 'token').prefetch_related('pot__admins').all() + donations = account.donations.select_related('donor', 'pot', 'pot__deployer', 'pot__owner', 'pot__chef', 'recipient', 'referrer', 'chef', 'token').prefetch_related('pot__admins').all()# (donor=account) #TODO: this takes more time than just doing a prefetch_related for the fields. results = self.paginate_queryset(donations, request, view=self) serializer = DonationSerializer(results, many=True) return self.get_paginated_response(serializer.data) diff --git a/lists/api.py b/lists/api.py index 0a3e86e..64745ce 100644 --- a/lists/api.py +++ b/lists/api.py @@ -18,7 +18,7 @@ from api.pagination import pagination_parameters from api.pagination import CustomSizePageNumberPagination -from .models import List, ListRegistrationStatus +from .models import List, ListRegistration, ListRegistrationStatus from .serializers import ( PAGINATED_LIST_EXAMPLE, PAGINATED_LIST_REGISTRATION_EXAMPLE, @@ -69,7 +69,7 @@ class ListsListAPI(APIView, CustomSizePageNumberPagination): ) @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): - lists = List.objects.all() + lists = List.objects.all().select_related("owner").prefetch_related("admins") account_id = request.query_params.get("account") if account_id: try: @@ -121,7 +121,7 @@ class ListDetailAPI(APIView): def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") try: - list_obj = List.objects.get(on_chain_id=list_id) + list_obj = List.objects.select_related("owner").prefetch_related("admins").get(on_chain_id=list_id) except List.DoesNotExist: return Response( {"message": f"List with onchain ID {list_id} not found."}, status=404 @@ -176,14 +176,10 @@ class ListRegistrationsAPI(APIView, CustomSizePageNumberPagination): @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") - try: - list_obj = List.objects.prefetch_related('registrations').get(on_chain_id=list_id) - except List.DoesNotExist: - return Response( - {"message": f"List with on chain ID {list_id} not found."}, status=404 - ) + #list_obj = List.objects.prefetch_related('registrations').get(on_chain_id=list_id) + registrations = ListRegistration.objects.filter(list__on_chain_id=list_id).select_related("list__owner", "registrant", "registered_by").prefetch_related("list__admins") - registrations = list_obj.registrations.select_related().all() + # registrations = list_obj.registrations.select_related().all() status_param = request.query_params.get("status") category_param = request.query_params.get("category") search_param = request.query_params.get("search") @@ -239,14 +235,10 @@ class ListRandomRegistrationAPI(APIView): ) def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") - try: - list_obj = List.objects.get(on_chain_id=list_id) - except List.DoesNotExist: - return Response( - {"message": f"List on chain ID {list_id} not found."}, status=404 - ) + # list_obj = List.objects.get(on_chain_id=list_id) + registrations = ListRegistration.objects.filter(list__on_chain_id=list_id).select_related("list__owner", "registrant", "registered_by").prefetch_related("list__admins") - registrations = list_obj.registrations.all() + # registrations = list_obj.registrations.all() status_param = request.query_params.get("status") if status_param: if status_param not in ListRegistrationStatus.values: diff --git a/pots/api.py b/pots/api.py index 2d90ac7..3363983 100644 --- a/pots/api.py +++ b/pots/api.py @@ -79,7 +79,7 @@ class PotsListAPI(APIView, CustomSizePageNumberPagination): ) @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): - pots = Pot.objects.all() + pots = Pot.objects.select_related('account', 'pot_factory', 'deployer', 'owner', 'chef').prefetch_related('admins').all() results = self.paginate_queryset(pots, request, view=self) serializer = PotSerializer(results, many=True) return self.get_paginated_response(serializer.data) @@ -250,7 +250,7 @@ def get(self, request: Request, *args, **kwargs): except Pot.DoesNotExist: return Response({"message": f"Pot with ID {pot_id} not found."}, status=404) - donations = pot.donations.all() + donations = pot.donations.select_related("donor", "token", 'pot', 'pot__deployer', 'pot__owner', 'pot__chef', 'recipient', 'referrer', 'chef').prefetch_related('pot__admins').all() results = self.paginate_queryset(donations, request, view=self) serializer = DonationSerializer(results, many=True) return self.get_paginated_response(serializer.data) From 4b869359fe0669aca6839b3a698115b52baebe74 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sat, 18 Jan 2025 23:14:20 +0100 Subject: [PATCH 357/454] optimize list fetching query --- lists/api.py | 11 ++++++----- lists/serializers.py | 8 +------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lists/api.py b/lists/api.py index 64745ce..930f5b2 100644 --- a/lists/api.py +++ b/lists/api.py @@ -10,6 +10,7 @@ OpenApiResponse, extend_schema, ) +from django.db.models import Count from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -69,7 +70,7 @@ class ListsListAPI(APIView, CustomSizePageNumberPagination): ) @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): - lists = List.objects.all().select_related("owner").prefetch_related("admins") + lists = List.objects.all().select_related("owner").prefetch_related("admins", "upvotes").annotate(registrations_count=Count('registrations')) account_id = request.query_params.get("account") if account_id: try: @@ -173,13 +174,13 @@ class ListRegistrationsAPI(APIView, CustomSizePageNumberPagination): 500: OpenApiResponse(description="Internal server error"), }, ) - @method_decorator(cache_page(60 * 1)) + # @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") - #list_obj = List.objects.prefetch_related('registrations').get(on_chain_id=list_id) - registrations = ListRegistration.objects.filter(list__on_chain_id=list_id).select_related("list__owner", "registrant", "registered_by").prefetch_related("list__admins") + # list_obj = List.objects.get(on_chain_id=list_id) + registrations = ListRegistration.objects.filter(list__on_chain_id=list_id).annotate(registrations_count=Count('list__registrations')).select_related("list", "list__owner", "registrant", "registered_by").prefetch_related("list__admins", "list__upvotes") - # registrations = list_obj.registrations.select_related().all() + # registrations = list_obj.registrations.select_related("list", "list__owner", "registrant", "registered_by").prefetch_related("list__admins").annotate(registrations_count=Count('list_registrations')).all() status_param = request.query_params.get("status") category_param = request.query_params.get("category") search_param = request.query_params.get("search") diff --git a/lists/serializers.py b/lists/serializers.py index 486abc6..c920c17 100644 --- a/lists/serializers.py +++ b/lists/serializers.py @@ -13,6 +13,7 @@ class Meta: class ListSerializer(ModelSerializer): + registrations_count = serializers.IntegerField(required=False) class Meta: model = List fields = [ @@ -34,10 +35,6 @@ class Meta: owner = AccountSerializer() admins = AccountSerializer(many=True) upvotes = ListUpvoteSerializer(many=True) - registrations_count = SerializerMethodField() - - def get_registrations_count(self, obj): - return obj.registrations.count() # def get_owner(self, obj): # return AccountSerializer(obj.owner).data @@ -51,7 +48,6 @@ class Meta: model = ListRegistration fields = [ "id", - "list", "registrant", "registered_by", "status", @@ -62,7 +58,6 @@ class Meta: "tx_hash", ] - list = ListSerializer() registrant = AccountSerializer() registered_by = AccountSerializer() @@ -104,7 +99,6 @@ class PaginatedListsResponseSerializer(serializers.Serializer): "registrant_notes": "I'm excited to apply for this list", "admin_notes": "This is a great project that I want on my list.", "tx_hash": "EVMQsXorrrxPLHfK9UnbzFUy1SVYWvc8hwSGQZs4RbTk", - "list": SIMPLE_LIST_EXAMPLE, "registrant": SIMPLE_ACCOUNT_EXAMPLE, "registered_by": SIMPLE_ACCOUNT_EXAMPLE, } From e6950123aba0131613f675ebe83c9abdedab6907 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sat, 18 Jan 2025 23:15:21 +0100 Subject: [PATCH 358/454] add cache --- lists/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lists/api.py b/lists/api.py index 930f5b2..da2fc1f 100644 --- a/lists/api.py +++ b/lists/api.py @@ -174,7 +174,7 @@ class ListRegistrationsAPI(APIView, CustomSizePageNumberPagination): 500: OpenApiResponse(description="Internal server error"), }, ) - # @method_decorator(cache_page(60 * 1)) + @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") # list_obj = List.objects.get(on_chain_id=list_id) From c6c8b271c653c536e9fc93fda2087b72f2550bab Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sat, 18 Jan 2025 23:35:30 +0100 Subject: [PATCH 359/454] remove annotate --- lists/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lists/api.py b/lists/api.py index da2fc1f..b906d21 100644 --- a/lists/api.py +++ b/lists/api.py @@ -178,7 +178,7 @@ class ListRegistrationsAPI(APIView, CustomSizePageNumberPagination): def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") # list_obj = List.objects.get(on_chain_id=list_id) - registrations = ListRegistration.objects.filter(list__on_chain_id=list_id).annotate(registrations_count=Count('list__registrations')).select_related("list", "list__owner", "registrant", "registered_by").prefetch_related("list__admins", "list__upvotes") + registrations = ListRegistration.objects.filter(list__on_chain_id=list_id).select_related("list", "list__owner", "registrant", "registered_by").prefetch_related("list__admins", "list__upvotes") # registrations = list_obj.registrations.select_related("list", "list__owner", "registrant", "registered_by").prefetch_related("list__admins").annotate(registrations_count=Count('list_registrations')).all() status_param = request.query_params.get("status") From 1408581089a0aaed52410fd6251ca8c8af1f15ef Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sun, 19 Jan 2025 21:02:00 +0100 Subject: [PATCH 360/454] add list_id to serializer --- accounts/api.py | 2 +- lists/serializers.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/accounts/api.py b/accounts/api.py index 242ffbc..959522d 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -431,7 +431,7 @@ def get(self, request: Request, *args, **kwargs): {"message": f"Account with ID {account_id} not found."}, status=404 ) - registrations = ListRegistration.objects.filter(registrant=account) + registrations = ListRegistration.objects.filter(registrant=account).select_related('registered_by', 'registrant') status_param = request.query_params.get("status") if status_param: if status_param not in ListRegistrationStatus.values: diff --git a/lists/serializers.py b/lists/serializers.py index c920c17..210d02a 100644 --- a/lists/serializers.py +++ b/lists/serializers.py @@ -48,6 +48,7 @@ class Meta: model = ListRegistration fields = [ "id", + "list_id", "registrant", "registered_by", "status", From 303e860e3e0d5a59b72d49c7b59fbf76efefb346 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sun, 19 Jan 2025 21:23:53 +0100 Subject: [PATCH 361/454] update schema: --- lists/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lists/serializers.py b/lists/serializers.py index 210d02a..697e9b2 100644 --- a/lists/serializers.py +++ b/lists/serializers.py @@ -94,6 +94,7 @@ class PaginatedListsResponseSerializer(serializers.Serializer): SIMPLE_LIST_REGISTRATION_EXAMPLE = { "id": 10, + "list_id": 1, "status": "Approved", "submitted_at": "2024-06-05T18:01:02.319Z", "updated_at": "2024-06-05T18:01:02.319Z", From 07a9f215a083f377fe9bb1906d6105aaa8a7c6b0 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 20 Jan 2025 13:20:02 +0100 Subject: [PATCH 362/454] get details at height --- pots/api.py | 10 +++++----- pots/serializers.py | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/pots/api.py b/pots/api.py index 3363983..b0325c5 100644 --- a/pots/api.py +++ b/pots/api.py @@ -382,7 +382,7 @@ def get_bulk_account_data(self, voter_ids): """Get account data for multiple voters in one query""" accounts = { account.id: AccountSerializer(account).data - for account in Account.objects.select_related().filter(id__in=voter_ids) + for account in Account.objects.filter(id__in=voter_ids) } return accounts @@ -418,7 +418,7 @@ class MpdaoVotersListAPI(MpdaoVoterMixin, APIView): 500: OpenApiResponse(description="Error fetching voters"), }, ) - @method_decorator(cache_page(14400)) + @method_decorator(cache_page(86400)) def get(self, request: Request, *args, **kwargs): try: return self.get_all_voters(request.query_params) @@ -515,13 +515,13 @@ class MpdaoVoterDetailAPI(MpdaoVoterMixin, APIView): 500: OpenApiResponse(description="Error fetching voter data"), }, ) - @method_decorator(cache_page(14400)) + @method_decorator(cache_page(86400)) def get(self, request: Request, voter_id: str, *args, **kwargs): try: voter_data = self.get_voter_data(voter_id) - accounts = self.get_bulk_account_data([voter_id]) - account_data = accounts.get(voter_id) + # accounts = self.get_bulk_account_data([voter_id]) + account_data = accounts = AccountSerializer(voter_id).data response_data = { "voter_id": voter_id, diff --git a/pots/serializers.py b/pots/serializers.py index b3b2669..78ef61b 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -3,6 +3,7 @@ from rest_framework.serializers import ModelSerializer from django.conf import settings +from django.core.cache import cache from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer, NearSocialProfileDataSerializer from base.serializers import TwoDecimalPlacesField @@ -355,19 +356,31 @@ class MpdaoSnapshotSerializer(serializers.Serializer): def get_is_human(self, obj) -> bool: voter_id = obj.get('voter_id') - url = f"https://rpc.web4.near.page/account/v1.nadabot.near/view/is_human?account_id={voter_id}" + cache_key = f'{voter_id}_is_human' + cached_res = cache.get(cache_key) + + if cached_res is not None: + return cached_res + url = f"https://rpc.web4.near.page/account/v1.nadabot.near/view/is_human?account_id={voter_id}&near_block_height=137346724" response = requests.get(url) if response.status_code == 200: is_human = response.json() + cache.set(cache_key, is_human, 8640000) return is_human return False def get_staking_token_balance(self, obj): voter_id = obj.get('voter_id') - url = f"https://rpc.web4.near.page/account/meta-pool.near/view/ft_balance_of?account_id={voter_id}" + cache_key = f'{voter_id}_token_balance' + cached_res = cache.get(cache_key) + + if cached_res is not None: + return cached_res + url = f"https://rpc.web4.near.page/account/meta-pool.near/view/ft_balance_of?account_id={voter_id}&near_block_height=137346724" response = requests.get(url) if response.status_code == 200: balance = response.json() + cache.set(cache_key, balance, 8640000) return balance return "0" From ac140eab3ea434e9461870c0defc709101af3727 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 20 Jan 2025 17:03:53 +0100 Subject: [PATCH 363/454] change block height --- pots/api.py | 3 +-- pots/serializers.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pots/api.py b/pots/api.py index b0325c5..093ea4e 100644 --- a/pots/api.py +++ b/pots/api.py @@ -520,8 +520,7 @@ def get(self, request: Request, voter_id: str, *args, **kwargs): try: voter_data = self.get_voter_data(voter_id) - # accounts = self.get_bulk_account_data([voter_id]) - account_data = accounts = AccountSerializer(voter_id).data + account_data = self.get_bulk_account_data([voter_id]) response_data = { "voter_id": voter_id, diff --git a/pots/serializers.py b/pots/serializers.py index 78ef61b..9a46c4c 100644 --- a/pots/serializers.py +++ b/pots/serializers.py @@ -361,7 +361,7 @@ def get_is_human(self, obj) -> bool: if cached_res is not None: return cached_res - url = f"https://rpc.web4.near.page/account/v1.nadabot.near/view/is_human?account_id={voter_id}&near_block_height=137346724" + url = f"https://rpc.web4.near.page/account/v1.nadabot.near/view/is_human?account_id={voter_id}&near_block_height=137273829" response = requests.get(url) if response.status_code == 200: is_human = response.json() @@ -376,7 +376,7 @@ def get_staking_token_balance(self, obj): if cached_res is not None: return cached_res - url = f"https://rpc.web4.near.page/account/meta-pool.near/view/ft_balance_of?account_id={voter_id}&near_block_height=137346724" + url = f"https://rpc.web4.near.page/account/meta-pool.near/view/ft_balance_of?account_id={voter_id}&near_block_height=137273829" response = requests.get(url) if response.status_code == 200: balance = response.json() From 42cba5b6b1b4958203e609bf294ea4238bcf6804 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 17 Mar 2025 21:38:02 +0100 Subject: [PATCH 364/454] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f1a82fb..c8e9dce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ readme = "README.md" python = "^3.11" django = "^5.0.4" psycopg2-binary = "^2.9.9" -near-lake-framework = "^0.0.7" +near-lake-framework = "^0.1.3" celery = "^5.3.6" redis = { extras = ["hiredis"], version = "^5.0.3" } requests = "^2.31.0" From dfc62a2601f45ff6de0da8eac83c2a6333da18be Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 17 Mar 2025 22:44:10 +0100 Subject: [PATCH 365/454] fix lake framework --- indexer_app/tasks.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 074fd4b..fce78de 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -36,22 +36,16 @@ async def indexer(from_block: int, to_block: int): """ # Initialize lake indexer logger.info(f"from block: {from_block}") - lake_config = ( - LakeConfig.testnet() + + lake_config = LakeConfig( + "testnet" if settings.ENVIRONMENT == "testnet" - else LakeConfig.mainnet() - ) - lake_config.start_block_height = ( + else "mainnet", + settings.AWS_ACCESS_KEY_ID, + settings.AWS_SECRET_ACCESS_KEY, from_block - if from_block - else logger.info( - "Starting to index from latest block" - ) # TODO: wtf is this shitty code ) - lake_config.aws_access_key_id = settings.AWS_ACCESS_KEY_ID - lake_config.aws_secret_key = settings.AWS_SECRET_ACCESS_KEY _, streamer_messages_queue = streamer(lake_config) - block_count = 0 while True: try: @@ -63,8 +57,6 @@ async def indexer(from_block: int, to_block: int): logger.info( f"Time to fetch new block: {fetch_end_time - fetch_start_time:.4f} seconds" ) - block_count += 1 - # Log time before caching block height save_start_time = time.time() # Update current block height From 72c24be1312dd2ded662b53c31528d6d48e5d1ee Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 17 Mar 2025 22:51:05 +0100 Subject: [PATCH 366/454] fix config netowrk --- indexer_app/tasks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index fce78de..44c4ecb 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -12,7 +12,7 @@ from django.conf import settings from django.db.models import Count, DecimalField, Q, Sum, Value from django.db.models.functions import Cast, NullIf -from near_lake_framework import LakeConfig, streamer +from near_lake_framework import LakeConfig, streamer, Network import stellar_sdk from stellar_sdk.soroban_server import EventFilter, EventFilterType from stellar_sdk import Address, stellar_xdr, scval @@ -38,9 +38,9 @@ async def indexer(from_block: int, to_block: int): logger.info(f"from block: {from_block}") lake_config = LakeConfig( - "testnet" + Network.TESTNET if settings.ENVIRONMENT == "testnet" - else "mainnet", + else Network.MAINNET, settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY, from_block From 5e41759ddb55558854d6589006cce6150196575b Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 27 Mar 2025 19:00:02 +0100 Subject: [PATCH 367/454] make endpoint for reclaim proof --- api/urls.py | 3 ++- base/api.py | 53 ++++++++++++++++++++++++++++++++++++++++++++---- base/settings.py | 3 +++ pyproject.toml | 1 + 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/api/urls.py b/api/urls.py index a5391c3..dfb3d44 100644 --- a/api/urls.py +++ b/api/urls.py @@ -17,7 +17,7 @@ AccountsListAPI, DonorsAPI, ) -from base.api import StatsAPI +from base.api import StatsAPI, ReclaimProofRequestView from donations.api import DonationContractConfigAPI from grantpicks.api import AccountProjectListAPI, ProjectListAPI, ProjectRoundVotesAPI, ProjectStatsAPI, RoundApplicationsAPI, RoundDetailAPI, RoundsListAPI from lists.api import ( @@ -143,6 +143,7 @@ ), # stats path("v1/stats", StatsAPI.as_view(), name="stats_api"), + path("v1/reclaim/generate-request", ReclaimProofRequestView.as_view(), name="stats_api"), # grantpicks path("v1/rounds", RoundsListAPI.as_view(), name="rounds_api"), diff --git a/base/api.py b/base/api.py index a5540ef..e0b1662 100644 --- a/base/api.py +++ b/base/api.py @@ -1,18 +1,19 @@ -from django.db.models import Count, Exists, OuterRef, Sum -from django.utils import timezone +from django.db.models import Sum +from django.http import JsonResponse from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from drf_spectacular.utils import ( OpenApiExample, - OpenApiParameter, OpenApiResponse, extend_schema, ) +from django.conf import settings +from asgiref.sync import async_to_sync from rest_framework import serializers from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView - +from reclaim_python_sdk import ReclaimProofRequest from accounts.models import Account from donations.models import Donation from pots.models import PotPayout @@ -25,6 +26,8 @@ class StatsResponseSerializer(serializers.Serializer): total_donors_count = serializers.IntegerField() total_recipients_count = serializers.IntegerField() +class ReclaimProofRequestConfigSerializer(serializers.Serializer): + reclaimProofRequestConfig = serializers.CharField() class StatsAPI(APIView): def dispatch(self, request, *args, **kwargs): @@ -87,3 +90,45 @@ def get(self, request: Request, *args, **kwargs): "total_recipients_count": total_recipients_count, } ) + + +class ReclaimProofRequestView(APIView): + + @extend_schema( + responses={ + 200: OpenApiResponse( + response=ReclaimProofRequestConfigSerializer, + description="Returns Reclaim proof request configuration", + examples=[ + OpenApiExample( + "example-1", + summary="Simple example", + description="Example response for Reclaim proof request config", + value={ + "reclaimProofRequestConfig": "{}" + }, + response_only=True, + ), + ], + ), + 500: OpenApiResponse(description="Internal server error"), + } + ) + def post(self, request: Request, *args, **kwargs): + APP_ID = settings.RECLAIM_APP_ID + APP_SECRET = settings.RECLAIM_APP_SECRET + PROVIDER_ID = settings.RECLAIM_TWITTER_PROVIDER_ID + + platform = request.query_params.get("platform") + handle = request.query_params.get("handle") + + try: + reclaim_proof_func = async_to_sync(ReclaimProofRequest.init) + reclaim_proof_request = reclaim_proof_func(APP_ID, APP_SECRET, PROVIDER_ID, {"context": {"handle": handle}}) + # reclaim_proof_request.set_app_callback_url("https://your-backend.com/receive-proofs") + reclaim_proof_request_config = reclaim_proof_request.to_json_string() + + return JsonResponse({"reclaimProofRequestConfig": reclaim_proof_request_config}) + except Exception as error: + print(f"Error generating request config: {error}") + return JsonResponse({"error": "Failed to generate request config"}, status=500) diff --git a/base/settings.py b/base/settings.py index 81d1fe0..8403f2d 100644 --- a/base/settings.py +++ b/base/settings.py @@ -59,6 +59,9 @@ REDIS_HOST = os.environ.get("PL_REDIS_HOST", "localhost") REDIS_PORT = os.environ.get("PL_REDIS_PORT", 6379) SENTRY_DSN = os.environ.get("PL_SENTRY_DSN") +RECLAIM_APP_ID = os.environ.get("PL_RECLAIM_APP_ID") +RECLAIM_APP_SECRET = os.environ.get("PL_RECLAIM_APP_SECRET") +RECLAIM_TWITTER_PROVIDER_ID = os.environ.get("PL_RECLAIM_TWITTER_PROVIDER_ID") # POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else "potlock.near" POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") diff --git a/pyproject.toml b/pyproject.toml index c8e9dce..e04d500 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ drf-spectacular = "^0.27.2" django-extensions = "^3.2.3" psutil = "^6.0.0" stellar-sdk = "^11.0.0" +reclaim-python-sdk = "^0.2.1" [tool.poetry.group.dev.dependencies] black = "^24.3.0" From 5d660fc14d5ca3491cdf538b32c2237b98270086 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 22 Apr 2025 13:13:00 +0100 Subject: [PATCH 368/454] change contract address --- base/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/settings.py b/base/settings.py index 8403f2d..25727e1 100644 --- a/base/settings.py +++ b/base/settings.py @@ -67,8 +67,8 @@ POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") # NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") -STELLAR_CONTRACT_ID = "CBEW52FEWIFWK4LCSMX5FAAG2MYB36C2RLKS5YEIF5AOJEDMCGYU3ZGB" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") -STELLAR_PROJECTS_REGISTRY_CONTRACT = "CBG2JAGBYUAM3YLQFNFR3ZSWVR7XR5BEQHQ6XGUA3HCWVCHWO3RS3YUD" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_CONTRACT_ID = "CA7A2776JYIOYXPAJFONDC7BVPDWWLZU524UTGIJIQH6HWWOR6LKYQQT" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_PROJECTS_REGISTRY_CONTRACT = "CA56XSY7YEZ7CJ5FYG7YODQIWE3JNRGZ5S7E7VJAQ675KDS4BLZJ5NJH" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) From e63cb54d6bda74908e70d39da81d0bfc05c1e3d4 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 7 May 2025 13:41:59 +0100 Subject: [PATCH 369/454] get use_whitelist of default to false --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 9dd1e9b..22576a8 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1647,7 +1647,7 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar 'application_end': datetime.fromtimestamp(event_data.get('application_end_ms') / 1000) if event_data.get('application_end_ms') else None, 'voting_start': datetime.fromtimestamp(event_data.get('voting_start_ms') / 1000), 'voting_end': datetime.fromtimestamp(event_data.get('voting_end_ms') / 1000), - 'use_whitelist': event_data.get('use_whitelist'), + 'use_whitelist': event_data.get('use_whitelist', False), 'use_vault': use_vault, 'num_picks_per_voter': event_data.get('num_picks_per_voter'), 'max_participants': event_data.get('max_participants'), From a4c2589fdc6c2d1745fb26cb5d3cf388de9c6abb Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 7 May 2025 16:58:10 +0100 Subject: [PATCH 370/454] make model changes --- ...007_remove_round_use_whitelist_and_more.py | 55 +++++++++++++++++++ grantpicks/models.py | 19 ++++++- grantpicks/serializers.py | 10 +++- indexer_app/utils.py | 5 +- 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 grantpicks/migrations/0007_remove_round_use_whitelist_and_more.py diff --git a/grantpicks/migrations/0007_remove_round_use_whitelist_and_more.py b/grantpicks/migrations/0007_remove_round_use_whitelist_and_more.py new file mode 100644 index 0000000..04bd366 --- /dev/null +++ b/grantpicks/migrations/0007_remove_round_use_whitelist_and_more.py @@ -0,0 +1,55 @@ +# Generated by Django 5.0.6 on 2025-05-07 15:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("grantpicks", "0006_alter_round_compliance_period_ms_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="round", + name="use_whitelist", + ), + migrations.AddField( + model_name="round", + name="application_wl_list_id", + field=models.IntegerField( + help_text="list for applicants", + null=True, + verbose_name="list for applicants", + ), + ), + migrations.AddField( + model_name="round", + name="use_whitelist_application", + field=models.BooleanField( + default=False, + help_text="Use whitelist for application.", + verbose_name="use whitelist", + ), + preserve_default=False, + ), + migrations.AddField( + model_name="round", + name="use_whitelist_voting", + field=models.BooleanField( + default=False, + help_text="Use whitelist for voting.", + verbose_name="use whitelist", + ), + preserve_default=False, + ), + migrations.AddField( + model_name="round", + name="voting_wl_list_id", + field=models.IntegerField( + help_text="list for voter whitelist", + null=True, + verbose_name="list for voter whitelist", + ), + ), + ] diff --git a/grantpicks/models.py b/grantpicks/models.py index acc2195..6b31732 100644 --- a/grantpicks/models.py +++ b/grantpicks/models.py @@ -184,10 +184,25 @@ class Round(models.Model): null=False, help_text=_("Round voting end date."), ) - use_whitelist = models.BooleanField( + use_whitelist_voting = models.BooleanField( _("use whitelist"), null=False, - help_text=_("Use whitelist."), + help_text=_("Use whitelist for voting."), + ) + use_whitelist_application = models.BooleanField( + _("use whitelist"), + null=False, + help_text=_("Use whitelist for application."), + ) + application_wl_list_id = models.IntegerField( + _("list for applicants"), + null=True, + help_text=_("list for applicants"), + ) + voting_wl_list_id = models.IntegerField( + _("list for voter whitelist"), + null=True, + help_text=_("list for voter whitelist"), ) use_vault = models.BooleanField( _("use vault"), diff --git a/grantpicks/serializers.py b/grantpicks/serializers.py index a611fa3..ba8e8e9 100644 --- a/grantpicks/serializers.py +++ b/grantpicks/serializers.py @@ -77,7 +77,10 @@ class Meta: "application_end", "voting_start", "voting_end", - "use_whitelist", + "use_whitelist_voting", + "use_whitelist_voting", + "application_wl_list_id", + "voting_wl_list_id", "use_vault", "num_picks_per_voter", "max_participants", @@ -190,7 +193,10 @@ class Meta: ], "use_vault": True, "description": "This is a test round", - "use_whitelist": False, + "use_whitelist_voting": False, + "use_whitelist_application": True, + "application_wl_list_id": 1, + "voting_wl_list_id": 2, "voting_start": "2024-06-25T00:00:00Z", "voting_end": "2024-06-30T00:00:00Z", "cooldown_end": None, diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 22576a8..e68db75 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1647,7 +1647,10 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar 'application_end': datetime.fromtimestamp(event_data.get('application_end_ms') / 1000) if event_data.get('application_end_ms') else None, 'voting_start': datetime.fromtimestamp(event_data.get('voting_start_ms') / 1000), 'voting_end': datetime.fromtimestamp(event_data.get('voting_end_ms') / 1000), - 'use_whitelist': event_data.get('use_whitelist', False), + 'use_whitelist_voting': event_data.get('use_whitelist_voting', False), + 'use_whitelist_application': event_data.get('use_whitelist_application', False), + 'application_wl_list_id': event_data.get('application_wl_list_id'), + 'voting_wl_list_id': event_data.get('voting_wl_list_id'), 'use_vault': use_vault, 'num_picks_per_voter': event_data.get('num_picks_per_voter'), 'max_participants': event_data.get('max_participants'), From 91da988a14e6aae3bd552316587e2e0e200d7cbc Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 8 May 2025 13:04:25 +0100 Subject: [PATCH 371/454] default use_vault to flse --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index e68db75..287c013 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1651,7 +1651,7 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar 'use_whitelist_application': event_data.get('use_whitelist_application', False), 'application_wl_list_id': event_data.get('application_wl_list_id'), 'voting_wl_list_id': event_data.get('voting_wl_list_id'), - 'use_vault': use_vault, + 'use_vault': use_vault or False, 'num_picks_per_voter': event_data.get('num_picks_per_voter'), 'max_participants': event_data.get('max_participants'), 'allow_applications': event_data.get('allow_applications'), From 07f1c6224b5486b8a609de6460d5665b40d4f8cf Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 22 May 2025 09:01:55 +0100 Subject: [PATCH 372/454] cors allow --- base/settings.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index 25727e1..e20f7cc 100644 --- a/base/settings.py +++ b/base/settings.py @@ -232,7 +232,11 @@ "https://bos.potlock.io", "https://app.potlock.io", "https://bos.potlock.app", - "https://app.potlock.app" + "https://app.potlock.app", + "https://staging.alpha.potlock.org", + "https://staging.alpha.potlock.io", + "https://staging.app.potlock.io", + "https://staging.app.potlock.org", ] CORS_ALLOWED_ORIGIN_REGEXES = [ From 623fc48a61cea841a4826ff462f2c576510a23c8 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 22 May 2025 13:30:39 +0100 Subject: [PATCH 373/454] use re gex for cors --- base/settings.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/base/settings.py b/base/settings.py index e20f7cc..4c489fa 100644 --- a/base/settings.py +++ b/base/settings.py @@ -232,16 +232,14 @@ "https://bos.potlock.io", "https://app.potlock.io", "https://bos.potlock.app", - "https://app.potlock.app", - "https://staging.alpha.potlock.org", - "https://staging.alpha.potlock.io", - "https://staging.app.potlock.io", - "https://staging.app.potlock.org", + "https://app.potlock.app" ] CORS_ALLOWED_ORIGIN_REGEXES = [ "^https:\/\/potlock-next-[\w-]+-potlock\.vercel\.app\/?$", - "^https?:\/\/.*\.?grantpicks\.com$" + "^https?:\/\/.*\.?grantpicks\.com$", + "^https:\/\/staging\.app\.potlock\.(org|io|xyz|app)\/?$", + "^https:\/\/staging\.alpha\.potlock\.(org|io|xyz|app)\/?$" ] # REDIS / CACHE CONFIGS From 63a85f0b672373e9132ac320306b0da4cc9629ac Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 27 May 2025 22:48:39 +0100 Subject: [PATCH 374/454] add approved projects from review --- indexer_app/utils.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 287c013..d106fca 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1401,10 +1401,21 @@ def update_application(event_data, txhash, reviewer_id=None, chain_id="stellar") defaults=defaults, ) + + project = Project.objects.get(on_chain_id=application_data.get("project_id")) + if status == PotApplicationStatus.APPROVED: + # If the application is approved, add the project to the round's approved projects + if not round_obj.approved_projects.filter(id=project.owner.id).exists(): + logger.info(f"Adding project {project.owner.id} to approved projects for Round {round_id}") + round_obj.approved_projects.add(project) + else: + round_obj.approved_projects.remove(project) + # Update the PotApplication object PotApplication.objects.filter(applicant=applicant, round=round_obj).update( **{"status": status, "updated_at": updated_at} ) + return True From 15ba09418092c473b17a326fa8e72b2d42d0e34b Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 28 May 2025 01:00:33 +0100 Subject: [PATCH 375/454] remove payout address from grantpicks --- .../0008_remove_project_payout_address.py | 17 +++++++++++++++++ grantpicks/models.py | 1 - grantpicks/serializers.py | 3 --- indexer_app/utils.py | 13 ------------- 4 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 grantpicks/migrations/0008_remove_project_payout_address.py diff --git a/grantpicks/migrations/0008_remove_project_payout_address.py b/grantpicks/migrations/0008_remove_project_payout_address.py new file mode 100644 index 0000000..8a84297 --- /dev/null +++ b/grantpicks/migrations/0008_remove_project_payout_address.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.6 on 2025-05-27 23:59 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("grantpicks", "0007_remove_round_use_whitelist_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="project", + name="payout_address", + ), + ] diff --git a/grantpicks/models.py b/grantpicks/models.py index 6b31732..5c18326 100644 --- a/grantpicks/models.py +++ b/grantpicks/models.py @@ -59,7 +59,6 @@ class Project(models.Model): name = models.CharField(max_length=255) overview = models.TextField() owner = models.ForeignKey(Account, related_name='owned_projects', on_delete=models.CASCADE) - payout_address = models.ForeignKey(Account, related_name='payout_projects', on_delete=models.CASCADE) contacts = models.ManyToManyField( ProjectContact, related_name="contact_lists", diff --git a/grantpicks/serializers.py b/grantpicks/serializers.py index ba8e8e9..b972743 100644 --- a/grantpicks/serializers.py +++ b/grantpicks/serializers.py @@ -30,7 +30,6 @@ class ProjectSerializer(serializers.ModelSerializer): contracts = ProjectContractSerializer(many=True, required=False) repositories = ProjectRepositorySerializer(many=True, required=False) owner = AccountSerializer() - payout_address = AccountSerializer() class Meta: model = Project @@ -42,7 +41,6 @@ class Meta: 'name', 'overview', 'owner', - 'payout_address', 'contacts', 'contracts', 'team_members', @@ -161,7 +159,6 @@ class Meta: "name": "My Project", "overview": "This project aims to do something impactful.", "owner": "GD4I4FXMIKKKVSGVCGNILRFFHDQHITMDW545SCLGEOKGBN6W44AV6367", - "payout_address": "GD4I4FXMIKKKVSGVCGNILRFFHDQHITMDW545SCLGEOKGBN6W44AV6367", "contacts": [ { "id": 2, diff --git a/indexer_app/utils.py b/indexer_app/utils.py index d106fca..031800a 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1401,21 +1401,10 @@ def update_application(event_data, txhash, reviewer_id=None, chain_id="stellar") defaults=defaults, ) - - project = Project.objects.get(on_chain_id=application_data.get("project_id")) - if status == PotApplicationStatus.APPROVED: - # If the application is approved, add the project to the round's approved projects - if not round_obj.approved_projects.filter(id=project.owner.id).exists(): - logger.info(f"Adding project {project.owner.id} to approved projects for Round {round_id}") - round_obj.approved_projects.add(project) - else: - round_obj.approved_projects.remove(project) - # Update the PotApplication object PotApplication.objects.filter(applicant=applicant, round=round_obj).update( **{"status": status, "updated_at": updated_at} ) - return True @@ -1544,7 +1533,6 @@ def process_project_event(event_data, chain_id="stellar"): owner, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=project_data['owner']) # Create or get the payout Account - payout_address, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=project_data['payout_address']) # Create the Project project, created = Project.objects.update_or_create( @@ -1555,7 +1543,6 @@ def process_project_event(event_data, chain_id="stellar"): 'name': project_data['name'], 'overview': project_data['overview'], 'owner': owner, - 'payout_address': payout_address, 'status': ProjectStatus("NEW").name, 'submited_ms': project_data['submited_ms'], 'updated_ms': project_data['updated_ms'], From a9c6922843e3bb7720fbc067c6528630a14a187e Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 28 May 2025 10:33:33 +0100 Subject: [PATCH 376/454] add approved application to projects --- indexer_app/utils.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 031800a..48323ff 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1401,6 +1401,15 @@ def update_application(event_data, txhash, reviewer_id=None, chain_id="stellar") defaults=defaults, ) + project = Project.objects.get(on_chain_id=application_data.get("project_id")) + if status == PotApplicationStatus.APPROVED: + # If the application is approved, add the project to the round's approved projects + if not round_obj.approved_projects.filter(id=project.owner.id).exists(): + logger.info(f"Adding project {project.owner.id} to approved projects for Round {round_id}") + round_obj.approved_projects.add(project) + else: + round_obj.approved_projects.remove(project) + # Update the PotApplication object PotApplication.objects.filter(applicant=applicant, round=round_obj).update( **{"status": status, "updated_at": updated_at} From 02a33838f5ae267092a1228e911a45067491dfd6 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 28 May 2025 13:15:22 +0100 Subject: [PATCH 377/454] use project owner account instance --- indexer_app/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 48323ff..6b09c66 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1406,9 +1406,9 @@ def update_application(event_data, txhash, reviewer_id=None, chain_id="stellar") # If the application is approved, add the project to the round's approved projects if not round_obj.approved_projects.filter(id=project.owner.id).exists(): logger.info(f"Adding project {project.owner.id} to approved projects for Round {round_id}") - round_obj.approved_projects.add(project) + round_obj.approved_projects.add(project.owner) else: - round_obj.approved_projects.remove(project) + round_obj.approved_projects.remove(project.owner) # Update the PotApplication object PotApplication.objects.filter(applicant=applicant, round=round_obj).update( From 82c81c543a4ad3518d06bd9d8b02fda18132a6c5 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 2 Jun 2025 14:56:51 +0100 Subject: [PATCH 378/454] add minimum_deposit --- .../migrations/0009_round_minimum_deposit.py | 23 +++++++++++++++++++ grantpicks/models.py | 5 ++++ grantpicks/serializers.py | 2 ++ indexer_app/utils.py | 1 + 4 files changed, 31 insertions(+) create mode 100644 grantpicks/migrations/0009_round_minimum_deposit.py diff --git a/grantpicks/migrations/0009_round_minimum_deposit.py b/grantpicks/migrations/0009_round_minimum_deposit.py new file mode 100644 index 0000000..dc9bdb8 --- /dev/null +++ b/grantpicks/migrations/0009_round_minimum_deposit.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2025-06-02 13:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("grantpicks", "0008_remove_project_payout_address"), + ] + + operations = [ + migrations.AddField( + model_name="round", + name="minimum_deposit", + field=models.CharField( + default=10000000, + help_text="Minimum deposit.", + verbose_name="minimum deposit", + ), + preserve_default=False, + ), + ] diff --git a/grantpicks/models.py b/grantpicks/models.py index 5c18326..0d76a8d 100644 --- a/grantpicks/models.py +++ b/grantpicks/models.py @@ -332,6 +332,11 @@ class Round(models.Model): blank=True, help_text=_("Vault total deposits in USD."), ) + + minimum_deposit = models.CharField( + _("minimum deposit"), + help_text=_("Minimum deposit."), + ) round_complete = models.DateTimeField( _("round complete"), null=True, diff --git a/grantpicks/serializers.py b/grantpicks/serializers.py index b972743..3a84df3 100644 --- a/grantpicks/serializers.py +++ b/grantpicks/serializers.py @@ -100,6 +100,7 @@ class Meta: "referrer_fee_basis_points", "vault_total_deposits", "vault_total_deposits_usd", + "minimum_deposit", "round_complete", ] owner = AccountSerializer() @@ -215,6 +216,7 @@ class Meta: "vault_total_deposits": 0, "current_vault_balance": 0, "referrer_fee_basis_points": 0, + "minimum_deposit": 10000000, } PAGINATED_ROUND_EXAMPLE = { diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 6b09c66..38c4c2e 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1676,6 +1676,7 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar 'remaining_dist_memo': event_data.get('remaining_dist_memo', event_data.get('remaining_funds_redistribution_memo')), 'round_complete': round_time_stamp, 'vault_total_deposits': event_data.get('vault_total_deposits'), + 'minimum_deposit': event_data.get('minimum_deposit'), 'current_vault_balance': event_data.get('current_vault_balance'), 'deployed_at': timestamp } From 44e0e38c294af2059ec9b13eda3aad559bd2b883 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 18 Jun 2025 22:31:58 +0100 Subject: [PATCH 379/454] change stellar testnet address --- base/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/settings.py b/base/settings.py index 4c489fa..10f8b3e 100644 --- a/base/settings.py +++ b/base/settings.py @@ -67,8 +67,8 @@ POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") # NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") -STELLAR_CONTRACT_ID = "CA7A2776JYIOYXPAJFONDC7BVPDWWLZU524UTGIJIQH6HWWOR6LKYQQT" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") -STELLAR_PROJECTS_REGISTRY_CONTRACT = "CA56XSY7YEZ7CJ5FYG7YODQIWE3JNRGZ5S7E7VJAQ675KDS4BLZJ5NJH" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_CONTRACT_ID = "CCWIGVGCIHRVSVLOR4BUODHWY5WWUSR3HR55LB7CTMQ6YSWBPPYQEUQN" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_PROJECTS_REGISTRY_CONTRACT = "CCPTYQ4WDBAF3OWCLGTRE7E4PXADY2WX7KP3M63VPGM4WKQDVITK5R3M" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) From 20f871ea271db11df87c91fb8eb067b5f30642c4 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 18 Jun 2025 22:56:11 +0100 Subject: [PATCH 380/454] starting ledger --- indexer_app/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 44c4ecb..e1e9f3d 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -341,7 +341,7 @@ def stellar_event_indexer(): start_sequence = get_ledger_sequence() # start_sequence = 12169 if not start_sequence: - start_sequence = 774951 + start_sequence = 2771 jobs_logger.info(f"Ingesting Stellar events from ledger {start_sequence}... contracts: {contract_ids}") try: # Fetch events for the current sequence From 1a96c3fbcbf551c6ae7fd5b01812d4fa431f0410 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 3 Jul 2025 12:48:18 +0100 Subject: [PATCH 381/454] change contract: --- base/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/settings.py b/base/settings.py index 10f8b3e..9f10d5b 100644 --- a/base/settings.py +++ b/base/settings.py @@ -67,8 +67,8 @@ POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") # NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") -STELLAR_CONTRACT_ID = "CCWIGVGCIHRVSVLOR4BUODHWY5WWUSR3HR55LB7CTMQ6YSWBPPYQEUQN" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") -STELLAR_PROJECTS_REGISTRY_CONTRACT = "CCPTYQ4WDBAF3OWCLGTRE7E4PXADY2WX7KP3M63VPGM4WKQDVITK5R3M" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_CONTRACT_ID = "CA2VBELXEOMCXCXNAIBUXM3TKFIDV4IUO7GJSMKCFL2DPNT5JBLGN4RV" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_PROJECTS_REGISTRY_CONTRACT = "CC7OJT737CWDBWAQZYWEEL42J6HJCT5LXPFC44635IVX7WTEU7GC6FHW" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) From dfa7c764091d25efa8841ecae075d8d779150180 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 3 Jul 2025 22:59:09 +0100 Subject: [PATCH 382/454] change contract address --- base/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/settings.py b/base/settings.py index 9f10d5b..f71fa8c 100644 --- a/base/settings.py +++ b/base/settings.py @@ -67,8 +67,8 @@ POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") # NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") -STELLAR_CONTRACT_ID = "CA2VBELXEOMCXCXNAIBUXM3TKFIDV4IUO7GJSMKCFL2DPNT5JBLGN4RV" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") -STELLAR_PROJECTS_REGISTRY_CONTRACT = "CC7OJT737CWDBWAQZYWEEL42J6HJCT5LXPFC44635IVX7WTEU7GC6FHW" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_CONTRACT_ID = "CDKE2ROJIXNAQDUAIY27L4EQVMJAEZEI3WEGYFF42V5IYNJDHRHIRR5Z" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_PROJECTS_REGISTRY_CONTRACT = "CDJFTRLRHOFYKK6PUJBGDC35EBCZG7Q7WOZIW22CKFCVAVZUHT3ZETBG" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) From 7bd3ab34208fb1c9ac0edf01d9023eb48a8d4ad0 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 8 Jul 2025 11:22:03 +0100 Subject: [PATCH 383/454] change contract id --- base/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/settings.py b/base/settings.py index f71fa8c..0f6397c 100644 --- a/base/settings.py +++ b/base/settings.py @@ -67,8 +67,8 @@ POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") # NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") -STELLAR_CONTRACT_ID = "CDKE2ROJIXNAQDUAIY27L4EQVMJAEZEI3WEGYFF42V5IYNJDHRHIRR5Z" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") -STELLAR_PROJECTS_REGISTRY_CONTRACT = "CDJFTRLRHOFYKK6PUJBGDC35EBCZG7Q7WOZIW22CKFCVAVZUHT3ZETBG" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_CONTRACT_ID = "CAAH3TSAEGM34FRIKT7DBCF26DQ63STVGFUEHLP4CTNDD5XKQGXSLSSO" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_PROJECTS_REGISTRY_CONTRACT = "CDKJMKQ7J7LQCWP4GFLY6LPC4ONJK7B3QARAFCJJHJJM3W34LXSASNM5" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) From a3978d87589447c97deb1c93dbbd66ad6e869370 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 21 Jul 2025 12:16:20 +0100 Subject: [PATCH 384/454] upgrade stellar sdk --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e04d500..0c3a36d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ django-cors-headers = "^4.3.1" drf-spectacular = "^0.27.2" django-extensions = "^3.2.3" psutil = "^6.0.0" -stellar-sdk = "^11.0.0" +stellar-sdk = "13.0.0-beta0" reclaim-python-sdk = "^0.2.1" [tool.poetry.group.dev.dependencies] From 7ccd5ebbff4d5161e0b5d8a46d068566f393f23b Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 24 Jul 2025 14:06:03 +0100 Subject: [PATCH 385/454] update stellar address --- base/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/settings.py b/base/settings.py index 0f6397c..eb7045c 100644 --- a/base/settings.py +++ b/base/settings.py @@ -67,8 +67,8 @@ POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") # NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") -STELLAR_CONTRACT_ID = "CAAH3TSAEGM34FRIKT7DBCF26DQ63STVGFUEHLP4CTNDD5XKQGXSLSSO" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") -STELLAR_PROJECTS_REGISTRY_CONTRACT = "CDKJMKQ7J7LQCWP4GFLY6LPC4ONJK7B3QARAFCJJHJJM3W34LXSASNM5" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_CONTRACT_ID = "CAG7ZLJNXZGFGIR2VB3JVHFA6XF4U3TAIEZLLZQWI6XFX7DC3EG6NU4G" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_PROJECTS_REGISTRY_CONTRACT = "CBFQ2T4GJI4NN4GI5MQXQNAORDGPKD5XGG7UZ5IVLAQLHJROQQ4KRPP5" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) From 94bf1e8b1e6b02bd8d49f460ca741730e95bc685 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 29 Jul 2025 19:12:36 +0100 Subject: [PATCH 386/454] index campaigns --- api/urls.py | 29 ++ base/settings.py | 3 +- campaigns/__init__.py | 0 campaigns/admin.py | 176 ++++++++++ campaigns/api.py | 358 ++++++++++++++++++++ campaigns/apps.py | 11 + campaigns/migrations/0001_initial.py | 472 +++++++++++++++++++++++++++ campaigns/migrations/__init__.py | 0 campaigns/models.py | 450 +++++++++++++++++++++++++ campaigns/serializers.py | 168 ++++++++++ campaigns/tests.py | 317 ++++++++++++++++++ campaigns/views.py | 3 + indexer_app/handler.py | 21 ++ indexer_app/tasks.py | 26 +- indexer_app/utils.py | 354 ++++++++++++++++++-- 15 files changed, 2355 insertions(+), 33 deletions(-) create mode 100644 campaigns/__init__.py create mode 100644 campaigns/admin.py create mode 100644 campaigns/api.py create mode 100644 campaigns/apps.py create mode 100644 campaigns/migrations/0001_initial.py create mode 100644 campaigns/migrations/__init__.py create mode 100644 campaigns/models.py create mode 100644 campaigns/serializers.py create mode 100644 campaigns/tests.py create mode 100644 campaigns/views.py diff --git a/api/urls.py b/api/urls.py index dfb3d44..59e60c5 100644 --- a/api/urls.py +++ b/api/urls.py @@ -18,6 +18,13 @@ DonorsAPI, ) from base.api import StatsAPI, ReclaimProofRequestView +from campaigns.api import ( + AllCampaignDonationsAPI, + CampaignContractConfigAPI, + CampaignDetailAPI, + CampaignDonationsAPI, + CampaignsAPI, +) from donations.api import DonationContractConfigAPI from grantpicks.api import AccountProjectListAPI, ProjectListAPI, ProjectRoundVotesAPI, ProjectStatsAPI, RoundApplicationsAPI, RoundDetailAPI, RoundsListAPI from lists.api import ( @@ -102,6 +109,28 @@ DonationContractConfigAPI.as_view(), name="donate_contract_config_api", ), + # campaigns + path("v1/campaigns", CampaignsAPI.as_view(), name="campaigns_api"), + path( + "v1/campaigns/", + CampaignDetailAPI.as_view(), + name="campaigns_api_by_id", + ), + path( + "v1/campaigns//donations", + CampaignDonationsAPI.as_view(), + name="campaigns_donations_api", + ), + path( + "v1/campaign_donations", + AllCampaignDonationsAPI.as_view(), + name="all_campaign_donations_api", + ), + path( + "v1/campaign_contract_config", + CampaignContractConfigAPI.as_view(), + name="campaign_contract_config_api", + ), # donors path("v1/donors", DonorsAPI.as_view(), name="donors_api"), # lists diff --git a/base/settings.py b/base/settings.py index 0f6397c..a37a3d6 100644 --- a/base/settings.py +++ b/base/settings.py @@ -125,7 +125,8 @@ "tokens", "nadabot", "chains", - "grantpicks" + "grantpicks", + "campaigns" ] DEFAULT_PAGE_SIZE = 30 diff --git a/campaigns/__init__.py b/campaigns/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/campaigns/admin.py b/campaigns/admin.py new file mode 100644 index 0000000..61ee160 --- /dev/null +++ b/campaigns/admin.py @@ -0,0 +1,176 @@ +from django.contrib import admin +from django.utils.dateformat import format +from django.utils.timezone import localtime + +from .models import Campaign, CampaignDonation + + +@admin.register(Campaign) +class CampaignAdmin(admin.ModelAdmin): + list_display = [ + "on_chain_id", + "name", + "owner_address", + "recipient_address", + "token_address", + "target_amount", + "total_raised_amount", + "net_raised_amount", + "start_at", + "end_at", + "created_at", + "allow_fee_avoidance", + ] + list_filter = ( + "start_at", + "end_at", + "created_at", + "allow_fee_avoidance", + "owner", + "recipient", + "token", + ) + search_fields = ( + "name", + "description", + "owner__id", + "recipient__id", + "on_chain_id", + ) + date_hierarchy = "created_at" + ordering = ("-created_at",) + readonly_fields = ( + "on_chain_id", + "created_at", + "total_raised_amount", + "total_raised_amount_usd", + "net_raised_amount", + "net_raised_amount_usd", + "escrow_balance", + "escrow_balance_usd", + ) + + def get_queryset(self, request): + return ( + super() + .get_queryset(request) + .select_related("owner", "recipient", "token") + .prefetch_related("donations") + ) + + def owner_address(self, obj): + return obj.owner.id + owner_address.admin_order_field = "owner__id" + owner_address.short_description = "Owner Address" + + def recipient_address(self, obj): + return obj.recipient.id + recipient_address.admin_order_field = "recipient__id" + recipient_address.short_description = "Recipient Address" + + def token_address(self, obj): + return obj.token.account.id if obj.token else "NEAR" + token_address.admin_order_field = "token__account__id" + token_address.short_description = "Token" + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + # Allow viewing but restrict editing of calculated fields + return True + + def has_delete_permission(self, request, obj=None): + return False + + +@admin.register(CampaignDonation) +class CampaignDonationAdmin(admin.ModelAdmin): + list_display = [ + "on_chain_id", + "campaign_id_display", + "campaign_name", + "donor_address", + "token_address", + "total_amount", + "total_amount_usd", + "net_amount", + "net_amount_usd", + "donated_at", + "returned_at", + "tx_hash", + ] + list_filter = ( + "donated_at", + "returned_at", + "campaign", + "donor", + "token", + "referrer", + ) + search_fields = ( + "message", + "donor__id", + "campaign__name", + "campaign__on_chain_id", + "on_chain_id", + "tx_hash", + ) + date_hierarchy = "donated_at" + ordering = ("-donated_at",) + readonly_fields = ( + "on_chain_id", + "total_amount_usd", + "net_amount_usd", + "protocol_fee_usd", + "referrer_fee_usd", + "creator_fee_usd", + "donated_at", + "returned_at", + "tx_hash", + ) + + def get_queryset(self, request): + return ( + super() + .get_queryset(request) + .select_related("campaign", "donor", "token", "referrer") + ) + + def campaign_id_display(self, obj): + return obj.campaign.on_chain_id + campaign_id_display.admin_order_field = "campaign__on_chain_id" + campaign_id_display.short_description = "Campaign ID" + + def campaign_name(self, obj): + return obj.campaign.name + campaign_name.admin_order_field = "campaign__name" + campaign_name.short_description = "Campaign Name" + + def donor_address(self, obj): + return obj.donor.id + donor_address.admin_order_field = "donor__id" + donor_address.short_description = "Donor Address" + + def token_address(self, obj): + return obj.token.account.id if obj.token else "NEAR" + token_address.admin_order_field = "token__account__id" + token_address.short_description = "Token" + + def formfield_for_dbfield(self, db_field, request, **kwargs): + field = super(CampaignDonationAdmin, self).formfield_for_dbfield( + db_field, request, **kwargs + ) + if db_field.name in ["donated_at", "returned_at"]: + field.widget.format = "%d-%m-%Y %H:%M" + field.widget.attrs.update({"class": "vDateField", "size": "20"}) + return field + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/campaigns/api.py b/campaigns/api.py new file mode 100644 index 0000000..7da728f --- /dev/null +++ b/campaigns/api.py @@ -0,0 +1,358 @@ +import requests +from django.conf import settings +from django.utils.decorators import method_decorator +from django.views.decorators.cache import cache_page +from drf_spectacular.utils import ( + OpenApiExample, + OpenApiParameter, + OpenApiResponse, + extend_schema, +) + +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.views import APIView + +from api.pagination import pagination_parameters +from api.pagination import CustomSizePageNumberPagination +from base.logging import logger + +from .models import Campaign, CampaignDonation +from .serializers import ( + CampaignSerializer, + CampaignDonationSerializer, + CampaignContractConfigSerializer, + PaginatedCampaignsResponseSerializer, + PaginatedCampaignDonationsResponseSerializer, + SIMPLE_CAMPAIGN_EXAMPLE, + SIMPLE_CAMPAIGN_DONATION_EXAMPLE, + PAGINATED_CAMPAIGNS_EXAMPLE, + PAGINATED_CAMPAIGN_DONATIONS_EXAMPLE, +) + +CAMPAIGN_CONTRACT = "v1.campaign." + settings.POTLOCK_TLA + + +class CampaignsAPI(APIView, CustomSizePageNumberPagination): + @extend_schema( + parameters=[ + *pagination_parameters, + OpenApiParameter( + name="owner", + description="Filter campaigns by owner account ID", + required=False, + type=str, + ), + OpenApiParameter( + name="recipient", + description="Filter campaigns by recipient account ID", + required=False, + type=str, + ), + OpenApiParameter( + name="token", + description="Filter campaigns by token account ID", + required=False, + type=str, + ), + OpenApiParameter( + name="active", + description="Filter by active campaigns (true/false)", + required=False, + type=bool, + ), + ], + responses={ + 200: OpenApiResponse( + response=PaginatedCampaignsResponseSerializer, + description="Returns paginated list of campaigns", + examples=[ + OpenApiExample( + "example-1", + summary="Paginated campaigns response", + description="Example response for campaigns list", + value=PAGINATED_CAMPAIGNS_EXAMPLE, + response_only=True, + ), + ], + ), + }, + ) + @method_decorator(cache_page(60 * 5)) + def get(self, request: Request, *args, **kwargs): + """Get paginated list of campaigns with optional filters""" + queryset = Campaign.objects.select_related('owner', 'recipient', 'token').all() + + # Apply filters + owner = request.query_params.get('owner') + if owner: + queryset = queryset.filter(owner__id=owner) + + recipient = request.query_params.get('recipient') + if recipient: + queryset = queryset.filter(recipient__id=recipient) + + token = request.query_params.get('token') + if token: + if token.lower() == 'near': + queryset = queryset.filter(token__isnull=True) + else: + queryset = queryset.filter(token__account__id=token) + + active = request.query_params.get('active') + if active is not None: + from django.utils import timezone + from django.db import models + now = timezone.now() + if active.lower() == 'true': + queryset = queryset.filter( + start_at__lte=now, + end_at__gte=now + ) + else: + queryset = queryset.filter( + models.Q(start_at__gt=now) | models.Q(end_at__lt=now) + ) + + # Paginate results + page = self.paginate_queryset(queryset, request) + if page is not None: + serializer = CampaignSerializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = CampaignSerializer(queryset, many=True) + return Response(serializer.data) + + +class CampaignDetailAPI(APIView): + @extend_schema( + parameters=[ + OpenApiParameter( + name="campaign_id", + description="Campaign ID", + required=True, + type=int, + location=OpenApiParameter.PATH, + ), + ], + responses={ + 200: OpenApiResponse( + response=CampaignSerializer, + description="Returns campaign details", + examples=[ + OpenApiExample( + "example-1", + summary="Campaign details", + description="Example response for campaign details", + value=SIMPLE_CAMPAIGN_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Campaign not found"), + }, + ) + @method_decorator(cache_page(60 * 5)) + def get(self, request: Request, campaign_id: int, *args, **kwargs): + """Get campaign details by ID""" + try: + campaign = Campaign.objects.select_related( + 'owner', 'recipient', 'token' + ).get(on_chain_id=campaign_id) + serializer = CampaignSerializer(campaign) + return Response(serializer.data) + except Campaign.DoesNotExist: + return Response({'error': 'Campaign not found'}, status=404) + + +class CampaignDonationsAPI(APIView, CustomSizePageNumberPagination): + @extend_schema( + parameters=[ + OpenApiParameter( + name="campaign_id", + description="Campaign ID", + required=True, + type=int, + location=OpenApiParameter.PATH, + ), + *pagination_parameters, + OpenApiParameter( + name="donor", + description="Filter donations by donor account ID", + required=False, + type=str, + ), + OpenApiParameter( + name="exclude_refunded", + description="Exclude refunded donations (true/false)", + required=False, + type=bool, + ), + ], + responses={ + 200: OpenApiResponse( + response=PaginatedCampaignDonationsResponseSerializer, + description="Returns paginated list of campaign donations", + examples=[ + OpenApiExample( + "example-1", + summary="Paginated campaign donations response", + description="Example response for campaign donations list", + value=PAGINATED_CAMPAIGN_DONATIONS_EXAMPLE, + response_only=True, + ), + ], + ), + 404: OpenApiResponse(description="Campaign not found"), + }, + ) + @method_decorator(cache_page(60 * 2)) + def get(self, request: Request, campaign_id: int, *args, **kwargs): + """Get paginated list of donations for a specific campaign""" + try: + campaign = Campaign.objects.get(on_chain_id=campaign_id) + except Campaign.DoesNotExist: + return Response({'error': 'Campaign not found'}, status=404) + + queryset = CampaignDonation.objects.select_related( + 'campaign', 'donor', 'token', 'referrer' + ).filter(campaign=campaign) + + # Apply filters + donor = request.query_params.get('donor') + if donor: + queryset = queryset.filter(donor__id=donor) + + exclude_refunded = request.query_params.get('exclude_refunded') + if exclude_refunded and exclude_refunded.lower() == 'true': + queryset = queryset.filter(returned_at__isnull=True) + + # Paginate results + page = self.paginate_queryset(queryset, request) + if page is not None: + serializer = CampaignDonationSerializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = CampaignDonationSerializer(queryset, many=True) + return Response(serializer.data) + + +class AllCampaignDonationsAPI(APIView, CustomSizePageNumberPagination): + @extend_schema( + parameters=[ + *pagination_parameters, + OpenApiParameter( + name="donor", + description="Filter donations by donor account ID", + required=False, + type=str, + ), + OpenApiParameter( + name="campaign_id", + description="Filter donations by campaign ID", + required=False, + type=int, + ), + OpenApiParameter( + name="exclude_refunded", + description="Exclude refunded donations (true/false)", + required=False, + type=bool, + ), + ], + responses={ + 200: OpenApiResponse( + response=PaginatedCampaignDonationsResponseSerializer, + description="Returns paginated list of all campaign donations", + examples=[ + OpenApiExample( + "example-1", + summary="Paginated campaign donations response", + description="Example response for all campaign donations", + value=PAGINATED_CAMPAIGN_DONATIONS_EXAMPLE, + response_only=True, + ), + ], + ), + }, + ) + @method_decorator(cache_page(60 * 2)) + def get(self, request: Request, *args, **kwargs): + """Get paginated list of all campaign donations with optional filters""" + queryset = CampaignDonation.objects.select_related( + 'campaign', 'donor', 'token', 'referrer' + ).all() + + # Apply filters + donor = request.query_params.get('donor') + if donor: + queryset = queryset.filter(donor__id=donor) + + campaign_id = request.query_params.get('campaign_id') + if campaign_id: + queryset = queryset.filter(campaign__on_chain_id=campaign_id) + + exclude_refunded = request.query_params.get('exclude_refunded') + if exclude_refunded and exclude_refunded.lower() == 'true': + queryset = queryset.filter(returned_at__isnull=True) + + # Paginate results + page = self.paginate_queryset(queryset, request) + if page is not None: + serializer = CampaignDonationSerializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = CampaignDonationSerializer(queryset, many=True) + return Response(serializer.data) + + +class CampaignContractConfigAPI(APIView): + @extend_schema( + responses={ + 200: OpenApiResponse( + response=CampaignContractConfigSerializer, + description=f"Returns config for {CAMPAIGN_CONTRACT}", + examples=[ + OpenApiExample( + "example-1", + summary="Campaign contract config", + description="Example response for campaign contract config", + value={ + "owner": "potlock.near", + "protocol_fee_basis_points": 250, + "protocol_fee_recipient_account": "impact.sputnik-dao.near", + "default_referral_fee_basis_points": 500, + "default_creator_fee_basis_points": 250, + }, + response_only=True, + ), + ], + ), + 500: OpenApiResponse(description="Internal server error"), + }, + ) + # @method_decorator(cache_page(60 * 5)) + def get(self, request: Request, *args, **kwargs): + """Get campaign contract configuration""" + url = f"{settings.FASTNEAR_RPC_URL}/account/{CAMPAIGN_CONTRACT}/view/get_config" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + # Remove fields we don't want to expose in the API + # fields_to_remove = [ + # 'next_campaign_id', + # 'next_donation_id', + # 'total_campaigns_count', + # 'total_donations_count', + # 'total_donations_amount', + # 'net_donations_amount', + # ] + # for field in fields_to_remove: + # data.pop(field, None) + + return Response(data) + else: + logger.error( + f"Request for {CAMPAIGN_CONTRACT} config failed ({response.status_code}) with message: {response.text}" + ) + return Response({"message": response.text}, status=response.status_code) diff --git a/campaigns/apps.py b/campaigns/apps.py new file mode 100644 index 0000000..4a21739 --- /dev/null +++ b/campaigns/apps.py @@ -0,0 +1,11 @@ +from django.apps import AppConfig + + +class CampaignsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "campaigns" + verbose_name = "Campaigns" + + def ready(self): + # Import signal handlers if any + pass diff --git a/campaigns/migrations/0001_initial.py b/campaigns/migrations/0001_initial.py new file mode 100644 index 0000000..4255ffc --- /dev/null +++ b/campaigns/migrations/0001_initial.py @@ -0,0 +1,472 @@ +# Generated by Django 5.0.6 on 2025-07-28 21:50 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ("tokens", "0006_rename_id_token_account"), + ] + + operations = [ + migrations.CreateModel( + name="Campaign", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("on_chain_id", models.BigIntegerField(db_index=True, unique=True)), + ( + "name", + models.TextField(help_text="Campaign name.", verbose_name="name"), + ), + ( + "description", + models.TextField( + blank=True, + help_text="Campaign description.", + null=True, + verbose_name="description", + ), + ), + ( + "cover_image_url", + models.TextField( + blank=True, + help_text="Campaign cover image URL.", + null=True, + verbose_name="cover image URL", + ), + ), + ( + "start_at", + models.DateTimeField( + db_index=True, + help_text="Campaign start date.", + verbose_name="start at", + ), + ), + ( + "end_at", + models.DateTimeField( + blank=True, + db_index=True, + help_text="Campaign end date.", + null=True, + verbose_name="end at", + ), + ), + ( + "created_at", + models.DateTimeField( + db_index=True, + help_text="Campaign creation date.", + verbose_name="created at", + ), + ), + ( + "target_amount", + models.CharField( + help_text="Campaign target amount.", + max_length=64, + verbose_name="target amount", + ), + ), + ( + "target_amount_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Target amount in USD.", + max_digits=20, + null=True, + verbose_name="target amount in USD", + ), + ), + ( + "min_amount", + models.CharField( + blank=True, + help_text="Campaign minimum amount.", + max_length=64, + null=True, + verbose_name="minimum amount", + ), + ), + ( + "min_amount_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Minimum amount in USD.", + max_digits=20, + null=True, + verbose_name="minimum amount in USD", + ), + ), + ( + "max_amount", + models.CharField( + blank=True, + help_text="Campaign maximum amount.", + max_length=64, + null=True, + verbose_name="maximum amount", + ), + ), + ( + "max_amount_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Maximum amount in USD.", + max_digits=20, + null=True, + verbose_name="maximum amount in USD", + ), + ), + ( + "total_raised_amount", + models.CharField( + default="0", + help_text="Campaign total raised amount.", + max_length=64, + verbose_name="total raised amount", + ), + ), + ( + "total_raised_amount_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Total raised amount in USD.", + max_digits=20, + null=True, + verbose_name="total raised amount in USD", + ), + ), + ( + "net_raised_amount", + models.CharField( + default="0", + help_text="Campaign net raised amount.", + max_length=64, + verbose_name="net raised amount", + ), + ), + ( + "net_raised_amount_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Net raised amount in USD.", + max_digits=20, + null=True, + verbose_name="net raised amount in USD", + ), + ), + ( + "escrow_balance", + models.CharField( + default="0", + help_text="Campaign escrow balance.", + max_length=64, + verbose_name="escrow balance", + ), + ), + ( + "escrow_balance_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Escrow balance in USD.", + max_digits=20, + null=True, + verbose_name="escrow balance in USD", + ), + ), + ( + "referral_fee_basis_points", + models.PositiveIntegerField( + help_text="Referral fee basis points.", + verbose_name="referral fee basis points", + ), + ), + ( + "creator_fee_basis_points", + models.PositiveIntegerField( + help_text="Creator fee basis points.", + verbose_name="creator fee basis points", + ), + ), + ( + "allow_fee_avoidance", + models.BooleanField( + default=False, + help_text="Allow fee avoidance.", + verbose_name="allow fee avoidance", + ), + ), + ( + "owner", + models.ForeignKey( + help_text="Campaign owner.", + on_delete=django.db.models.deletion.CASCADE, + related_name="owned_campaigns", + to="accounts.account", + ), + ), + ( + "recipient", + models.ForeignKey( + help_text="Campaign recipient.", + on_delete=django.db.models.deletion.CASCADE, + related_name="recipient_of_campaign", + to="accounts.account", + ), + ), + ( + "token", + models.ForeignKey( + blank=True, + help_text="Campaign token (null for NEAR).", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="campaigns", + to="tokens.token", + ), + ), + ], + options={ + "ordering": ["-created_at"], + }, + ), + migrations.CreateModel( + name="CampaignDonation", + fields=[ + ( + "id", + models.AutoField( + help_text="Donation id.", + primary_key=True, + serialize=False, + verbose_name="donation id", + ), + ), + ( + "on_chain_id", + models.IntegerField( + db_index=True, + help_text="Campaign donation id in contract", + verbose_name="campaign donation id", + ), + ), + ( + "total_amount", + models.CharField( + help_text="Total amount.", + max_length=64, + verbose_name="total amount", + ), + ), + ( + "total_amount_usd", + models.DecimalField( + blank=True, + db_index=True, + decimal_places=2, + help_text="Total amount in USD.", + max_digits=20, + null=True, + verbose_name="total amount in USD", + ), + ), + ( + "net_amount", + models.CharField( + help_text="Net amount.", + max_length=64, + verbose_name="net amount", + ), + ), + ( + "net_amount_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Net amount in USD.", + max_digits=20, + null=True, + verbose_name="net amount in USD", + ), + ), + ( + "message", + models.TextField( + blank=True, + help_text="Donation message.", + max_length=1024, + null=True, + verbose_name="message", + ), + ), + ( + "donated_at", + models.DateTimeField( + db_index=True, + help_text="Donation date.", + verbose_name="donated at", + ), + ), + ( + "protocol_fee", + models.CharField( + help_text="Protocol fee.", + max_length=64, + verbose_name="protocol fee", + ), + ), + ( + "protocol_fee_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Protocol fee in USD.", + max_digits=20, + null=True, + verbose_name="protocol fee in USD", + ), + ), + ( + "referrer_fee", + models.CharField( + blank=True, + help_text="Referrer fee.", + max_length=64, + null=True, + verbose_name="referrer fee", + ), + ), + ( + "referrer_fee_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Referrer fee in USD.", + max_digits=20, + null=True, + verbose_name="referrer fee in USD", + ), + ), + ( + "creator_fee", + models.CharField( + blank=True, + help_text="Creator fee.", + max_length=64, + null=True, + verbose_name="creator fee", + ), + ), + ( + "creator_fee_usd", + models.DecimalField( + blank=True, + decimal_places=2, + help_text="Creator fee in USD.", + max_digits=20, + null=True, + verbose_name="creator fee in USD", + ), + ), + ( + "returned_at", + models.DateTimeField( + blank=True, + db_index=True, + help_text="Donation returned date.", + null=True, + verbose_name="donation returned at", + ), + ), + ( + "escrowed", + models.BooleanField( + default=False, + help_text="Is Donation Escrowed.", + verbose_name="escrowed donation", + ), + ), + ( + "tx_hash", + models.CharField( + blank=True, + help_text="Transaction hash.", + max_length=64, + null=True, + verbose_name="transaction hash", + ), + ), + ( + "campaign", + models.ForeignKey( + help_text="Campaign donation.", + on_delete=django.db.models.deletion.CASCADE, + related_name="donations", + to="campaigns.campaign", + ), + ), + ( + "donor", + models.ForeignKey( + help_text="Donor.", + on_delete=django.db.models.deletion.CASCADE, + related_name="campaign_donations", + to="accounts.account", + ), + ), + ( + "referrer", + models.ForeignKey( + blank=True, + help_text="Donation referrer.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="referred_campaign_donations", + to="accounts.account", + ), + ), + ( + "token", + models.ForeignKey( + blank=True, + help_text="Donation token (null for NEAR).", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="campaign_donations", + to="tokens.token", + ), + ), + ], + options={ + "ordering": ["-donated_at"], + }, + ), + migrations.AddConstraint( + model_name="campaigndonation", + constraint=models.UniqueConstraint( + fields=("on_chain_id", "campaign"), name="unique_campaign_donation_id" + ), + ), + ] diff --git a/campaigns/migrations/__init__.py b/campaigns/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/campaigns/models.py b/campaigns/models.py new file mode 100644 index 0000000..4f3b1cb --- /dev/null +++ b/campaigns/models.py @@ -0,0 +1,450 @@ +from django.db import models +from accounts.models import Account +from tokens.models import Token +from django.utils.translation import gettext_lazy as _ +from django.forms.models import model_to_dict +from asgiref.sync import sync_to_async +from base.logging import logger + + +class Campaign(models.Model): + on_chain_id = models.BigIntegerField(unique=True, db_index=True) + owner = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="owned_campaigns", + null=False, + help_text=_("Campaign owner."), + db_index=True, + ) + name = models.TextField( + _("name"), + null=False, + help_text=_("Campaign name."), + ) + description = models.TextField( + _("description"), + null=True, + blank=True, + help_text=_("Campaign description."), + ) + cover_image_url = models.TextField( + _("cover image URL"), + null=True, + blank=True, + help_text=_("Campaign cover image URL."), + ) + recipient = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="recipient_of_campaign", + null=False, + help_text=_("Campaign recipient."), + db_index=True, + ) + token = models.ForeignKey( + Token, + on_delete=models.CASCADE, + related_name="campaigns", + null=True, + blank=True, + help_text=_("Campaign token (null for NEAR)."), + db_index=True, + ) + start_at = models.DateTimeField( + _("start at"), + null=False, + help_text=_("Campaign start date."), + db_index=True, + ) + end_at = models.DateTimeField( + _("end at"), + null=True, + blank=True, + help_text=_("Campaign end date."), + db_index=True, + ) + created_at = models.DateTimeField( + _("created at"), + null=False, + help_text=_("Campaign creation date."), + db_index=True, + ) + target_amount = models.CharField( + _("target amount"), + max_length=64, + null=False, + help_text=_("Campaign target amount."), + ) + target_amount_usd = models.DecimalField( + _("target amount in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Target amount in USD."), + ) + min_amount = models.CharField( + _("minimum amount"), + max_length=64, + null=True, + blank=True, + help_text=_("Campaign minimum amount."), + ) + min_amount_usd = models.DecimalField( + _("minimum amount in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Minimum amount in USD."), + ) + max_amount = models.CharField( + _("maximum amount"), + max_length=64, + null=True, + blank=True, + help_text=_("Campaign maximum amount."), + ) + max_amount_usd = models.DecimalField( + _("maximum amount in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Maximum amount in USD."), + ) + total_raised_amount = models.CharField( + _("total raised amount"), + max_length=64, + default="0", + help_text=_("Campaign total raised amount."), + ) + total_raised_amount_usd = models.DecimalField( + _("total raised amount in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Total raised amount in USD."), + ) + net_raised_amount = models.CharField( + _("net raised amount"), + max_length=64, + default="0", + help_text=_("Campaign net raised amount."), + ) + net_raised_amount_usd = models.DecimalField( + _("net raised amount in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Net raised amount in USD."), + ) + escrow_balance = models.CharField( + _("escrow balance"), + max_length=64, + default="0", + help_text=_("Campaign escrow balance."), + ) + escrow_balance_usd = models.DecimalField( + _("escrow balance in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Escrow balance in USD."), + ) + referral_fee_basis_points = models.PositiveIntegerField( + _("referral fee basis points"), + null=False, + help_text=_("Referral fee basis points."), + ) + creator_fee_basis_points = models.PositiveIntegerField( + _("creator fee basis points"), + null=False, + help_text=_("Creator fee basis points."), + ) + allow_fee_avoidance = models.BooleanField( + _("allow fee avoidance"), + null=False, + default=False, + help_text=_("Allow fee avoidance."), + ) + + class Meta: + ordering = ['-created_at'] + + def __str__(self): + return f"Campaign {self.on_chain_id}: {self.name}" + + def to_dict(self): + return model_to_dict(self) + + async def fetch_usd_prices_async(self): + fetch_prices = sync_to_async(self.fetch_usd_prices) + await fetch_prices() + + def fetch_usd_prices(self): + """Fetch USD prices for campaign amounts""" + logger.info(f"fecthing usd price for {self.on_chain_id}") + if not self.token: + # For NEAR campaigns, we could fetch NEAR price here + return + + try: + token = self.token + # Use campaign creation date for price lookup + price_usd = token.fetch_usd_prices_common(self.created_at) + if not price_usd: + logger.info( + f"No USD price found for token {token.name} ({token.account.id}) at {self.created_at}" + ) + return + + # Convert amounts to USD + if self.target_amount: + target_amount = token.format_price(self.target_amount) + self.target_amount_usd = target_amount * price_usd + + if self.min_amount: + min_amount = token.format_price(self.min_amount) + self.min_amount_usd = min_amount * price_usd + + if self.max_amount: + max_amount = token.format_price(self.max_amount) + self.max_amount_usd = max_amount * price_usd + + if self.total_raised_amount: + total_raised = token.format_price(self.total_raised_amount) + self.total_raised_amount_usd = total_raised * price_usd + + if self.net_raised_amount: + net_raised = token.format_price(self.net_raised_amount) + self.net_raised_amount_usd = net_raised * price_usd + + if self.escrow_balance: + escrow = token.format_price(self.escrow_balance) + self.escrow_balance_usd = escrow * price_usd + + self.save() + logger.info(f"Saved USD prices for campaign: {self.on_chain_id}") + except Exception as e: + logger.error(f"Failed to calculate and save USD prices for campaign {self.on_chain_id}: {e}") + + +class CampaignDonation(models.Model): + id = models.AutoField( + _("donation id"), + primary_key=True, + help_text=_("Donation id."), + ) + on_chain_id = models.IntegerField( + _("campaign donation id"), + null=False, + help_text=_("Campaign donation id in contract"), + db_index=True, + ) + campaign = models.ForeignKey( + Campaign, + on_delete=models.CASCADE, + related_name="donations", + null=False, + help_text=_("Campaign donation."), + db_index=True, + ) + donor = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="campaign_donations", + null=False, + help_text=_("Donor."), + db_index=True, + ) + token = models.ForeignKey( + Token, + on_delete=models.CASCADE, + related_name="campaign_donations", + null=True, + blank=True, + help_text=_("Donation token (null for NEAR)."), + db_index=True, + ) + total_amount = models.CharField( + _("total amount"), + max_length=64, + null=False, + help_text=_("Total amount."), + ) + total_amount_usd = models.DecimalField( + _("total amount in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Total amount in USD."), + db_index=True, + ) + net_amount = models.CharField( + _("net amount"), + max_length=64, + null=False, + help_text=_("Net amount."), + ) + net_amount_usd = models.DecimalField( + _("net amount in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Net amount in USD."), + ) + message = models.TextField( + _("message"), + max_length=1024, + null=True, + blank=True, + help_text=_("Donation message."), + ) + donated_at = models.DateTimeField( + _("donated at"), + null=False, + help_text=_("Donation date."), + db_index=True, + ) + protocol_fee = models.CharField( + _("protocol fee"), + max_length=64, + null=False, + help_text=_("Protocol fee."), + ) + protocol_fee_usd = models.DecimalField( + _("protocol fee in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Protocol fee in USD."), + ) + referrer = models.ForeignKey( + Account, + on_delete=models.CASCADE, + related_name="referred_campaign_donations", + null=True, + blank=True, + help_text=_("Donation referrer."), + ) + referrer_fee = models.CharField( + _("referrer fee"), + max_length=64, + null=True, + blank=True, + help_text=_("Referrer fee."), + ) + referrer_fee_usd = models.DecimalField( + _("referrer fee in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Referrer fee in USD."), + ) + creator_fee = models.CharField( + _("creator fee"), + max_length=64, + null=True, + blank=True, + help_text=_("Creator fee."), + ) + creator_fee_usd = models.DecimalField( + _("creator fee in USD"), + max_digits=20, + decimal_places=2, + null=True, + blank=True, + help_text=_("Creator fee in USD."), + ) + returned_at = models.DateTimeField( + _("donation returned at"), + null=True, + blank=True, + help_text=_("Donation returned date."), + db_index=True, + ) + + escrowed = models.BooleanField( + _("escrowed donation"), + null=False, + default=False, + help_text=_("Is Donation Escrowed."), + ) + + tx_hash = models.CharField( + _("transaction hash"), + max_length=64, + null=True, + blank=True, + help_text=_("Transaction hash."), + ) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["on_chain_id", "campaign"], + name="unique_campaign_donation_id", + ), + ] + ordering = ['-donated_at'] + + def __str__(self): + return f"Donation {self.on_chain_id} to Campaign {self.campaign.on_chain_id}" + + def to_dict(self): + return model_to_dict(self) + + async def fetch_usd_prices_async(self): + fetch_prices = sync_to_async(self.fetch_usd_prices) + await fetch_prices() + + def fetch_usd_prices(self): + """Fetch USD prices for campaign donation amounts""" + try: + # Use campaign token if donation token is not set + token = self.token or self.campaign.token + if not token: + # For NEAR donations, we could fetch NEAR price here + return + + price_usd = token.fetch_usd_prices_common(self.donated_at) + if not price_usd: + logger.info( + f"No USD price found for token {token.name} ({token.account.id}) at {self.donated_at}" + ) + return + + # Calculate USD amounts + total_amount = token.format_price(self.total_amount) + net_amount = token.format_price(self.net_amount) + protocol_amount = token.format_price(self.protocol_fee) + referrer_amount = ( + None if not self.referrer_fee else token.format_price(self.referrer_fee) + ) + creator_amount = ( + None if not self.creator_fee else token.format_price(self.creator_fee) + ) + + self.total_amount_usd = total_amount * price_usd + self.net_amount_usd = net_amount * price_usd + self.protocol_fee_usd = protocol_amount * price_usd + self.referrer_fee_usd = ( + None if not referrer_amount else referrer_amount * price_usd + ) + self.creator_fee_usd = ( + None if not creator_amount else creator_amount * price_usd + ) + + self.save() + logger.info(f"Saved USD prices for campaign donation: {self.on_chain_id}") + except Exception as e: + logger.error(f"Failed to calculate and save USD prices for campaign donation {self.on_chain_id}: {e}") diff --git a/campaigns/serializers.py b/campaigns/serializers.py new file mode 100644 index 0000000..e7434ce --- /dev/null +++ b/campaigns/serializers.py @@ -0,0 +1,168 @@ +from rest_framework import serializers +from rest_framework.serializers import ( + ModelSerializer, + Serializer, + SerializerMethodField, +) + +from accounts.serializers import SIMPLE_ACCOUNT_EXAMPLE, AccountSerializer +from tokens.serializers import SIMPLE_TOKEN_EXAMPLE, TokenSerializer + +from .models import Campaign, CampaignDonation + + +class CampaignSerializer(ModelSerializer): + class Meta: + model = Campaign + fields = [ + "on_chain_id", + "name", + "description", + "cover_image_url", + "start_at", + "end_at", + "created_at", + "target_amount", + "target_amount_usd", + "min_amount", + "min_amount_usd", + "max_amount", + "max_amount_usd", + "total_raised_amount", + "total_raised_amount_usd", + "net_raised_amount", + "net_raised_amount_usd", + "escrow_balance", + "escrow_balance_usd", + "referral_fee_basis_points", + "creator_fee_basis_points", + "allow_fee_avoidance", + "owner", + "recipient", + "token", + ] + + owner = AccountSerializer() + recipient = AccountSerializer() + token = TokenSerializer() + + +class CampaignDonationSerializer(ModelSerializer): + class Meta: + model = CampaignDonation + fields = [ + "id", + "on_chain_id", + "total_amount", + "total_amount_usd", + "net_amount", + "net_amount_usd", + "message", + "donated_at", + "protocol_fee", + "protocol_fee_usd", + "referrer_fee", + "referrer_fee_usd", + "creator_fee", + "creator_fee_usd", + "returned_at", + "escrowed", + "tx_hash", + "campaign", + "donor", + "token", + "referrer", + ] + + campaign = CampaignSerializer() + donor = AccountSerializer() + token = TokenSerializer() + referrer = AccountSerializer() + + +SIMPLE_CAMPAIGN_EXAMPLE = { + "on_chain_id": 1, + "name": "Help Build Community Center", + "description": "Fundraising campaign to build a new community center for local residents.", + "cover_image_url": "https://example.com/campaign-image.jpg", + "start_at": "2024-01-01T00:00:00Z", + "end_at": "2024-03-01T00:00:00Z", + "created_at": "2023-12-15T10:30:00Z", + "target_amount": "10000000000000000000000000", + "target_amount_usd": "10000.00", + "min_amount": "1000000000000000000000000", + "min_amount_usd": "1000.00", + "max_amount": None, + "max_amount_usd": None, + "total_raised_amount": "2500000000000000000000000", + "total_raised_amount_usd": "2500.00", + "net_raised_amount": "2375000000000000000000000", + "net_raised_amount_usd": "2375.00", + "escrow_balance": "0", + "escrow_balance_usd": "0.00", + "referral_fee_basis_points": 500, + "creator_fee_basis_points": 250, + "allow_fee_avoidance": False, + "owner": SIMPLE_ACCOUNT_EXAMPLE, + "recipient": SIMPLE_ACCOUNT_EXAMPLE, + "token": SIMPLE_TOKEN_EXAMPLE, +} + +SIMPLE_CAMPAIGN_DONATION_EXAMPLE = { + "id": 100, + "on_chain_id": 50, + "total_amount": "1000000000000000000000000", + "total_amount_usd": "1000.00", + "net_amount": "925000000000000000000000", + "net_amount_usd": "925.00", + "message": "Great cause! Happy to support.", + "donated_at": "2024-01-15T14:20:30Z", + "protocol_fee": "25000000000000000000000", + "protocol_fee_usd": "25.00", + "referrer_fee": "25000000000000000000000", + "referrer_fee_usd": "25.00", + "creator_fee": "25000000000000000000000", + "creator_fee_usd": "25.00", + "returned_at": None, + "tx_hash": "ABC123DEF456GHI789JKL012MNO345PQR678STU901VWX234YZA567BCD890", + "campaign": SIMPLE_CAMPAIGN_EXAMPLE, + "donor": SIMPLE_ACCOUNT_EXAMPLE, + "token": SIMPLE_TOKEN_EXAMPLE, + "referrer": SIMPLE_ACCOUNT_EXAMPLE, +} + +PAGINATED_CAMPAIGNS_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_CAMPAIGN_EXAMPLE], +} + +PAGINATED_CAMPAIGN_DONATIONS_EXAMPLE = { + "count": 1, + "next": None, + "previous": None, + "results": [SIMPLE_CAMPAIGN_DONATION_EXAMPLE], +} + + +class PaginatedCampaignsResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = CampaignSerializer(many=True) + + +class PaginatedCampaignDonationsResponseSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField(allow_null=True) + previous = serializers.CharField(allow_null=True) + results = CampaignDonationSerializer(many=True) + + +class CampaignContractConfigSerializer(Serializer): + owner = serializers.CharField() + protocol_fee_basis_points = serializers.IntegerField() + protocol_fee_recipient_account = serializers.CharField() + default_referral_fee_basis_points = serializers.IntegerField() + default_creator_fee_basis_points = serializers.IntegerField() diff --git a/campaigns/tests.py b/campaigns/tests.py new file mode 100644 index 0000000..0547f9f --- /dev/null +++ b/campaigns/tests.py @@ -0,0 +1,317 @@ +from datetime import datetime, timezone +from decimal import Decimal +from django.test import TestCase +from django.utils import timezone as django_timezone + +from accounts.models import Account +from tokens.models import Token +from .models import Campaign, CampaignDonation + + +class CampaignModelTests(TestCase): + def setUp(self): + """Set up test data""" + self.owner = Account.objects.create(id="owner.near") + self.recipient = Account.objects.create(id="recipient.near") + self.token = Token.objects.create( + account=Account.objects.create(id="token.near"), + name="Test Token", + symbol="TEST", + decimals=18, + ) + + self.campaign_data = { + "on_chain_id": 1, + "owner": self.owner, + "name": "Test Campaign", + "description": "A test campaign", + "recipient": self.recipient, + "start_at": django_timezone.now(), + "created_at": django_timezone.now(), + "target_amount": "1000000000000000000000000", + "referral_fee_basis_points": 500, + "creator_fee_basis_points": 250, + } + + def test_campaign_creation(self): + """Test basic campaign creation""" + campaign = Campaign.objects.create(**self.campaign_data) + + self.assertEqual(campaign.on_chain_id, 1) + self.assertEqual(campaign.name, "Test Campaign") + self.assertEqual(campaign.owner, self.owner) + self.assertEqual(campaign.recipient, self.recipient) + self.assertEqual(campaign.total_raised_amount, "0") + self.assertEqual(campaign.net_raised_amount, "0") + self.assertEqual(campaign.escrow_balance, "0") + self.assertFalse(campaign.allow_fee_avoidance) + + def test_campaign_with_token(self): + """Test campaign creation with token""" + self.campaign_data["token"] = self.token + campaign = Campaign.objects.create(**self.campaign_data) + + self.assertEqual(campaign.token, self.token) + + def test_campaign_with_optional_fields(self): + """Test campaign with optional fields""" + end_time = django_timezone.now() + self.campaign_data.update({ + "end_at": end_time, + "cover_image_url": "https://example.com/image.jpg", + "min_amount": "100000000000000000000", # 100 tokens + "max_amount": "10000000000000000000000000", # 10M tokens + "allow_fee_avoidance": True, + }) + + campaign = Campaign.objects.create(**self.campaign_data) + + self.assertEqual(campaign.end_at, end_time) + self.assertEqual(campaign.cover_image_url, "https://example.com/image.jpg") + self.assertEqual(campaign.min_amount, "100000000000000000000") + self.assertEqual(campaign.max_amount, "10000000000000000000000000") + self.assertTrue(campaign.allow_fee_avoidance) + + def test_campaign_string_representation(self): + """Test campaign string representation""" + campaign = Campaign.objects.create(**self.campaign_data) + expected = f"Campaign {campaign.on_chain_id}: {campaign.name}" + self.assertEqual(str(campaign), expected) + + def test_campaign_ordering(self): + """Test campaign ordering by creation date""" + # Create first campaign + campaign1 = Campaign.objects.create(**self.campaign_data) + + # Create second campaign with different data + campaign_data_2 = self.campaign_data.copy() + campaign_data_2["on_chain_id"] = 2 + campaign_data_2["name"] = "Second Campaign" + campaign_data_2["created_at"] = django_timezone.now() + campaign2 = Campaign.objects.create(**campaign_data_2) + + # Test ordering (newest first) + campaigns = list(Campaign.objects.all()) + self.assertEqual(campaigns[0], campaign2) # Newer campaign first + self.assertEqual(campaigns[1], campaign1) + + def test_unique_on_chain_id(self): + """Test that on_chain_id must be unique""" + Campaign.objects.create(**self.campaign_data) + + # Try to create another campaign with same on_chain_id + campaign_data_2 = self.campaign_data.copy() + campaign_data_2["name"] = "Duplicate Campaign" + + with self.assertRaises(Exception): # Should raise IntegrityError + Campaign.objects.create(**campaign_data_2) + + +class CampaignDonationModelTests(TestCase): + def setUp(self): + """Set up test data""" + self.owner = Account.objects.create(id="owner.near") + self.recipient = Account.objects.create(id="recipient.near") + self.donor = Account.objects.create(id="donor.near") + self.referrer = Account.objects.create(id="referrer.near") + + self.campaign = Campaign.objects.create( + on_chain_id=1, + owner=self.owner, + name="Test Campaign", + recipient=self.recipient, + start_at=django_timezone.now(), + created_at=django_timezone.now(), + target_amount="1000000000000000000000000", + referral_fee_basis_points=500, + creator_fee_basis_points=250, + ) + + self.donation_data = { + "on_chain_id": 1, + "campaign": self.campaign, + "donor": self.donor, + "total_amount": "1000000000000000000000000", # 1M tokens + "net_amount": "925000000000000000000000", # After fees + "donated_at": django_timezone.now(), + "protocol_fee": "25000000000000000000000", # 2.5% + "creator_fee": "25000000000000000000000", # 2.5% + } + + def test_campaign_donation_creation(self): + """Test basic campaign donation creation""" + donation = CampaignDonation.objects.create(**self.donation_data) + + self.assertEqual(donation.on_chain_id, 1) + self.assertEqual(donation.campaign, self.campaign) + self.assertEqual(donation.donor, self.donor) + self.assertEqual(donation.total_amount, "1000000000000000000000000") + self.assertEqual(donation.net_amount, "925000000000000000000000") + self.assertIsNone(donation.returned_at) + + def test_campaign_donation_with_optional_fields(self): + """Test campaign donation with optional fields""" + self.donation_data.update({ + "message": "Great cause!", + "referrer": self.referrer, + "referrer_fee": "25000000000000000000000", + "tx_hash": "ABC123DEF456", + }) + + donation = CampaignDonation.objects.create(**self.donation_data) + + self.assertEqual(donation.message, "Great cause!") + self.assertEqual(donation.referrer, self.referrer) + self.assertEqual(donation.referrer_fee, "25000000000000000000000") + self.assertEqual(donation.tx_hash, "ABC123DEF456") + + def test_campaign_donation_with_usd_amounts(self): + """Test campaign donation with USD amounts""" + self.donation_data.update({ + "total_amount_usd": Decimal("1000.00"), + "net_amount_usd": Decimal("925.00"), + "protocol_fee_usd": Decimal("25.00"), + "creator_fee_usd": Decimal("25.00"), + }) + + donation = CampaignDonation.objects.create(**self.donation_data) + + self.assertEqual(donation.total_amount_usd, Decimal("1000.00")) + self.assertEqual(donation.net_amount_usd, Decimal("925.00")) + self.assertEqual(donation.protocol_fee_usd, Decimal("25.00")) + self.assertEqual(donation.creator_fee_usd, Decimal("25.00")) + + def test_campaign_donation_refund(self): + """Test marking donation as refunded""" + donation = CampaignDonation.objects.create(**self.donation_data) + + # Initially not refunded + self.assertIsNone(donation.returned_at) + + # Mark as refunded + refund_time = django_timezone.now() + donation.returned_at = refund_time + donation.save() + + self.assertEqual(donation.returned_at, refund_time) + + def test_campaign_donation_string_representation(self): + """Test campaign donation string representation""" + donation = CampaignDonation.objects.create(**self.donation_data) + expected = f"Donation {donation.on_chain_id} to Campaign {self.campaign.on_chain_id}" + self.assertEqual(str(donation), expected) + + def test_campaign_donation_ordering(self): + """Test campaign donation ordering by donated_at""" + # Create first donation + donation1 = CampaignDonation.objects.create(**self.donation_data) + + # Create second donation + donation_data_2 = self.donation_data.copy() + donation_data_2["on_chain_id"] = 2 + donation_data_2["donated_at"] = django_timezone.now() + donation2 = CampaignDonation.objects.create(**donation_data_2) + + # Test ordering (newest first) + donations = list(CampaignDonation.objects.all()) + self.assertEqual(donations[0], donation2) # Newer donation first + self.assertEqual(donations[1], donation1) + + def test_unique_constraint(self): + """Test unique constraint on (on_chain_id, campaign)""" + CampaignDonation.objects.create(**self.donation_data) + + # Try to create another donation with same on_chain_id and campaign + donation_data_2 = self.donation_data.copy() + donation_data_2["total_amount"] = "500000000000000000000000" + + with self.assertRaises(Exception): # Should raise IntegrityError + CampaignDonation.objects.create(**donation_data_2) + + def test_campaign_relationship(self): + """Test relationship between campaign and donations""" + donation = CampaignDonation.objects.create(**self.donation_data) + + # Test forward relationship + self.assertEqual(donation.campaign, self.campaign) + + # Test reverse relationship + self.assertIn(donation, self.campaign.donations.all()) + + def test_donor_relationship(self): + """Test relationship between donor and campaign donations""" + donation = CampaignDonation.objects.create(**self.donation_data) + + # Test forward relationship + self.assertEqual(donation.donor, self.donor) + + # Test reverse relationship + self.assertIn(donation, self.donor.campaign_donations.all()) + + +class CampaignModelMethodTests(TestCase): + def setUp(self): + """Set up test data""" + self.owner = Account.objects.create(id="owner.near") + self.recipient = Account.objects.create(id="recipient.near") + + self.campaign = Campaign.objects.create( + on_chain_id=1, + owner=self.owner, + name="Test Campaign", + recipient=self.recipient, + start_at=django_timezone.now(), + created_at=django_timezone.now(), + target_amount="1000000000000000000000000", + referral_fee_basis_points=500, + creator_fee_basis_points=250, + ) + + def test_to_dict_method(self): + """Test campaign to_dict method""" + campaign_dict = self.campaign.to_dict() + + self.assertIsInstance(campaign_dict, dict) + self.assertEqual(campaign_dict['on_chain_id'], 1) + self.assertEqual(campaign_dict['name'], "Test Campaign") + self.assertEqual(campaign_dict['target_amount'], "1000000000000000000000000") + + +class CampaignDonationMethodTests(TestCase): + def setUp(self): + """Set up test data""" + self.owner = Account.objects.create(id="owner.near") + self.recipient = Account.objects.create(id="recipient.near") + self.donor = Account.objects.create(id="donor.near") + + self.campaign = Campaign.objects.create( + on_chain_id=1, + owner=self.owner, + name="Test Campaign", + recipient=self.recipient, + start_at=django_timezone.now(), + created_at=django_timezone.now(), + target_amount="1000000000000000000000000", + referral_fee_basis_points=500, + creator_fee_basis_points=250, + ) + + self.donation = CampaignDonation.objects.create( + on_chain_id=1, + campaign=self.campaign, + donor=self.donor, + total_amount="1000000000000000000000000", + net_amount="925000000000000000000000", + donated_at=django_timezone.now(), + protocol_fee="25000000000000000000000", + creator_fee="25000000000000000000000", + ) + + def test_to_dict_method(self): + """Test campaign donation to_dict method""" + donation_dict = self.donation.to_dict() + + self.assertIsInstance(donation_dict, dict) + self.assertEqual(donation_dict['on_chain_id'], 1) + self.assertEqual(donation_dict['total_amount'], "1000000000000000000000000") + self.assertEqual(donation_dict['net_amount'], "925000000000000000000000") diff --git a/campaigns/views.py b/campaigns/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/campaigns/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/indexer_app/handler.py b/indexer_app/handler.py index c620820..bba301e 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -24,7 +24,11 @@ create_round_payout, handle_add_nadabot_admin, # handle_batch_donations, handle_add_stamp, + handle_campaign_donation, + handle_campaign_donation_refund, + handle_campaign_donation_unescrowed, handle_default_list_status_change, + handle_delete_campaign, handle_delete_list, handle_list_admin_ops, handle_list_owner_change, @@ -32,6 +36,7 @@ handle_list_registration_update, handle_list_update, handle_list_upvote, + handle_new_campaign, handle_new_donation, handle_new_group, handle_new_list, @@ -53,6 +58,7 @@ handle_set_payouts, handle_social_profile_update, handle_transfer_payout, + handle_update_campaign, handle_update_default_human_threshold, process_rounds_deposit_event, process_vote_event, @@ -205,6 +211,18 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess ) if event_name == "payouts_challenge_created": pass + if event_name == "campaign_create": + await handle_new_campaign(parsed_log.get("data")[0], now_datetime) + if event_name == "campaign_update": + await handle_update_campaign(parsed_log.get("data")[0]) + if event_name == "campaign_delete": + await handle_delete_campaign(parsed_log.get("data")[0]) + if event_name == "donation" or event_name == "escrow_insert": + await handle_campaign_donation(parsed_log.get("data")[0], receipt.receipt_id) + if event_name == "escrow_refund": + await handle_campaign_donation_refund(parsed_log.get("data")[0], now_datetime) + if event_name == "escrow_process": + await handle_campaign_donation_unescrowed(parsed_log.get("data")[0]) except json.JSONDecodeError: logger.warning( f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" @@ -359,6 +377,9 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess donation_type = ( "direct" if receiver_id == DONATE_CONTRACT else "pot" ) + if args_dict.get("campaign_id"): + logger.info("Campaign donation. Skipping...") + break logger.info( f"New {donation_type} donation ({method_name}) --- ARGS: {args_dict}, RECEIPT: {receipt}, STATUS: {status_obj}, OUTCOME: {receipt_execution_outcome}, LOGS: {log_data}" ) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index e1e9f3d..7cd000c 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -371,7 +371,7 @@ def stellar_event_indexer(): transaction_hash=event.transaction_hash, data=event_value )) - + if len(stellar_events) > 0: StellarEvent.objects.bulk_create( objs=stellar_events, @@ -398,39 +398,39 @@ def process_stellar_events(): if event_name == 'c_project': event.processed = process_project_event(event_data) - + elif event_name == 'c_round' or event_name == 'u_round': - + # Mark event as processed event.processed = create_or_update_round(event_data, event.contract_id, event.ingested_at) elif event_name == 'apply_to_round': - + # Mark event as processed - event.processed = process_application_to_round(event_data, event.transaction_hash) + event.processed = process_application_to_round(event_data, event.transaction_hash) elif event_name == 'c_app': - + event.processed = create_round_application(event_data, event.transaction_hash) - elif event_name == 'u_app': # application review and aproval + elif event_name == 'u_app': # application review and aproval event.processed = update_application(event_data, event.transaction_hash) - - elif event_name == 'u_ap': + + elif event_name == 'u_ap': event.processed = update_approved_projects(event_data) - + elif event_name == 'c_depo': - + event.processed = process_rounds_deposit_event(event_data, event.transaction_hash) elif event_name == 'c_vote': - + event.processed = process_vote_event(event_data, event.transaction_hash) elif event_name == "c_pay": event.processed = create_round_payout(event_data, event.transaction_hash) elif event_name == "u_pay": - + event.processed = update_round_payout(event_data, event.transaction_hash) event.save() diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 38c4c2e..0a016c2 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -15,6 +15,7 @@ from accounts.models import Account from activities.models import Activity +from campaigns.models import Campaign, CampaignDonation from chains.models import Chain from donations.models import Donation from grantpicks.models import Project, ProjectContact, ProjectContract, ProjectFundingHistory, ProjectRepository, ProjectStatus, Round, RoundDeposit, Vote, VotePair @@ -517,11 +518,11 @@ async def handle_list_registration_removal( receiver_id: str, ): logger.info(f"list reg removal: {data}, {receiver_id}") - + try: list_obj = await List.objects.aget(on_chain_id=data["list_id"]) await list_obj.registrations.filter(id=data["registration_id"]).adelete() - + except Exception as e: logger.error(f"Encountered error trying to remove reg: {e}") @@ -1326,7 +1327,7 @@ def get_block_height() -> int: if record: return record.block_height return 178243042 - + def update_ledger_sequence(sequence, timestamp: datetime): @@ -1389,7 +1390,7 @@ def update_application(event_data, txhash, reviewer_id=None, chain_id="stellar") "status": status, "tx_hash": txhash, } - + appl = PotApplication.objects.filter( applicant=applicant ).first() @@ -1446,9 +1447,9 @@ def get_pair_projects(pair_id: int, round_id: int, chain_id: str) -> Dict: ) .set_timeout(30) .build() - ) + ) + - if pair_result.results: xdr = pair_result.results[0].xdr data = stellar_sdk.scval.to_native(xdr) @@ -1496,12 +1497,12 @@ def process_vote_event(event_data, tx_hash, chain_id="stellar"): if chain_id == "NEAR": pair_id = pick['pair_id'] project_id = pick['voted_project'] - - + + else: pair_id = pick['pair_id'] project_id = Project.objects.get(on_chain_id=pick['project_id']).owner.id - + pair_data = get_pair_projects(pair_id, round_id, chain_id) logger.info(f"pair data from contract...:,{pair_data}") if pair_data: @@ -1511,9 +1512,9 @@ def process_vote_event(event_data, tx_hash, chain_id="stellar"): project_2 = Project.objects.get(on_chain_id=project_id_2).owner.id else: project_1 = project_id_1 - project_2 = project_id_2 + project_2 = project_id_2 + - vp, created = VotePair.objects.update_or_create( vote=vote, pair_id=pair_id, @@ -1603,7 +1604,7 @@ def process_project_event(event_data, chain_id="stellar"): logger.info(f"Created new Project: {project.id}") else: logger.info(f"Updated existing Project: {project.id}") - + return True except Exception as e: @@ -1631,7 +1632,7 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar if remaining_dist_by: remaining_dist_by_obj, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=remaining_dist_by) - if event_data.get('round_complete_ms', event_data.get('round_complete')): + if event_data.get('round_complete_ms', event_data.get('round_complete')): round_time_stamp = datetime.fromtimestamp(event_data.get('round_complete_ms', event_data.get('round_complete')) / 1000) else: round_time_stamp = None @@ -1689,7 +1690,7 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar value=contact['value'] ) round_obj.contacts.add(contact_obj) - + logger.info(f"Created/Updated Round: {round_id}") return True except Exception as e: @@ -1747,7 +1748,7 @@ def create_round_application(event_data, tx_hash, chain_id="stellar"): else: status = PotApplicationStatus[application_data['status'][0].upper()] logger.info(f"Creating application for round: {round_id}") - + appl_defaults = { "message": application_data["applicant_note"], "submitted_at": datetime.fromtimestamp(application_data["submited_ms"] / 1000), @@ -1755,7 +1756,7 @@ def create_round_application(event_data, tx_hash, chain_id="stellar"): "tx_hash": tx_hash, } - + PotApplication.objects.update_or_create( applicant=applicant, round=round_obj, @@ -1782,7 +1783,7 @@ def process_rounds_deposit_event(event_data, tx_hash, chain_id="stellar"): round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) amount = deposit_data["total_amount"] depositor, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=deposit_data["depositor_id"]) - + # Create or update a RoundDeposit object deposit, created = RoundDeposit.objects.update_or_create( round=round_obj, @@ -1797,7 +1798,7 @@ def process_rounds_deposit_event(event_data, tx_hash, chain_id="stellar"): 'deposit_at': datetime.fromtimestamp(deposit_data["deposited_at"] / 1000), } ) - + round_obj.vault_total_deposits = str(int(round_obj.vault_total_deposits or 0) + int(amount)) round_obj.current_vault_balance = str(int(round_obj.current_vault_balance or 0) + int(deposit_data["net_amount"])) round_obj.save() @@ -1865,4 +1866,319 @@ def update_round_payout(event_data, tx_hash, chain_id="stellar"): return True except Exception as e: logger.error(f"Error updating Payout. {str(e)}") - return False \ No newline at end of file + return False + +# Campaign Event Indexing Methods + +async def handle_new_campaign(data: dict, created_at): + """ + Index a new campaign creation event. + Expected data format: + { + "id": campaign_id, + "owner": "account.near", + "name": "Campaign Name", + "description": "Campaign description", + "cover_image_url": "https://...", + "recipient": "recipient.near", + "start_ms": 1234567890000, + "end_ms": 1234567890000 or null, + "created_ms": 1234567890000, + "ft_id": "token.near" or null, + "target_amount": "1000000000000000000000000", + "min_amount": "100000000000000000000000" or null, + "max_amount": "10000000000000000000000000" or null, + "total_raised_amount": "0", + "net_raised_amount": "0", + "escrow_balance": "0", + "referral_fee_basis_points": 500, + "creator_fee_basis_points": 250, + "allow_fee_avoidance": false + } + """ + + try: + logger.info(f"Indexing new campaign: {data}") + + data = data["campaign"] + + # Get or create accounts + owner, _ = await Account.objects.aget_or_create(id=data["owner"]) + recipient, _ = await Account.objects.aget_or_create(id=data["recipient"]) + + # Get token if specified + token_id = data.get("ft_id") or "near" + token, _ = await Token.objects.aget_or_create(account_id=token_id) + + # Convert timestamps to datetime objects + start_at = datetime.fromtimestamp(data["start_ms"] / 1000) + end_at = None + if data.get("end_ms"): + end_at = datetime.fromtimestamp(data["end_ms"] / 1000) + created_at_dt = datetime.fromtimestamp(data["created_ms"] / 1000) + + campaign_defaults = { + "owner": owner, + "name": data["name"], + "description": data.get("description"), + "cover_image_url": data.get("cover_image_url"), + "recipient": recipient, + "token": token, + "start_at": start_at, + "end_at": end_at, + "created_at": created_at_dt, + "target_amount": data["target_amount"], + "min_amount": data.get("min_amount"), + "max_amount": data.get("max_amount"), + "total_raised_amount": data.get("total_raised_amount", "0"), + "net_raised_amount": data.get("net_raised_amount", "0"), + "escrow_balance": data.get("escrow_balance", "0"), + "referral_fee_basis_points": data["referral_fee_basis_points"], + "creator_fee_basis_points": data["creator_fee_basis_points"], + "allow_fee_avoidance": data.get("allow_fee_avoidance", False), + } + + campaign, created = await Campaign.objects.aupdate_or_create( + on_chain_id=data["id"], defaults=campaign_defaults + ) + + if created: + logger.info(f"Created new campaign: {campaign.on_chain_id}") + else: + logger.info(f"Updated existing campaign: {campaign.on_chain_id}") + + # Fetch USD prices asynchronously + await campaign.fetch_usd_prices_async() + + except Exception as e: + logger.error(f"Failed to index new campaign: {e}") + + + +async def handle_update_campaign(data: dict): + """ + Index a campaign update event. + Expected data format: + { + "id": campaign_id, + "name": "Updated Name" (optional), + "description": "Updated description" (optional), + "cover_image_url": "https://..." (optional), + "start_ms": 1234567890000 (optional), + "end_ms": 1234567890000 (optional), + "ft_id": "token.near" (optional), + "target_amount": "2000000000000000000000000" (optional), + "min_amount": "200000000000000000000000" (optional), + "max_amount": "20000000000000000000000000" (optional), + "allow_fee_avoidance": true (optional) + } + """ + + try: + logger.info(f"Updating campaign: {data}") + + data = data["campaign"] + + token_id = data.get("ft_id") or "near" + token, _ = await Token.objects.aget_or_create(account_id=token_id) + + campaign, created = await Campaign.objects.aupdate_or_create( + on_chain_id=data["id"], + defaults={ + "name": data["name"], + "description": data["description"], + "cover_image_url": data["cover_image_url"], + "start_at": datetime.fromtimestamp(data["start_ms"] / 1000), + "end_at": datetime.fromtimestamp(data["end_ms"] / 1000) if data["end_ms"] else None, + "token": token, + "target_amount": data["target_amount"], + "min_amount": data["min_amount"], + "max_amount": data["max_amount"], + "allow_fee_avoidance": data["allow_fee_avoidance"], + } + ) + + # Fetch updated USD prices + # await campaign.fetch_usd_prices_async() + + logger.info(f"Successfully updated campaign: {campaign.on_chain_id}, or created? {created}") + + except Campaign.DoesNotExist: + logger.error(f"Campaign {data['id']} not found for update") + except Exception as e: + logger.error(f"Failed to update campaign: {e}") + + +async def handle_delete_campaign(campaign_id: int): + """ + Index a campaign deletion event. + campaign_id: on_chain_id of the campaign to delete + """ + + try: + logger.info(f"Deleting campaign: {campaign_id}") + + deleted_count, _ = await Campaign.objects.filter(on_chain_id=campaign_id).adelete() + + if deleted_count > 0: + logger.info(f"Successfully deleted campaign: {campaign_id}") + else: + logger.warning(f"Campaign {campaign_id} not found for deletion") + + except Exception as e: + logger.error(f"Failed to delete campaign {campaign_id}: {e}") + + + +async def handle_campaign_donation(data: dict, receipt_id): + """ + Index a campaign donation event. + Expected data format: + { + "id": donation_id, + "campaign_id": campaign_id, + "donor_id": "donor.near", + "total_amount": "1000000000000000000000000", + "net_amount": "950000000000000000000000", + "message": "Good luck!" (optional), + "donated_at_ms": 1234567890000, + "protocol_fee": "25000000000000000000000", + "referrer_id": "referrer.near" (optional), + "referrer_fee": "25000000000000000000000" (optional), + "creator_fee": "25000000000000000000000", + } + """ + + try: + logger.info(f"Indexing campaign donation: {data}") + + data = data["donation"] + + # Get or create accounts + donor, _ = await Account.objects.aget_or_create(id=data["donor_id"]) + + logger.info(f"got donor: {donor}") + referrer = None + if data.get("referrer_id"): + referrer, _ = await Account.objects.aget_or_create(id=data["referrer_id"]) + + # Get campaign + campaign = await Campaign.objects.aget(on_chain_id=data["campaign_id"]) + + logger.info(f"got campaign: {campaign}") + + # Convert timestamp to datetime + donated_at_dt = datetime.fromtimestamp(data["donated_at_ms"] / 1000) + + logger.info(f"campaign donation timestamp: {donated_at_dt}") + + token_id = data.get("ft_id") or "near" + + donation_defaults = { + "token_id": token_id, # Use campaign's token + "total_amount": data["total_amount"], + "net_amount": data["net_amount"], + "message": data.get("message"), + "donated_at": donated_at_dt, + "protocol_fee": data["protocol_fee"], + "referrer": referrer, + "referrer_fee": data.get("referrer_fee"), + "creator_fee": data["creator_fee"], + "escrowed": data["is_in_escrow"], + "tx_hash": receipt_id, + } + + logger.info(f"creating campaign donation: {token_id}") + donation, created = await CampaignDonation.objects.aupdate_or_create( + on_chain_id=data["id"], + campaign=campaign, + donor=donor, + defaults=donation_defaults + ) + + logger.info(f"before respective: {donation, created}") + + if created: + logger.info(f"Created new campaign donation: {donation.on_chain_id}") + else: + logger.info(f"Updated existing campaign donation: {donation.on_chain_id}") + + # Fetch USD prices asynchronously + await donation.fetch_usd_prices_async() + + except Campaign.DoesNotExist: + logger.error(f"Campaign {data['campaign_id']} not found for donation") + except Exception as e: + logger.error(f"Failed to index campaign donation: {e}") + + + +async def handle_campaign_donation_refund(data: dict, refunded_at): + """ + Index a campaign donation refund event. + Expected data format: + { + "amount": "994710000000000000000000", + "campaign_id": 8, + "donations": [13], + "escrow_balance": "891380000000000000000000" + } + """ + + try: + logger.info(f"Indexing campaign donation refund: {data}") + + campaign_id = data["campaign_id"] + donation_ids = data.get("donations") + escrow_balance = data.get("escrow_balance") + + updated_count = await CampaignDonation.objects.filter( + on_chain_id__in=donation_ids, + campaign__on_chain_id=campaign_id + ).aupdate(returned_at=refunded_at) + + if updated_count > 0: + logger.info(f"Successfully marked {updated_count} donations as refunded: {donation_ids}") + else: + logger.warning(f"No donations found for refund: {donation_ids}") + + # Update campaign escrow balance + try: + campaign = await Campaign.objects.aget(on_chain_id=campaign_id) + campaign.escrow_balance = int(campaign.escrow_balance) - int(escrow_balance) + await campaign.asave() + logger.info(f"Updated campaign {campaign_id} escrow balance to {escrow_balance}") + except Campaign.DoesNotExist: + logger.error(f"Campaign {campaign_id} not found for escrow balance update") + + except Exception as e: + logger.error(f"Failed to index campaign donation refund: {e}") + + +async def handle_campaign_donation_unescrowed(data: dict): + """ + Index a campaign donation refund event. + Expected data format: + { + "donation_ids": [ + 14 + ] + } + """ + + try: + logger.info(f"Indexing campaign donation unescrow(release to recipient): {data}") + donation_ids = data.get("donation_ids") + + updated_count = await CampaignDonation.objects.filter( + on_chain_id__in=donation_ids + ).aupdate(escrowed=False) + + if updated_count > 0: + logger.info(f"Successfully marked donation {data['donation_ids']} as unescrowed") + + else: + logger.warning(f"Donation {data['donation_ids']} not found to be unescrowed") + + except Exception as e: + logger.error(f"Failed to index campaign donation unescrow: {e}") From c7fa55874c999b38336a76a3d49690a465dca8b0 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 31 Jul 2025 01:06:50 +0100 Subject: [PATCH 387/454] add script to backfil --- campaigns/management/__init__.py | 0 campaigns/management/commands/__init__.py | 0 .../commands/populatecampaigndata.py | 327 ++++++++++++++++++ campaigns/serializers.py | 33 ++ 4 files changed, 360 insertions(+) create mode 100644 campaigns/management/__init__.py create mode 100644 campaigns/management/commands/__init__.py create mode 100644 campaigns/management/commands/populatecampaigndata.py diff --git a/campaigns/management/__init__.py b/campaigns/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/campaigns/management/commands/__init__.py b/campaigns/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/campaigns/management/commands/populatecampaigndata.py b/campaigns/management/commands/populatecampaigndata.py new file mode 100644 index 0000000..99da560 --- /dev/null +++ b/campaigns/management/commands/populatecampaigndata.py @@ -0,0 +1,327 @@ +import time +from datetime import datetime +import requests +from django.conf import settings +from django.core.management.base import BaseCommand +from django.utils import timezone + +from accounts.models import Account +from campaigns.models import Campaign, CampaignDonation +from tokens.models import Token + + + +CAMPAIGN_CONTRACT_ID =f"v1.campaign.{settings.POTLOCK_TLA}" if settings.ENVIRONMENT=="testnet" else f"v1.campaigns.staging.{settings.POTLOCK_TLA}" + +class Command(BaseCommand): + help = "Pull campaigns data from contract & populate campaigns table." + + def add_arguments(self, parser): + parser.add_argument( + '--campaign-id', + type=int, + help='Populate specific campaign by ID (optional)', + ) + parser.add_argument( + '--skip-donations', + action='store_true', + help='Skip fetching donations for campaigns', + ) + parser.add_argument( + '--limit', + type=int, + default=100, + help='Limit number of campaigns to fetch (default: 100)', + ) + + def handle(self, *args, **options): + + self.stdout.write( + self.style.SUCCESS(f'Starting to populate campaign data from {CAMPAIGN_CONTRACT_ID}') + ) + + if options['campaign_id']: + # Fetch specific campaign + self.fetch_single_campaign(CAMPAIGN_CONTRACT_ID, options['campaign_id'], options['skip_donations']) + else: + # Fetch all campaigns + self.fetch_all_campaigns(CAMPAIGN_CONTRACT_ID, options['limit'], options['skip_donations']) + + self.stdout.write( + self.style.SUCCESS('Successfully populated campaign data') + ) + + def fetch_all_campaigns(self, contract_id, limit, skip_donations): + """Fetch all campaigns from the contract""" + + # Get campaigns + url = f"{settings.FASTNEAR_RPC_URL}/account/{contract_id}/view/get_campaigns" + params = { + "from_index.json": 0, + "limit.json": limit, + } + + self.stdout.write(f"Fetching campaigns from {url}") + + response = requests.get(url, params=params) + if response.status_code != 200: + self.stdout.write( + self.style.ERROR( + f"Request for campaigns data failed ({response.status_code}) with message: {response.text}" + ) + ) + return + + campaigns = response.json() + self.stdout.write(f"Found {len(campaigns)} campaigns") + + for campaign_data in campaigns: + self.process_campaign(campaign_data, skip_donations) + # Small delay to avoid rate limiting + time.sleep(0.1) + + def fetch_single_campaign(self, contract_id, campaign_id, skip_donations): + """Fetch a specific campaign by ID""" + + url = f"{settings.FASTNEAR_RPC_URL}/account/{contract_id}/view/get_campaign" + params = { + "campaign_id.json": campaign_id, + } + + self.stdout.write(f"Fetching campaign {campaign_id} from {url}") + + response = requests.get(url, params=params) + if response.status_code != 200: + self.stdout.write( + self.style.ERROR( + f"Request for campaign {campaign_id} failed ({response.status_code}) with message: {response.text}" + ) + ) + return + + campaign_data = response.json() + self.process_campaign(campaign_data, skip_donations) + + def process_campaign(self, campaign_data, skip_donations): + """Process and save a single campaign""" + + try: + campaign_id = campaign_data["id"] + self.stdout.write(f"Processing campaign {campaign_id}: {campaign_data.get('name', 'Unnamed')}") + + # Get or create accounts + owner, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, + id=campaign_data["owner"] + ) + recipient, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, + id=campaign_data["recipient"] + ) + + # Get token if specified + + ft_id = campaign_data.get("ft_id") or "near" + token_acct, token_acct_created = Account.objects.get_or_create(defaults={"chain_id":1},id=ft_id) + token_defaults = { + "decimals": 24, + } + if token_acct_created: + print(f"Created new token account: {token_acct}") + if ft_id != "near": + url = f"{settings.FASTNEAR_RPC_URL}/account/{ft_id}/view/ft_metadata" + ft_metadata = requests.get(url) + if ft_metadata.status_code != 200: + self.stdout.write( + self.style.ERROR( + f"Request for campaigns data failed ({ft_metadata.status_code}) with message: {ft_metadata.text}" + ) + ) + return + else: + ft_metadata = ft_metadata.json() + if "name" in ft_metadata: + token_defaults["name"] = ft_metadata["name"] + if "symbol" in ft_metadata: + token_defaults["symbol"] = ft_metadata["symbol"] + if "icon" in ft_metadata: + token_defaults["icon"] = ft_metadata["icon"] + if "decimals" in ft_metadata: + token_defaults["decimals"] = ft_metadata["decimals"] + token, _ = Token.objects.get_or_create( + account=token_acct, defaults=token_defaults + ) + + # Convert timestamps to datetime objects + start_at = datetime.fromtimestamp(campaign_data["start_ms"] / 1000) + end_at = None + if campaign_data.get("end_ms"): + end_at = datetime.fromtimestamp(campaign_data["end_ms"] / 1000) + created_at = datetime.fromtimestamp(campaign_data["created_ms"] / 1000) + + # Campaign defaults + campaign_defaults = { + "owner": owner, + "name": campaign_data["name"], + "description": campaign_data.get("description"), + "cover_image_url": campaign_data.get("cover_image_url"), + "recipient": recipient, + "token": token, + "start_at": start_at, + "end_at": end_at, + "created_at": created_at, + "target_amount": str(campaign_data["target_amount"]), + "min_amount": str(campaign_data.get("min_amount", "")) if campaign_data.get("min_amount") else None, + "max_amount": str(campaign_data.get("max_amount", "")) if campaign_data.get("max_amount") else None, + "total_raised_amount": str(campaign_data.get("total_raised_amount", "0")), + "net_raised_amount": str(campaign_data.get("net_raised_amount", "0")), + "escrow_balance": str(campaign_data.get("escrow_balance", "0")), + "referral_fee_basis_points": campaign_data["referral_fee_basis_points"], + "creator_fee_basis_points": campaign_data["creator_fee_basis_points"], + "allow_fee_avoidance": campaign_data.get("allow_fee_avoidance", False), + } + + # Create or update campaign + campaign, created = Campaign.objects.update_or_create( + on_chain_id=campaign_id, + defaults=campaign_defaults + ) + + action = "Created" if created else "Updated" + self.stdout.write(f" {action} campaign: {campaign.on_chain_id}") + + # Fetch USD prices for the campaign + try: + campaign.fetch_usd_prices() + self.stdout.write(f" Fetched USD prices for campaign {campaign.on_chain_id}") + except Exception as e: + self.stdout.write( + self.style.WARNING(f" Failed to fetch USD prices for campaign {campaign.on_chain_id}: {e}") + ) + + # Fetch donations for this campaign if not skipped + if not skip_donations: + self.fetch_campaign_donations(campaign) + + except Exception as e: + self.stdout.write( + self.style.ERROR(f"Failed to process campaign {campaign_data.get('id', 'unknown')}: {e}") + ) + + def fetch_campaign_donations(self, campaign): + """Fetch donations for a specific campaign""" + + # CAMPAIGN_CONTRACT_ID = f"v1.campaigns.staging.{settings.POTLOCK_TLA}" + + # Get donations for campaign with pagination + page = 0 + limit = 100 + total_donations = 0 + + while True: + url = f"{settings.FASTNEAR_RPC_URL}/account/{CAMPAIGN_CONTRACT_ID}/view/get_donations_for_campaign" + params = { + "campaign_id.json": campaign.on_chain_id, + "from_index.json": page * limit, + "limit.json": limit, + } + + self.stdout.write(f" Fetching donations page {page + 1} for campaign {campaign.on_chain_id}") + + response = requests.get(url, params=params) + if response.status_code != 200: + self.stdout.write( + self.style.WARNING( + f" Request for donations failed ({response.status_code}) with message: {response.text}" + ) + ) + break + + donations = response.json() + if not donations: + break + + self.stdout.write(f" Processing {len(donations)} donations") + + for donation_data in donations: + self.process_campaign_donation(donation_data, campaign) + total_donations += 1 + + # Break if we got fewer results than the limit (last page) + if len(donations) < limit: + break + + page += 1 + # Small delay to avoid rate limiting + time.sleep(0.1) + + self.stdout.write(f" Processed {total_donations} donations for campaign {campaign.on_chain_id}") + + def process_campaign_donation(self, donation_data, campaign): + """Process and save a single campaign donation""" + + try: + donation_id = donation_data["id"] + + # Get or create donor + donor, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, + id=donation_data["donor_id"] + ) + + # Get referrer if present + referrer = None + if donation_data.get("referrer_id"): + referrer, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, + id=donation_data["referrer_id"] + ) + + # Convert timestamp + donated_at = datetime.fromtimestamp(donation_data["donated_at_ms"] / 1000) + + # Handle returned_at if present + returned_at = None + if donation_data.get("returned_at_ms"): + returned_at = datetime.fromtimestamp(donation_data["returned_at_ms"] / 1000) + + # Donation defaults + donation_defaults = { + "campaign": campaign, + "donor": donor, + "token": campaign.token, # Use campaign's token + "total_amount": str(donation_data["total_amount"]), + "net_amount": str(donation_data["net_amount"]), + "message": donation_data.get("message"), + "donated_at": donated_at, + "protocol_fee": str(donation_data["protocol_fee"]), + "referrer": referrer, + "referrer_fee": str(donation_data.get("referrer_fee", "")) if donation_data.get("referrer_fee") else None, + "creator_fee": str(donation_data["creator_fee"]), + "returned_at": returned_at, + "escrowed": donation_data.get("escrowed", False), + "tx_hash": donation_data.get("tx_hash"), + } + + # Create or update donation + donation, created = CampaignDonation.objects.update_or_create( + on_chain_id=donation_id, + campaign=campaign, + defaults=donation_defaults + ) + + if created: + self.stdout.write(f" Created donation: {donation.on_chain_id}") + + # Fetch USD prices for the donation + try: + donation.fetch_usd_prices() + except Exception as e: + self.stdout.write( + self.style.WARNING(f" Failed to fetch USD prices for donation {donation.on_chain_id}: {e}") + ) + + except Exception as e: + self.stdout.write( + self.style.ERROR(f" Failed to process donation {donation_data.get('id', 'unknown')}: {e}") + ) diff --git a/campaigns/serializers.py b/campaigns/serializers.py index e7434ce..1c53683 100644 --- a/campaigns/serializers.py +++ b/campaigns/serializers.py @@ -1,3 +1,4 @@ +from django.utils import timezone from rest_framework import serializers from rest_framework.serializers import ( ModelSerializer, @@ -40,11 +41,42 @@ class Meta: "owner", "recipient", "token", + "is_active", ] owner = AccountSerializer() recipient = AccountSerializer() token = TokenSerializer() + is_active = serializers.SerializerMethodField() + + def get_is_active(self, obj): + """ + Check if campaign is active based on: + 1. Campaign has started (start_at <= current_time) + 2. Campaign hasn't ended yet (end_at > current_time or end_at is None) + 3. Campaign hasn't reached max amount (net_raised_amount < max_amount or max_amount is None) + """ + + now = timezone.now() + + if obj.start_at > now: + return False + + if obj.end_at is not None and obj.end_at <= now: + return False + + if obj.max_amount is not None: + try: + net_raised = int(obj.net_raised_amount) + max_amount = int(obj.max_amount) + if net_raised >= max_amount: + return False + except (ValueError, TypeError): + # If we can't parse the amounts, assume not maxed out + pass + + return True + class CampaignDonationSerializer(ModelSerializer): @@ -103,6 +135,7 @@ class Meta: "referral_fee_basis_points": 500, "creator_fee_basis_points": 250, "allow_fee_avoidance": False, + "is_active": True, "owner": SIMPLE_ACCOUNT_EXAMPLE, "recipient": SIMPLE_ACCOUNT_EXAMPLE, "token": SIMPLE_TOKEN_EXAMPLE, From 09d4594d4109f0f9a528c3d766bdc8315862a0d6 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 5 Aug 2025 18:08:31 +0100 Subject: [PATCH 388/454] feat: implement filters --- campaigns/api.py | 50 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/campaigns/api.py b/campaigns/api.py index 7da728f..820d703 100644 --- a/campaigns/api.py +++ b/campaigns/api.py @@ -1,4 +1,7 @@ import requests +from django.utils import timezone +from django.db.models import Q, F, FloatField +from django.db.models.functions import Cast from django.conf import settings from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page @@ -56,10 +59,11 @@ class CampaignsAPI(APIView, CustomSizePageNumberPagination): type=str, ), OpenApiParameter( - name="active", + name="status", description="Filter by active campaigns (true/false)", required=False, - type=bool, + type=str, + enum=["active", "upcoming", "ended", "unfufilled"], ), ], responses={ @@ -94,25 +98,37 @@ def get(self, request: Request, *args, **kwargs): token = request.query_params.get('token') if token: - if token.lower() == 'near': - queryset = queryset.filter(token__isnull=True) - else: - queryset = queryset.filter(token__account__id=token) + queryset = queryset.filter(token__account__id=token.lower()) - active = request.query_params.get('active') - if active is not None: - from django.utils import timezone - from django.db import models + status = request.query_params.get('status') + if status: now = timezone.now() - if active.lower() == 'true': + status = status.lower() + queryset = queryset.annotate( + cast_net_raised=Cast('net_raised_amount', FloatField()), + cast_max_amount=Cast('max_amount', FloatField()), + cast_target=Cast('target_amount', FloatField()) + ) + if status == 'upcoming': + queryset = queryset.filter(start_at__gt=now) + elif status == 'active': queryset = queryset.filter( - start_at__lte=now, - end_at__gte=now - ) - else: + start_at__lte=now + ).filter( + Q(end_at__isnull=True) | Q(end_at__gt=now) + ).filter( + Q(max_amount__isnull=True) | Q(cast_net_raised__lt=F('cast_max_amount')) + ) + elif status == 'ended': queryset = queryset.filter( - models.Q(start_at__gt=now) | models.Q(end_at__lt=now) - ) + Q(end_at__isnull=False, end_at__lte=now) | + Q(max_amount__isnull=False, cast_net_raised__gte=F('cast_max_amount')) + ) + elif status == 'unfufilled': + queryset = queryset.filter( + Q(end_at__isnull=False, end_at__lte=now), + cast_net_raised__lt=F('cast_target') + ) # Paginate results page = self.paginate_queryset(queryset, request) From 482f8c78274cea9e4a7d8e4713637695d7b70641 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 5 Aug 2025 18:51:17 +0100 Subject: [PATCH 389/454] update campaign totals --- indexer_app/utils.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 0a016c2..eb72088 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -2106,6 +2106,19 @@ async def handle_campaign_donation(data: dict, receipt_id): # Fetch USD prices asynchronously await donation.fetch_usd_prices_async() + # Update campaign totals + try: + total_amount = int(data["total_amount"]) + net_amount = int(data["net_amount"]) + + campaign.total_raised_amount = str(int(campaign.total_raised_amount) + total_amount) + campaign.net_raised_amount = str(int(campaign.net_raised_amount) + net_amount) + await campaign.asave() + + logger.info(f"Updated campaign {campaign.on_chain_id} totals: +{total_amount} total, +{net_amount} net") + except (ValueError, TypeError) as e: + logger.error(f"Failed to update campaign totals: {e}") + except Campaign.DoesNotExist: logger.error(f"Campaign {data['campaign_id']} not found for donation") except Exception as e: @@ -2142,14 +2155,28 @@ async def handle_campaign_donation_refund(data: dict, refunded_at): else: logger.warning(f"No donations found for refund: {donation_ids}") - # Update campaign escrow balance + # Update campaign escrow balance and totals try: campaign = await Campaign.objects.aget(on_chain_id=campaign_id) - campaign.escrow_balance = int(campaign.escrow_balance) - int(escrow_balance) + campaign.escrow_balance = str(int(campaign.escrow_balance) - int(escrow_balance)) + + refunded_donations = CampaignDonation.objects.filter( + on_chain_id__in=donation_ids, + campaign__on_chain_id=campaign_id + ).values_list('total_amount', 'net_amount') + + total_refunded = sum(int(donation[0]) for donation in refunded_donations) + net_refunded = sum(int(donation[1]) for donation in refunded_donations) + + campaign.total_raised_amount = str(int(campaign.total_raised_amount) - total_refunded) + campaign.net_raised_amount = str(int(campaign.net_raised_amount) - net_refunded) + await campaign.asave() - logger.info(f"Updated campaign {campaign_id} escrow balance to {escrow_balance}") + logger.info(f"Updated campaign {campaign_id}: -{total_refunded} total, -{net_refunded} net, escrow={campaign.escrow_balance}") except Campaign.DoesNotExist: logger.error(f"Campaign {campaign_id} not found for escrow balance update") + except (ValueError, TypeError) as e: + logger.error(f"Failed to update campaign totals after refund: {e}") except Exception as e: logger.error(f"Failed to index campaign donation refund: {e}") From 768e4535b6f3c5d8150776f4a1f666a31d359671 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 5 Aug 2025 18:58:54 +0100 Subject: [PATCH 390/454] campaign id tla --- campaigns/management/commands/populatecampaigndata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/campaigns/management/commands/populatecampaigndata.py b/campaigns/management/commands/populatecampaigndata.py index 99da560..c6fcb45 100644 --- a/campaigns/management/commands/populatecampaigndata.py +++ b/campaigns/management/commands/populatecampaigndata.py @@ -11,7 +11,7 @@ -CAMPAIGN_CONTRACT_ID =f"v1.campaign.{settings.POTLOCK_TLA}" if settings.ENVIRONMENT=="testnet" else f"v1.campaigns.staging.{settings.POTLOCK_TLA}" +CAMPAIGN_CONTRACT_ID =f"v1.campaign.{settings.POTLOCK_TLA}" if settings.ENVIRONMENT=="testnet" else f"v1.campaigns.{settings.POTLOCK_TLA}" class Command(BaseCommand): help = "Pull campaigns data from contract & populate campaigns table." From 6a4907e9e92454c3ce4d1429b0e12f97c8414332 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 13 Aug 2025 13:36:41 +0100 Subject: [PATCH 391/454] add status field --- campaigns/serializers.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/campaigns/serializers.py b/campaigns/serializers.py index 1c53683..4834620 100644 --- a/campaigns/serializers.py +++ b/campaigns/serializers.py @@ -41,41 +41,39 @@ class Meta: "owner", "recipient", "token", - "is_active", + "status", ] owner = AccountSerializer() recipient = AccountSerializer() token = TokenSerializer() - is_active = serializers.SerializerMethodField() + status = serializers.SerializerMethodField() - def get_is_active(self, obj): + def get_status(self, obj): """ - Check if campaign is active based on: - 1. Campaign has started (start_at <= current_time) - 2. Campaign hasn't ended yet (end_at > current_time or end_at is None) - 3. Campaign hasn't reached max amount (net_raised_amount < max_amount or max_amount is None) + Get campaign status: active, ended, or upcoming """ + from django.utils import timezone now = timezone.now() if obj.start_at > now: - return False + return "upcoming" if obj.end_at is not None and obj.end_at <= now: - return False + return "ended" if obj.max_amount is not None: try: net_raised = int(obj.net_raised_amount) max_amount = int(obj.max_amount) if net_raised >= max_amount: - return False + return "ended" except (ValueError, TypeError): # If we can't parse the amounts, assume not maxed out pass - return True + return "active" @@ -135,7 +133,7 @@ class Meta: "referral_fee_basis_points": 500, "creator_fee_basis_points": 250, "allow_fee_avoidance": False, - "is_active": True, + "status": "active", "owner": SIMPLE_ACCOUNT_EXAMPLE, "recipient": SIMPLE_ACCOUNT_EXAMPLE, "token": SIMPLE_TOKEN_EXAMPLE, From 13a9b9fd8c3a4f3a2c3ee9205ae22e54ecadbba5 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 18 Aug 2025 11:01:34 +0100 Subject: [PATCH 392/454] stellar update --- base/settings.py | 4 ++-- indexer_app/tasks.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/base/settings.py b/base/settings.py index 2a630af..66b6687 100644 --- a/base/settings.py +++ b/base/settings.py @@ -67,8 +67,8 @@ POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") # NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") -STELLAR_CONTRACT_ID = "CAG7ZLJNXZGFGIR2VB3JVHFA6XF4U3TAIEZLLZQWI6XFX7DC3EG6NU4G" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") -STELLAR_PROJECTS_REGISTRY_CONTRACT = "CBFQ2T4GJI4NN4GI5MQXQNAORDGPKD5XGG7UZ5IVLAQLHJROQQ4KRPP5" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_CONTRACT_ID = "CCT5MBDD4QGHNHG3VWSJ5AC6FL7DIZHVFQRPXW2OATBWZZRHTQMHSW4O" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_PROJECTS_REGISTRY_CONTRACT = "CAUX3WSEWUR2Z7N3XKJBQXS5LBVRW62D2C7EYDOAUJXXI3T3YWCBTVMW" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 7cd000c..d91b153 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -341,7 +341,7 @@ def stellar_event_indexer(): start_sequence = get_ledger_sequence() # start_sequence = 12169 if not start_sequence: - start_sequence = 2771 + start_sequence = 63727 jobs_logger.info(f"Ingesting Stellar events from ledger {start_sequence}... contracts: {contract_ids}") try: # Fetch events for the current sequence From 74c0e359e9718b9c565f1f9de41f2a06985c192e Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 27 Aug 2025 14:54:55 +0100 Subject: [PATCH 393/454] pick address from env --- base/settings.py | 4 ++-- indexer_app/tasks.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/base/settings.py b/base/settings.py index 66b6687..3f663e8 100644 --- a/base/settings.py +++ b/base/settings.py @@ -67,8 +67,8 @@ POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") # NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") -STELLAR_CONTRACT_ID = "CCT5MBDD4QGHNHG3VWSJ5AC6FL7DIZHVFQRPXW2OATBWZZRHTQMHSW4O" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") -STELLAR_PROJECTS_REGISTRY_CONTRACT = "CAUX3WSEWUR2Z7N3XKJBQXS5LBVRW62D2C7EYDOAUJXXI3T3YWCBTVMW" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +STELLAR_CONTRACT_ID = os.environ.get("PL_STELLAR_CONTRACT_ID", "") +STELLAR_PROJECTS_REGISTRY_CONTRACT = os.environ.get("PL_STELLAR_PROJECTS_REGISTRY_CONTRACT", "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index d91b153..c343a9a 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -341,7 +341,7 @@ def stellar_event_indexer(): start_sequence = get_ledger_sequence() # start_sequence = 12169 if not start_sequence: - start_sequence = 63727 + start_sequence = 58655649 jobs_logger.info(f"Ingesting Stellar events from ledger {start_sequence}... contracts: {contract_ids}") try: # Fetch events for the current sequence @@ -362,7 +362,6 @@ def stellar_event_indexer(): if event.value is not None: event_value = stellar_sdk.scval.to_native(event.value) event_value = json.loads(json.dumps(event_value, default=address_to_string)) - print("event value:. ", event_value) stellar_events.append(StellarEvent( ledger_sequence=event.ledger, event_type=event_name, From fe97729538c2777fc15f9cc8b2f316c8bfa52c20 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 27 Aug 2025 16:44:21 +0100 Subject: [PATCH 394/454] change stellar mainnet rpc --- base/settings.py | 6 ++++++ indexer_app/tasks.py | 4 +--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/base/settings.py b/base/settings.py index 3f663e8..2631612 100644 --- a/base/settings.py +++ b/base/settings.py @@ -90,6 +90,12 @@ else "https://rpc.web4.near.page" ) +STELLAR_RPC_URL = ( + "https://soroban-testnet.stellar.org" + if ENVIRONMENT == "testnet" + else "https://stellar-soroban-public.nodies.app" +) + BLOCK_SAVE_HEIGHT = os.environ.get("BLOCK_SAVE_HEIGHT") COINGECKO_URL = ( diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index c343a9a..3976b1d 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -331,9 +331,7 @@ def address_to_string(obj): @shared_task def stellar_event_indexer(): server = stellar_sdk.SorobanServer( - "https://soroban-testnet.stellar.org" - if settings.ENVIRONMENT == "testnet" or settings.ENVIRONMENT == "local" - else "https://horizon.stellar.org" + settings.STELLAR_RPC_URL ) contract_ids = [settings.STELLAR_CONTRACT_ID, settings.STELLAR_PROJECTS_REGISTRY_CONTRACT] if contract_ids == ['', '']: From 79cc8d81ab3633b8830db078818ad6d0f4b6f8fe Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sun, 31 Aug 2025 21:11:01 +0100 Subject: [PATCH 395/454] add asyncio wait for to tackle task execution hanging --- indexer_app/tasks.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 3976b1d..cd05b63 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -52,7 +52,7 @@ async def indexer(from_block: int, to_block: int): # Log time before fetching a new block fetch_start_time = time.time() # streamer_message is the current block - streamer_message = await streamer_messages_queue.get() + streamer_message = await asyncio.wait_for(streamer_messages_queue.get(), 30) fetch_end_time = time.time() logger.info( f"Time to fetch new block: {fetch_end_time - fetch_start_time:.4f} seconds" @@ -85,6 +85,11 @@ async def indexer(from_block: int, to_block: int): logger.info( f"Total time for one iteration: {iteration_end_time - fetch_start_time:.4f} seconds" ) + + + except asyncio.TimeoutError: + logger.warning("Stream stalled: no new blocks within timeout, restarting...") # raise Exception so sytemd can restart the worker + raise Exception("Stream stalled: restarting...") except Exception as e: logger.error(f"Error in streamer_messages_queue: {e}") From 3c8bb8a00d2448c40a23f5c3a6aa04193399480a Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 4 Sep 2025 15:30:15 +0100 Subject: [PATCH 396/454] lax timeout --- base/settings.py | 1 + indexer_app/tasks.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index 2631612..838899b 100644 --- a/base/settings.py +++ b/base/settings.py @@ -62,6 +62,7 @@ RECLAIM_APP_ID = os.environ.get("PL_RECLAIM_APP_ID") RECLAIM_APP_SECRET = os.environ.get("PL_RECLAIM_APP_SECRET") RECLAIM_TWITTER_PROVIDER_ID = os.environ.get("PL_RECLAIM_TWITTER_PROVIDER_ID") +INDEXER_STREAMER_WAIT_TIME = os.environ.get("PL_INDEXER_STREAMER_WAIT_TIME", 300) # in seconds # POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else "potlock.near" POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index cd05b63..c279da5 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -52,7 +52,7 @@ async def indexer(from_block: int, to_block: int): # Log time before fetching a new block fetch_start_time = time.time() # streamer_message is the current block - streamer_message = await asyncio.wait_for(streamer_messages_queue.get(), 30) + streamer_message = await asyncio.wait_for(streamer_messages_queue.get(), settings.INDEXER_STREAMER_WAIT_TIME) fetch_end_time = time.time() logger.info( f"Time to fetch new block: {fetch_end_time - fetch_start_time:.4f} seconds" From 69a6b376f023907057a53c08990ffc798dc4beb3 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 8 Sep 2025 14:24:45 +0100 Subject: [PATCH 397/454] allow app to dev: --- base/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/settings.py b/base/settings.py index 838899b..06ccd0c 100644 --- a/base/settings.py +++ b/base/settings.py @@ -214,6 +214,8 @@ "https://staging.alpha.potlock.app", "http://dev.local", "https://dev.local", + "https://app.potlock.app", + "https://app.potlock.org", ] else: CORS_ALLOWED_ORIGINS = [ From 41fdf96b5bccafa1ea491f59407cabb566635f4f Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 16 Sep 2025 08:55:36 +0100 Subject: [PATCH 398/454] change applications project to actual grantpicks project --- grantpicks/api.py | 12 ++++-------- grantpicks/serializers.py | 8 +++++++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/grantpicks/api.py b/grantpicks/api.py index 5f5005d..25b6d5c 100644 --- a/grantpicks/api.py +++ b/grantpicks/api.py @@ -83,7 +83,6 @@ class RoundsListAPI(APIView, CustomSizePageNumberPagination): 500: OpenApiResponse(description="Internal server error"), }, ) - @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): account_id = kwargs.get("account_id") if account_id: @@ -136,11 +135,10 @@ class RoundDetailAPI(APIView): 404: OpenApiResponse(description="Round not found"), }, ) - @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): round_id = kwargs.get("round_id") try: - round = Round.objects.get(id=round_id) + round = Round.objects.get(on_chain_id=round_id) except Round.DoesNotExist: return Response({"message": f"Round with ID {round_id} not found."}, status=404) serializer = RoundSerializer(round) @@ -171,11 +169,10 @@ class RoundApplicationsAPI(APIView, CustomSizePageNumberPagination): 404: OpenApiResponse(description="Round not found"), }, ) - @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): round_id = kwargs.get("round_id") try: - round = Round.objects.get(id=round_id) + round = Round.objects.get(on_chain_id=round_id) except Round.DoesNotExist: return Response({"message": f"Round with ID {round_id} not found."}, status=404) @@ -210,12 +207,11 @@ class ProjectRoundVotesAPI(APIView, CustomSizePageNumberPagination): 404: OpenApiResponse(description="Round or project not found"), }, ) - @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): round_id = kwargs.get("round_id") project_id = kwargs.get("project_id") # Get project_id from kwargs try: - round_obj = Round.objects.get(id=round_id) + round_obj = Round.objects.get(on_chain_id=round_id) # project = Project.objects.get(id=project_id) # comment out now, might use later if decide to return vote pairs instead except Round.DoesNotExist: return Response({"message": f"Round with ID {round_id} not found."}, status=404) @@ -278,7 +274,7 @@ class ProjectListAPI(APIView, CustomSizePageNumberPagination): 500: OpenApiResponse(description="Internal server error"), }, ) - @method_decorator(cache_page(60 * 5)) + @method_decorator(cache_page(60 * 2)) def get(self, request: Request, *args, **kwargs): projects = Project.objects.all() status_param = request.query_params.get("status") diff --git a/grantpicks/serializers.py b/grantpicks/serializers.py index 3a84df3..88d95d2 100644 --- a/grantpicks/serializers.py +++ b/grantpicks/serializers.py @@ -130,6 +130,7 @@ class Meta: class RoundApplicationSerializer(ModelSerializer): + project = serializers.SerializerMethodField() class Meta: model = PotApplication @@ -149,7 +150,12 @@ class Meta: reviews = ApplicationReviewSerializer(many=True) round = RoundSerializer() applicant = AccountSerializer() - project = AccountSerializer() + + def get_project(self, obj): + if obj.project: + round_project = Project.objects.filter(owner=obj.project.id).first() + return ProjectSerializer(round_project).data + return None SIMPLE_PROJECT_EXAMPLE = { From e651c612f186b15f081dcafd5cf8815706c744cb Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 16 Sep 2025 13:59:11 +0100 Subject: [PATCH 399/454] create application for approved projects by owner --- indexer_app/tasks.py | 3 +-- indexer_app/utils.py | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index c279da5..9aba52d 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -415,12 +415,11 @@ def process_stellar_events(): event.processed = create_round_application(event_data, event.transaction_hash) - elif event_name == 'u_app': # application review and aproval event.processed = update_application(event_data, event.transaction_hash) elif event_name == 'u_ap': - event.processed = update_approved_projects(event_data) + event.processed = update_approved_projects(event_data, time_stamp=event.ingested_at, tx_hash=event.transaction_hash) elif event_name == 'c_depo': diff --git a/indexer_app/utils.py b/indexer_app/utils.py index eb72088..e6a59a2 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1346,7 +1346,7 @@ def get_ledger_sequence() -> int: return record.block_height -def update_approved_projects(event_data, chain_id="stellar"): +def update_approved_projects(event_data, chain_id="stellar", time_stamp=None, tx_hash=None): round_id, project_ids = event_data[0], event_data[1] with transaction.atomic(): @@ -1356,6 +1356,22 @@ def update_approved_projects(event_data, chain_id="stellar"): for ids in project_ids: project = Project.objects.get(on_chain_id=ids) round_obj.approved_projects.add(project.owner) + logger.info(f"Creating application for round: {round_id} for approved projects") + status = PotApplicationStatus['Approved'.upper()] + + appl_defaults = { + "message": "added by owner", + "submitted_at": time_stamp or datetime.now(), + "status": status, + "tx_hash": tx_hash, + } + + PotApplication.objects.update_or_create( + applicant=project.owner, + round=round_obj, + project=project.owner, + defaults=appl_defaults, + ) return True except Exception as e: @@ -1683,6 +1699,10 @@ def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar } ) + for admin_address in event_data.get('admins', []): + admin, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=admin_address) + round_obj.admins.add(admin) + # Create contacts for the round for contact in event_data.get('contacts', []): contact_obj, created = ProjectContact.objects.update_or_create( From 2e193ab9d67b711c41c4fcf36635811a13abb51d Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 23 Sep 2025 12:56:43 +0100 Subject: [PATCH 400/454] index lists on stellar --- .gitignore | 1 + base/celery.py | 30 ++-- base/settings.py | 3 +- indexer_app/tasks.py | 30 +++- indexer_app/utils.py | 144 +++++++++++++++++- lists/api.py | 17 +++ lists/migrations/0008_list_chain.py | 27 ++++ .../0009_alter_list_on_chain_id_and_more.py | 28 ++++ lists/models.py | 19 ++- 9 files changed, 274 insertions(+), 25 deletions(-) create mode 100644 lists/migrations/0008_list_chain.py create mode 100644 lists/migrations/0009_alter_list_on_chain_id_and_more.py diff --git a/.gitignore b/.gitignore index 923869c..bef67d0 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,7 @@ media/ # Celery beat schedule file celerybeat-schedule dump.rdb +celerybeat-schedule.db # Flask stuff: instance/ diff --git a/base/celery.py b/base/celery.py index 9ba059d..fdc2044 100644 --- a/base/celery.py +++ b/base/celery.py @@ -25,21 +25,21 @@ app.autodiscover_tasks() app.conf.beat_schedule = { - "update_account_statistics_every_5_minutes": { - "task": "indexer_app.tasks.update_account_statistics", - "schedule": crontab(minute="*/5"), # Executes every 5 minutes - "options": {"queue": "beat_tasks"}, - }, - "fetch_usd_prices_every_5_minutes": { - "task": "indexer_app.tasks.fetch_usd_prices", - "schedule": crontab(minute="*/5"), # Executes every 5 minutes - "options": {"queue": "beat_tasks"}, - }, - "update_pot_statistics_every_5_minutes": { - "task": "indexer_app.tasks.update_pot_statistics", - "schedule": crontab(minute="*/5"), # Executes every 5 minutes - "options": {"queue": "beat_tasks"}, - }, + # "update_account_statistics_every_5_minutes": { + # "task": "indexer_app.tasks.update_account_statistics", + # "schedule": crontab(minute="*/5"), # Executes every 5 minutes + # "options": {"queue": "beat_tasks"}, + # }, + # "fetch_usd_prices_every_5_minutes": { + # "task": "indexer_app.tasks.fetch_usd_prices", + # "schedule": crontab(minute="*/5"), # Executes every 5 minutes + # "options": {"queue": "beat_tasks"}, + # }, + # "update_pot_statistics_every_5_minutes": { + # "task": "indexer_app.tasks.update_pot_statistics", + # "schedule": crontab(minute="*/5"), # Executes every 5 minutes + # "options": {"queue": "beat_tasks"}, + # }, "fetch_stellar_events_every_minute": { "task": "indexer_app.tasks.stellar_event_indexer", "schedule": crontab(minute="*/1"), # Executes every 1 minutes diff --git a/base/settings.py b/base/settings.py index 06ccd0c..5affa2d 100644 --- a/base/settings.py +++ b/base/settings.py @@ -70,6 +70,7 @@ NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") STELLAR_CONTRACT_ID = os.environ.get("PL_STELLAR_CONTRACT_ID", "") STELLAR_PROJECTS_REGISTRY_CONTRACT = os.environ.get("PL_STELLAR_PROJECTS_REGISTRY_CONTRACT", "") +STELLAR_LIST_CONTRACT = os.environ.get("PL_STELLAR_LIST_CONTRACT", "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) @@ -93,7 +94,7 @@ STELLAR_RPC_URL = ( "https://soroban-testnet.stellar.org" - if ENVIRONMENT == "testnet" + if ENVIRONMENT == "local" else "https://stellar-soroban-public.nodies.app" ) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 9aba52d..064565c 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -25,7 +25,13 @@ from pots.models import Pot, PotApplication, PotApplicationStatus, PotPayout from .logging import logger -from .utils import create_or_update_round, create_round_application, create_round_payout, get_block_height, get_ledger_sequence, process_application_to_round, process_project_event, process_rounds_deposit_event, process_vote_event, save_block_height, update_application, update_approved_projects, update_ledger_sequence, update_round_payout +from .utils import ( + create_or_update_round, create_round_application, create_round_payout, + get_block_height, get_ledger_sequence, handle_stellar_list_admin_ops, handle_stellar_list_update, process_application_to_round, + process_project_event, process_rounds_deposit_event, process_vote_event, + save_block_height, update_application, update_approved_projects, + update_ledger_sequence, update_round_payout, handle_stellar_list +) CURRENT_BLOCK_HEIGHT_KEY = "current_block_height" @@ -85,7 +91,7 @@ async def indexer(from_block: int, to_block: int): logger.info( f"Total time for one iteration: {iteration_end_time - fetch_start_time:.4f} seconds" ) - + except asyncio.TimeoutError: logger.warning("Stream stalled: no new blocks within timeout, restarting...") # raise Exception so sytemd can restart the worker @@ -333,16 +339,22 @@ def address_to_string(obj): return obj.address raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable") + + +# Todo: Change model so thatthe event indexer saves the event and queues a task to immediately process the event, +# so we don;t have a separate beat that's looping through + + @shared_task def stellar_event_indexer(): server = stellar_sdk.SorobanServer( settings.STELLAR_RPC_URL ) - contract_ids = [settings.STELLAR_CONTRACT_ID, settings.STELLAR_PROJECTS_REGISTRY_CONTRACT] - if contract_ids == ['', '']: + contract_ids = [settings.STELLAR_CONTRACT_ID, settings.STELLAR_PROJECTS_REGISTRY_CONTRACT, settings.STELLAR_LIST_CONTRACT] + if contract_ids == ['', '', '']: return start_sequence = get_ledger_sequence() - # start_sequence = 12169 + # start_sequence = 668843 if not start_sequence: start_sequence = 58655649 jobs_logger.info(f"Ingesting Stellar events from ledger {start_sequence}... contracts: {contract_ids}") @@ -433,6 +445,14 @@ def process_stellar_events(): elif event_name == "u_pay": event.processed = update_round_payout(event_data, event.transaction_hash) + elif event_name == "c_list": + event.processed = handle_stellar_list(event_data, event.contract_id, event.ingested_at) + elif event_name == "u_list": + event.processed = handle_stellar_list_update(event_data, event.contract_id, event.ingested_at) + elif event_name == "c_reg": + event.processed = handle_stellar_list(event_data, event.contract_id, event.transaction_hash) + elif event_name == "u_adm": + event.processed = handle_stellar_list_admin_ops(event_data, event.contract_id, event.ingested_at, event.transaction_hash) event.save() except Exception as e: diff --git a/indexer_app/utils.py b/indexer_app/utils.py index e6a59a2..b356e2c 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -906,7 +906,7 @@ async def handle_list_admin_ops(data, receiver_id, signer_id, receiptId): } activity, activity_created = await Activity.objects.aupdate_or_create( - type="List_Admin_Ops", defaults=activity + type="Add_List_Admin", defaults=activity ) except Exception as e: logger.error(f"Failed to remove list admin, Error: {e}") @@ -1612,7 +1612,7 @@ def process_project_event(event_data, chain_id="stellar"): project.admins.add(admin) # Associate team members - for team_member_data in project_data['team_members']: + for team_member_data in project_data.get('team_members', []): team_member, _ = Account.objects.get_or_create(id=team_member_data['value']) project.team_members.add(team_member) @@ -1888,6 +1888,146 @@ def update_round_payout(event_data, tx_hash, chain_id="stellar"): logger.error(f"Error updating Payout. {str(e)}") return False + + + +def handle_stellar_list(data, contract_id, timestamp, chain_id="stellar"): + # receipt = block.receipts().filter(receiptId=receiptId)[0] + try: + logger.info("upserting involveed accts...") + + owner_address = data.get('owner') + chain = Chain.objects.get(name=chain_id) + Account.objects.get_or_create(defaults={"chain":chain},id=owner_address) + + + logger.info(f"creating list..... {data}") + + listObject = List.objects.create( + on_chain_id=data["id"], + chain=chain, + owner_id=data["owner"], + default_registration_status=data["default_registration_status"][0], + name=data["name"], + description=data["description"], + cover_image_url=data["cover_img_url"], + admin_only_registrations=data["admin_only_registrations"], + created_at=datetime.fromtimestamp(data["created_ms"] / 1000), + updated_at=datetime.fromtimestamp(data["updated_ms"] / 1000), + ) + + if data.get("admins"): + for admin_id in data["admins"]: + admin_object, _ = Account.objects.get_or_create(defaults={"chain":chain}, + id=admin_id, + ) + listObject.admins.add(admin_object) + logger.info(f"created list for chain {chain.name}.....") + return True + except Exception as e: + logger.error(f"Failed to handle new list, Error: {e}") + return False + + +def handle_stellar_list_update(data, contract_id, timestamp, chain_id="stellar"): + try: + logger.info(f"updating list from result..... {data}") + + listObject = List.objects.filter(on_chain_id=data["id"]).update( + owner_id=data["owner"], + default_registration_status=data["default_registration_status"][0], + name=data["name"], + description=data["description"], + cover_image_url=data["cover_image_url"], + admin_only_registrations=data["admin_only_registrations"], + created_at=datetime.fromtimestamp(data["created_at"] / 1000), + updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), + ) + return True + except Exception as e: + logger.error(f"Failed to handle list update, Error: {e}") + return False + + +def handle_new_stellar_list_registration(data, contract_id, tx_hash, chain_id="stellar"): + logger.info(f"new Project data: {data}") + # Prepare data for insertion + chain = Chain.objects.get(name=chain_id) + parent_list = List.objects.get(on_chain_id=data["list_id"]) + try: + project = Account.objects.get_or_create({"chain":chain, "id": data["registrant_id"]}) + except Exception as e: + logger.error(f"Encountered error trying to get create acct: {e}") + + logger.info(f"creating new List registration") + + try: + _ = ListRegistration.objects.create( + **{ + "id": data["id"], + "registrant_id": data["registrant_id"], + "list_id": parent_list.id, + "status": data["status"], + "submitted_at": datetime.fromtimestamp(data["submitted_ms"] / 1000), + "updated_at": datetime.fromtimestamp(data["updated_ms"] / 1000), + "registered_by_id": data["registered_by"], + "admin_notes": data.get("admin_notes"), + "registrant_notes": data.get("registrant_notes"), + "tx_hash": tx_hash, + } + ) + except Exception as e: + logger.error(f"Encountered error trying to create list: {e}") + + # Insert activity + try: + defaults = { + "signer_id": data["registered_by"], + "receiver_id": contract_id, + "timestamp": data["submitted_ms"], + "tx_hash": tx_hash, + } + + activity, activity_created = Activity.objects.update_or_create( + action_result=data, type="Register", defaults=defaults + ) + return True + except Exception as e: + logger.error(f"Encountered error trying to insert activity: {e}") + return False + + +def handle_stellar_list_admin_ops(data, contract_id, timestamp, tx_hash): + try: + round_id, admins = data[0], data[1] + logger.info(f"updating admins: {admins} for round {round_id}") + round_obj = Round.objects.get(on_chain_id=round_id) # select related? + chain = Chain.objects.get(name="stellar") + + for acct in admins: + admin, _ = Account.objects.get_or_create(defaults={"chain":chain},id=acct) + contains = round_obj.admins.acontains(admin) + if not contains: + round_obj.admins.add(admin) + for admin in round_obj.admins.all(): + if not admin.id in admins: + round_obj.admins.remove(admin) + + activity = { + "signer_id": round_obj.owner.id, + "receiver_id": contract_id, + "timestamp": timestamp, + "tx_hash": tx_hash, + } + + activity, activity_created = Activity.objects.update_or_create( + type="Add_List_Admin", defaults=activity + ) + return True + except Exception as e: + logger.error(f"Failed to remove list admin, Error: {e}") + return False + # Campaign Event Indexing Methods async def handle_new_campaign(data: dict, created_at): diff --git a/lists/api.py b/lists/api.py index b906d21..639e219 100644 --- a/lists/api.py +++ b/lists/api.py @@ -42,6 +42,12 @@ class ListsListAPI(APIView, CustomSizePageNumberPagination): OpenApiParameter.QUERY, description="Filter lists by account", ), + OpenApiParameter( + "chain", + str, + OpenApiParameter.QUERY, + description="Filter lists by chain id", + ), OpenApiParameter( "admin", str, @@ -72,6 +78,17 @@ class ListsListAPI(APIView, CustomSizePageNumberPagination): def get(self, request: Request, *args, **kwargs): lists = List.objects.all().select_related("owner").prefetch_related("admins", "upvotes").annotate(registrations_count=Count('registrations')) account_id = request.query_params.get("account") + chain = request.query_params.get("chain") + if chain: + lists = lists.filter(chain__id=chain) + if account_id: + try: + account = Chain.objects.get(name=account_id) + lists = lists.filter(owner=account) + except Account.DoesNotExist: + return Response( + {"message": f"Account with ID {account_id} not found."}, status=404 + ) if account_id: try: account = Account.objects.get(id=account_id) diff --git a/lists/migrations/0008_list_chain.py b/lists/migrations/0008_list_chain.py new file mode 100644 index 0000000..958d8e3 --- /dev/null +++ b/lists/migrations/0008_list_chain.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.6 on 2025-09-22 12:10 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("chains", "0003_add_stellar_chain"), + ("lists", "0007_alter_list_cover_image_url_alter_list_description_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="list", + name="chain", + field=models.ForeignKey( + default=1, + help_text="Blockchain this list was created on.", + on_delete=django.db.models.deletion.CASCADE, + related_name="lists", + related_query_name="list", + to="chains.chain", + ), + ), + ] diff --git a/lists/migrations/0009_alter_list_on_chain_id_and_more.py b/lists/migrations/0009_alter_list_on_chain_id_and_more.py new file mode 100644 index 0000000..afb6a58 --- /dev/null +++ b/lists/migrations/0009_alter_list_on_chain_id_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.6 on 2025-09-23 11:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ("chains", "0003_add_stellar_chain"), + ("lists", "0008_list_chain"), + ] + + operations = [ + migrations.AlterField( + model_name="list", + name="on_chain_id", + field=models.IntegerField( + help_text="List ID in contract", verbose_name="contract list ID" + ), + ), + migrations.AddConstraint( + model_name="list", + constraint=models.UniqueConstraint( + fields=("on_chain_id", "chain"), name="unique_on_chain_id_per_chain" + ), + ), + ] diff --git a/lists/models.py b/lists/models.py index 9754f93..639d978 100644 --- a/lists/models.py +++ b/lists/models.py @@ -2,6 +2,7 @@ from django.utils.translation import gettext_lazy as _ from accounts.models import Account +from chains.models import Chain class ListRegistrationStatus(models.TextChoices): @@ -12,6 +13,8 @@ class ListRegistrationStatus(models.TextChoices): BLACKLISTED = "Blacklisted", "Blacklisted" + + class List(models.Model): id = models.AutoField( _("list id"), @@ -21,9 +24,16 @@ class List(models.Model): on_chain_id = models.IntegerField( _("contract list ID"), null=False, - unique=True, help_text=_("List ID in contract"), ) + chain = models.ForeignKey( + Chain, + default=1, + on_delete=models.CASCADE, + related_name="lists", + related_query_name="list", + help_text=_("Blockchain this list was created on."), + ) owner = models.ForeignKey( Account, on_delete=models.CASCADE, @@ -79,7 +89,12 @@ class List(models.Model): class Meta: indexes = [ - models.Index(fields=["created_at", "updated_at"], name="idx_list_stamps") + models.Index(fields=["created_at", "updated_at"], name="idx_list_stamps"), + ] + constraints = [ + models.UniqueConstraint( + fields=["on_chain_id", "chain"], name="unique_on_chain_id_per_chain" + ) ] From 5061ac07a60f1c98a405c242af0fd1b35079cf6d Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 24 Sep 2025 09:48:44 +0100 Subject: [PATCH 401/454] add chain to serializer --- base/settings.py | 2 +- lists/serializers.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/base/settings.py b/base/settings.py index 5affa2d..5ef90b6 100644 --- a/base/settings.py +++ b/base/settings.py @@ -94,7 +94,7 @@ STELLAR_RPC_URL = ( "https://soroban-testnet.stellar.org" - if ENVIRONMENT == "local" + if ENVIRONMENT == "testnet" else "https://stellar-soroban-public.nodies.app" ) diff --git a/lists/serializers.py b/lists/serializers.py index 697e9b2..a5cb068 100644 --- a/lists/serializers.py +++ b/lists/serializers.py @@ -19,6 +19,7 @@ class Meta: fields = [ "id", "on_chain_id", + "chain", "owner", "admins", "name", From 504efcc81c18f2f094f566b1acb785a21928964c Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 24 Sep 2025 14:34:05 +0100 Subject: [PATCH 402/454] fetch list detail by chain and id --- api/urls.py | 2 +- lists/api.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/api/urls.py b/api/urls.py index 59e60c5..07cf61f 100644 --- a/api/urls.py +++ b/api/urls.py @@ -135,7 +135,7 @@ path("v1/donors", DonorsAPI.as_view(), name="donors_api"), # lists path("v1/lists", ListsListAPI.as_view(), name="lists_api"), - path("v1/lists/", ListDetailAPI.as_view(), name="lists_api_by_id"), + path("v1/lists//", ListDetailAPI.as_view(), name="lists_api_by_id"), path( "v1/lists//registrations", ListRegistrationsAPI.as_view(), diff --git a/lists/api.py b/lists/api.py index 639e219..631c344 100644 --- a/lists/api.py +++ b/lists/api.py @@ -116,6 +116,7 @@ class ListDetailAPI(APIView): @extend_schema( parameters=[ OpenApiParameter("list_id", int, OpenApiParameter.PATH), + OpenApiParameter("chain", int, OpenApiParameter.PATH), ], responses={ 200: OpenApiResponse( @@ -138,11 +139,12 @@ class ListDetailAPI(APIView): @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") + chain = kwargs.get("chain") try: - list_obj = List.objects.select_related("owner").prefetch_related("admins").get(on_chain_id=list_id) + list_obj = List.objects.select_related("owner").prefetch_related("admins").get(on_chain_id=list_id, chain=chain) except List.DoesNotExist: return Response( - {"message": f"List with onchain ID {list_id} not found."}, status=404 + {"message": f"List with onchain ID {list_id} not found on chain {chain}."}, status=404 ) serializer = ListSerializer(list_obj) return Response(serializer.data) From a4ded254e2cca34c5cd5feee3bfba77ad6ce00b3 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 24 Sep 2025 16:16:23 +0100 Subject: [PATCH 403/454] make chain query param for list deets --- api/urls.py | 2 +- lists/api.py | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/api/urls.py b/api/urls.py index 07cf61f..59e60c5 100644 --- a/api/urls.py +++ b/api/urls.py @@ -135,7 +135,7 @@ path("v1/donors", DonorsAPI.as_view(), name="donors_api"), # lists path("v1/lists", ListsListAPI.as_view(), name="lists_api"), - path("v1/lists//", ListDetailAPI.as_view(), name="lists_api_by_id"), + path("v1/lists/", ListDetailAPI.as_view(), name="lists_api_by_id"), path( "v1/lists//registrations", ListRegistrationsAPI.as_view(), diff --git a/lists/api.py b/lists/api.py index 631c344..c523848 100644 --- a/lists/api.py +++ b/lists/api.py @@ -116,7 +116,12 @@ class ListDetailAPI(APIView): @extend_schema( parameters=[ OpenApiParameter("list_id", int, OpenApiParameter.PATH), - OpenApiParameter("chain", int, OpenApiParameter.PATH), + OpenApiParameter( + "chain", + str, + OpenApiParameter.QUERY, + description="Filter lists by chain id", + ), ], responses={ 200: OpenApiResponse( @@ -139,12 +144,12 @@ class ListDetailAPI(APIView): @method_decorator(cache_page(60 * 5)) def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") - chain = kwargs.get("chain") + chain = request.query_params.get("chain") try: - list_obj = List.objects.select_related("owner").prefetch_related("admins").get(on_chain_id=list_id, chain=chain) + list_obj = List.objects.select_related("owner").prefetch_related("admins").get(on_chain_id=list_id, chain=1 if not chain else chain) except List.DoesNotExist: return Response( - {"message": f"List with onchain ID {list_id} not found on chain {chain}."}, status=404 + {"message": f"List with onchain ID {list_id} not found."}, status=404 ) serializer = ListSerializer(list_obj) return Response(serializer.data) From 4a9a375d1b8044447a0f00c1adad9f0ad4af0dad Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 24 Sep 2025 19:41:11 +0100 Subject: [PATCH 404/454] filter by chain name --- lists/api.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lists/api.py b/lists/api.py index c523848..85bfa4b 100644 --- a/lists/api.py +++ b/lists/api.py @@ -78,9 +78,8 @@ class ListsListAPI(APIView, CustomSizePageNumberPagination): def get(self, request: Request, *args, **kwargs): lists = List.objects.all().select_related("owner").prefetch_related("admins", "upvotes").annotate(registrations_count=Count('registrations')) account_id = request.query_params.get("account") - chain = request.query_params.get("chain") - if chain: - lists = lists.filter(chain__id=chain) + chain = request.query_params.get("chain", "NEAR") + lists = lists.filter(chain__name=chain) if account_id: try: account = Chain.objects.get(name=account_id) @@ -146,7 +145,7 @@ def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") chain = request.query_params.get("chain") try: - list_obj = List.objects.select_related("owner").prefetch_related("admins").get(on_chain_id=list_id, chain=1 if not chain else chain) + list_obj = List.objects.select_related("owner").prefetch_related("admins").get(on_chain_id=list_id, chain__name="NEAR" if not chain else chain) except List.DoesNotExist: return Response( {"message": f"List with onchain ID {list_id} not found."}, status=404 @@ -160,6 +159,12 @@ class ListRegistrationsAPI(APIView, CustomSizePageNumberPagination): @extend_schema( parameters=[ OpenApiParameter("list_id", int, OpenApiParameter.PATH), + OpenApiParameter( + "chain", + str, + OpenApiParameter.QUERY, + description="Filter registrations by list chain: ('NEAR', 'stellar')", + ), OpenApiParameter( "status", str, @@ -201,8 +206,9 @@ class ListRegistrationsAPI(APIView, CustomSizePageNumberPagination): @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") + chain = request.query_params.get("chain") # list_obj = List.objects.get(on_chain_id=list_id) - registrations = ListRegistration.objects.filter(list__on_chain_id=list_id).select_related("list", "list__owner", "registrant", "registered_by").prefetch_related("list__admins", "list__upvotes") + registrations = ListRegistration.objects.filter(list__on_chain_id=list_id, list__chain__name="NEAR" if not chain else chain).select_related("list__chain", "list__owner", "registrant", "registered_by").prefetch_related("list__admins", "list__upvotes") # registrations = list_obj.registrations.select_related("list", "list__owner", "registrant", "registered_by").prefetch_related("list__admins").annotate(registrations_count=Count('list_registrations')).all() status_param = request.query_params.get("status") From b78a6d0320bdd78464832b7f3217894a0c1d17f9 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 26 Sep 2025 12:25:51 +0100 Subject: [PATCH 405/454] fix susbcription: --- indexer_app/utils.py | 794 +++++++++++++++++++++++++++---------------- 1 file changed, 492 insertions(+), 302 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index b356e2c..5568d61 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -18,7 +18,18 @@ from campaigns.models import Campaign, CampaignDonation from chains.models import Chain from donations.models import Donation -from grantpicks.models import Project, ProjectContact, ProjectContract, ProjectFundingHistory, ProjectRepository, ProjectStatus, Round, RoundDeposit, Vote, VotePair +from grantpicks.models import ( + Project, + ProjectContact, + ProjectContract, + ProjectFundingHistory, + ProjectRepository, + ProjectStatus, + Round, + RoundDeposit, + Vote, + VotePair, +) from indexer_app.models import BlockHeight from lists.models import List, ListRegistration, ListUpvote from nadabot.models import BlackList, Group, NadabotRegistry, Provider, Stamp @@ -64,8 +75,12 @@ async def handle_new_nadabot_registry( logger.info(f"nadabot registry init... {data}") try: - registry, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=receiverId) - owner, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=data["owner"]) + registry, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=receiverId + ) + owner, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=data["owner"] + ) nadabot_registry, created = await NadabotRegistry.objects.aupdate_or_create( account=registry, owner=owner, @@ -76,7 +91,9 @@ async def handle_new_nadabot_registry( if data.get("admins"): for admin_id in data["admins"]: - admin, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=admin_id) + admin, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=admin_id + ) await nadabot_registry.admins.aadd(admin) except Exception as e: logger.error(f"Error in registry initiialization: {e}") @@ -88,10 +105,14 @@ async def handle_registry_blacklist_action( logger.info(f"Registry blacklist action....... {data}") try: - registry, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=receiverId) + registry, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=receiverId + ) bulk_obj = [] for acct in data["accounts"]: - account, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=acct) + account, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=acct + ) bulk_obj.append( { "registry": registry, @@ -113,7 +134,9 @@ async def handle_registry_unblacklist_action( logger.info(f"Registry remove blacklisted accts....... {data}") try: - registry, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=receiverId) + registry, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=receiverId + ) entries = BlackList.objects.filter(account__in=data["accounts"]) await entries.adelete() except Exception as e: @@ -129,16 +152,21 @@ async def handle_new_pot( created_at: datetime, ): try: - logger.info("new pot deployment process... upsert accounts,") # Upsert accounts owner_id = ( data.get("owner") or signer_id ) # owner is optional; if not provided, owner will be transaction signer (this logic is implemented by Pot contract's "new" method) - owner, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=owner_id) - signer, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=signer_id) - receiver, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=receiver_id) + owner, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=owner_id + ) + signer, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=signer_id + ) + receiver, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=receiver_id + ) # check if pot exists pot = await Pot.objects.filter(account=receiver).afirst() @@ -150,7 +178,9 @@ async def handle_new_pot( logger.info("upsert chef") if data.get("chef"): - chef, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=data["chef"]) + chef, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=data["chef"] + ) # Create Pot object logger.info(f"creating pot with owner {owner_id}....") @@ -178,7 +208,10 @@ async def handle_new_pot( data["public_round_end_ms"] / 1000 ), "registry_provider": data["registry_provider"], - "min_matching_pool_donation_amount": data.get("min_matching_pool_donation_amount") or "0", + "min_matching_pool_donation_amount": data.get( + "min_matching_pool_donation_amount" + ) + or "0", "sybil_wrapper_provider": data.get("sybil_wrapper_provider"), "custom_sybil_checks": data.get("custom_sybil_checks"), "custom_min_threshold_score": data.get("custom_min_threshold_score"), @@ -203,7 +236,9 @@ async def handle_new_pot( # Add admins to the Pot if data.get("admins"): for admin_id in data["admins"]: - admin, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=admin_id) + admin, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=admin_id + ) await pot.admins.aadd(admin) defaults = { @@ -288,18 +323,20 @@ async def handle_pot_config_update( async def handle_new_pot_factory(data: dict, receiver_id: str, created_at: datetime): try: - logger.info("upserting accounts...") # Upsert accounts - owner, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, + owner, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=data["owner"], ) - protocol_fee_recipient_account, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, + protocol_fee_recipient_account, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=data["protocol_fee_recipient_account"], ) - receiver, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, + receiver, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=receiver_id, ) @@ -320,7 +357,8 @@ async def handle_new_pot_factory(data: dict, receiver_id: str, created_at: datet # Add admins to the PotFactory if data.get("admins"): for admin_id in data["admins"]: - admin, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, + admin, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=admin_id, ) await factory.admins.aadd(admin) @@ -328,30 +366,38 @@ async def handle_new_pot_factory(data: dict, receiver_id: str, created_at: datet # Add whitelisted deployers to the PotFactory if data.get("whitelisted_deployers"): for deployer_id in data["whitelisted_deployers"]: - deployer, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=deployer_id) + deployer, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=deployer_id + ) await factory.whitelisted_deployers.aadd(deployer) except Exception as e: logger.error(f"Failed to handle new pot Factory, Error: {e}") -async def handle_new_list_and_reg(signer_id: str, receiver_id: str, status_obj: ExecutionOutcome, receipt: Receipt): +async def handle_new_list_and_reg( + signer_id: str, receiver_id: str, status_obj: ExecutionOutcome, receipt: Receipt +): create_data, reg_data = json.loads( - base64.b64decode(status_obj.status.get("SuccessValue")).decode( - "utf-8" - ) # TODO: RECEIVE AS A FUNCTION ARGUMENT - ) + base64.b64decode(status_obj.status.get("SuccessValue")).decode( + "utf-8" + ) # TODO: RECEIVE AS A FUNCTION ARGUMENT + ) await handle_new_list(signer_id, receiver_id, None, create_data) if reg_data: - await handle_new_list_registration(reg_data, receiver_id, signer_id, receipt, None) + await handle_new_list_registration( + reg_data, receiver_id, signer_id, receipt, None + ) pass async def handle_new_list( - signer_id: str, receiver_id: str, status_obj: ExecutionOutcome | None, data: dict | None + signer_id: str, + receiver_id: str, + status_obj: ExecutionOutcome | None, + data: dict | None, ): # receipt = block.receipts().filter(receiptId=receiptId)[0] try: - if not data: data = json.loads( base64.b64decode(status_obj.status.get("SuccessValue")).decode( @@ -361,11 +407,11 @@ async def handle_new_list( logger.info("upserting involveed accts...") - await Account.objects.aget_or_create(defaults={"chain_id":1},id=data["owner"]) + await Account.objects.aget_or_create(defaults={"chain_id": 1}, id=data["owner"]) - await Account.objects.aget_or_create(defaults={"chain_id":1},id=signer_id) + await Account.objects.aget_or_create(defaults={"chain_id": 1}, id=signer_id) - await Account.objects.aget_or_create(defaults={"chain_id":1},id=receiver_id) + await Account.objects.aget_or_create(defaults={"chain_id": 1}, id=receiver_id) logger.info(f"creating list..... {data}") @@ -383,7 +429,8 @@ async def handle_new_list( if data.get("admins"): for admin_id in data["admins"]: - admin_object, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, + admin_object, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=admin_id, ) await listObject.admins.aadd(admin_object) @@ -391,12 +438,13 @@ async def handle_new_list( logger.error(f"Failed to handle new list, Error: {e}") - async def handle_list_update( - signer_id: str, receiver_id: str, status_obj: ExecutionOutcome | None, data: dict | None + signer_id: str, + receiver_id: str, + status_obj: ExecutionOutcome | None, + data: dict | None, ): try: - if not data: data = json.loads( base64.b64decode(status_obj.status.get("SuccessValue")).decode( @@ -404,7 +452,6 @@ async def handle_list_update( ) # TODO: RECEIVE AS A FUNCTION ARGUMENT ) - logger.info(f"updating list from result..... {data}") listObject = await List.objects.filter(on_chain_id=data["id"]).aupdate( @@ -428,15 +475,14 @@ async def handle_list_update( logger.error(f"Failed to handle list update, Error: {e}") -async def handle_delete_list( - data: dict -): +async def handle_delete_list(data: dict): try: logger.info(f"deleting list..... {data}") lst = await List.objects.filter(on_chain_id=data["list_id"]).adelete() except Exception as e: logger.error(f"Failed to delete, Error: {e}") + async def handle_new_list_registration( data: dict, receiver_id: str, @@ -464,7 +510,7 @@ async def handle_new_list_registration( parent_list = await List.objects.aget(on_chain_id=reg_data[0]["list_id"]) for dt in reg_data: logger.info(f"dt: {dt}") - project_list.append({"chain_id":1, "id": dt["registrant_id"]}) + project_list.append({"chain_id": 1, "id": dt["registrant_id"]}) insert_data.append( { "id": dt["id"], @@ -485,7 +531,7 @@ async def handle_new_list_registration( await Account.objects.abulk_create( objs=[Account(**data) for data in project_list], ignore_conflicts=True ) - await Account.objects.aget_or_create(defaults={"chain_id":1},id=signer_id) + await Account.objects.aget_or_create(defaults={"chain_id": 1}, id=signer_id) logger.info("Upserted accounts/registrants(signer)") except Exception as e: logger.error(f"Encountered error trying to get create acct: {e}") @@ -526,6 +572,7 @@ async def handle_list_registration_removal( except Exception as e: logger.error(f"Encountered error trying to remove reg: {e}") + async def handle_list_registration_update( data: dict, receiver_id: str, status_obj: ExecutionOutcome ): @@ -560,7 +607,6 @@ async def handle_pot_application( created_at: datetime, ): try: - # receipt = block.receipts().filter(lambda receipt: receipt.receiptId == receiptId)[0] result = status_obj.status.get("SuccessValue") if not result: @@ -572,12 +618,14 @@ async def handle_pot_application( logger.info(f"new pot application data: {data}, {appl_data}") # Update or create the account - project, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, + project, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=appl_data["project_id"], ) # TODO: wouldn't this be the same as the project_id? should inspect - signer, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, + signer, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=signer_id, ) @@ -590,12 +638,13 @@ async def handle_pot_application( "status": appl_data["status"], "tx_hash": receipt.receipt_id, } - application, application_created = ( - await PotApplication.objects.aupdate_or_create( - applicant=project, - pot_id=receiver_id, - defaults=appl_defaults, - ) + ( + application, + application_created, + ) = await PotApplication.objects.aupdate_or_create( + applicant=project, + pot_id=receiver_id, + defaults=appl_defaults, ) # Create the activity object @@ -627,7 +676,6 @@ async def handle_pot_application_status_change( status_obj: ExecutionOutcome, ): try: - logger.info(f"pot application update data: {data}, {receiver_id}") # receipt = next(receipt for receipt in block.receipts() if receipt.receiptId == receiptId) @@ -638,7 +686,13 @@ async def handle_pot_application_status_change( ) # Retrieve the PotApplication object - appl = await PotApplication.objects.select_related('round', 'pot', 'project', 'applicant').filter(applicant_id=data["project_id"], pot_id=receiver_id).afirst() + appl = ( + await PotApplication.objects.select_related( + "round", "pot", "project", "applicant" + ) + .filter(applicant_id=data["project_id"], pot_id=receiver_id) + .afirst() + ) if not appl: logger.error( @@ -664,8 +718,12 @@ async def handle_pot_application_status_change( ) # Update the PotApplication object - await PotApplication.objects.select_related('round', 'pot', 'project', 'applicant').filter(applicant_id=data["project_id"], pot_id=receiver_id).aupdate( - **{"status": update_data["status"], "updated_at": updated_at} + await ( + PotApplication.objects.select_related( + "round", "pot", "project", "applicant" + ) + .filter(applicant_id=data["project_id"], pot_id=receiver_id) + .aupdate(**{"status": update_data["status"], "updated_at": updated_at}) ) logger.info("PotApplicationReview and PotApplication updated successfully.") @@ -677,7 +735,6 @@ async def handle_default_list_status_change( data: dict, receiver_id: str, status_obj: ExecutionOutcome ): try: - logger.info(f"update project data: {data}, {receiver_id}") result_data = json.loads( @@ -710,24 +767,19 @@ async def handle_list_upvote( data: dict, receiver_id: str, signer_id: str, receiptId: str, created_at: datetime ): try: - logger.info(f"upvote list: {data}, {receiver_id}") - acct, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, + acct, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=signer_id, ) - - up_default = { - "created_at": created_at - } + up_default = {"created_at": created_at} list_obj = await List.objects.aget(on_chain_id=data.get("list_id")) await ListUpvote.objects.aupdate_or_create( - list=list_obj, - account_id=signer_id, - defaults=up_default + list=list_obj, account_id=signer_id, defaults=up_default ) defaults = { @@ -748,30 +800,25 @@ async def handle_list_upvote( logger.error(f"Failed to upvote list, Error: {e}") - -async def handle_remove_upvote( - data: dict, receiver_id: str, signer_id: str -): +async def handle_remove_upvote(data: dict, receiver_id: str, signer_id: str): try: - logger.info(f"remove upvote from list: {data}, {receiver_id}") list_obj = await List.objects.aget(on_chain_id=data.get("list_id")) await ListUpvote.objects.filter(list=list_obj, account_id=signer_id).adelete() - logger.info( - f"Upvote removed successfully" - ) + logger.info(f"Upvote removed successfully") except Exception as e: logger.error(f"Failed to remove upvote from list, Error: {e}") async def handle_set_payouts(data: dict, receiver_id: str, receipt: Receipt): try: - logger.info(f"set payout data: {data}, {receiver_id}") payouts = data.get("payouts", []) pot = await Pot.objects.aget(account=receiver_id) - near_acct, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id="near") + near_acct, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id="near" + ) near_token, _ = await Token.objects.aget_or_create( account=near_acct ) # Pots only support native NEAR @@ -807,7 +854,6 @@ async def handle_transfer_payout( data: dict, receiver_id: str, receiptId: str, created_at: datetime ): try: - data = data["payout"] logger.info(f"fulfill payout data: {data}, {receiver_id}, {created_at}") payout = { @@ -837,7 +883,9 @@ async def handle_payout_challenge( data: dict, receiver_id: str, signer_id: str, receiptId: str, created_at: datetime ): try: - acct, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=signer_id) + acct, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=signer_id + ) logger.info(f"challenging payout..: {data}, {receiver_id}") payoutChallenge = { "created_at": created_at, @@ -885,12 +933,13 @@ async def handle_payout_challenge_response( async def handle_list_admin_ops(data, receiver_id, signer_id, receiptId): try: - logger.info(f"updating admin...: {data}, {receiver_id}") list_obj = await List.objects.aget(on_chain_id=data["list_id"]) for acct in data["admins"]: - admin, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=acct) + admin, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=acct + ) contains = await list_obj.admins.acontains(admin) if not contains: await list_obj.admins.aadd(admin) @@ -915,20 +964,23 @@ async def handle_list_admin_ops(data, receiver_id, signer_id, receiptId): async def handle_list_owner_change(data): try: logger.info(f"changing owner... ...: {data}") - await List.objects.filter(id=data["list_id"]).aupdate(**{ - "owner": data["new_owner_id"] - }) + await List.objects.filter(id=data["list_id"]).aupdate( + **{"owner": data["new_owner_id"]} + ) except Exception as e: logger.error(f"Failed to change list owner, Error: {e}") + async def handle_add_nadabot_admin(data, receiverId): logger.info(f"adding admin...: {data}, {receiverId}") try: obj = await NadabotRegistry.objects.aget(account=receiverId) for acct in data["account_ids"]: - user, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=acct) + user, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=acct + ) await obj.admins.aadd(user) except Exception as e: logger.error(f"Failed to add nadabot admin, Error: {e}") @@ -939,7 +991,9 @@ async def handle_add_factory_deployers(data, receiverId): try: factory = await PotFactory.objects.aget(account=receiverId) for acct in data["whitelisted_deployers"]: - user, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=acct) + user, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=acct + ) await factory.whitelisted_deployers.aadd(user) except Exception as e: logger.error(f"Failed to add factory whitelisted deployers, Error: {e}") @@ -955,8 +1009,6 @@ async def handle_set_factory_configs(data, receiverId): logger.error(f"Failed to update factory configs, Error: {e}") - - # # TODO: Need to abstract some actions. # async def handle_batch_donations( # receiver_id: str, @@ -1008,33 +1060,41 @@ async def handle_new_donation( try: # insert donate contract which is the receiver id(because of activity relationship mainly) - donate_contract, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=receiver_id) + donate_contract, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=receiver_id + ) # Upsert donor account - donor, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=donation_data["donor_id"]) + donor, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=donation_data["donor_id"] + ) recipient = None referrer = None chef = None if donation_data.get("recipient_id"): # direct donations have recipient_id - recipient, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, - id=donation_data["recipient_id"] + recipient, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=donation_data["recipient_id"] ) if donation_data.get("project_id"): # pot donations have project_id - recipient, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, - id=donation_data["project_id"] + recipient, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=donation_data["project_id"] ) if donation_data.get("referrer_id"): - referrer, _ = await Account.objects.aget_or_create(defaults={"chain_id":1}, - id=donation_data["referrer_id"] + referrer, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=donation_data["referrer_id"] ) if donation_data.get("chef_id"): - chef, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=donation_data["chef_id"]) + chef, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=donation_data["chef_id"] + ) # Upsert token account ft_id = donation_data.get("ft_id") or "near" - token_acct, token_acct_created = await Account.objects.aget_or_create(defaults={"chain_id":1},id=ft_id) + token_acct, token_acct_created = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=ft_id + ) token_defaults = { "decimals": 24, } @@ -1065,7 +1125,6 @@ async def handle_new_donation( logger.error(f"Failed to create/get an account involved in donation: {e}") try: - total_amount = donation_data["total_amount"] logger.info(f"inserting {donation_type} donation") @@ -1199,7 +1258,6 @@ async def handle_update_default_human_threshold(data: dict, receiverId: str): logger.info(f"update threshold data... {data}") try: - reg = await NadabotRegistry.objects.filter(account=receiverId).aupdate( **{"default_human_threshold": data["default_human_threshold"]} ) @@ -1217,8 +1275,12 @@ async def handle_new_provider(data: dict, receiverId: str, signerId: str): ) try: - submitter, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=data["submitted_by"]) - contract, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=data["contract_id"]) + submitter, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=data["submitted_by"] + ) + contract, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=data["contract_id"] + ) provider_id = data["id"] @@ -1263,7 +1325,9 @@ async def handle_add_stamp(data: dict, receiverId: str, signerId: str): logger.info(f"upserting accounts involved, {data['user_id']}") - user, _ = await Account.objects.aget_or_create(defaults={"chain_id":1},id=data["user_id"]) + user, _ = await Account.objects.aget_or_create( + defaults={"chain_id": 1}, id=data["user_id"] + ) provider, _ = await Provider.objects.aget_or_create(on_chain_id=data["provider_id"]) try: @@ -1329,7 +1393,6 @@ def get_block_height() -> int: return 178243042 - def update_ledger_sequence(sequence, timestamp: datetime): BlockHeight.objects.update_or_create( id=2, @@ -1340,13 +1403,16 @@ def update_ledger_sequence(sequence, timestamp: datetime): }, ) + def get_ledger_sequence() -> int: record = BlockHeight.objects.filter(id=2).first() if record: return record.block_height -def update_approved_projects(event_data, chain_id="stellar", time_stamp=None, tx_hash=None): +def update_approved_projects( + event_data, chain_id="stellar", time_stamp=None, tx_hash=None +): round_id, project_ids = event_data[0], event_data[1] with transaction.atomic(): @@ -1356,8 +1422,10 @@ def update_approved_projects(event_data, chain_id="stellar", time_stamp=None, tx for ids in project_ids: project = Project.objects.get(on_chain_id=ids) round_obj.approved_projects.add(project.owner) - logger.info(f"Creating application for round: {round_id} for approved projects") - status = PotApplicationStatus['Approved'.upper()] + logger.info( + f"Creating application for round: {round_id} for approved projects" + ) + status = PotApplicationStatus["Approved".upper()] appl_defaults = { "message": "added by owner", @@ -1378,28 +1446,41 @@ def update_approved_projects(event_data, chain_id="stellar", time_stamp=None, tx logger.error(f"Error updating application for Round {round_id}: {e}") return False + def update_application(event_data, txhash, reviewer_id=None, chain_id="stellar"): if type(event_data) == list: - round_id, application_data, reviewer_id = event_data[0], event_data[1], event_data[2] + round_id, application_data, reviewer_id = ( + event_data[0], + event_data[1], + event_data[2], + ) else: - event_data = event_data['application'] - round_id, application_data, reviewer_id = event_data["round_id"], event_data, reviewer_id + event_data = event_data["application"] + round_id, application_data, reviewer_id = ( + event_data["round_id"], + event_data, + reviewer_id, + ) with transaction.atomic(): try: chain = Chain.objects.get(name=chain_id) round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) - applicant = Account.objects.get(id=application_data['applicant_id'], chain=chain) + applicant = Account.objects.get( + id=application_data["applicant_id"], chain=chain + ) reviewer = Account.objects.get(id=reviewer_id, chain=chain) if chain_id == "NEAR": - status = PotApplicationStatus[application_data['status'].upper()] + status = PotApplicationStatus[application_data["status"].upper()] else: - status = PotApplicationStatus[application_data['status'][0].upper()] + status = PotApplicationStatus[application_data["status"][0].upper()] - submitted_at = datetime.fromtimestamp(application_data['submited_ms'] / 1000) - updated_at = datetime.fromtimestamp(application_data['updated_ms'] / 1000) + submitted_at = datetime.fromtimestamp( + application_data["submited_ms"] / 1000 + ) + updated_at = datetime.fromtimestamp(application_data["updated_ms"] / 1000) defaults = { "notes": application_data.get("review_note"), @@ -1407,9 +1488,7 @@ def update_application(event_data, txhash, reviewer_id=None, chain_id="stellar") "tx_hash": txhash, } - appl = PotApplication.objects.filter( - applicant=applicant - ).first() + appl = PotApplication.objects.filter(applicant=applicant).first() PotApplicationReview.objects.update_or_create( application_id=appl.id, @@ -1418,11 +1497,15 @@ def update_application(event_data, txhash, reviewer_id=None, chain_id="stellar") defaults=defaults, ) - project = Project.objects.get(on_chain_id=application_data.get("project_id")) + project = Project.objects.get( + on_chain_id=application_data.get("project_id") + ) if status == PotApplicationStatus.APPROVED: - # If the application is approved, add the project to the round's approved projects + # If the application is approved, add the project to the round's approved projects if not round_obj.approved_projects.filter(id=project.owner.id).exists(): - logger.info(f"Adding project {project.owner.id} to approved projects for Round {round_id}") + logger.info( + f"Adding project {project.owner.id} to approved projects for Round {round_id}" + ) round_obj.approved_projects.add(project.owner) else: round_obj.approved_projects.remove(project.owner) @@ -1449,23 +1532,22 @@ def get_pair_projects(pair_id: int, round_id: int, chain_id: str) -> Dict: contract_id = settings.STELLAR_CONTRACT_ID function_name = "get_pair_by_index" - parameters = [stellar_sdk.scval.to_uint128(round_id), stellar_sdk.scval.to_uint32(pair_id)] + parameters = [ + stellar_sdk.scval.to_uint128(round_id), + stellar_sdk.scval.to_uint32(pair_id), + ] public_key = "GAMFYFI7TIAPMLSAWIECFZCN52TR3NUIO74YM7ECBCPM6J743KENH367" # TODO: move to settings acct = server.load_account(public_key) pair_result = server.simulate_transaction( transaction_envelope=stellar_sdk.TransactionBuilder( source_account=acct, - ).append_invoke_contract_function_op( - contract_id, - function_name, - parameters ) + .append_invoke_contract_function_op(contract_id, function_name, parameters) .set_timeout(30) .build() ) - if pair_result.results: xdr = pair_result.results[0].xdr data = stellar_sdk.scval.to_native(xdr) @@ -1489,57 +1571,58 @@ def process_vote_event(event_data, tx_hash, chain_id="stellar"): round_id, vote_data = event_data[0], event_data[1] else: # vote_event_data = event_data['vote'] - round_id, vote_data = event_data.get("round_id"), event_data['vote'] + round_id, vote_data = event_data.get("round_id"), event_data["vote"] chain = Chain.objects.get(name=chain_id) round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) - voter, _ = Account.objects.get_or_create(id=vote_data['voter'], chain=chain) - voted_at = datetime.fromtimestamp(vote_data['voted_ms'] / 1000) + voter, _ = Account.objects.get_or_create(id=vote_data["voter"], chain=chain) + voted_at = datetime.fromtimestamp(vote_data["voted_ms"] / 1000) # Create or update the Vote vote, created = Vote.objects.update_or_create( round=round_obj, voter=voter, - defaults={ - 'tx_hash': tx_hash, - 'voted_at': voted_at - } + defaults={"tx_hash": tx_hash, "voted_at": voted_at}, ) - - # Process vote pairs - for pick in vote_data['picks']: + for pick in vote_data["picks"]: if chain_id == "NEAR": - pair_id = pick['pair_id'] - project_id = pick['voted_project'] - + pair_id = pick["pair_id"] + project_id = pick["voted_project"] else: - pair_id = pick['pair_id'] - project_id = Project.objects.get(on_chain_id=pick['project_id']).owner.id + pair_id = pick["pair_id"] + project_id = Project.objects.get( + on_chain_id=pick["project_id"] + ).owner.id pair_data = get_pair_projects(pair_id, round_id, chain_id) logger.info(f"pair data from contract...:,{pair_data}") if pair_data: - project_id_1, project_id_2 = pair_data.get('projects') + project_id_1, project_id_2 = pair_data.get("projects") if chain_id == "stellar": - project_1 = Project.objects.get(on_chain_id=project_id_1).owner.id - project_2 = Project.objects.get(on_chain_id=project_id_2).owner.id + project_1 = Project.objects.get( + on_chain_id=project_id_1 + ).owner.id + project_2 = Project.objects.get( + on_chain_id=project_id_2 + ).owner.id else: project_1 = project_id_1 project_2 = project_id_2 - vp, created = VotePair.objects.update_or_create( vote=vote, pair_id=pair_id, - defaults={'voted_project_id': project_id} + defaults={"voted_project_id": project_id}, ) vp.projects.add(project_1) vp.projects.add(project_2) - logger.info(f"Processed vote for Round: {round_id}, Voter: {voter.id}, Project: {project_id}") + logger.info( + f"Processed vote for Round: {round_id}, Voter: {voter.id}, Project: {project_id}" + ) return True except Exception as e: logger.error(f"Error processing vote for Round: {str(e)}") @@ -1556,64 +1639,68 @@ def process_project_event(event_data, chain_id="stellar"): chain = Chain.objects.get(name=chain_id) # Create or get the owner Account - owner, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=project_data['owner']) + owner, _ = Account.objects.get_or_create( + defaults={"chain": chain}, id=project_data["owner"] + ) # Create or get the payout Account # Create the Project project, created = Project.objects.update_or_create( - on_chain_id=project_data['id'], + on_chain_id=project_data["id"], defaults={ - 'image_url': project_data['image_url'], - 'video_url': project_data['video_url'], - 'name': project_data['name'], - 'overview': project_data['overview'], - 'owner': owner, - 'status': ProjectStatus("NEW").name, - 'submited_ms': project_data['submited_ms'], - 'updated_ms': project_data['updated_ms'], - } + "image_url": project_data["image_url"], + "video_url": project_data["video_url"], + "name": project_data["name"], + "overview": project_data["overview"], + "owner": owner, + "status": ProjectStatus("NEW").name, + "submited_ms": project_data["submited_ms"], + "updated_ms": project_data["updated_ms"], + }, ) - for contact_data in project_data['contacts']: + for contact_data in project_data["contacts"]: contact, _ = ProjectContact.objects.get_or_create( - name=contact_data['name'], - value=contact_data['value'] + name=contact_data["name"], value=contact_data["value"] ) project.contacts.add(contact) - for contract_data in project_data['contracts']: + for contract_data in project_data["contracts"]: contract, _ = ProjectContract.objects.get_or_create( - name=contract_data['name'], - contract_address=contract_data['contract_address'] + name=contract_data["name"], + contract_address=contract_data["contract_address"], ) project.contracts.add(contract) # Create and associate ProjectRepositories - for repo_data in project_data['repositories']: + for repo_data in project_data["repositories"]: repo, _ = ProjectRepository.objects.get_or_create( - label=repo_data['label'], - url=repo_data['url'] + label=repo_data["label"], url=repo_data["url"] ) project.repositories.add(repo) - for funding_data in project_data['funding_histories']: + for funding_data in project_data["funding_histories"]: ProjectFundingHistory.objects.create( - source=funding_data['source'], - amount=funding_data['amount'], - denomination=funding_data['denomination'], # Note: There's a typo in the event data - description=funding_data['description'], - timestamp=timezone.datetime.fromtimestamp(funding_data['funded_ms'] / 1000) + source=funding_data["source"], + amount=funding_data["amount"], + denomination=funding_data[ + "denomination" + ], # Note: There's a typo in the event data + description=funding_data["description"], + timestamp=timezone.datetime.fromtimestamp( + funding_data["funded_ms"] / 1000 + ), ) # Associate admins - for admin_address in project_data['admins']: + for admin_address in project_data["admins"]: admin, _ = Account.objects.get_or_create(id=admin_address) project.admins.add(admin) # Associate team members - for team_member_data in project_data.get('team_members', []): - team_member, _ = Account.objects.get_or_create(id=team_member_data['value']) + for team_member_data in project_data.get("team_members", []): + team_member, _ = Account.objects.get_or_create(id=team_member_data["value"]) project.team_members.add(team_member) if created: @@ -1628,86 +1715,140 @@ def process_project_event(event_data, chain_id="stellar"): return False - def create_or_update_round(event_data, contract_id, timestamp, chain_id="stellar"): try: logger.info(f"create_or_update_round: {event_data}, {contract_id}, {chain_id}") # Create Round if chain_id == "NEAR": - event_data = event_data.get('round_detail') - round_id = event_data.get('id') - owner_address = event_data.get('owner') + event_data = event_data.get("round_detail") + round_id = event_data.get("id") + owner_address = event_data.get("owner") chain = Chain.objects.get(name=chain_id) - owner, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=owner_address) - factory_contract, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=contract_id) - remaining_dist_address = event_data.get('remaining_dist_address', event_data.get('remaining_funds_redistribution_recipient')) + owner, _ = Account.objects.get_or_create( + defaults={"chain": chain}, id=owner_address + ) + factory_contract, _ = Account.objects.get_or_create( + defaults={"chain": chain}, id=contract_id + ) + remaining_dist_address = event_data.get( + "remaining_dist_address", + event_data.get("remaining_funds_redistribution_recipient"), + ) if remaining_dist_address: - remaining_dist_address_obj, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=remaining_dist_address) + remaining_dist_address_obj, _ = Account.objects.get_or_create( + defaults={"chain": chain}, id=remaining_dist_address + ) - remaining_dist_by = event_data.get('remaining_dist_by', event_data.get('remaining_funds_redistributed_by')) + remaining_dist_by = event_data.get( + "remaining_dist_by", event_data.get("remaining_funds_redistributed_by") + ) if remaining_dist_by: - remaining_dist_by_obj, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=remaining_dist_by) + remaining_dist_by_obj, _ = Account.objects.get_or_create( + defaults={"chain": chain}, id=remaining_dist_by + ) - if event_data.get('round_complete_ms', event_data.get('round_complete')): - round_time_stamp = datetime.fromtimestamp(event_data.get('round_complete_ms', event_data.get('round_complete')) / 1000) + if event_data.get("round_complete_ms", event_data.get("round_complete")): + round_time_stamp = datetime.fromtimestamp( + event_data.get("round_complete_ms", event_data.get("round_complete")) + / 1000 + ) else: round_time_stamp = None if chain_id == "NEAR": use_vault = True else: - use_vault = event_data.get('use_vault', False) + use_vault = event_data.get("use_vault", False) round_obj, created = Round.objects.update_or_create( on_chain_id=round_id, chain=chain, defaults={ - 'owner': owner, - 'factory_contract': factory_contract, - 'chain': chain, - 'name': event_data.get('name'), - 'description': event_data.get('description'), - 'expected_amount': event_data.get('expected_amount'), - 'application_start': datetime.fromtimestamp(event_data.get('application_start_ms') / 1000) if event_data.get('application_start_ms') else None, - 'application_end': datetime.fromtimestamp(event_data.get('application_end_ms') / 1000) if event_data.get('application_end_ms') else None, - 'voting_start': datetime.fromtimestamp(event_data.get('voting_start_ms') / 1000), - 'voting_end': datetime.fromtimestamp(event_data.get('voting_end_ms') / 1000), - 'use_whitelist_voting': event_data.get('use_whitelist_voting', False), - 'use_whitelist_application': event_data.get('use_whitelist_application', False), - 'application_wl_list_id': event_data.get('application_wl_list_id'), - 'voting_wl_list_id': event_data.get('voting_wl_list_id'), - 'use_vault': use_vault or False, - 'num_picks_per_voter': event_data.get('num_picks_per_voter'), - 'max_participants': event_data.get('max_participants'), - 'allow_applications': event_data.get('allow_applications'), - 'allow_remaining_dist': event_data.get('allow_remaining_dist', event_data.get('allow_remaining_funds_redistribution')), - 'compliance_end': datetime.fromtimestamp(event_data.get('compliance_end_ms') / 1000) if event_data.get('compliance_end_ms') else None, - 'compliance_period_ms': event_data.get('compliance_period_ms'), - 'compliance_req_desc': event_data.get('compliance_req_desc', event_data.get('compliance_requirement_description')), - 'cooldown_end': datetime.fromtimestamp(event_data.get('cooldown_end_ms') / 1000) if event_data.get('cooldown_end_ms') else None, - 'cooldown_period_ms': event_data.get('cooldown_period_ms'), - 'is_video_required': event_data.get('is_video_required') or event_data.get('application_requires_video', False) , - 'referrer_fee_basis_points': event_data.get('referrer_fee_basis_points'), - 'remaining_dist_address_id': remaining_dist_address, - 'remaining_dist_at_ms': datetime.fromtimestamp(event_data.get('remaining_dist_at_ms') / 1000) if event_data.get('remaining_dist_at_ms') else None, - 'remaining_dist_by_id': remaining_dist_by, - 'remaining_dist_memo': event_data.get('remaining_dist_memo', event_data.get('remaining_funds_redistribution_memo')), - 'round_complete': round_time_stamp, - 'vault_total_deposits': event_data.get('vault_total_deposits'), - 'minimum_deposit': event_data.get('minimum_deposit'), - 'current_vault_balance': event_data.get('current_vault_balance'), - 'deployed_at': timestamp - } + "owner": owner, + "factory_contract": factory_contract, + "chain": chain, + "name": event_data.get("name"), + "description": event_data.get("description"), + "expected_amount": event_data.get("expected_amount"), + "application_start": datetime.fromtimestamp( + event_data.get("application_start_ms") / 1000 + ) + if event_data.get("application_start_ms") + else None, + "application_end": datetime.fromtimestamp( + event_data.get("application_end_ms") / 1000 + ) + if event_data.get("application_end_ms") + else None, + "voting_start": datetime.fromtimestamp( + event_data.get("voting_start_ms") / 1000 + ), + "voting_end": datetime.fromtimestamp( + event_data.get("voting_end_ms") / 1000 + ), + "use_whitelist_voting": event_data.get("use_whitelist_voting", False), + "use_whitelist_application": event_data.get( + "use_whitelist_application", False + ), + "application_wl_list_id": event_data.get("application_wl_list_id"), + "voting_wl_list_id": event_data.get("voting_wl_list_id"), + "use_vault": use_vault or False, + "num_picks_per_voter": event_data.get("num_picks_per_voter"), + "max_participants": event_data.get("max_participants"), + "allow_applications": event_data.get("allow_applications"), + "allow_remaining_dist": event_data.get( + "allow_remaining_dist", + event_data.get("allow_remaining_funds_redistribution"), + ), + "compliance_end": datetime.fromtimestamp( + event_data.get("compliance_end_ms") / 1000 + ) + if event_data.get("compliance_end_ms") + else None, + "compliance_period_ms": event_data.get("compliance_period_ms"), + "compliance_req_desc": event_data.get( + "compliance_req_desc", + event_data.get("compliance_requirement_description"), + ), + "cooldown_end": datetime.fromtimestamp( + event_data.get("cooldown_end_ms") / 1000 + ) + if event_data.get("cooldown_end_ms") + else None, + "cooldown_period_ms": event_data.get("cooldown_period_ms"), + "is_video_required": event_data.get("is_video_required") + or event_data.get("application_requires_video", False), + "referrer_fee_basis_points": event_data.get( + "referrer_fee_basis_points" + ), + "remaining_dist_address_id": remaining_dist_address, + "remaining_dist_at_ms": datetime.fromtimestamp( + event_data.get("remaining_dist_at_ms") / 1000 + ) + if event_data.get("remaining_dist_at_ms") + else None, + "remaining_dist_by_id": remaining_dist_by, + "remaining_dist_memo": event_data.get( + "remaining_dist_memo", + event_data.get("remaining_funds_redistribution_memo"), + ), + "round_complete": round_time_stamp, + "vault_total_deposits": event_data.get("vault_total_deposits"), + "minimum_deposit": event_data.get("minimum_deposit"), + "current_vault_balance": event_data.get("current_vault_balance"), + "deployed_at": timestamp, + }, ) - for admin_address in event_data.get('admins', []): - admin, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=admin_address) + for admin_address in event_data.get("admins", []): + admin, _ = Account.objects.get_or_create( + defaults={"chain": chain}, id=admin_address + ) round_obj.admins.add(admin) # Create contacts for the round - for contact in event_data.get('contacts', []): + for contact in event_data.get("contacts", []): contact_obj, created = ProjectContact.objects.update_or_create( - name=contact['name'], - value=contact['value'] + name=contact["name"], value=contact["value"] ) round_obj.contacts.add(contact_obj) @@ -1722,12 +1863,13 @@ def process_application_to_round(event_data, tx_hash): try: # Process application to Round round_id, application_data = event_data[0], event_data[1] - applicant_id = application_data.get('applicant_id') - status = PotApplicationStatus[application_data['status'].upper()] - submitted_at = datetime.fromtimestamp(application_data['submited_ms'] / 1000) + applicant_id = application_data.get("applicant_id") + status = PotApplicationStatus[application_data["status"].upper()] + submitted_at = datetime.fromtimestamp(application_data["submited_ms"] / 1000) updated_at = ( - datetime.fromtimestamp(application_data['updated_ms'] / 1000) - if application_data['updated_ms'] else None + datetime.fromtimestamp(application_data["updated_ms"] / 1000) + if application_data["updated_ms"] + else None ) round_obj = Round.objects.get(on_chain_id=round_id) @@ -1737,46 +1879,50 @@ def process_application_to_round(event_data, tx_hash): round=round_obj, applicant=applicant, defaults={ - 'message': application_data['applicant_note'], - 'status': status, - 'submitted_at': submitted_at, - 'updated_at': updated_at, - 'tx_hash': tx_hash - } + "message": application_data["applicant_note"], + "status": status, + "submitted_at": submitted_at, + "updated_at": updated_at, + "tx_hash": tx_hash, + }, + ) + logger.info( + f"Processed application for Round: {round_id}, Applicant: {applicant_id}" ) - logger.info(f"Processed application for Round: {round_id}, Applicant: {applicant_id}") return True except Exception as e: logger.error(f"Error processing rounds applications event: {str(e)}") return False - def create_round_application(event_data, tx_hash, chain_id="stellar"): try: logger.info(f"create_round_application: {event_data}, {tx_hash}, {chain_id}") if type(event_data) == list: round_id, application_data = event_data[0], event_data[1] else: - event_data = event_data['application'] + event_data = event_data["application"] round_id, application_data = event_data["round_id"], event_data chain = Chain.objects.get(name=chain_id) - applicant, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=application_data["applicant_id"]) + applicant, _ = Account.objects.get_or_create( + defaults={"chain": chain}, id=application_data["applicant_id"] + ) round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) if chain_id == "NEAR": - status = PotApplicationStatus[application_data['status'].upper()] + status = PotApplicationStatus[application_data["status"].upper()] else: - status = PotApplicationStatus[application_data['status'][0].upper()] + status = PotApplicationStatus[application_data["status"][0].upper()] logger.info(f"Creating application for round: {round_id}") appl_defaults = { "message": application_data["applicant_note"], - "submitted_at": datetime.fromtimestamp(application_data["submited_ms"] / 1000), + "submitted_at": datetime.fromtimestamp( + application_data["submited_ms"] / 1000 + ), "status": status, "tx_hash": tx_hash, } - PotApplication.objects.update_or_create( applicant=applicant, round=round_obj, @@ -1792,17 +1938,21 @@ def create_round_application(event_data, tx_hash, chain_id="stellar"): def process_rounds_deposit_event(event_data, tx_hash, chain_id="stellar"): try: - logger.info(f"process_rounds_deposit_event: {event_data}, {tx_hash}, {chain_id}") + logger.info( + f"process_rounds_deposit_event: {event_data}, {tx_hash}, {chain_id}" + ) # Process deposit event if type(event_data) == list: round_id, deposit_data = event_data else: - event_data = event_data['deposit'] + event_data = event_data["deposit"] round_id, deposit_data = event_data["round_id"], event_data chain = Chain.objects.get(name=chain_id) round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) amount = deposit_data["total_amount"] - depositor, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=deposit_data["depositor_id"]) + depositor, _ = Account.objects.get_or_create( + defaults={"chain": chain}, id=deposit_data["depositor_id"] + ) # Create or update a RoundDeposit object deposit, created = RoundDeposit.objects.update_or_create( @@ -1810,28 +1960,35 @@ def process_rounds_deposit_event(event_data, tx_hash, chain_id="stellar"): on_chain_id=deposit_data.get("deposit_id", deposit_data.get("id")), depositor=depositor, defaults={ - 'amount': amount, - 'protocol_fee': deposit_data["protocol_fee"], - 'referrer_fee': deposit_data["referrer_fee"], - 'memo': deposit_data["memo"], - 'tx_hash': tx_hash, - 'deposit_at': datetime.fromtimestamp(deposit_data["deposited_at"] / 1000), - } + "amount": amount, + "protocol_fee": deposit_data["protocol_fee"], + "referrer_fee": deposit_data["referrer_fee"], + "memo": deposit_data["memo"], + "tx_hash": tx_hash, + "deposit_at": datetime.fromtimestamp( + deposit_data["deposited_at"] / 1000 + ), + }, ) - round_obj.vault_total_deposits = str(int(round_obj.vault_total_deposits or 0) + int(amount)) - round_obj.current_vault_balance = str(int(round_obj.current_vault_balance or 0) + int(deposit_data["net_amount"])) + round_obj.vault_total_deposits = str( + int(round_obj.vault_total_deposits or 0) + int(amount) + ) + round_obj.current_vault_balance = str( + int(round_obj.current_vault_balance or 0) + int(deposit_data["net_amount"]) + ) round_obj.save() round_obj.update_vault_usd_equivalent() - logger.info(f"Processed deposit for Round: {round_id}, Depositor: {depositor.id}, Amount: {amount}") + logger.info( + f"Processed deposit for Round: {round_id}, Depositor: {depositor.id}, Amount: {amount}" + ) return True except Exception as e: logger.error(f"Error processing deposits to rounds: {str(e)}") return False - def create_round_payout(event_data, tx_hash, chain_id="stellar"): try: logger.info(f"create_round_payout: {event_data}, {tx_hash}, {chain_id}") @@ -1841,10 +1998,10 @@ def create_round_payout(event_data, tx_hash, chain_id="stellar"): memo = payout_data.get("memo") chain = Chain.objects.get(name=chain_id) - token_acct, _ = Account.objects.get_or_create(defaults={"chain":chain},id=chain_id.lower()) - token, _ = Token.objects.get_or_create( - account=token_acct + token_acct, _ = Account.objects.get_or_create( + defaults={"chain": chain}, id=chain_id.lower() ) + token, _ = Token.objects.get_or_create(account=token_acct) round_obj = Round.objects.get(on_chain_id=round_id, chain=chain) payout = PotPayout( @@ -1858,7 +2015,9 @@ def create_round_payout(event_data, tx_hash, chain_id="stellar"): tx_hash=tx_hash, ) payout.save() - logger.info(f"Created payout for round {round_id} to {recipient_id} for amount {amount}, on chain {chain_id}") + logger.info( + f"Created payout for round {round_id} to {recipient_id} for amount {amount}, on chain {chain_id}" + ) return True except Exception as e: logger.error(f"Error creating round payout: {str(e)}") @@ -1873,7 +2032,9 @@ def update_round_payout(event_data, tx_hash, chain_id="stellar"): amount = payout_data["amount"] paid_at_ms = payout_data.get("paid_at_ms") chain = Chain.objects.get(name=chain_id) - recipient_id, _ = Account.objects.get_or_create(defaults={"chain":chain}, id=payout_data["recipient_id"]) + recipient_id, _ = Account.objects.get_or_create( + defaults={"chain": chain}, id=payout_data["recipient_id"] + ) memo = payout_data.get("memo") payout = PotPayout.objects.get(on_chain_id=payout_on_chain_id) payout.amount = amount @@ -1882,24 +2043,23 @@ def update_round_payout(event_data, tx_hash, chain_id="stellar"): payout.tx_hash = tx_hash payout.memo = memo payout.save() - logger.info(f"Updated payout {payout_on_chain_id} for recipient {recipient_id} with amount {amount}.") + logger.info( + f"Updated payout {payout_on_chain_id} for recipient {recipient_id} with amount {amount}." + ) return True except Exception as e: logger.error(f"Error updating Payout. {str(e)}") return False - - def handle_stellar_list(data, contract_id, timestamp, chain_id="stellar"): # receipt = block.receipts().filter(receiptId=receiptId)[0] try: logger.info("upserting involveed accts...") - owner_address = data.get('owner') + owner_address = data.get("owner") chain = Chain.objects.get(name=chain_id) - Account.objects.get_or_create(defaults={"chain":chain},id=owner_address) - + Account.objects.get_or_create(defaults={"chain": chain}, id=owner_address) logger.info(f"creating list..... {data}") @@ -1918,7 +2078,8 @@ def handle_stellar_list(data, contract_id, timestamp, chain_id="stellar"): if data.get("admins"): for admin_id in data["admins"]: - admin_object, _ = Account.objects.get_or_create(defaults={"chain":chain}, + admin_object, _ = Account.objects.get_or_create( + defaults={"chain": chain}, id=admin_id, ) listObject.admins.add(admin_object) @@ -1938,7 +2099,7 @@ def handle_stellar_list_update(data, contract_id, timestamp, chain_id="stellar") default_registration_status=data["default_registration_status"][0], name=data["name"], description=data["description"], - cover_image_url=data["cover_image_url"], + cover_image_url=data["cover_img_url"], admin_only_registrations=data["admin_only_registrations"], created_at=datetime.fromtimestamp(data["created_at"] / 1000), updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), @@ -1949,13 +2110,17 @@ def handle_stellar_list_update(data, contract_id, timestamp, chain_id="stellar") return False -def handle_new_stellar_list_registration(data, contract_id, tx_hash, chain_id="stellar"): +def handle_new_stellar_list_registration( + data, contract_id, tx_hash, chain_id="stellar" +): logger.info(f"new Project data: {data}") # Prepare data for insertion chain = Chain.objects.get(name=chain_id) parent_list = List.objects.get(on_chain_id=data["list_id"]) try: - project = Account.objects.get_or_create({"chain":chain, "id": data["registrant_id"]}) + project = Account.objects.get_or_create( + {"chain": chain, "id": data["registrant_id"]} + ) except Exception as e: logger.error(f"Encountered error trying to get create acct: {e}") @@ -2001,11 +2166,11 @@ def handle_stellar_list_admin_ops(data, contract_id, timestamp, tx_hash): try: round_id, admins = data[0], data[1] logger.info(f"updating admins: {admins} for round {round_id}") - round_obj = Round.objects.get(on_chain_id=round_id) # select related? + round_obj = Round.objects.get(on_chain_id=round_id) # select related? chain = Chain.objects.get(name="stellar") for acct in admins: - admin, _ = Account.objects.get_or_create(defaults={"chain":chain},id=acct) + admin, _ = Account.objects.get_or_create(defaults={"chain": chain}, id=acct) contains = round_obj.admins.acontains(admin) if not contains: round_obj.admins.add(admin) @@ -2028,8 +2193,10 @@ def handle_stellar_list_admin_ops(data, contract_id, timestamp, tx_hash): logger.error(f"Failed to remove list admin, Error: {e}") return False + # Campaign Event Indexing Methods + async def handle_new_campaign(data: dict, created_at): """ Index a new campaign creation event. @@ -2114,7 +2281,6 @@ async def handle_new_campaign(data: dict, created_at): logger.error(f"Failed to index new campaign: {e}") - async def handle_update_campaign(data: dict): """ Index a campaign update event. @@ -2149,19 +2315,23 @@ async def handle_update_campaign(data: dict): "description": data["description"], "cover_image_url": data["cover_image_url"], "start_at": datetime.fromtimestamp(data["start_ms"] / 1000), - "end_at": datetime.fromtimestamp(data["end_ms"] / 1000) if data["end_ms"] else None, + "end_at": datetime.fromtimestamp(data["end_ms"] / 1000) + if data["end_ms"] + else None, "token": token, "target_amount": data["target_amount"], "min_amount": data["min_amount"], "max_amount": data["max_amount"], "allow_fee_avoidance": data["allow_fee_avoidance"], - } + }, ) # Fetch updated USD prices # await campaign.fetch_usd_prices_async() - logger.info(f"Successfully updated campaign: {campaign.on_chain_id}, or created? {created}") + logger.info( + f"Successfully updated campaign: {campaign.on_chain_id}, or created? {created}" + ) except Campaign.DoesNotExist: logger.error(f"Campaign {data['id']} not found for update") @@ -2178,7 +2348,9 @@ async def handle_delete_campaign(campaign_id: int): try: logger.info(f"Deleting campaign: {campaign_id}") - deleted_count, _ = await Campaign.objects.filter(on_chain_id=campaign_id).adelete() + deleted_count, _ = await Campaign.objects.filter( + on_chain_id=campaign_id + ).adelete() if deleted_count > 0: logger.info(f"Successfully deleted campaign: {campaign_id}") @@ -2189,7 +2361,6 @@ async def handle_delete_campaign(campaign_id: int): logger.error(f"Failed to delete campaign {campaign_id}: {e}") - async def handle_campaign_donation(data: dict, receipt_id): """ Index a campaign donation event. @@ -2253,7 +2424,7 @@ async def handle_campaign_donation(data: dict, receipt_id): on_chain_id=data["id"], campaign=campaign, donor=donor, - defaults=donation_defaults + defaults=donation_defaults, ) logger.info(f"before respective: {donation, created}") @@ -2271,11 +2442,17 @@ async def handle_campaign_donation(data: dict, receipt_id): total_amount = int(data["total_amount"]) net_amount = int(data["net_amount"]) - campaign.total_raised_amount = str(int(campaign.total_raised_amount) + total_amount) - campaign.net_raised_amount = str(int(campaign.net_raised_amount) + net_amount) + campaign.total_raised_amount = str( + int(campaign.total_raised_amount) + total_amount + ) + campaign.net_raised_amount = str( + int(campaign.net_raised_amount) + net_amount + ) await campaign.asave() - logger.info(f"Updated campaign {campaign.on_chain_id} totals: +{total_amount} total, +{net_amount} net") + logger.info( + f"Updated campaign {campaign.on_chain_id} totals: +{total_amount} total, +{net_amount} net" + ) except (ValueError, TypeError) as e: logger.error(f"Failed to update campaign totals: {e}") @@ -2285,7 +2462,6 @@ async def handle_campaign_donation(data: dict, receipt_id): logger.error(f"Failed to index campaign donation: {e}") - async def handle_campaign_donation_refund(data: dict, refunded_at): """ Index a campaign donation refund event. @@ -2306,33 +2482,41 @@ async def handle_campaign_donation_refund(data: dict, refunded_at): escrow_balance = data.get("escrow_balance") updated_count = await CampaignDonation.objects.filter( - on_chain_id__in=donation_ids, - campaign__on_chain_id=campaign_id + on_chain_id__in=donation_ids, campaign__on_chain_id=campaign_id ).aupdate(returned_at=refunded_at) if updated_count > 0: - logger.info(f"Successfully marked {updated_count} donations as refunded: {donation_ids}") + logger.info( + f"Successfully marked {updated_count} donations as refunded: {donation_ids}" + ) else: logger.warning(f"No donations found for refund: {donation_ids}") # Update campaign escrow balance and totals try: campaign = await Campaign.objects.aget(on_chain_id=campaign_id) - campaign.escrow_balance = str(int(campaign.escrow_balance) - int(escrow_balance)) + campaign.escrow_balance = str( + int(campaign.escrow_balance) - int(escrow_balance) + ) refunded_donations = CampaignDonation.objects.filter( - on_chain_id__in=donation_ids, - campaign__on_chain_id=campaign_id - ).values_list('total_amount', 'net_amount') + on_chain_id__in=donation_ids, campaign__on_chain_id=campaign_id + ).values_list("total_amount", "net_amount") total_refunded = sum(int(donation[0]) for donation in refunded_donations) net_refunded = sum(int(donation[1]) for donation in refunded_donations) - campaign.total_raised_amount = str(int(campaign.total_raised_amount) - total_refunded) - campaign.net_raised_amount = str(int(campaign.net_raised_amount) - net_refunded) + campaign.total_raised_amount = str( + int(campaign.total_raised_amount) - total_refunded + ) + campaign.net_raised_amount = str( + int(campaign.net_raised_amount) - net_refunded + ) await campaign.asave() - logger.info(f"Updated campaign {campaign_id}: -{total_refunded} total, -{net_refunded} net, escrow={campaign.escrow_balance}") + logger.info( + f"Updated campaign {campaign_id}: -{total_refunded} total, -{net_refunded} net, escrow={campaign.escrow_balance}" + ) except Campaign.DoesNotExist: logger.error(f"Campaign {campaign_id} not found for escrow balance update") except (ValueError, TypeError) as e: @@ -2354,7 +2538,9 @@ async def handle_campaign_donation_unescrowed(data: dict): """ try: - logger.info(f"Indexing campaign donation unescrow(release to recipient): {data}") + logger.info( + f"Indexing campaign donation unescrow(release to recipient): {data}" + ) donation_ids = data.get("donation_ids") updated_count = await CampaignDonation.objects.filter( @@ -2362,10 +2548,14 @@ async def handle_campaign_donation_unescrowed(data: dict): ).aupdate(escrowed=False) if updated_count > 0: - logger.info(f"Successfully marked donation {data['donation_ids']} as unescrowed") + logger.info( + f"Successfully marked donation {data['donation_ids']} as unescrowed" + ) else: - logger.warning(f"Donation {data['donation_ids']} not found to be unescrowed") + logger.warning( + f"Donation {data['donation_ids']} not found to be unescrowed" + ) except Exception as e: logger.error(f"Failed to index campaign donation unescrow: {e}") From 707b33fc692015b775572fe995e553b30ac59935 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 10 Oct 2025 23:18:17 +0100 Subject: [PATCH 406/454] make video url optional --- .gitignore | 2 + .../0010_alter_project_video_url.py | 18 ++ grantpicks/models.py | 62 +++--- indexer_app/tasks.py | 183 +++++++++++------- 4 files changed, 163 insertions(+), 102 deletions(-) create mode 100644 grantpicks/migrations/0010_alter_project_video_url.py diff --git a/.gitignore b/.gitignore index bef67d0..a89fe62 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,5 @@ dmypy.json # static /static/ + +.DS_Store diff --git a/grantpicks/migrations/0010_alter_project_video_url.py b/grantpicks/migrations/0010_alter_project_video_url.py new file mode 100644 index 0000000..1954872 --- /dev/null +++ b/grantpicks/migrations/0010_alter_project_video_url.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2025-10-10 22:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("grantpicks", "0009_round_minimum_deposit"), + ] + + operations = [ + migrations.AlterField( + model_name="project", + name="video_url", + field=models.URLField(blank=True, null=True), + ), + ] diff --git a/grantpicks/models.py b/grantpicks/models.py index 0d76a8d..f110a3e 100644 --- a/grantpicks/models.py +++ b/grantpicks/models.py @@ -26,11 +26,13 @@ class ProjectContact(models.Model): name = models.CharField(max_length=255) value = models.CharField(max_length=255) + class ProjectContract(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=255) contract_address = models.CharField(max_length=255) + class ProjectRepository(models.Model): id = models.AutoField(primary_key=True) label = models.CharField(max_length=255) @@ -44,21 +46,23 @@ class ProjectFundingHistory(models.Model): denomination = models.CharField(max_length=255) description = models.TextField() timestamp = models.DateTimeField(auto_now_add=True) - + class Project(models.Model): id = models.AutoField(primary_key=True) - on_chain_id =models.IntegerField( + on_chain_id = models.IntegerField( _("contract project id"), null=False, unique=True, help_text=_("Project id in contract"), ) image_url = models.URLField(max_length=200) - video_url = models.URLField(max_length=200) + video_url = models.URLField(max_length=200, null=True, blank=True) name = models.CharField(max_length=255) overview = models.TextField() - owner = models.ForeignKey(Account, related_name='owned_projects', on_delete=models.CASCADE) + owner = models.ForeignKey( + Account, related_name="owned_projects", on_delete=models.CASCADE + ) contacts = models.ManyToManyField( ProjectContact, related_name="contact_lists", @@ -89,8 +93,6 @@ class Project(models.Model): ) - - class Round(models.Model): id = models.AutoField( _("round id"), @@ -155,7 +157,7 @@ class Round(models.Model): null=False, help_text=_("Expected amount."), ) - + base_currency = models.CharField( _("base currency"), max_length=64, @@ -345,10 +347,7 @@ class Round(models.Model): ) class Meta: - unique_together = ('chain', 'on_chain_id') - - - + unique_together = ("chain", "on_chain_id") def update_vault_usd_equivalent(self): # first, see if there is a TokenHistoricalPrice within 1 day (or HISTORICAL_PRICE_QUERY_HOURS) of self.paid_at @@ -360,16 +359,17 @@ def update_vault_usd_equivalent(self): f"No USD price found for token {token.symbol} at {datetime.now()}" ) return - self.vault_total_deposits_usd = token.format_price(self.vault_total_deposits) * price_usd - self.current_vault_balance_usd = token.format_price(self.current_vault_balance) * price_usd - self.save() - logger.info( - f"Saved USD prices for round vault for round id: {self.id}" + self.vault_total_deposits_usd = ( + token.format_price(self.vault_total_deposits) * price_usd ) + self.current_vault_balance_usd = ( + token.format_price(self.current_vault_balance) * price_usd + ) + self.save() + logger.info(f"Saved USD prices for round vault for round id: {self.id}") except Exception as e: logger.error(f"Failed to calculate and stellar vault USD prices: {e}") - - + def save(self, *args, **kwargs): if self._state.adding: # If the account is being created (not updated) if not self.chain_id: @@ -456,11 +456,12 @@ class RoundDeposit(models.Model): ) class Meta: - unique_together = ('round', 'on_chain_id') + unique_together = ("round", "on_chain_id") + class Vote(models.Model): - round = models.ForeignKey(Round, on_delete=models.CASCADE, related_name='votes') - voter = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='votes') + round = models.ForeignKey(Round, on_delete=models.CASCADE, related_name="votes") + voter = models.ForeignKey(Account, on_delete=models.CASCADE, related_name="votes") tx_hash = models.CharField( _("transaction hash"), null=True, @@ -470,25 +471,24 @@ class Vote(models.Model): voted_at = models.DateTimeField() class Meta: - unique_together = ('round', 'voter', 'voted_at') - + unique_together = ("round", "voter", "voted_at") class VotePair(models.Model): - vote = models.ForeignKey(Vote, on_delete=models.CASCADE, related_name='pairs') + vote = models.ForeignKey(Vote, on_delete=models.CASCADE, related_name="pairs") pair_id = models.PositiveIntegerField() - projects = models.ManyToManyField(Account, related_name='vote_pairs_included_in') + projects = models.ManyToManyField(Account, related_name="vote_pairs_included_in") voted_project = models.ForeignKey( Account, - on_delete=models.CASCADE, - related_name='vote_pairs_voted_for_in', + on_delete=models.CASCADE, + related_name="vote_pairs_voted_for_in", null=True, - blank=True + blank=True, ) - #old_project = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='vote_pairs') + # old_project = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='vote_pairs') class Meta: - unique_together = ('vote', 'pair_id') + unique_together = ("vote", "pair_id") class StellarEvent(models.Model): @@ -504,4 +504,4 @@ class StellarEvent(models.Model): blank=True, help_text=_("Transaction hash."), ) - processed = models.BooleanField(default=False) \ No newline at end of file + processed = models.BooleanField(default=False) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 064565c..900164a 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -26,11 +26,24 @@ from .logging import logger from .utils import ( - create_or_update_round, create_round_application, create_round_payout, - get_block_height, get_ledger_sequence, handle_stellar_list_admin_ops, handle_stellar_list_update, process_application_to_round, - process_project_event, process_rounds_deposit_event, process_vote_event, - save_block_height, update_application, update_approved_projects, - update_ledger_sequence, update_round_payout, handle_stellar_list + create_or_update_round, + create_round_application, + create_round_payout, + get_block_height, + get_ledger_sequence, + handle_stellar_list_admin_ops, + handle_stellar_list_update, + process_application_to_round, + process_project_event, + process_rounds_deposit_event, + process_vote_event, + save_block_height, + update_application, + update_approved_projects, + update_ledger_sequence, + update_round_payout, + handle_stellar_list, + handle_new_stellar_list_registration, ) CURRENT_BLOCK_HEIGHT_KEY = "current_block_height" @@ -44,12 +57,10 @@ async def indexer(from_block: int, to_block: int): logger.info(f"from block: {from_block}") lake_config = LakeConfig( - Network.TESTNET - if settings.ENVIRONMENT == "testnet" - else Network.MAINNET, + Network.TESTNET if settings.ENVIRONMENT == "testnet" else Network.MAINNET, settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY, - from_block + from_block, ) _, streamer_messages_queue = streamer(lake_config) @@ -58,7 +69,9 @@ async def indexer(from_block: int, to_block: int): # Log time before fetching a new block fetch_start_time = time.time() # streamer_message is the current block - streamer_message = await asyncio.wait_for(streamer_messages_queue.get(), settings.INDEXER_STREAMER_WAIT_TIME) + streamer_message = await asyncio.wait_for( + streamer_messages_queue.get(), settings.INDEXER_STREAMER_WAIT_TIME + ) fetch_end_time = time.time() logger.info( f"Time to fetch new block: {fetch_end_time - fetch_start_time:.4f} seconds" @@ -92,9 +105,10 @@ async def indexer(from_block: int, to_block: int): f"Total time for one iteration: {iteration_end_time - fetch_start_time:.4f} seconds" ) - except asyncio.TimeoutError: - logger.warning("Stream stalled: no new blocks within timeout, restarting...") # raise Exception so sytemd can restart the worker + logger.warning( + "Stream stalled: no new blocks within timeout, restarting..." + ) # raise Exception so sytemd can restart the worker raise Exception("Stream stalled: restarting...") except Exception as e: @@ -111,7 +125,7 @@ def listen_to_near_events(): # Update below with desired network & block height start_block = get_block_height() # start_block = 112682360 - logger.info(f"what's the start block, pray tell? {start_block-1}") + logger.info(f"what's the start block, pray tell? {start_block - 1}") loop.run_until_complete(indexer(start_block - 1, None)) except WorkerLostError: pass # don't log to Sentry @@ -126,7 +140,7 @@ def spot_index_near_events(start_block): asyncio.set_event_loop(loop) try: - logger.info(f"Spot index start block: {start_block-1}") + logger.info(f"Spot index start block: {start_block - 1}") loop.run_until_complete(indexer(start_block - 1, None)) except WorkerLostError: pass # don't log to Sentry @@ -282,7 +296,6 @@ def update_pot_statistics(): @shared_task def update_account_statistics(): - accounts = Account.objects.all() accounts_count = accounts.count() jobs_logger.info(f"Updating statistics for {accounts_count} accounts...") @@ -334,40 +347,43 @@ def update_account_statistics(): ) jobs_logger.info(f"Account stats for {accounts.count()} accounts updated.") + def address_to_string(obj): if isinstance(obj, Address): return obj.address raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable") - # Todo: Change model so thatthe event indexer saves the event and queues a task to immediately process the event, # so we don;t have a separate beat that's looping through @shared_task def stellar_event_indexer(): - server = stellar_sdk.SorobanServer( - settings.STELLAR_RPC_URL - ) - contract_ids = [settings.STELLAR_CONTRACT_ID, settings.STELLAR_PROJECTS_REGISTRY_CONTRACT, settings.STELLAR_LIST_CONTRACT] - if contract_ids == ['', '', '']: + server = stellar_sdk.SorobanServer(settings.STELLAR_RPC_URL) + contract_ids = [ + settings.STELLAR_CONTRACT_ID, + settings.STELLAR_PROJECTS_REGISTRY_CONTRACT, + settings.STELLAR_LIST_CONTRACT, + ] + if contract_ids == ["", "", ""]: return start_sequence = get_ledger_sequence() # start_sequence = 668843 if not start_sequence: start_sequence = 58655649 - jobs_logger.info(f"Ingesting Stellar events from ledger {start_sequence}... contracts: {contract_ids}") + jobs_logger.info( + f"Ingesting Stellar events from ledger {start_sequence}... contracts: {contract_ids}" + ) try: # Fetch events for the current sequence events = server.get_events( start_ledger=start_sequence, filters=[ EventFilter( - event_type=EventFilterType.CONTRACT, - contract_ids=contract_ids - ) - ] + event_type=EventFilterType.CONTRACT, contract_ids=contract_ids + ) + ], ) stellar_events = [] ledger_timestamp = datetime.now() @@ -376,93 +392,118 @@ def stellar_event_indexer(): event_value = event.value if event.value is not None: event_value = stellar_sdk.scval.to_native(event.value) - event_value = json.loads(json.dumps(event_value, default=address_to_string)) - stellar_events.append(StellarEvent( - ledger_sequence=event.ledger, - event_type=event_name, - contract_id=event.contract_id, - ingested_at=event.ledger_close_at, - transaction_hash=event.transaction_hash, - data=event_value - )) + event_value = json.loads( + json.dumps(event_value, default=address_to_string) + ) + stellar_events.append( + StellarEvent( + ledger_sequence=event.ledger, + event_type=event_name, + contract_id=event.contract_id, + ingested_at=event.ledger_close_at, + transaction_hash=event.transaction_hash, + data=event_value, + ) + ) if len(stellar_events) > 0: - StellarEvent.objects.bulk_create( - objs=stellar_events, - ignore_conflicts=True - ) + StellarEvent.objects.bulk_create(objs=stellar_events, ignore_conflicts=True) ledger_timestamp = event.ledger_close_at - jobs_logger.info(f"Ingested {len(stellar_events)} Stellar events from ledger {start_sequence} to {events.latest_ledger}...") + jobs_logger.info( + f"Ingested {len(stellar_events)} Stellar events from ledger {start_sequence} to {events.latest_ledger}..." + ) update_ledger_sequence(events.latest_ledger, ledger_timestamp) except Exception as e: jobs_logger.error(f"Error processing ledger {start_sequence}: {e}") - @shared_task def process_stellar_events(): - unprocessed_events = StellarEvent.objects.filter(processed=False).order_by('id') - jobs_logger.info(f"Processing {unprocessed_events.count()} unprocessed Stellar events...") + unprocessed_events = StellarEvent.objects.filter(processed=False).order_by("id") + jobs_logger.info( + f"Processing {unprocessed_events.count()} unprocessed Stellar events..." + ) for event in unprocessed_events: try: event_data = event.data event_name = event.event_type - if event_name == 'c_project': + if event_name == "c_project": event.processed = process_project_event(event_data) - elif event_name == 'c_round' or event_name == 'u_round': - + elif event_name == "c_round" or event_name == "u_round": # Mark event as processed - event.processed = create_or_update_round(event_data, event.contract_id, event.ingested_at) - - elif event_name == 'apply_to_round': + event.processed = create_or_update_round( + event_data, event.contract_id, event.ingested_at + ) + elif event_name == "apply_to_round": # Mark event as processed - event.processed = process_application_to_round(event_data, event.transaction_hash) - - elif event_name == 'c_app': + event.processed = process_application_to_round( + event_data, event.transaction_hash + ) - event.processed = create_round_application(event_data, event.transaction_hash) + elif event_name == "c_app": + event.processed = create_round_application( + event_data, event.transaction_hash + ) - elif event_name == 'u_app': # application review and aproval + elif event_name == "u_app": # application review and aproval event.processed = update_application(event_data, event.transaction_hash) - elif event_name == 'u_ap': - event.processed = update_approved_projects(event_data, time_stamp=event.ingested_at, tx_hash=event.transaction_hash) - - elif event_name == 'c_depo': - - event.processed = process_rounds_deposit_event(event_data, event.transaction_hash) + elif event_name == "u_ap": + event.processed = update_approved_projects( + event_data, + time_stamp=event.ingested_at, + tx_hash=event.transaction_hash, + ) - elif event_name == 'c_vote': + elif event_name == "c_depo": + event.processed = process_rounds_deposit_event( + event_data, event.transaction_hash + ) + elif event_name == "c_vote": event.processed = process_vote_event(event_data, event.transaction_hash) elif event_name == "c_pay": - event.processed = create_round_payout(event_data, event.transaction_hash) + event.processed = create_round_payout( + event_data, event.transaction_hash + ) elif event_name == "u_pay": - - event.processed = update_round_payout(event_data, event.transaction_hash) + event.processed = update_round_payout( + event_data, event.transaction_hash + ) elif event_name == "c_list": - event.processed = handle_stellar_list(event_data, event.contract_id, event.ingested_at) + event.processed = handle_stellar_list( + event_data, event.contract_id, event.ingested_at + ) elif event_name == "u_list": - event.processed = handle_stellar_list_update(event_data, event.contract_id, event.ingested_at) + event.processed = handle_stellar_list_update( + event_data, event.contract_id, event.ingested_at + ) elif event_name == "c_reg": - event.processed = handle_stellar_list(event_data, event.contract_id, event.transaction_hash) + event.processed = handle_new_stellar_list_registration( + event_data, event.contract_id, event.transaction_hash + ) elif event_name == "u_adm": - event.processed = handle_stellar_list_admin_ops(event_data, event.contract_id, event.ingested_at, event.transaction_hash) + event.processed = handle_stellar_list_admin_ops( + event_data, + event.contract_id, + event.ingested_at, + event.transaction_hash, + ) event.save() except Exception as e: - jobs_logger.error(f"Error processing Stellar event { event_name, event.id}: {e}") + jobs_logger.error( + f"Error processing Stellar event {event_name, event.id}: {e}" + ) jobs_logger.info(f"Finished processing Stellar events.") - - @task_revoked.connect def on_task_revoked(request, terminated, signum, expired, **kwargs): logger.info( From 27cc9129a93d76aae8432174aa2cee5b89d35560 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sat, 11 Oct 2025 01:45:57 +0100 Subject: [PATCH 407/454] list registration --- indexer_app/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 5568d61..838d199 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -2113,8 +2113,9 @@ def handle_stellar_list_update(data, contract_id, timestamp, chain_id="stellar") def handle_new_stellar_list_registration( data, contract_id, tx_hash, chain_id="stellar" ): - logger.info(f"new Project data: {data}") + logger.info(f"new list reg data: {data}") # Prepare data for insertion + data = data[2] chain = Chain.objects.get(name=chain_id) parent_list = List.objects.get(on_chain_id=data["list_id"]) try: From f2684cfedaa3c1309dfd627cd7e7f15a22f4d626 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sat, 11 Oct 2025 11:33:38 +0100 Subject: [PATCH 408/454] filter with chain name --- indexer_app/utils.py | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 838d199..fb75bbf 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -454,7 +454,9 @@ async def handle_list_update( logger.info(f"updating list from result..... {data}") - listObject = await List.objects.filter(on_chain_id=data["id"]).aupdate( + listObject = await List.objects.filter( + on_chain_id=data["id"], chain__name="NEAR" + ).aupdate( owner_id=data["owner"], default_registration_status=data["default_registration_status"], name=data["name"], @@ -478,7 +480,9 @@ async def handle_list_update( async def handle_delete_list(data: dict): try: logger.info(f"deleting list..... {data}") - lst = await List.objects.filter(on_chain_id=data["list_id"]).adelete() + lst = await List.objects.filter( + on_chain_id=data["list_id"], chain__name="NEAR" + ).adelete() except Exception as e: logger.error(f"Failed to delete, Error: {e}") @@ -507,7 +511,9 @@ async def handle_new_list_registration( # Prepare data for insertion project_list = [] insert_data = [] - parent_list = await List.objects.aget(on_chain_id=reg_data[0]["list_id"]) + parent_list = await List.objects.aget( + on_chain_id=reg_data[0]["list_id"], chain__name="NEAR" + ) for dt in reg_data: logger.info(f"dt: {dt}") project_list.append({"chain_id": 1, "id": dt["registrant_id"]}) @@ -566,7 +572,9 @@ async def handle_list_registration_removal( logger.info(f"list reg removal: {data}, {receiver_id}") try: - list_obj = await List.objects.aget(on_chain_id=data["list_id"]) + list_obj = await List.objects.aget( + on_chain_id=data["list_id"], chain__name="NEAR" + ) await list_obj.registrations.filter(id=data["registration_id"]).adelete() except Exception as e: @@ -756,7 +764,7 @@ async def handle_default_list_status_change( if result_data.get("cover_image_url"): list_update["cover_image_url"] = result_data["cover_image_url"] - await List.objects.filter(id=list_id).aupdate(**list_update) + await List.objects.filter(id=list_id, chain__name="NEAR").aupdate(**list_update) logger.info("List updated successfully.") except Exception as e: @@ -776,7 +784,9 @@ async def handle_list_upvote( up_default = {"created_at": created_at} - list_obj = await List.objects.aget(on_chain_id=data.get("list_id")) + list_obj = await List.objects.aget( + on_chain_id=data.get("list_id"), chain__name="NEAR" + ) await ListUpvote.objects.aupdate_or_create( list=list_obj, account_id=signer_id, defaults=up_default @@ -803,7 +813,9 @@ async def handle_list_upvote( async def handle_remove_upvote(data: dict, receiver_id: str, signer_id: str): try: logger.info(f"remove upvote from list: {data}, {receiver_id}") - list_obj = await List.objects.aget(on_chain_id=data.get("list_id")) + list_obj = await List.objects.aget( + on_chain_id=data.get("list_id"), chain__name="NEAR" + ) await ListUpvote.objects.filter(list=list_obj, account_id=signer_id).adelete() logger.info(f"Upvote removed successfully") @@ -934,7 +946,9 @@ async def handle_payout_challenge_response( async def handle_list_admin_ops(data, receiver_id, signer_id, receiptId): try: logger.info(f"updating admin...: {data}, {receiver_id}") - list_obj = await List.objects.aget(on_chain_id=data["list_id"]) + list_obj = await List.objects.aget( + on_chain_id=data["list_id"], chain__name="NEAR" + ) for acct in data["admins"]: admin, _ = await Account.objects.aget_or_create( @@ -964,7 +978,7 @@ async def handle_list_admin_ops(data, receiver_id, signer_id, receiptId): async def handle_list_owner_change(data): try: logger.info(f"changing owner... ...: {data}") - await List.objects.filter(id=data["list_id"]).aupdate( + await List.objects.filter(id=data["list_id"], chain__name="NEAR").aupdate( **{"owner": data["new_owner_id"]} ) @@ -2094,7 +2108,9 @@ def handle_stellar_list_update(data, contract_id, timestamp, chain_id="stellar") try: logger.info(f"updating list from result..... {data}") - listObject = List.objects.filter(on_chain_id=data["id"]).update( + listObject = List.objects.filter( + on_chain_id=data["id"], chain__name=chain_id + ).update( owner_id=data["owner"], default_registration_status=data["default_registration_status"][0], name=data["name"], @@ -2117,7 +2133,7 @@ def handle_new_stellar_list_registration( # Prepare data for insertion data = data[2] chain = Chain.objects.get(name=chain_id) - parent_list = List.objects.get(on_chain_id=data["list_id"]) + parent_list = List.objects.get(on_chain_id=data["list_id"], chain=chain) try: project = Account.objects.get_or_create( {"chain": chain, "id": data["registrant_id"]} From 18e01d2fcec2f3e6abe051b1c2339fd881f0abb3 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 13 Oct 2025 15:23:34 +0100 Subject: [PATCH 409/454] fix list update on stellar --- indexer_app/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index fb75bbf..1988915 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -2117,8 +2117,8 @@ def handle_stellar_list_update(data, contract_id, timestamp, chain_id="stellar") description=data["description"], cover_image_url=data["cover_img_url"], admin_only_registrations=data["admin_only_registrations"], - created_at=datetime.fromtimestamp(data["created_at"] / 1000), - updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), + created_at=datetime.fromtimestamp(data["created_ms"] / 1000), + updated_at=datetime.fromtimestamp(data["updated_ms"] / 1000), ) return True except Exception as e: From 7f60e0f9957ca225f42cd65c9ba491e28b46c5ee Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 14 Oct 2025 09:36:14 +0100 Subject: [PATCH 410/454] extend cors --- base/settings.py | 54 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/base/settings.py b/base/settings.py index 5ef90b6..1259f25 100644 --- a/base/settings.py +++ b/base/settings.py @@ -28,7 +28,10 @@ # SECURITY WARNING: keep the secret key used in production secret! # TODO: update before prod release -SECRET_KEY = os.environ.get("PL_DJANGO_SECRET_KEY", "django-insecure-=r_v_es6w6rxv42^#kc2hca6p%=fe_*cog_5!t%19zea!enlju") +SECRET_KEY = os.environ.get( + "PL_DJANGO_SECRET_KEY", + "django-insecure-=r_v_es6w6rxv42^#kc2hca6p%=fe_*cog_5!t%19zea!enlju", +) ALLOWED_HOSTS = [ "ec2-100-27-57-47.compute-1.amazonaws.com", @@ -36,7 +39,7 @@ "127.0.0.1", "dev.potlock.io", "test-dev.potlock.io", - "api.potlock.io" + "api.potlock.io", # "alpha.potlock.io", ] @@ -62,29 +65,45 @@ RECLAIM_APP_ID = os.environ.get("PL_RECLAIM_APP_ID") RECLAIM_APP_SECRET = os.environ.get("PL_RECLAIM_APP_SECRET") RECLAIM_TWITTER_PROVIDER_ID = os.environ.get("PL_RECLAIM_TWITTER_PROVIDER_ID") -INDEXER_STREAMER_WAIT_TIME = os.environ.get("PL_INDEXER_STREAMER_WAIT_TIME", 300) # in seconds +INDEXER_STREAMER_WAIT_TIME = os.environ.get( + "PL_INDEXER_STREAMER_WAIT_TIME", 300 +) # in seconds # POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else "potlock.near" -POTLOCK_TLA = "potlock.testnet" if ENVIRONMENT == "testnet" else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") +POTLOCK_TLA = ( + "potlock.testnet" + if ENVIRONMENT == "testnet" + else ("staging.potlock.near" if ENVIRONMENT == "dev" else "potlock.near") +) # NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else "nadabot.near" -NADABOT_TLA = "nadabot.testnet" if ENVIRONMENT == "testnet" else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") +NADABOT_TLA = ( + "nadabot.testnet" + if ENVIRONMENT == "testnet" + else ("staging.nadabot.near" if ENVIRONMENT == "dev" else "nadabot.near") +) STELLAR_CONTRACT_ID = os.environ.get("PL_STELLAR_CONTRACT_ID", "") -STELLAR_PROJECTS_REGISTRY_CONTRACT = os.environ.get("PL_STELLAR_PROJECTS_REGISTRY_CONTRACT", "") +STELLAR_PROJECTS_REGISTRY_CONTRACT = os.environ.get( + "PL_STELLAR_PROJECTS_REGISTRY_CONTRACT", "" +) STELLAR_LIST_CONTRACT = os.environ.get("PL_STELLAR_LIST_CONTRACT", "") NEAR_SOCIAL_CONTRACT_ADDRESS = ( "v1.social08.testnet" if ENVIRONMENT == "testnet" else "social.near" ) -NEAR_GRANTPICKS_CONTRACT_ID = "v2.grantpicks.potlock.testnet" if ENVIRONMENT == "testnet" else ("" if ENVIRONMENT == "dev" else "") +NEAR_GRANTPICKS_CONTRACT_ID = ( + "v2.grantpicks.potlock.testnet" + if ENVIRONMENT == "testnet" + else ("" if ENVIRONMENT == "dev" else "") +) # TODO: split settigns file by enviroment if ENVIRONMENT == "testnet": - POTLOCK_PATTERN = r'\.potlock\.testnet$' - NADABOT_PATTERN = r'\.nadabot\.testnet$' + POTLOCK_PATTERN = r"\.potlock\.testnet$" + NADABOT_PATTERN = r"\.nadabot\.testnet$" elif ENVIRONMENT == "dev": - POTLOCK_PATTERN = r'\.staging\.potlock\.near$' - NADABOT_PATTERN = r'\.staging\.nadabot\.near$' + POTLOCK_PATTERN = r"\.staging\.potlock\.near$" + NADABOT_PATTERN = r"\.staging\.nadabot\.near$" else: # mainnet/prod - POTLOCK_PATTERN = r'(? Date: Tue, 14 Oct 2025 09:36:58 +0100 Subject: [PATCH 411/454] extend cors --- base/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/settings.py b/base/settings.py index 1259f25..0b928fd 100644 --- a/base/settings.py +++ b/base/settings.py @@ -237,6 +237,8 @@ "https://app.potlock.app", "https://app.potlock.org", "http://alpha.potlock.org", + "https://alpha.potlock.xyz", + "https://alpha.potlock.app", ] else: CORS_ALLOWED_ORIGINS = [ From 3f2ba377118fb19b82a77e02b0a6de655f1b02c3 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Wed, 22 Oct 2025 17:12:45 +0100 Subject: [PATCH 412/454] fix get or create --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 1988915..dde3122 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -2136,7 +2136,7 @@ def handle_new_stellar_list_registration( parent_list = List.objects.get(on_chain_id=data["list_id"], chain=chain) try: project = Account.objects.get_or_create( - {"chain": chain, "id": data["registrant_id"]} + defaults={"chain": chain}, id=data["registrant_id"] ) except Exception as e: logger.error(f"Encountered error trying to get create acct: {e}") From 8bcab7a5c7473dd0c043d197075fac2ff5706413 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Thu, 23 Oct 2025 00:48:56 +0100 Subject: [PATCH 413/454] add onchain id to list reg --- indexer_app/utils.py | 4 ++-- .../0010_listregistration_on_chain_id.py | 22 +++++++++++++++++++ lists/models.py | 7 ++++-- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 lists/migrations/0010_listregistration_on_chain_id.py diff --git a/indexer_app/utils.py b/indexer_app/utils.py index dde3122..f0c917e 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -519,7 +519,7 @@ async def handle_new_list_registration( project_list.append({"chain_id": 1, "id": dt["registrant_id"]}) insert_data.append( { - "id": dt["id"], + "on_chain_id": data["id"], "registrant_id": dt["registrant_id"], "list_id": parent_list.id, "status": dt["status"], @@ -2146,7 +2146,7 @@ def handle_new_stellar_list_registration( try: _ = ListRegistration.objects.create( **{ - "id": data["id"], + "on_chain_id": data["id"], "registrant_id": data["registrant_id"], "list_id": parent_list.id, "status": data["status"], diff --git a/lists/migrations/0010_listregistration_on_chain_id.py b/lists/migrations/0010_listregistration_on_chain_id.py new file mode 100644 index 0000000..cb68b7f --- /dev/null +++ b/lists/migrations/0010_listregistration_on_chain_id.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.6 on 2025-10-22 23:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("lists", "0009_alter_list_on_chain_id_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="listregistration", + name="on_chain_id", + field=models.IntegerField( + help_text="list registration id in contract", + null=True, + verbose_name="list registration id on chain", + ), + ), + ] diff --git a/lists/models.py b/lists/models.py index 639d978..fef79cc 100644 --- a/lists/models.py +++ b/lists/models.py @@ -13,8 +13,6 @@ class ListRegistrationStatus(models.TextChoices): BLACKLISTED = "Blacklisted", "Blacklisted" - - class List(models.Model): id = models.AutoField( _("list id"), @@ -136,6 +134,11 @@ class ListRegistration(models.Model): primary_key=True, help_text=_("Registration id."), ) + on_chain_id = models.IntegerField( + _("list registration id on chain"), + null=True, + help_text=_("list registration id in contract"), + ) list = models.ForeignKey( List, on_delete=models.CASCADE, From 9f9f2da810ff6b9afdb846b61073cbede1247f88 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Fri, 24 Oct 2025 02:47:52 +0100 Subject: [PATCH 414/454] replace public address --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index f0c917e..9a3548b 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1550,7 +1550,7 @@ def get_pair_projects(pair_id: int, round_id: int, chain_id: str) -> Dict: stellar_sdk.scval.to_uint128(round_id), stellar_sdk.scval.to_uint32(pair_id), ] - public_key = "GAMFYFI7TIAPMLSAWIECFZCN52TR3NUIO74YM7ECBCPM6J743KENH367" # TODO: move to settings + public_key = "GAA3KC7HAHPZ2OGSAV5WBOFCJ3NSSPHKCYZAEI36DQJP2EB2FCGKSEFB" # TODO: move to settings acct = server.load_account(public_key) pair_result = server.simulate_transaction( From 98e8df1983dd6f6d20d004a6da19afcad54d92c5 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sun, 26 Oct 2025 04:16:59 +0100 Subject: [PATCH 415/454] add list reg update --- indexer_app/tasks.py | 5 +++++ indexer_app/utils.py | 22 ++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 900164a..69fa656 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -44,6 +44,7 @@ update_round_payout, handle_stellar_list, handle_new_stellar_list_registration, + update_list_registrations, ) CURRENT_BLOCK_HEIGHT_KEY = "current_block_height" @@ -487,6 +488,10 @@ def process_stellar_events(): event.processed = handle_new_stellar_list_registration( event_data, event.contract_id, event.transaction_hash ) + elif event_name == "u_reg": + event.processed = update_list_registrations( + event_data, event.contract_id, event.transaction_hash + ) elif event_name == "u_adm": event.processed = handle_stellar_list_admin_ops( event_data, diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 9a3548b..8c6534a 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -2133,7 +2133,6 @@ def handle_new_stellar_list_registration( # Prepare data for insertion data = data[2] chain = Chain.objects.get(name=chain_id) - parent_list = List.objects.get(on_chain_id=data["list_id"], chain=chain) try: project = Account.objects.get_or_create( defaults={"chain": chain}, id=data["registrant_id"] @@ -2148,7 +2147,7 @@ def handle_new_stellar_list_registration( **{ "on_chain_id": data["id"], "registrant_id": data["registrant_id"], - "list_id": parent_list.id, + "list_id": data["list_id"], "status": data["status"], "submitted_at": datetime.fromtimestamp(data["submitted_ms"] / 1000), "updated_at": datetime.fromtimestamp(data["updated_ms"] / 1000), @@ -2179,6 +2178,25 @@ def handle_new_stellar_list_registration( return False +def update_list_registrations(data, contract_id, chain_id="stellar"): + data = data[2] + + regUpdate = { + "status": data["status"][0], + "admin_notes": data["admin_notes"], + "updated_at": datetime.fromtimestamp(data["updated_ms"] / 1000), + } + + try: + # Perform the update + list = List.objects.get(on_chain_id=data["list_id"], chain__name=chain_id) + ListRegistration.objects.filter(on_chain_id=data["id"], list=list).update( + **regUpdate + ) + except Exception as e: + logger.error(f"Encountered error trying to update ListRegistration: {e}") + + def handle_stellar_list_admin_ops(data, contract_id, timestamp, tx_hash): try: round_id, admins = data[0], data[1] From bc6a3499517c9a5869400d2c4c8e9276b355d27c Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sun, 26 Oct 2025 13:32:58 +0100 Subject: [PATCH 416/454] add model constraint --- indexer_app/utils.py | 4 ++++ ...istregistration_unique_on_chain_id_list.py | 20 +++++++++++++++++++ lists/models.py | 5 +++++ 3 files changed, 29 insertions(+) create mode 100644 lists/migrations/0011_listregistration_unique_on_chain_id_list.py diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 8c6534a..cab0f44 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -2187,6 +2187,10 @@ def update_list_registrations(data, contract_id, chain_id="stellar"): "updated_at": datetime.fromtimestamp(data["updated_ms"] / 1000), } + logger.info( + f"updating ListRegistration with data: {data} and updatdata: {regUpdate}" + ) + try: # Perform the update list = List.objects.get(on_chain_id=data["list_id"], chain__name=chain_id) diff --git a/lists/migrations/0011_listregistration_unique_on_chain_id_list.py b/lists/migrations/0011_listregistration_unique_on_chain_id_list.py new file mode 100644 index 0000000..f2da1bf --- /dev/null +++ b/lists/migrations/0011_listregistration_unique_on_chain_id_list.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.6 on 2025-10-26 12:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ("lists", "0010_listregistration_on_chain_id"), + ] + + operations = [ + migrations.AddConstraint( + model_name="listregistration", + constraint=models.UniqueConstraint( + fields=("on_chain_id", "list"), name="unique_on_chain_id_list" + ), + ), + ] diff --git a/lists/models.py b/lists/models.py index fef79cc..fa01893 100644 --- a/lists/models.py +++ b/lists/models.py @@ -203,3 +203,8 @@ class Meta: indexes = [models.Index(fields=["id", "status"], name="idx_list_id_status")] unique_together = (("list", "registrant"),) + constraints = [ + models.UniqueConstraint( + fields=["on_chain_id", "list"], name="unique_on_chain_id_list" + ) + ] From 23a1b0266966446844b0968d22d3df2115af5259 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sun, 26 Oct 2025 14:12:34 +0100 Subject: [PATCH 417/454] addd logging --- indexer_app/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index cab0f44..588768c 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -2191,14 +2191,20 @@ def update_list_registrations(data, contract_id, chain_id="stellar"): f"updating ListRegistration with data: {data} and updatdata: {regUpdate}" ) + logger.debug( + f"checkout list query: {List.objects.filter(on_chain_id=data['list_id'], chain__name=chain_id)}, chain: {chain_id}, data: {data['list_id']}" + ) + try: # Perform the update list = List.objects.get(on_chain_id=data["list_id"], chain__name=chain_id) ListRegistration.objects.filter(on_chain_id=data["id"], list=list).update( **regUpdate ) + return True except Exception as e: logger.error(f"Encountered error trying to update ListRegistration: {e}") + return False def handle_stellar_list_admin_ops(data, contract_id, timestamp, tx_hash): From 114a7cc4fc1e90a46dfe40554820fe736db620bd Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sun, 26 Oct 2025 15:09:22 +0100 Subject: [PATCH 418/454] addd logging --- indexer_app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 588768c..28e7da4 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -2191,7 +2191,7 @@ def update_list_registrations(data, contract_id, chain_id="stellar"): f"updating ListRegistration with data: {data} and updatdata: {regUpdate}" ) - logger.debug( + logger.info( f"checkout list query: {List.objects.filter(on_chain_id=data['list_id'], chain__name=chain_id)}, chain: {chain_id}, data: {data['list_id']}" ) From 0feae39fce70566c46cb33a0720eedb6196bb608 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Sun, 26 Oct 2025 17:25:13 +0100 Subject: [PATCH 419/454] fix list reg updte --- indexer_app/tasks.py | 2 +- indexer_app/utils.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 69fa656..dc6ac49 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -490,7 +490,7 @@ def process_stellar_events(): ) elif event_name == "u_reg": event.processed = update_list_registrations( - event_data, event.contract_id, event.transaction_hash + event_data, event.contract_id ) elif event_name == "u_adm": event.processed = handle_stellar_list_admin_ops( diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 28e7da4..265eb58 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -2191,10 +2191,6 @@ def update_list_registrations(data, contract_id, chain_id="stellar"): f"updating ListRegistration with data: {data} and updatdata: {regUpdate}" ) - logger.info( - f"checkout list query: {List.objects.filter(on_chain_id=data['list_id'], chain__name=chain_id)}, chain: {chain_id}, data: {data['list_id']}" - ) - try: # Perform the update list = List.objects.get(on_chain_id=data["list_id"], chain__name=chain_id) From 2fd8090d514d359f8fe7bd804275aae4a2482496 Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Mon, 3 Nov 2025 03:42:53 +0100 Subject: [PATCH 420/454] fix: remove caching --- indexer_app/utils.py | 13 +++++++------ lists/api.py | 4 +--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index 265eb58..f10a198 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1462,7 +1462,7 @@ def update_approved_projects( def update_application(event_data, txhash, reviewer_id=None, chain_id="stellar"): - if type(event_data) == list: + if isinstance(event_data, list): round_id, application_data, reviewer_id = ( event_data[0], event_data[1], @@ -1540,8 +1540,8 @@ def get_pair_projects(pair_id: int, round_id: int, chain_id: str) -> Dict: if chain_id == "stellar": server = stellar_sdk.SorobanServer( "https://soroban-testnet.stellar.org" - if settings.ENVIRONMENT == "testnet" or settings.ENVIRONMENT == "local" - else "https://horizon.stellar.org" + if settings.ENVIRONMENT == "testnet" + else "https://rpc.lightsail.network" ) contract_id = settings.STELLAR_CONTRACT_ID @@ -1550,7 +1550,8 @@ def get_pair_projects(pair_id: int, round_id: int, chain_id: str) -> Dict: stellar_sdk.scval.to_uint128(round_id), stellar_sdk.scval.to_uint32(pair_id), ] - public_key = "GAA3KC7HAHPZ2OGSAV5WBOFCJ3NSSPHKCYZAEI36DQJP2EB2FCGKSEFB" # TODO: move to settings + # TODO: move to settings + public_key = "GAA3KC7HAHPZ2OGSAV5WBOFCJ3NSSPHKCYZAEI36DQJP2EB2FCGKSEFB" acct = server.load_account(public_key) pair_result = server.simulate_transaction( @@ -1581,7 +1582,7 @@ def process_vote_event(event_data, tx_hash, chain_id="stellar"): try: logger.info(f"process_vote_event: {event_data}, {tx_hash}, {chain_id}") with transaction.atomic(): - if type(event_data) == list: + if isinstance(event_data, list): round_id, vote_data = event_data[0], event_data[1] else: # vote_event_data = event_data['vote'] @@ -2165,7 +2166,7 @@ def handle_new_stellar_list_registration( defaults = { "signer_id": data["registered_by"], "receiver_id": contract_id, - "timestamp": data["submitted_ms"], + "timestamp": datetime.fromtimestamp(data["submitted_ms"] / 1000), "tx_hash": tx_hash, } diff --git a/lists/api.py b/lists/api.py index 85bfa4b..b343b6f 100644 --- a/lists/api.py +++ b/lists/api.py @@ -74,7 +74,6 @@ class ListsListAPI(APIView, CustomSizePageNumberPagination): 500: OpenApiResponse(description="Internal server error"), }, ) - @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): lists = List.objects.all().select_related("owner").prefetch_related("admins", "upvotes").annotate(registrations_count=Count('registrations')) account_id = request.query_params.get("account") @@ -140,7 +139,7 @@ class ListDetailAPI(APIView): 500: OpenApiResponse(description="Internal server error"), }, ) - @method_decorator(cache_page(60 * 5)) + @method_decorator(cache_page(60 * 3)) def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") chain = request.query_params.get("chain") @@ -203,7 +202,6 @@ class ListRegistrationsAPI(APIView, CustomSizePageNumberPagination): 500: OpenApiResponse(description="Internal server error"), }, ) - @method_decorator(cache_page(60 * 1)) def get(self, request: Request, *args, **kwargs): list_id = kwargs.get("list_id") chain = request.query_params.get("chain") From 6295826bbbceddc684a4e8c6b964ebf2b145890b Mon Sep 17 00:00:00 2001 From: Boluwatife Popoola Date: Tue, 25 Nov 2025 07:30:27 +0100 Subject: [PATCH 421/454] fix campaign delete --- indexer_app/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/indexer_app/utils.py b/indexer_app/utils.py index f10a198..8433316 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -5,13 +5,13 @@ from typing import Dict import requests +import stellar_sdk from asgiref.sync import sync_to_async from django.conf import settings from django.core.cache import cache from django.db import transaction from django.utils import timezone from near_lake_framework.near_primitives import ExecutionOutcome, Receipt -import stellar_sdk from accounts.models import Account from activities.models import Activity @@ -2381,12 +2381,14 @@ async def handle_update_campaign(data: dict): logger.error(f"Failed to update campaign: {e}") -async def handle_delete_campaign(campaign_id: int): +async def handle_delete_campaign(data: dict): """ Index a campaign deletion event. campaign_id: on_chain_id of the campaign to delete """ + campaign_id = data["campaign_id"] + try: logger.info(f"Deleting campaign: {campaign_id}") From 4ef2dedaa34149d31608ca769d2ccb737477fb80 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Wed, 28 Jan 2026 13:08:31 +0500 Subject: [PATCH 422/454] Added campaigns sycn API's --- api/urls.py | 15 +++ campaigns/sync.py | 274 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 campaigns/sync.py diff --git a/api/urls.py b/api/urls.py index 59e60c5..914e338 100644 --- a/api/urls.py +++ b/api/urls.py @@ -25,6 +25,10 @@ CampaignDonationsAPI, CampaignsAPI, ) +from campaigns.sync import ( + CampaignSyncAPI, + CampaignDonationsSyncAPI, +) from donations.api import DonationContractConfigAPI from grantpicks.api import AccountProjectListAPI, ProjectListAPI, ProjectRoundVotesAPI, ProjectStatsAPI, RoundApplicationsAPI, RoundDetailAPI, RoundsListAPI from lists.api import ( @@ -131,6 +135,17 @@ CampaignContractConfigAPI.as_view(), name="campaign_contract_config_api", ), + # campaign sync endpoints + path( + "v1/campaigns//sync", + CampaignSyncAPI.as_view(), + name="campaign_sync_api", + ), + path( + "v1/campaigns//donations/sync", + CampaignDonationsSyncAPI.as_view(), + name="campaign_donations_sync_api", + ), # donors path("v1/donors", DonorsAPI.as_view(), name="donors_api"), # lists diff --git a/campaigns/sync.py b/campaigns/sync.py new file mode 100644 index 0000000..0b9a0d5 --- /dev/null +++ b/campaigns/sync.py @@ -0,0 +1,274 @@ +""" +Sync endpoints - fetch data from blockchain RPC and store in database. + +Called by frontend after user creates/updates a campaign or donates. +Replaces the 24/7 indexer - now we fetch on-demand when user acts. + +Endpoints: + POST /api/v1/campaigns/{campaign_id}/sync - Sync single campaign + POST /api/v1/campaigns/{campaign_id}/donations/sync - Sync donations for a campaign +""" +import base64 +import json +import logging +from datetime import datetime, timezone + +import requests +from django.conf import settings +from drf_spectacular.utils import OpenApiResponse, extend_schema +from rest_framework.response import Response +from rest_framework.views import APIView + +from accounts.models import Account +from campaigns.models import Campaign, CampaignDonation +from tokens.models import Token + +logger = logging.getLogger(__name__) + +CAMPAIGNS_CONTRACT = ( + f"v1.campaign.{settings.POTLOCK_TLA}" + if settings.ENVIRONMENT == "testnet" + else f"v1.campaigns.{settings.POTLOCK_TLA}" +) + + +def fetch_from_rpc(method_name: str, args: dict = None, contract_id: str = None): + """Fetch data from blockchain via Web4 RPC with JSON-RPC fallback.""" + account_id = contract_id or CAMPAIGNS_CONTRACT + web4_url = f"{settings.FASTNEAR_RPC_URL}/account/{account_id}/view/{method_name}" + + try: + response = requests.post(web4_url, json=args or {}, timeout=15) + if response.status_code == 200: + return response.json() + except Exception as e: + logger.warning(f"web4 RPC failed, trying standard JSON-RPC: {e}") + + # Fallback to standard JSON-RPC if Web4 fails + rpc_url = ( + "https://test.rpc.fastnear.com" + if settings.ENVIRONMENT == "testnet" + else "https://free.rpc.fastnear.com" + ) + + args_base64 = base64.b64encode(json.dumps(args or {}).encode()).decode() + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "query", + "params": { + "request_type": "call_function", + "account_id": account_id, + "method_name": method_name, + "args_base64": args_base64, + "finality": "optimistic", + }, + } + + response = requests.post(rpc_url, json=payload, timeout=15) + result = response.json() + + if "error" in result: + raise Exception(f"RPC error: {result['error']}") + + result_bytes = bytes(result["result"]["result"]) + return json.loads(result_bytes.decode()) + + +class CampaignSyncAPI(APIView): + """ + Sync a campaign from blockchain to database. + + Called by frontend after user creates or updates a campaign. + Fetches current state from RPC via get_campaign, creates/updates in DB. + """ + + @extend_schema( + summary="Sync campaign from blockchain", + responses={ + 200: OpenApiResponse(description="Campaign synced"), + 404: OpenApiResponse(description="Campaign not found on chain"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request, campaign_id: int): + try: + data = fetch_from_rpc("get_campaign", {"campaign_id": int(campaign_id)}) + + if not data: + return Response({"error": "Campaign not found on chain"}, status=404) + + # Upsert accounts + owner, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=data["owner"]) + recipient, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=data["recipient"]) + + # Get token (Account must exist first since Token PK is a OneToOneField) + token_id = data.get("ft_id") or "near" + token_acct, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=token_id) + token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) + + # Timestamps + start_at = datetime.fromtimestamp(data["start_ms"] / 1000, tz=timezone.utc) + end_at = ( + datetime.fromtimestamp(data["end_ms"] / 1000, tz=timezone.utc) + if data.get("end_ms") + else None + ) + + campaign_defaults = { + "owner": owner, + "name": data["name"], + "description": data.get("description"), + "cover_image_url": data.get("cover_image_url"), + "recipient": recipient, + "token": token, + "start_at": start_at, + "end_at": end_at, + "created_at": datetime.fromtimestamp(data["created_ms"] / 1000, tz=timezone.utc) + if data.get("created_ms") + else datetime.now(tz=timezone.utc), + "target_amount": str(data["target_amount"]), + "min_amount": str(data["min_amount"]) if data.get("min_amount") else None, + "max_amount": str(data["max_amount"]) if data.get("max_amount") else None, + "total_raised_amount": str(data.get("total_raised_amount", "0")), + "net_raised_amount": str(data.get("net_raised_amount", "0")), + "escrow_balance": str(data.get("escrow_balance", "0")), + "referral_fee_basis_points": data["referral_fee_basis_points"], + "creator_fee_basis_points": data["creator_fee_basis_points"], + "allow_fee_avoidance": data.get("allow_fee_avoidance", False), + } + + campaign, created = Campaign.objects.update_or_create( + on_chain_id=int(campaign_id), + defaults=campaign_defaults, + ) + + # Fetch USD prices (matches indexer behavior) + try: + campaign.fetch_usd_prices() + except Exception as e: + logger.warning(f"Failed to fetch USD prices for campaign {campaign_id}: {e}") + + return Response( + { + "success": True, + "message": "Campaign created" if created else "Campaign updated", + "on_chain_id": campaign.on_chain_id, + } + ) + + except Exception as e: + logger.error(f"Error syncing campaign {campaign_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class CampaignDonationsSyncAPI(APIView): + """ + Sync all donations for a campaign from blockchain. + + Called by frontend after a user donates to a campaign. + Fetches donations via get_donations_for_campaign RPC call. + """ + + @extend_schema( + summary="Sync donations for a campaign", + responses={ + 200: OpenApiResponse(description="Donations synced"), + 404: OpenApiResponse(description="Campaign not found"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request, campaign_id: int): + try: + # Ensure campaign exists in DB + campaign = Campaign.objects.filter(on_chain_id=int(campaign_id)).first() + if not campaign: + # Sync campaign first + campaign_sync = CampaignSyncAPI() + resp = campaign_sync.post(request, campaign_id) + if resp.status_code != 200: + return Response({"error": "Campaign not found"}, status=404) + campaign = Campaign.objects.get(on_chain_id=int(campaign_id)) + + # Fetch donations from RPC + donations = fetch_from_rpc( + "get_donations_for_campaign", {"campaign_id": int(campaign_id)} + ) + + if not donations: + donations = [] + + synced = 0 + for don in donations: + # Upsert accounts + donor, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=don["donor_id"]) + + referrer = None + if don.get("referrer_id"): + referrer, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=don["referrer_id"]) + + donated_at = datetime.fromtimestamp(don["donated_at_ms"] / 1000, tz=timezone.utc) + returned_at = ( + datetime.fromtimestamp(don["returned_at_ms"] / 1000, tz=timezone.utc) + if don.get("returned_at_ms") + else None + ) + + donation_defaults = { + "token": campaign.token, + "total_amount": str(don["total_amount"]), + "net_amount": str(don["net_amount"]), + "message": don.get("message"), + "donated_at": donated_at, + "protocol_fee": str(don["protocol_fee"]), + "referrer": referrer, + "referrer_fee": str(don["referrer_fee"]) if don.get("referrer_fee") else None, + "creator_fee": str(don["creator_fee"]), + "returned_at": returned_at, + "escrowed": don.get("is_in_escrow", False), + } + + donation, _ = CampaignDonation.objects.update_or_create( + on_chain_id=don["id"], + campaign=campaign, + donor=donor, + defaults=donation_defaults, + ) + + # Fetch USD prices (matches indexer behavior) + try: + donation.fetch_usd_prices() + except Exception as e: + logger.warning(f"Failed to fetch USD prices for donation {don['id']}: {e}") + + synced += 1 + + # Also update campaign totals from chain + campaign_data = fetch_from_rpc( + "get_campaign", {"campaign_id": int(campaign_id)} + ) + if campaign_data: + campaign.total_raised_amount = str(campaign_data.get( + "total_raised_amount", campaign.total_raised_amount + )) + campaign.net_raised_amount = str(campaign_data.get( + "net_raised_amount", campaign.net_raised_amount + )) + campaign.escrow_balance = str(campaign_data.get( + "escrow_balance", campaign.escrow_balance + )) + campaign.save() + + return Response( + { + "success": True, + "message": f"Synced {synced} donations", + "synced_count": synced, + } + ) + + except Exception as e: + logger.error( + f"Error syncing donations for campaign {campaign_id}: {e}" + ) + return Response({"error": str(e)}, status=502) From 52d67fd348c3cbec353eead35b741d5cf631e625 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Mon, 2 Feb 2026 22:05:12 +0500 Subject: [PATCH 423/454] Resolved PR comments --- api/urls.py | 6 +- campaigns/sync.py | 381 ++++++++++++++++++++++++++++++---------------- 2 files changed, 256 insertions(+), 131 deletions(-) diff --git a/api/urls.py b/api/urls.py index 914e338..725866c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -27,7 +27,7 @@ ) from campaigns.sync import ( CampaignSyncAPI, - CampaignDonationsSyncAPI, + CampaignDonationSyncAPI, ) from donations.api import DonationContractConfigAPI from grantpicks.api import AccountProjectListAPI, ProjectListAPI, ProjectRoundVotesAPI, ProjectStatsAPI, RoundApplicationsAPI, RoundDetailAPI, RoundsListAPI @@ -143,8 +143,8 @@ ), path( "v1/campaigns//donations/sync", - CampaignDonationsSyncAPI.as_view(), - name="campaign_donations_sync_api", + CampaignDonationSyncAPI.as_view(), + name="campaign_donation_sync_api", ), # donors path("v1/donors", DonorsAPI.as_view(), name="donors_api"), diff --git a/campaigns/sync.py b/campaigns/sync.py index 0b9a0d5..e1ad42a 100644 --- a/campaigns/sync.py +++ b/campaigns/sync.py @@ -6,7 +6,7 @@ Endpoints: POST /api/v1/campaigns/{campaign_id}/sync - Sync single campaign - POST /api/v1/campaigns/{campaign_id}/donations/sync - Sync donations for a campaign + POST /api/v1/campaigns/{campaign_id}/donations/sync - Sync single donation via tx_hash """ import base64 import json @@ -15,7 +15,7 @@ import requests from django.conf import settings -from drf_spectacular.utils import OpenApiResponse, extend_schema +from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema from rest_framework.response import Response from rest_framework.views import APIView @@ -75,6 +75,191 @@ def fetch_from_rpc(method_name: str, args: dict = None, contract_id: str = None) return json.loads(result_bytes.decode()) +def fetch_tx_result(tx_hash: str, sender_id: str): + """ + Fetch transaction result from NEAR RPC. + Returns the parsed result from the transaction execution. + """ + rpc_url = ( + "https://test.rpc.fastnear.com" + if settings.ENVIRONMENT == "testnet" + else "https://free.rpc.fastnear.com" + ) + + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "tx", + "params": { + "tx_hash": tx_hash, + "sender_account_id": sender_id, + "wait_until": "EXECUTED_OPTIMISTIC", + }, + } + + response = requests.post(rpc_url, json=payload, timeout=30) + result = response.json() + + if "error" in result: + raise Exception(f"RPC error fetching tx: {result['error']}") + + return result.get("result") + + +def parse_donation_from_tx(tx_result: dict) -> dict: + """ + Parse donation data from transaction execution result. + Looks through receipts_outcome to find the SuccessValue containing donation data. + """ + receipts_outcome = tx_result.get("receipts_outcome", []) + + for outcome in receipts_outcome: + status = outcome.get("outcome", {}).get("status", {}) + if isinstance(status, dict) and "SuccessValue" in status: + success_value = status["SuccessValue"] + if success_value: + try: + decoded = base64.b64decode(success_value).decode() + data = json.loads(decoded) + # Check if this looks like donation data + if isinstance(data, dict) and "donor_id" in data and "total_amount" in data: + return data + except (json.JSONDecodeError, UnicodeDecodeError): + continue + + return None + + +def sync_campaign_from_chain(campaign_id: int) -> tuple[Campaign, bool]: + """ + Fetch campaign from blockchain and sync to database. + Returns (campaign, created) tuple. + + This is a shared utility used by both CampaignSyncAPI and when + ensuring a campaign exists before syncing donations. + """ + data = fetch_from_rpc("get_campaign", {"campaign_id": int(campaign_id)}) + + if not data: + return None, False + + # Upsert accounts + owner, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=data["owner"]) + recipient, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=data["recipient"]) + + # Get token (Account must exist first since Token PK is a OneToOneField) + token_id = data.get("ft_id") or "near" + token_acct, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=token_id) + token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) + + # Timestamps + start_at = datetime.fromtimestamp(data["start_ms"] / 1000, tz=timezone.utc) + end_at = ( + datetime.fromtimestamp(data["end_ms"] / 1000, tz=timezone.utc) + if data.get("end_ms") + else None + ) + + campaign_defaults = { + "owner": owner, + "name": data["name"], + "description": data.get("description"), + "cover_image_url": data.get("cover_image_url"), + "recipient": recipient, + "token": token, + "start_at": start_at, + "end_at": end_at, + "created_at": datetime.fromtimestamp(data["created_ms"] / 1000, tz=timezone.utc) + if data.get("created_ms") + else datetime.now(tz=timezone.utc), + "target_amount": str(data["target_amount"]), + "min_amount": str(data["min_amount"]) if data.get("min_amount") else None, + "max_amount": str(data["max_amount"]) if data.get("max_amount") else None, + "total_raised_amount": str(data.get("total_raised_amount", "0")), + "net_raised_amount": str(data.get("net_raised_amount", "0")), + "escrow_balance": str(data.get("escrow_balance", "0")), + "referral_fee_basis_points": data["referral_fee_basis_points"], + "creator_fee_basis_points": data["creator_fee_basis_points"], + "allow_fee_avoidance": data.get("allow_fee_avoidance", False), + } + + campaign, created = Campaign.objects.update_or_create( + on_chain_id=int(campaign_id), + defaults=campaign_defaults, + ) + + # Note: USD price fetching is skipped here intentionally. + # A daily cron job will handle USD price updates for all records. + + return campaign, created + + +def sync_donation_from_data(campaign: Campaign, donation_data: dict, tx_hash: str = None) -> CampaignDonation: + """ + Sync a single donation from parsed data to database. + + This is a shared utility for creating/updating a campaign donation. + """ + # Upsert accounts + donor, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, id=donation_data["donor_id"] + ) + + referrer = None + if donation_data.get("referrer_id"): + referrer, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, id=donation_data["referrer_id"] + ) + + donated_at = datetime.fromtimestamp( + donation_data["donated_at_ms"] / 1000, tz=timezone.utc + ) + returned_at = ( + datetime.fromtimestamp(donation_data["returned_at_ms"] / 1000, tz=timezone.utc) + if donation_data.get("returned_at_ms") + else None + ) + + # Get token if specified, otherwise use campaign's token + token = campaign.token + if donation_data.get("ft_id"): + token_acct, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, id=donation_data["ft_id"] + ) + token, _ = Token.objects.get_or_create( + account=token_acct, defaults={"decimals": 24} + ) + + donation_defaults = { + "token": token, + "total_amount": str(donation_data["total_amount"]), + "net_amount": str(donation_data["net_amount"]), + "message": donation_data.get("message"), + "donated_at": donated_at, + "protocol_fee": str(donation_data["protocol_fee"]), + "referrer": referrer, + "referrer_fee": str(donation_data["referrer_fee"]) if donation_data.get("referrer_fee") else None, + "creator_fee": str(donation_data["creator_fee"]), + "returned_at": returned_at, + "escrowed": donation_data.get("is_in_escrow", False), + } + + if tx_hash: + donation_defaults["tx_hash"] = tx_hash + + donation, created = CampaignDonation.objects.update_or_create( + on_chain_id=donation_data["id"], + campaign=campaign, + donor=donor, + defaults=donation_defaults, + ) + + # Note: USD price fetching is skipped here intentionally. + # A daily cron job will handle USD price updates for all records. + + return donation + + class CampaignSyncAPI(APIView): """ Sync a campaign from blockchain to database. @@ -93,62 +278,11 @@ class CampaignSyncAPI(APIView): ) def post(self, request, campaign_id: int): try: - data = fetch_from_rpc("get_campaign", {"campaign_id": int(campaign_id)}) + campaign, created = sync_campaign_from_chain(campaign_id) - if not data: + if not campaign: return Response({"error": "Campaign not found on chain"}, status=404) - # Upsert accounts - owner, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=data["owner"]) - recipient, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=data["recipient"]) - - # Get token (Account must exist first since Token PK is a OneToOneField) - token_id = data.get("ft_id") or "near" - token_acct, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=token_id) - token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) - - # Timestamps - start_at = datetime.fromtimestamp(data["start_ms"] / 1000, tz=timezone.utc) - end_at = ( - datetime.fromtimestamp(data["end_ms"] / 1000, tz=timezone.utc) - if data.get("end_ms") - else None - ) - - campaign_defaults = { - "owner": owner, - "name": data["name"], - "description": data.get("description"), - "cover_image_url": data.get("cover_image_url"), - "recipient": recipient, - "token": token, - "start_at": start_at, - "end_at": end_at, - "created_at": datetime.fromtimestamp(data["created_ms"] / 1000, tz=timezone.utc) - if data.get("created_ms") - else datetime.now(tz=timezone.utc), - "target_amount": str(data["target_amount"]), - "min_amount": str(data["min_amount"]) if data.get("min_amount") else None, - "max_amount": str(data["max_amount"]) if data.get("max_amount") else None, - "total_raised_amount": str(data.get("total_raised_amount", "0")), - "net_raised_amount": str(data.get("net_raised_amount", "0")), - "escrow_balance": str(data.get("escrow_balance", "0")), - "referral_fee_basis_points": data["referral_fee_basis_points"], - "creator_fee_basis_points": data["creator_fee_basis_points"], - "allow_fee_avoidance": data.get("allow_fee_avoidance", False), - } - - campaign, created = Campaign.objects.update_or_create( - on_chain_id=int(campaign_id), - defaults=campaign_defaults, - ) - - # Fetch USD prices (matches indexer behavior) - try: - campaign.fetch_usd_prices() - except Exception as e: - logger.warning(f"Failed to fetch USD prices for campaign {campaign_id}: {e}") - return Response( { "success": True, @@ -162,113 +296,104 @@ def post(self, request, campaign_id: int): return Response({"error": str(e)}, status=502) -class CampaignDonationsSyncAPI(APIView): +class CampaignDonationSyncAPI(APIView): """ - Sync all donations for a campaign from blockchain. + Sync a single donation for a campaign from blockchain. Called by frontend after a user donates to a campaign. - Fetches donations via get_donations_for_campaign RPC call. + Frontend passes the transaction hash, backend parses the donation from tx result. """ @extend_schema( - summary="Sync donations for a campaign", + summary="Sync a donation for a campaign", + description="Sync a single donation using the transaction hash from the donation response.", + parameters=[ + OpenApiParameter( + name="tx_hash", + description="Transaction hash from the donation transaction", + required=True, + type=str, + ), + OpenApiParameter( + name="sender_id", + description="Account ID of the transaction sender (donor)", + required=True, + type=str, + ), + ], responses={ - 200: OpenApiResponse(description="Donations synced"), - 404: OpenApiResponse(description="Campaign not found"), + 200: OpenApiResponse(description="Donation synced"), + 400: OpenApiResponse(description="Missing required parameters"), + 404: OpenApiResponse(description="Campaign or donation not found"), 502: OpenApiResponse(description="RPC failed"), }, ) def post(self, request, campaign_id: int): try: + # Get required parameters + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + # Ensure campaign exists in DB campaign = Campaign.objects.filter(on_chain_id=int(campaign_id)).first() if not campaign: - # Sync campaign first - campaign_sync = CampaignSyncAPI() - resp = campaign_sync.post(request, campaign_id) - if resp.status_code != 200: + # Sync campaign first using shared utility + campaign, _ = sync_campaign_from_chain(campaign_id) + if not campaign: return Response({"error": "Campaign not found"}, status=404) - campaign = Campaign.objects.get(on_chain_id=int(campaign_id)) - - # Fetch donations from RPC - donations = fetch_from_rpc( - "get_donations_for_campaign", {"campaign_id": int(campaign_id)} - ) - - if not donations: - donations = [] - synced = 0 - for don in donations: - # Upsert accounts - donor, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=don["donor_id"]) + # Fetch transaction result and parse donation data + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) - referrer = None - if don.get("referrer_id"): - referrer, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=don["referrer_id"]) - - donated_at = datetime.fromtimestamp(don["donated_at_ms"] / 1000, tz=timezone.utc) - returned_at = ( - datetime.fromtimestamp(don["returned_at_ms"] / 1000, tz=timezone.utc) - if don.get("returned_at_ms") - else None + donation_data = parse_donation_from_tx(tx_result) + if not donation_data: + return Response( + {"error": "Could not parse donation from transaction result"}, + status=404, ) - donation_defaults = { - "token": campaign.token, - "total_amount": str(don["total_amount"]), - "net_amount": str(don["net_amount"]), - "message": don.get("message"), - "donated_at": donated_at, - "protocol_fee": str(don["protocol_fee"]), - "referrer": referrer, - "referrer_fee": str(don["referrer_fee"]) if don.get("referrer_fee") else None, - "creator_fee": str(don["creator_fee"]), - "returned_at": returned_at, - "escrowed": don.get("is_in_escrow", False), - } - - donation, _ = CampaignDonation.objects.update_or_create( - on_chain_id=don["id"], - campaign=campaign, - donor=donor, - defaults=donation_defaults, + # Verify this donation belongs to the specified campaign + if donation_data.get("campaign_id") != int(campaign_id): + return Response( + {"error": "Donation does not belong to this campaign"}, + status=400, ) - # Fetch USD prices (matches indexer behavior) - try: - donation.fetch_usd_prices() - except Exception as e: - logger.warning(f"Failed to fetch USD prices for donation {don['id']}: {e}") - - synced += 1 + # Sync the donation using shared utility + donation = sync_donation_from_data(campaign, donation_data, tx_hash) - # Also update campaign totals from chain + # Update campaign totals from chain (single RPC call to get fresh totals) campaign_data = fetch_from_rpc( "get_campaign", {"campaign_id": int(campaign_id)} ) if campaign_data: - campaign.total_raised_amount = str(campaign_data.get( - "total_raised_amount", campaign.total_raised_amount - )) - campaign.net_raised_amount = str(campaign_data.get( - "net_raised_amount", campaign.net_raised_amount - )) - campaign.escrow_balance = str(campaign_data.get( - "escrow_balance", campaign.escrow_balance - )) + campaign.total_raised_amount = str( + campaign_data.get("total_raised_amount", campaign.total_raised_amount) + ) + campaign.net_raised_amount = str( + campaign_data.get("net_raised_amount", campaign.net_raised_amount) + ) + campaign.escrow_balance = str( + campaign_data.get("escrow_balance", campaign.escrow_balance) + ) campaign.save() return Response( { "success": True, - "message": f"Synced {synced} donations", - "synced_count": synced, + "message": "Donation synced", + "donation_id": donation.on_chain_id, } ) except Exception as e: - logger.error( - f"Error syncing donations for campaign {campaign_id}: {e}" - ) + logger.error(f"Error syncing donation for campaign {campaign_id}: {e}") return Response({"error": str(e)}, status=502) From 9bf16189d947810d136678b5cd49e6c7b57fa3a8 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Sun, 8 Feb 2026 20:14:29 +0500 Subject: [PATCH 424/454] Added accounts and list sync endpoints --- api/sync.py | 399 ++++++++++++++++++++++++++++++++++++++++++++++++++++ api/urls.py | 27 ++++ 2 files changed, 426 insertions(+) create mode 100644 api/sync.py diff --git a/api/sync.py b/api/sync.py new file mode 100644 index 0000000..537c5ee --- /dev/null +++ b/api/sync.py @@ -0,0 +1,399 @@ +""" +Sync endpoints - fetch data from blockchain RPC and store in database. + +Called by frontend after user creates/updates a list/registration or account profile. +Replaces the 24/7 indexer - now we fetch on-demand when user acts. + +Endpoints: + Lists: + POST /api/v1/lists/{list_id}/sync - Sync single list + POST /api/v1/lists/{list_id}/registrations/sync - Sync all registrations + POST /api/v1/lists/{list_id}/registrations/{registrant_id}/sync - Sync single registration + + Accounts: + POST /api/v1/accounts/{account_id}/sync - Sync account profile and recalculate stats +""" +import base64 +import json +import logging +from datetime import datetime + +import requests +from django.conf import settings +from drf_spectacular.utils import OpenApiResponse, extend_schema +from rest_framework.response import Response +from rest_framework.views import APIView + +from accounts.models import Account +from donations.models import Donation +from lists.models import List, ListRegistration + +logger = logging.getLogger(__name__) + +LISTS_CONTRACT = f"lists.{settings.POTLOCK_TLA}" + + +def fetch_from_rpc(method_name: str, args: dict = None, contract_id: str = None, timeout: int = 60): + """ + Fetch data from NEAR RPC with multiple fallbacks and retry logic. + + Order of attempts: + 1. Web4 RPC (most efficient) + 2. FastNear free RPC + 3. Official NEAR RPC (most reliable but rate-limited) + """ + from requests.adapters import HTTPAdapter + from urllib3.util.retry import Retry + + account_id = contract_id or LISTS_CONTRACT + + # Create session with retry logic + session = requests.Session() + retries = Retry(total=2, backoff_factor=0.5, status_forcelist=[502, 503, 504]) + session.mount("https://", HTTPAdapter(max_retries=retries)) + + # Try Web4 RPC first (skip for heavy methods that often fail) + heavy_methods = ["get_registrations_for_list"] + if method_name not in heavy_methods: + web4_url = f"{settings.FASTNEAR_RPC_URL}/account/{account_id}/view/{method_name}" + try: + response = session.post(web4_url, json=args or {}, timeout=timeout) + if response.status_code == 200: + return response.json() + logger.warning(f"web4 RPC returned status {response.status_code}") + except Exception as e: + logger.warning(f"web4 RPC failed: {e}") + + # RPC endpoints to try in order + rpc_endpoints = [ + "https://free.rpc.fastnear.com" if settings.ENVIRONMENT != "testnet" else "https://test.rpc.fastnear.com", + "https://rpc.mainnet.near.org" if settings.ENVIRONMENT != "testnet" else "https://rpc.testnet.near.org", + ] + + args_base64 = base64.b64encode(json.dumps(args or {}).encode()).decode() + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "query", + "params": { + "request_type": "call_function", + "account_id": account_id, + "method_name": method_name, + "args_base64": args_base64, + "finality": "optimistic" + } + } + + last_error = None + for rpc_url in rpc_endpoints: + try: + logger.info(f"Trying RPC: {rpc_url} for {method_name}") + response = session.post(rpc_url, json=payload, timeout=timeout) + result = response.json() + + if "error" in result: + logger.warning(f"RPC error from {rpc_url}: {result['error']}") + last_error = result["error"] + continue + + if "error" in result.get("result", {}): + logger.warning(f"Contract error from {rpc_url}: {result['result']['error']}") + last_error = result["result"]["error"] + continue + + # Decode result bytes to JSON + if "result" not in result.get("result", {}): + return None + result_bytes = bytes(result["result"]["result"]) + return json.loads(result_bytes.decode()) + + except requests.exceptions.Timeout: + logger.warning(f"RPC {rpc_url} timed out after {timeout}s") + last_error = f"Timeout after {timeout}s" + except Exception as e: + logger.warning(f"RPC {rpc_url} failed: {e}") + last_error = str(e) + + raise Exception(f"All RPC endpoints failed. Last error: {last_error}") + + +class ListSyncAPI(APIView): + """ + Sync a list from blockchain to database. + + Called by frontend after user creates a list. + Fetches current state from RPC, creates/updates in DB. + """ + + @extend_schema( + summary="Sync list from blockchain", + responses={ + 200: OpenApiResponse(description="List synced"), + 404: OpenApiResponse(description="List not found on chain"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, list_id: int): + try: + # Fetch from RPC + data = fetch_from_rpc("get_list", {"list_id": int(list_id)}) + + if not data: + return Response({"error": "List not found on chain"}, status=404) + + # Check if already exists + existing_list = List.objects.filter(on_chain_id=int(list_id)).first() + + if existing_list: + # Update existing list + existing_list.name = data["name"] + existing_list.description = data.get("description", "") + existing_list.cover_image_url = data.get("cover_image_url") + existing_list.admin_only_registrations = data.get("admin_only_registrations", False) + existing_list.default_registration_status = data.get("default_registration_status", "Pending") + existing_list.updated_at = datetime.fromtimestamp(data["updated_at"] / 1000) + existing_list.save() + + # Update admins + existing_list.admins.clear() + for admin_id in data.get("admins", []): + admin, _ = Account.objects.get_or_create(id=admin_id) + existing_list.admins.add(admin) + + return Response({ + "success": True, + "message": "List updated", + "on_chain_id": list_id + }) + + # Create list (on_chain_id is the blockchain ID, id is auto-generated) + list_obj = List.objects.create( + on_chain_id=data["id"], + owner_id=data["owner"], + name=data["name"], + description=data.get("description", ""), + cover_image_url=data.get("cover_image_url"), + admin_only_registrations=data.get("admin_only_registrations", False), + default_registration_status=data.get("default_registration_status", "Pending"), + created_at=datetime.fromtimestamp(data["created_at"] / 1000), + updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), + ) + + # Create owner account + Account.objects.get_or_create(id=data["owner"]) + + # Add admins + for admin_id in data.get("admins", []): + admin, _ = Account.objects.get_or_create(id=admin_id) + list_obj.admins.add(admin) + + return Response({ + "success": True, + "message": "List created", + "on_chain_id": list_obj.on_chain_id + }) + + except Exception as e: + logger.error(f"Error syncing list {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class ListRegistrationsSyncAPI(APIView): + """ + Sync all registrations for a list. + + Called after user registers to a list. + """ + + @extend_schema( + summary="Sync all registrations for a list", + responses={ + 200: OpenApiResponse(description="Registrations synced"), + 404: OpenApiResponse(description="List not found"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, list_id: int): + try: + # Ensure list exists + try: + list_obj = List.objects.get(on_chain_id=int(list_id)) + except List.DoesNotExist: + # Sync list first + list_sync = ListSyncAPI() + resp = list_sync.post(request, list_id) + if resp.status_code != 200: + return Response({"error": "List not found"}, status=404) + list_obj = List.objects.get(on_chain_id=int(list_id)) + + # Fetch registrations from RPC (use longer timeout for large lists) + registrations = fetch_from_rpc("get_registrations_for_list", {"list_id": int(list_id)}, timeout=120) + + if not registrations: + registrations = [] + + synced = 0 + for reg in registrations: + # Create accounts + Account.objects.get_or_create(id=reg["registrant_id"]) + Account.objects.get_or_create(id=reg.get("registered_by", reg["registrant_id"])) + + # Create/update registration (id is AutoField, use list+registrant as unique key) + ListRegistration.objects.update_or_create( + list=list_obj, + registrant_id=reg["registrant_id"], + defaults={ + "registered_by_id": reg.get("registered_by", reg["registrant_id"]), + "status": reg.get("status", "Pending"), + "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000), + "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), + "admin_notes": reg.get("admin_notes"), + "registrant_notes": reg.get("registrant_notes"), + } + ) + synced += 1 + + return Response({ + "success": True, + "message": f"Synced {synced} registrations", + "synced_count": synced + }) + + except Exception as e: + logger.error(f"Error syncing registrations for list {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class SingleRegistrationSyncAPI(APIView): + """ + Sync a single registration. + + More efficient than syncing all when you know the registrant. + """ + + @extend_schema( + summary="Sync single registration", + responses={ + 200: OpenApiResponse(description="Registration synced"), + 404: OpenApiResponse(description="Not found"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, list_id: int, registrant_id: str): + try: + # Ensure list exists + try: + list_obj = List.objects.get(on_chain_id=int(list_id)) + except List.DoesNotExist: + list_sync = ListSyncAPI() + resp = list_sync.post(request, list_id) + if resp.status_code != 200: + return Response({"error": "List not found"}, status=404) + list_obj = List.objects.get(on_chain_id=int(list_id)) + + # Fetch all registrations and filter (contract doesn't have single-registration lookup by registrant_id) + registrations = fetch_from_rpc("get_registrations_for_list", {"list_id": int(list_id)}, timeout=120) + + if not registrations: + return Response({"error": "No registrations found for list"}, status=404) + + # Find the specific registration + reg = next((r for r in registrations if r.get("registrant_id") == registrant_id), None) + + if not reg: + return Response({"error": "Registration not found"}, status=404) + + # Create accounts + Account.objects.get_or_create(id=reg["registrant_id"]) + Account.objects.get_or_create(id=reg.get("registered_by", reg["registrant_id"])) + + # Create/update registration (id is AutoField, use list+registrant as unique key) + registration, created = ListRegistration.objects.update_or_create( + list=list_obj, + registrant_id=reg["registrant_id"], + defaults={ + "registered_by_id": reg.get("registered_by", reg["registrant_id"]), + "status": reg.get("status", "Pending"), + "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000), + "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), + "admin_notes": reg.get("admin_notes"), + "registrant_notes": reg.get("registrant_notes"), + } + ) + + return Response({ + "success": True, + "message": "Registration synced", + "registrant_id": registrant_id, + "status": registration.status + }) + + except Exception as e: + logger.error(f"Error syncing registration: {e}") + return Response({"error": str(e)}, status=502) + + +class AccountSyncAPI(APIView): + """ + Sync account data and recalculate donation stats. + + Called by frontend after actions that affect an account's totals. + Fetches NEAR Social profile and recalculates donation totals from DB. + """ + + @extend_schema( + summary="Sync account and recalculate stats", + responses={ + 200: OpenApiResponse(description="Account synced"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, account_id: str): + from django.db.models import Sum, Count + + try: + account, created = Account.objects.get_or_create(id=account_id) + + # Fetch NEAR Social profile + try: + profile_data = fetch_from_rpc( + "get", + {"keys": [f"{account_id}/profile/**"]}, + contract_id="social.near" if settings.ENVIRONMENT != "testnet" else "v1.social08.testnet" + ) + if profile_data and account_id in profile_data: + account.near_social_profile_data = profile_data[account_id].get("profile", {}) + except Exception as e: + logger.warning(f"Failed to fetch social profile for {account_id}: {e}") + + # Recalculate donation totals from existing DB records + donations_received = Donation.objects.filter(recipient_id=account_id).aggregate( + total_usd=Sum('total_amount_usd'), + count=Count('id') + ) + donations_sent = Donation.objects.filter(donor_id=account_id).aggregate( + total_usd=Sum('total_amount_usd'), + count=Count('id') + ) + + account.total_donations_in_usd = donations_received['total_usd'] or 0 + account.total_donations_out_usd = donations_sent['total_usd'] or 0 + account.donors_count = Donation.objects.filter( + recipient_id=account_id + ).values('donor').distinct().count() + + account.save() + + return Response({ + "success": True, + "message": "Account synced", + "account_id": account_id, + "created": created, + "total_donations_in_usd": str(account.total_donations_in_usd), + "total_donations_out_usd": str(account.total_donations_out_usd), + "donors_count": account.donors_count + }) + + except Exception as e: + logger.error(f"Error syncing account {account_id}: {e}") + return Response({"error": str(e)}, status=502) diff --git a/api/urls.py b/api/urls.py index 725866c..bec629b 100644 --- a/api/urls.py +++ b/api/urls.py @@ -17,6 +17,12 @@ AccountsListAPI, DonorsAPI, ) +from api.sync import ( + AccountSyncAPI, + ListSyncAPI, + ListRegistrationsSyncAPI, + SingleRegistrationSyncAPI, +) from base.api import StatsAPI, ReclaimProofRequestView from campaigns.api import ( AllCampaignDonationsAPI, @@ -212,4 +218,25 @@ MpdaoVoterDetailAPI.as_view(), name="mpdao_voter_detail", ), + # sync endpoints (for on-demand data fetching from blockchain) + path( + "v1/lists//sync", + ListSyncAPI.as_view(), + name="list_sync_api", + ), + path( + "v1/lists//registrations/sync", + ListRegistrationsSyncAPI.as_view(), + name="list_registrations_sync_api", + ), + path( + "v1/lists//registrations//sync", + SingleRegistrationSyncAPI.as_view(), + name="single_registration_sync_api", + ), + path( + "v1/accounts//sync", + AccountSyncAPI.as_view(), + name="account_sync_api", + ), ] From db66701e8f2f0b6444caaed4f5aecb942e67eb3f Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Sun, 8 Feb 2026 21:39:05 +0500 Subject: [PATCH 425/454] Fix bug in get lists api --- lists/api.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lists/api.py b/lists/api.py index b343b6f..b8d542b 100644 --- a/lists/api.py +++ b/lists/api.py @@ -79,14 +79,6 @@ def get(self, request: Request, *args, **kwargs): account_id = request.query_params.get("account") chain = request.query_params.get("chain", "NEAR") lists = lists.filter(chain__name=chain) - if account_id: - try: - account = Chain.objects.get(name=account_id) - lists = lists.filter(owner=account) - except Account.DoesNotExist: - return Response( - {"message": f"Account with ID {account_id} not found."}, status=404 - ) if account_id: try: account = Account.objects.get(id=account_id) From c2d899088f3bff8e68f43fcef969f706a89a06cb Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Mon, 9 Feb 2026 11:46:37 +0500 Subject: [PATCH 426/454] Add diret donation sync for lists donations --- api/urls.py | 7 ++ donations/sync.py | 197 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 donations/sync.py diff --git a/api/urls.py b/api/urls.py index bec629b..439f11a 100644 --- a/api/urls.py +++ b/api/urls.py @@ -36,6 +36,7 @@ CampaignDonationSyncAPI, ) from donations.api import DonationContractConfigAPI +from donations.sync import DirectDonationSyncAPI from grantpicks.api import AccountProjectListAPI, ProjectListAPI, ProjectRoundVotesAPI, ProjectStatsAPI, RoundApplicationsAPI, RoundDetailAPI, RoundsListAPI from lists.api import ( ListDetailAPI, @@ -119,6 +120,12 @@ DonationContractConfigAPI.as_view(), name="donate_contract_config_api", ), + # direct donation sync + path( + "v1/donations/sync", + DirectDonationSyncAPI.as_view(), + name="direct_donation_sync_api", + ), # campaigns path("v1/campaigns", CampaignsAPI.as_view(), name="campaigns_api"), path( diff --git a/donations/sync.py b/donations/sync.py new file mode 100644 index 0000000..84f8f3d --- /dev/null +++ b/donations/sync.py @@ -0,0 +1,197 @@ +""" +Sync endpoints for direct donations - fetch data from blockchain RPC and store in database. + +Called by frontend after user makes a direct donation. + +Endpoints: + POST /api/v1/donations/sync - Sync single donation via tx_hash +""" +import base64 +import json +import logging +from datetime import datetime, timezone + +import requests +from django.conf import settings +from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema +from rest_framework.response import Response +from rest_framework.views import APIView + +from accounts.models import Account +from donations.models import Donation +from tokens.models import Token + +logger = logging.getLogger(__name__) + +DONATION_CONTRACT = f"donate.{settings.POTLOCK_TLA}" + + +def fetch_tx_result(tx_hash: str, sender_id: str): + """ + Fetch transaction result from NEAR RPC. + Returns the parsed result from the transaction execution. + """ + rpc_url = ( + "https://test.rpc.fastnear.com" + if settings.ENVIRONMENT == "testnet" + else "https://free.rpc.fastnear.com" + ) + + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "tx", + "params": { + "tx_hash": tx_hash, + "sender_account_id": sender_id, + "wait_until": "EXECUTED_OPTIMISTIC", + }, + } + + response = requests.post(rpc_url, json=payload, timeout=30) + result = response.json() + + if "error" in result: + raise Exception(f"RPC error fetching tx: {result['error']}") + + return result.get("result") + + +def parse_donation_from_tx(tx_result: dict) -> dict: + """ + Parse donation data from transaction execution result. + Looks through receipts_outcome to find the SuccessValue containing donation data. + """ + receipts_outcome = tx_result.get("receipts_outcome", []) + + for outcome in receipts_outcome: + status = outcome.get("outcome", {}).get("status", {}) + if isinstance(status, dict) and "SuccessValue" in status: + success_value = status["SuccessValue"] + if success_value: + try: + decoded = base64.b64decode(success_value).decode() + data = json.loads(decoded) + # Check if this looks like direct donation data + if isinstance(data, dict) and "donor_id" in data and "recipient_id" in data: + return data + except (json.JSONDecodeError, UnicodeDecodeError): + continue + + return None + + +class DirectDonationSyncAPI(APIView): + """ + Sync a direct donation from blockchain to database. + + Called by frontend after a user makes a direct donation. + Frontend passes the transaction hash, backend parses the donation from tx result. + """ + + @extend_schema( + summary="Sync a direct donation", + description="Sync a single direct donation using the transaction hash from the donation response.", + parameters=[ + OpenApiParameter( + name="tx_hash", + description="Transaction hash from the donation transaction", + required=True, + type=str, + ), + OpenApiParameter( + name="sender_id", + description="Account ID of the transaction sender (donor)", + required=True, + type=str, + ), + ], + responses={ + 200: OpenApiResponse(description="Donation synced"), + 400: OpenApiResponse(description="Missing required parameters"), + 404: OpenApiResponse(description="Donation not found in transaction"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request): + try: + # Get required parameters + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + + # Fetch transaction result and parse donation data + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) + + donation_data = parse_donation_from_tx(tx_result) + if not donation_data: + return Response( + {"error": "Could not parse donation from transaction result"}, + status=404, + ) + + # Upsert accounts + donor, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, id=donation_data["donor_id"] + ) + recipient, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, id=donation_data["recipient_id"] + ) + + referrer = None + if donation_data.get("referrer_id"): + referrer, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, id=donation_data["referrer_id"] + ) + + # Get or create token + token_id = donation_data.get("ft_id") or "near" + token_acct, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=token_id) + token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) + + # Parse timestamp + donated_at = datetime.fromtimestamp( + donation_data["donated_at_ms"] / 1000, tz=timezone.utc + ) + + # Create or update donation + donation_defaults = { + "donor": donor, + "recipient": recipient, + "token": token, + "total_amount": str(donation_data["total_amount"]), + "net_amount": str(donation_data["net_amount"]), + "message": donation_data.get("message"), + "donated_at": donated_at, + "protocol_fee": str(donation_data.get("protocol_fee", "0")), + "referrer": referrer, + "referrer_fee": str(donation_data["referrer_fee"]) if donation_data.get("referrer_fee") else None, + "matching_pool": False, + "tx_hash": tx_hash, + } + + donation, created = Donation.objects.update_or_create( + on_chain_id=donation_data["id"], + pot__isnull=True, # Direct donations have no pot + defaults=donation_defaults, + ) + + return Response( + { + "success": True, + "message": "Donation synced", + "donation_id": donation.on_chain_id, + "created": created, + } + ) + + except Exception as e: + logger.error(f"Error syncing direct donation: {e}") + return Response({"error": str(e)}, status=502) From 74921a0edee0b85017ae7003dbbe9ada0f1585ec Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Mon, 9 Feb 2026 12:19:14 +0500 Subject: [PATCH 427/454] Fixed net amount calculation in direct donation sync --- donations/sync.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/donations/sync.py b/donations/sync.py index 84f8f3d..aaff2cf 100644 --- a/donations/sync.py +++ b/donations/sync.py @@ -161,18 +161,26 @@ def post(self, request): donation_data["donated_at_ms"] / 1000, tz=timezone.utc ) + # Calculate net_amount if not provided (total - protocol_fee - referrer_fee) + total_amount = int(donation_data["total_amount"]) + protocol_fee = int(donation_data.get("protocol_fee", 0)) + referrer_fee = int(donation_data.get("referrer_fee", 0) or 0) + net_amount = donation_data.get("net_amount") + if net_amount is None: + net_amount = total_amount - protocol_fee - referrer_fee + # Create or update donation donation_defaults = { "donor": donor, "recipient": recipient, "token": token, - "total_amount": str(donation_data["total_amount"]), - "net_amount": str(donation_data["net_amount"]), + "total_amount": str(total_amount), + "net_amount": str(net_amount), "message": donation_data.get("message"), "donated_at": donated_at, - "protocol_fee": str(donation_data.get("protocol_fee", "0")), + "protocol_fee": str(protocol_fee), "referrer": referrer, - "referrer_fee": str(donation_data["referrer_fee"]) if donation_data.get("referrer_fee") else None, + "referrer_fee": str(referrer_fee) if referrer_fee else None, "matching_pool": False, "tx_hash": tx_hash, } From 9cde0095039a7902b11a6f395ee26f3e6d2f63ad Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Tue, 10 Feb 2026 00:31:36 +0500 Subject: [PATCH 428/454] minor change to trigger deployment --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index da160fc..4b1e544 100644 --- a/README.md +++ b/README.md @@ -207,3 +207,5 @@ Returns: - `total_donations_count` - `total_donors_count` - `total_recipients_count` + +.. From d4cab010ac8431140750bee03879ada2c9d00ecb Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Tue, 10 Feb 2026 23:32:31 +0500 Subject: [PATCH 429/454] Add sync endpoints for lists, donations, and accounts --- api/sync.py | 399 ++++++++++++++++++++++++++++++++++++++++++++++ api/urls.py | 35 ++++ donations/sync.py | 205 ++++++++++++++++++++++++ 3 files changed, 639 insertions(+) create mode 100644 api/sync.py create mode 100644 donations/sync.py diff --git a/api/sync.py b/api/sync.py new file mode 100644 index 0000000..537c5ee --- /dev/null +++ b/api/sync.py @@ -0,0 +1,399 @@ +""" +Sync endpoints - fetch data from blockchain RPC and store in database. + +Called by frontend after user creates/updates a list/registration or account profile. +Replaces the 24/7 indexer - now we fetch on-demand when user acts. + +Endpoints: + Lists: + POST /api/v1/lists/{list_id}/sync - Sync single list + POST /api/v1/lists/{list_id}/registrations/sync - Sync all registrations + POST /api/v1/lists/{list_id}/registrations/{registrant_id}/sync - Sync single registration + + Accounts: + POST /api/v1/accounts/{account_id}/sync - Sync account profile and recalculate stats +""" +import base64 +import json +import logging +from datetime import datetime + +import requests +from django.conf import settings +from drf_spectacular.utils import OpenApiResponse, extend_schema +from rest_framework.response import Response +from rest_framework.views import APIView + +from accounts.models import Account +from donations.models import Donation +from lists.models import List, ListRegistration + +logger = logging.getLogger(__name__) + +LISTS_CONTRACT = f"lists.{settings.POTLOCK_TLA}" + + +def fetch_from_rpc(method_name: str, args: dict = None, contract_id: str = None, timeout: int = 60): + """ + Fetch data from NEAR RPC with multiple fallbacks and retry logic. + + Order of attempts: + 1. Web4 RPC (most efficient) + 2. FastNear free RPC + 3. Official NEAR RPC (most reliable but rate-limited) + """ + from requests.adapters import HTTPAdapter + from urllib3.util.retry import Retry + + account_id = contract_id or LISTS_CONTRACT + + # Create session with retry logic + session = requests.Session() + retries = Retry(total=2, backoff_factor=0.5, status_forcelist=[502, 503, 504]) + session.mount("https://", HTTPAdapter(max_retries=retries)) + + # Try Web4 RPC first (skip for heavy methods that often fail) + heavy_methods = ["get_registrations_for_list"] + if method_name not in heavy_methods: + web4_url = f"{settings.FASTNEAR_RPC_URL}/account/{account_id}/view/{method_name}" + try: + response = session.post(web4_url, json=args or {}, timeout=timeout) + if response.status_code == 200: + return response.json() + logger.warning(f"web4 RPC returned status {response.status_code}") + except Exception as e: + logger.warning(f"web4 RPC failed: {e}") + + # RPC endpoints to try in order + rpc_endpoints = [ + "https://free.rpc.fastnear.com" if settings.ENVIRONMENT != "testnet" else "https://test.rpc.fastnear.com", + "https://rpc.mainnet.near.org" if settings.ENVIRONMENT != "testnet" else "https://rpc.testnet.near.org", + ] + + args_base64 = base64.b64encode(json.dumps(args or {}).encode()).decode() + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "query", + "params": { + "request_type": "call_function", + "account_id": account_id, + "method_name": method_name, + "args_base64": args_base64, + "finality": "optimistic" + } + } + + last_error = None + for rpc_url in rpc_endpoints: + try: + logger.info(f"Trying RPC: {rpc_url} for {method_name}") + response = session.post(rpc_url, json=payload, timeout=timeout) + result = response.json() + + if "error" in result: + logger.warning(f"RPC error from {rpc_url}: {result['error']}") + last_error = result["error"] + continue + + if "error" in result.get("result", {}): + logger.warning(f"Contract error from {rpc_url}: {result['result']['error']}") + last_error = result["result"]["error"] + continue + + # Decode result bytes to JSON + if "result" not in result.get("result", {}): + return None + result_bytes = bytes(result["result"]["result"]) + return json.loads(result_bytes.decode()) + + except requests.exceptions.Timeout: + logger.warning(f"RPC {rpc_url} timed out after {timeout}s") + last_error = f"Timeout after {timeout}s" + except Exception as e: + logger.warning(f"RPC {rpc_url} failed: {e}") + last_error = str(e) + + raise Exception(f"All RPC endpoints failed. Last error: {last_error}") + + +class ListSyncAPI(APIView): + """ + Sync a list from blockchain to database. + + Called by frontend after user creates a list. + Fetches current state from RPC, creates/updates in DB. + """ + + @extend_schema( + summary="Sync list from blockchain", + responses={ + 200: OpenApiResponse(description="List synced"), + 404: OpenApiResponse(description="List not found on chain"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, list_id: int): + try: + # Fetch from RPC + data = fetch_from_rpc("get_list", {"list_id": int(list_id)}) + + if not data: + return Response({"error": "List not found on chain"}, status=404) + + # Check if already exists + existing_list = List.objects.filter(on_chain_id=int(list_id)).first() + + if existing_list: + # Update existing list + existing_list.name = data["name"] + existing_list.description = data.get("description", "") + existing_list.cover_image_url = data.get("cover_image_url") + existing_list.admin_only_registrations = data.get("admin_only_registrations", False) + existing_list.default_registration_status = data.get("default_registration_status", "Pending") + existing_list.updated_at = datetime.fromtimestamp(data["updated_at"] / 1000) + existing_list.save() + + # Update admins + existing_list.admins.clear() + for admin_id in data.get("admins", []): + admin, _ = Account.objects.get_or_create(id=admin_id) + existing_list.admins.add(admin) + + return Response({ + "success": True, + "message": "List updated", + "on_chain_id": list_id + }) + + # Create list (on_chain_id is the blockchain ID, id is auto-generated) + list_obj = List.objects.create( + on_chain_id=data["id"], + owner_id=data["owner"], + name=data["name"], + description=data.get("description", ""), + cover_image_url=data.get("cover_image_url"), + admin_only_registrations=data.get("admin_only_registrations", False), + default_registration_status=data.get("default_registration_status", "Pending"), + created_at=datetime.fromtimestamp(data["created_at"] / 1000), + updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), + ) + + # Create owner account + Account.objects.get_or_create(id=data["owner"]) + + # Add admins + for admin_id in data.get("admins", []): + admin, _ = Account.objects.get_or_create(id=admin_id) + list_obj.admins.add(admin) + + return Response({ + "success": True, + "message": "List created", + "on_chain_id": list_obj.on_chain_id + }) + + except Exception as e: + logger.error(f"Error syncing list {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class ListRegistrationsSyncAPI(APIView): + """ + Sync all registrations for a list. + + Called after user registers to a list. + """ + + @extend_schema( + summary="Sync all registrations for a list", + responses={ + 200: OpenApiResponse(description="Registrations synced"), + 404: OpenApiResponse(description="List not found"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, list_id: int): + try: + # Ensure list exists + try: + list_obj = List.objects.get(on_chain_id=int(list_id)) + except List.DoesNotExist: + # Sync list first + list_sync = ListSyncAPI() + resp = list_sync.post(request, list_id) + if resp.status_code != 200: + return Response({"error": "List not found"}, status=404) + list_obj = List.objects.get(on_chain_id=int(list_id)) + + # Fetch registrations from RPC (use longer timeout for large lists) + registrations = fetch_from_rpc("get_registrations_for_list", {"list_id": int(list_id)}, timeout=120) + + if not registrations: + registrations = [] + + synced = 0 + for reg in registrations: + # Create accounts + Account.objects.get_or_create(id=reg["registrant_id"]) + Account.objects.get_or_create(id=reg.get("registered_by", reg["registrant_id"])) + + # Create/update registration (id is AutoField, use list+registrant as unique key) + ListRegistration.objects.update_or_create( + list=list_obj, + registrant_id=reg["registrant_id"], + defaults={ + "registered_by_id": reg.get("registered_by", reg["registrant_id"]), + "status": reg.get("status", "Pending"), + "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000), + "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), + "admin_notes": reg.get("admin_notes"), + "registrant_notes": reg.get("registrant_notes"), + } + ) + synced += 1 + + return Response({ + "success": True, + "message": f"Synced {synced} registrations", + "synced_count": synced + }) + + except Exception as e: + logger.error(f"Error syncing registrations for list {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class SingleRegistrationSyncAPI(APIView): + """ + Sync a single registration. + + More efficient than syncing all when you know the registrant. + """ + + @extend_schema( + summary="Sync single registration", + responses={ + 200: OpenApiResponse(description="Registration synced"), + 404: OpenApiResponse(description="Not found"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, list_id: int, registrant_id: str): + try: + # Ensure list exists + try: + list_obj = List.objects.get(on_chain_id=int(list_id)) + except List.DoesNotExist: + list_sync = ListSyncAPI() + resp = list_sync.post(request, list_id) + if resp.status_code != 200: + return Response({"error": "List not found"}, status=404) + list_obj = List.objects.get(on_chain_id=int(list_id)) + + # Fetch all registrations and filter (contract doesn't have single-registration lookup by registrant_id) + registrations = fetch_from_rpc("get_registrations_for_list", {"list_id": int(list_id)}, timeout=120) + + if not registrations: + return Response({"error": "No registrations found for list"}, status=404) + + # Find the specific registration + reg = next((r for r in registrations if r.get("registrant_id") == registrant_id), None) + + if not reg: + return Response({"error": "Registration not found"}, status=404) + + # Create accounts + Account.objects.get_or_create(id=reg["registrant_id"]) + Account.objects.get_or_create(id=reg.get("registered_by", reg["registrant_id"])) + + # Create/update registration (id is AutoField, use list+registrant as unique key) + registration, created = ListRegistration.objects.update_or_create( + list=list_obj, + registrant_id=reg["registrant_id"], + defaults={ + "registered_by_id": reg.get("registered_by", reg["registrant_id"]), + "status": reg.get("status", "Pending"), + "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000), + "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), + "admin_notes": reg.get("admin_notes"), + "registrant_notes": reg.get("registrant_notes"), + } + ) + + return Response({ + "success": True, + "message": "Registration synced", + "registrant_id": registrant_id, + "status": registration.status + }) + + except Exception as e: + logger.error(f"Error syncing registration: {e}") + return Response({"error": str(e)}, status=502) + + +class AccountSyncAPI(APIView): + """ + Sync account data and recalculate donation stats. + + Called by frontend after actions that affect an account's totals. + Fetches NEAR Social profile and recalculates donation totals from DB. + """ + + @extend_schema( + summary="Sync account and recalculate stats", + responses={ + 200: OpenApiResponse(description="Account synced"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, account_id: str): + from django.db.models import Sum, Count + + try: + account, created = Account.objects.get_or_create(id=account_id) + + # Fetch NEAR Social profile + try: + profile_data = fetch_from_rpc( + "get", + {"keys": [f"{account_id}/profile/**"]}, + contract_id="social.near" if settings.ENVIRONMENT != "testnet" else "v1.social08.testnet" + ) + if profile_data and account_id in profile_data: + account.near_social_profile_data = profile_data[account_id].get("profile", {}) + except Exception as e: + logger.warning(f"Failed to fetch social profile for {account_id}: {e}") + + # Recalculate donation totals from existing DB records + donations_received = Donation.objects.filter(recipient_id=account_id).aggregate( + total_usd=Sum('total_amount_usd'), + count=Count('id') + ) + donations_sent = Donation.objects.filter(donor_id=account_id).aggregate( + total_usd=Sum('total_amount_usd'), + count=Count('id') + ) + + account.total_donations_in_usd = donations_received['total_usd'] or 0 + account.total_donations_out_usd = donations_sent['total_usd'] or 0 + account.donors_count = Donation.objects.filter( + recipient_id=account_id + ).values('donor').distinct().count() + + account.save() + + return Response({ + "success": True, + "message": "Account synced", + "account_id": account_id, + "created": created, + "total_donations_in_usd": str(account.total_donations_in_usd), + "total_donations_out_usd": str(account.total_donations_out_usd), + "donors_count": account.donors_count + }) + + except Exception as e: + logger.error(f"Error syncing account {account_id}: {e}") + return Response({"error": str(e)}, status=502) diff --git a/api/urls.py b/api/urls.py index dfb3d44..3069476 100644 --- a/api/urls.py +++ b/api/urls.py @@ -17,8 +17,15 @@ AccountsListAPI, DonorsAPI, ) +from api.sync import ( + AccountSyncAPI, + ListSyncAPI, + ListRegistrationsSyncAPI, + SingleRegistrationSyncAPI, +) from base.api import StatsAPI, ReclaimProofRequestView from donations.api import DonationContractConfigAPI +from donations.sync import DirectDonationSyncAPI from grantpicks.api import AccountProjectListAPI, ProjectListAPI, ProjectRoundVotesAPI, ProjectStatsAPI, RoundApplicationsAPI, RoundDetailAPI, RoundsListAPI from lists.api import ( ListDetailAPI, @@ -168,4 +175,32 @@ MpdaoVoterDetailAPI.as_view(), name="mpdao_voter_detail", ), + # sync endpoints (for on-demand data fetching from blockchain) + path( + "v1/lists//sync", + ListSyncAPI.as_view(), + name="list_sync_api", + ), + path( + "v1/lists//registrations/sync", + ListRegistrationsSyncAPI.as_view(), + name="list_registrations_sync_api", + ), + path( + "v1/lists//registrations//sync", + SingleRegistrationSyncAPI.as_view(), + name="single_registration_sync_api", + ), + # direct donation sync + path( + "v1/donations/sync", + DirectDonationSyncAPI.as_view(), + name="direct_donation_sync_api", + ), + # account sync + path( + "v1/accounts//sync", + AccountSyncAPI.as_view(), + name="account_sync_api", + ), ] diff --git a/donations/sync.py b/donations/sync.py new file mode 100644 index 0000000..aaff2cf --- /dev/null +++ b/donations/sync.py @@ -0,0 +1,205 @@ +""" +Sync endpoints for direct donations - fetch data from blockchain RPC and store in database. + +Called by frontend after user makes a direct donation. + +Endpoints: + POST /api/v1/donations/sync - Sync single donation via tx_hash +""" +import base64 +import json +import logging +from datetime import datetime, timezone + +import requests +from django.conf import settings +from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema +from rest_framework.response import Response +from rest_framework.views import APIView + +from accounts.models import Account +from donations.models import Donation +from tokens.models import Token + +logger = logging.getLogger(__name__) + +DONATION_CONTRACT = f"donate.{settings.POTLOCK_TLA}" + + +def fetch_tx_result(tx_hash: str, sender_id: str): + """ + Fetch transaction result from NEAR RPC. + Returns the parsed result from the transaction execution. + """ + rpc_url = ( + "https://test.rpc.fastnear.com" + if settings.ENVIRONMENT == "testnet" + else "https://free.rpc.fastnear.com" + ) + + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "tx", + "params": { + "tx_hash": tx_hash, + "sender_account_id": sender_id, + "wait_until": "EXECUTED_OPTIMISTIC", + }, + } + + response = requests.post(rpc_url, json=payload, timeout=30) + result = response.json() + + if "error" in result: + raise Exception(f"RPC error fetching tx: {result['error']}") + + return result.get("result") + + +def parse_donation_from_tx(tx_result: dict) -> dict: + """ + Parse donation data from transaction execution result. + Looks through receipts_outcome to find the SuccessValue containing donation data. + """ + receipts_outcome = tx_result.get("receipts_outcome", []) + + for outcome in receipts_outcome: + status = outcome.get("outcome", {}).get("status", {}) + if isinstance(status, dict) and "SuccessValue" in status: + success_value = status["SuccessValue"] + if success_value: + try: + decoded = base64.b64decode(success_value).decode() + data = json.loads(decoded) + # Check if this looks like direct donation data + if isinstance(data, dict) and "donor_id" in data and "recipient_id" in data: + return data + except (json.JSONDecodeError, UnicodeDecodeError): + continue + + return None + + +class DirectDonationSyncAPI(APIView): + """ + Sync a direct donation from blockchain to database. + + Called by frontend after a user makes a direct donation. + Frontend passes the transaction hash, backend parses the donation from tx result. + """ + + @extend_schema( + summary="Sync a direct donation", + description="Sync a single direct donation using the transaction hash from the donation response.", + parameters=[ + OpenApiParameter( + name="tx_hash", + description="Transaction hash from the donation transaction", + required=True, + type=str, + ), + OpenApiParameter( + name="sender_id", + description="Account ID of the transaction sender (donor)", + required=True, + type=str, + ), + ], + responses={ + 200: OpenApiResponse(description="Donation synced"), + 400: OpenApiResponse(description="Missing required parameters"), + 404: OpenApiResponse(description="Donation not found in transaction"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request): + try: + # Get required parameters + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + + # Fetch transaction result and parse donation data + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) + + donation_data = parse_donation_from_tx(tx_result) + if not donation_data: + return Response( + {"error": "Could not parse donation from transaction result"}, + status=404, + ) + + # Upsert accounts + donor, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, id=donation_data["donor_id"] + ) + recipient, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, id=donation_data["recipient_id"] + ) + + referrer = None + if donation_data.get("referrer_id"): + referrer, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, id=donation_data["referrer_id"] + ) + + # Get or create token + token_id = donation_data.get("ft_id") or "near" + token_acct, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=token_id) + token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) + + # Parse timestamp + donated_at = datetime.fromtimestamp( + donation_data["donated_at_ms"] / 1000, tz=timezone.utc + ) + + # Calculate net_amount if not provided (total - protocol_fee - referrer_fee) + total_amount = int(donation_data["total_amount"]) + protocol_fee = int(donation_data.get("protocol_fee", 0)) + referrer_fee = int(donation_data.get("referrer_fee", 0) or 0) + net_amount = donation_data.get("net_amount") + if net_amount is None: + net_amount = total_amount - protocol_fee - referrer_fee + + # Create or update donation + donation_defaults = { + "donor": donor, + "recipient": recipient, + "token": token, + "total_amount": str(total_amount), + "net_amount": str(net_amount), + "message": donation_data.get("message"), + "donated_at": donated_at, + "protocol_fee": str(protocol_fee), + "referrer": referrer, + "referrer_fee": str(referrer_fee) if referrer_fee else None, + "matching_pool": False, + "tx_hash": tx_hash, + } + + donation, created = Donation.objects.update_or_create( + on_chain_id=donation_data["id"], + pot__isnull=True, # Direct donations have no pot + defaults=donation_defaults, + ) + + return Response( + { + "success": True, + "message": "Donation synced", + "donation_id": donation.on_chain_id, + "created": created, + } + ) + + except Exception as e: + logger.error(f"Error syncing direct donation: {e}") + return Response({"error": str(e)}, status=502) From 73dd6b896a8d5a7ea1ea3a289217e389f8d619e1 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Tue, 10 Feb 2026 23:53:55 +0500 Subject: [PATCH 430/454] Revert "Merge pull request #190 from PotLock/feature/sync-endpoints-prod" This reverts commit cc5253305ab53ddf0f1481b46feef1e349ab14af, reversing changes made to e78bcd95e841d09037e531bd42b606b2733309b7. --- api/sync.py | 399 ---------------------------------------------- api/urls.py | 35 ---- donations/sync.py | 205 ------------------------ 3 files changed, 639 deletions(-) delete mode 100644 api/sync.py delete mode 100644 donations/sync.py diff --git a/api/sync.py b/api/sync.py deleted file mode 100644 index 537c5ee..0000000 --- a/api/sync.py +++ /dev/null @@ -1,399 +0,0 @@ -""" -Sync endpoints - fetch data from blockchain RPC and store in database. - -Called by frontend after user creates/updates a list/registration or account profile. -Replaces the 24/7 indexer - now we fetch on-demand when user acts. - -Endpoints: - Lists: - POST /api/v1/lists/{list_id}/sync - Sync single list - POST /api/v1/lists/{list_id}/registrations/sync - Sync all registrations - POST /api/v1/lists/{list_id}/registrations/{registrant_id}/sync - Sync single registration - - Accounts: - POST /api/v1/accounts/{account_id}/sync - Sync account profile and recalculate stats -""" -import base64 -import json -import logging -from datetime import datetime - -import requests -from django.conf import settings -from drf_spectacular.utils import OpenApiResponse, extend_schema -from rest_framework.response import Response -from rest_framework.views import APIView - -from accounts.models import Account -from donations.models import Donation -from lists.models import List, ListRegistration - -logger = logging.getLogger(__name__) - -LISTS_CONTRACT = f"lists.{settings.POTLOCK_TLA}" - - -def fetch_from_rpc(method_name: str, args: dict = None, contract_id: str = None, timeout: int = 60): - """ - Fetch data from NEAR RPC with multiple fallbacks and retry logic. - - Order of attempts: - 1. Web4 RPC (most efficient) - 2. FastNear free RPC - 3. Official NEAR RPC (most reliable but rate-limited) - """ - from requests.adapters import HTTPAdapter - from urllib3.util.retry import Retry - - account_id = contract_id or LISTS_CONTRACT - - # Create session with retry logic - session = requests.Session() - retries = Retry(total=2, backoff_factor=0.5, status_forcelist=[502, 503, 504]) - session.mount("https://", HTTPAdapter(max_retries=retries)) - - # Try Web4 RPC first (skip for heavy methods that often fail) - heavy_methods = ["get_registrations_for_list"] - if method_name not in heavy_methods: - web4_url = f"{settings.FASTNEAR_RPC_URL}/account/{account_id}/view/{method_name}" - try: - response = session.post(web4_url, json=args or {}, timeout=timeout) - if response.status_code == 200: - return response.json() - logger.warning(f"web4 RPC returned status {response.status_code}") - except Exception as e: - logger.warning(f"web4 RPC failed: {e}") - - # RPC endpoints to try in order - rpc_endpoints = [ - "https://free.rpc.fastnear.com" if settings.ENVIRONMENT != "testnet" else "https://test.rpc.fastnear.com", - "https://rpc.mainnet.near.org" if settings.ENVIRONMENT != "testnet" else "https://rpc.testnet.near.org", - ] - - args_base64 = base64.b64encode(json.dumps(args or {}).encode()).decode() - payload = { - "jsonrpc": "2.0", - "id": "dontcare", - "method": "query", - "params": { - "request_type": "call_function", - "account_id": account_id, - "method_name": method_name, - "args_base64": args_base64, - "finality": "optimistic" - } - } - - last_error = None - for rpc_url in rpc_endpoints: - try: - logger.info(f"Trying RPC: {rpc_url} for {method_name}") - response = session.post(rpc_url, json=payload, timeout=timeout) - result = response.json() - - if "error" in result: - logger.warning(f"RPC error from {rpc_url}: {result['error']}") - last_error = result["error"] - continue - - if "error" in result.get("result", {}): - logger.warning(f"Contract error from {rpc_url}: {result['result']['error']}") - last_error = result["result"]["error"] - continue - - # Decode result bytes to JSON - if "result" not in result.get("result", {}): - return None - result_bytes = bytes(result["result"]["result"]) - return json.loads(result_bytes.decode()) - - except requests.exceptions.Timeout: - logger.warning(f"RPC {rpc_url} timed out after {timeout}s") - last_error = f"Timeout after {timeout}s" - except Exception as e: - logger.warning(f"RPC {rpc_url} failed: {e}") - last_error = str(e) - - raise Exception(f"All RPC endpoints failed. Last error: {last_error}") - - -class ListSyncAPI(APIView): - """ - Sync a list from blockchain to database. - - Called by frontend after user creates a list. - Fetches current state from RPC, creates/updates in DB. - """ - - @extend_schema( - summary="Sync list from blockchain", - responses={ - 200: OpenApiResponse(description="List synced"), - 404: OpenApiResponse(description="List not found on chain"), - 502: OpenApiResponse(description="RPC failed"), - } - ) - def post(self, request, list_id: int): - try: - # Fetch from RPC - data = fetch_from_rpc("get_list", {"list_id": int(list_id)}) - - if not data: - return Response({"error": "List not found on chain"}, status=404) - - # Check if already exists - existing_list = List.objects.filter(on_chain_id=int(list_id)).first() - - if existing_list: - # Update existing list - existing_list.name = data["name"] - existing_list.description = data.get("description", "") - existing_list.cover_image_url = data.get("cover_image_url") - existing_list.admin_only_registrations = data.get("admin_only_registrations", False) - existing_list.default_registration_status = data.get("default_registration_status", "Pending") - existing_list.updated_at = datetime.fromtimestamp(data["updated_at"] / 1000) - existing_list.save() - - # Update admins - existing_list.admins.clear() - for admin_id in data.get("admins", []): - admin, _ = Account.objects.get_or_create(id=admin_id) - existing_list.admins.add(admin) - - return Response({ - "success": True, - "message": "List updated", - "on_chain_id": list_id - }) - - # Create list (on_chain_id is the blockchain ID, id is auto-generated) - list_obj = List.objects.create( - on_chain_id=data["id"], - owner_id=data["owner"], - name=data["name"], - description=data.get("description", ""), - cover_image_url=data.get("cover_image_url"), - admin_only_registrations=data.get("admin_only_registrations", False), - default_registration_status=data.get("default_registration_status", "Pending"), - created_at=datetime.fromtimestamp(data["created_at"] / 1000), - updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), - ) - - # Create owner account - Account.objects.get_or_create(id=data["owner"]) - - # Add admins - for admin_id in data.get("admins", []): - admin, _ = Account.objects.get_or_create(id=admin_id) - list_obj.admins.add(admin) - - return Response({ - "success": True, - "message": "List created", - "on_chain_id": list_obj.on_chain_id - }) - - except Exception as e: - logger.error(f"Error syncing list {list_id}: {e}") - return Response({"error": str(e)}, status=502) - - -class ListRegistrationsSyncAPI(APIView): - """ - Sync all registrations for a list. - - Called after user registers to a list. - """ - - @extend_schema( - summary="Sync all registrations for a list", - responses={ - 200: OpenApiResponse(description="Registrations synced"), - 404: OpenApiResponse(description="List not found"), - 502: OpenApiResponse(description="RPC failed"), - } - ) - def post(self, request, list_id: int): - try: - # Ensure list exists - try: - list_obj = List.objects.get(on_chain_id=int(list_id)) - except List.DoesNotExist: - # Sync list first - list_sync = ListSyncAPI() - resp = list_sync.post(request, list_id) - if resp.status_code != 200: - return Response({"error": "List not found"}, status=404) - list_obj = List.objects.get(on_chain_id=int(list_id)) - - # Fetch registrations from RPC (use longer timeout for large lists) - registrations = fetch_from_rpc("get_registrations_for_list", {"list_id": int(list_id)}, timeout=120) - - if not registrations: - registrations = [] - - synced = 0 - for reg in registrations: - # Create accounts - Account.objects.get_or_create(id=reg["registrant_id"]) - Account.objects.get_or_create(id=reg.get("registered_by", reg["registrant_id"])) - - # Create/update registration (id is AutoField, use list+registrant as unique key) - ListRegistration.objects.update_or_create( - list=list_obj, - registrant_id=reg["registrant_id"], - defaults={ - "registered_by_id": reg.get("registered_by", reg["registrant_id"]), - "status": reg.get("status", "Pending"), - "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000), - "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), - "admin_notes": reg.get("admin_notes"), - "registrant_notes": reg.get("registrant_notes"), - } - ) - synced += 1 - - return Response({ - "success": True, - "message": f"Synced {synced} registrations", - "synced_count": synced - }) - - except Exception as e: - logger.error(f"Error syncing registrations for list {list_id}: {e}") - return Response({"error": str(e)}, status=502) - - -class SingleRegistrationSyncAPI(APIView): - """ - Sync a single registration. - - More efficient than syncing all when you know the registrant. - """ - - @extend_schema( - summary="Sync single registration", - responses={ - 200: OpenApiResponse(description="Registration synced"), - 404: OpenApiResponse(description="Not found"), - 502: OpenApiResponse(description="RPC failed"), - } - ) - def post(self, request, list_id: int, registrant_id: str): - try: - # Ensure list exists - try: - list_obj = List.objects.get(on_chain_id=int(list_id)) - except List.DoesNotExist: - list_sync = ListSyncAPI() - resp = list_sync.post(request, list_id) - if resp.status_code != 200: - return Response({"error": "List not found"}, status=404) - list_obj = List.objects.get(on_chain_id=int(list_id)) - - # Fetch all registrations and filter (contract doesn't have single-registration lookup by registrant_id) - registrations = fetch_from_rpc("get_registrations_for_list", {"list_id": int(list_id)}, timeout=120) - - if not registrations: - return Response({"error": "No registrations found for list"}, status=404) - - # Find the specific registration - reg = next((r for r in registrations if r.get("registrant_id") == registrant_id), None) - - if not reg: - return Response({"error": "Registration not found"}, status=404) - - # Create accounts - Account.objects.get_or_create(id=reg["registrant_id"]) - Account.objects.get_or_create(id=reg.get("registered_by", reg["registrant_id"])) - - # Create/update registration (id is AutoField, use list+registrant as unique key) - registration, created = ListRegistration.objects.update_or_create( - list=list_obj, - registrant_id=reg["registrant_id"], - defaults={ - "registered_by_id": reg.get("registered_by", reg["registrant_id"]), - "status": reg.get("status", "Pending"), - "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000), - "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), - "admin_notes": reg.get("admin_notes"), - "registrant_notes": reg.get("registrant_notes"), - } - ) - - return Response({ - "success": True, - "message": "Registration synced", - "registrant_id": registrant_id, - "status": registration.status - }) - - except Exception as e: - logger.error(f"Error syncing registration: {e}") - return Response({"error": str(e)}, status=502) - - -class AccountSyncAPI(APIView): - """ - Sync account data and recalculate donation stats. - - Called by frontend after actions that affect an account's totals. - Fetches NEAR Social profile and recalculates donation totals from DB. - """ - - @extend_schema( - summary="Sync account and recalculate stats", - responses={ - 200: OpenApiResponse(description="Account synced"), - 502: OpenApiResponse(description="RPC failed"), - } - ) - def post(self, request, account_id: str): - from django.db.models import Sum, Count - - try: - account, created = Account.objects.get_or_create(id=account_id) - - # Fetch NEAR Social profile - try: - profile_data = fetch_from_rpc( - "get", - {"keys": [f"{account_id}/profile/**"]}, - contract_id="social.near" if settings.ENVIRONMENT != "testnet" else "v1.social08.testnet" - ) - if profile_data and account_id in profile_data: - account.near_social_profile_data = profile_data[account_id].get("profile", {}) - except Exception as e: - logger.warning(f"Failed to fetch social profile for {account_id}: {e}") - - # Recalculate donation totals from existing DB records - donations_received = Donation.objects.filter(recipient_id=account_id).aggregate( - total_usd=Sum('total_amount_usd'), - count=Count('id') - ) - donations_sent = Donation.objects.filter(donor_id=account_id).aggregate( - total_usd=Sum('total_amount_usd'), - count=Count('id') - ) - - account.total_donations_in_usd = donations_received['total_usd'] or 0 - account.total_donations_out_usd = donations_sent['total_usd'] or 0 - account.donors_count = Donation.objects.filter( - recipient_id=account_id - ).values('donor').distinct().count() - - account.save() - - return Response({ - "success": True, - "message": "Account synced", - "account_id": account_id, - "created": created, - "total_donations_in_usd": str(account.total_donations_in_usd), - "total_donations_out_usd": str(account.total_donations_out_usd), - "donors_count": account.donors_count - }) - - except Exception as e: - logger.error(f"Error syncing account {account_id}: {e}") - return Response({"error": str(e)}, status=502) diff --git a/api/urls.py b/api/urls.py index 3069476..dfb3d44 100644 --- a/api/urls.py +++ b/api/urls.py @@ -17,15 +17,8 @@ AccountsListAPI, DonorsAPI, ) -from api.sync import ( - AccountSyncAPI, - ListSyncAPI, - ListRegistrationsSyncAPI, - SingleRegistrationSyncAPI, -) from base.api import StatsAPI, ReclaimProofRequestView from donations.api import DonationContractConfigAPI -from donations.sync import DirectDonationSyncAPI from grantpicks.api import AccountProjectListAPI, ProjectListAPI, ProjectRoundVotesAPI, ProjectStatsAPI, RoundApplicationsAPI, RoundDetailAPI, RoundsListAPI from lists.api import ( ListDetailAPI, @@ -175,32 +168,4 @@ MpdaoVoterDetailAPI.as_view(), name="mpdao_voter_detail", ), - # sync endpoints (for on-demand data fetching from blockchain) - path( - "v1/lists//sync", - ListSyncAPI.as_view(), - name="list_sync_api", - ), - path( - "v1/lists//registrations/sync", - ListRegistrationsSyncAPI.as_view(), - name="list_registrations_sync_api", - ), - path( - "v1/lists//registrations//sync", - SingleRegistrationSyncAPI.as_view(), - name="single_registration_sync_api", - ), - # direct donation sync - path( - "v1/donations/sync", - DirectDonationSyncAPI.as_view(), - name="direct_donation_sync_api", - ), - # account sync - path( - "v1/accounts//sync", - AccountSyncAPI.as_view(), - name="account_sync_api", - ), ] diff --git a/donations/sync.py b/donations/sync.py deleted file mode 100644 index aaff2cf..0000000 --- a/donations/sync.py +++ /dev/null @@ -1,205 +0,0 @@ -""" -Sync endpoints for direct donations - fetch data from blockchain RPC and store in database. - -Called by frontend after user makes a direct donation. - -Endpoints: - POST /api/v1/donations/sync - Sync single donation via tx_hash -""" -import base64 -import json -import logging -from datetime import datetime, timezone - -import requests -from django.conf import settings -from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema -from rest_framework.response import Response -from rest_framework.views import APIView - -from accounts.models import Account -from donations.models import Donation -from tokens.models import Token - -logger = logging.getLogger(__name__) - -DONATION_CONTRACT = f"donate.{settings.POTLOCK_TLA}" - - -def fetch_tx_result(tx_hash: str, sender_id: str): - """ - Fetch transaction result from NEAR RPC. - Returns the parsed result from the transaction execution. - """ - rpc_url = ( - "https://test.rpc.fastnear.com" - if settings.ENVIRONMENT == "testnet" - else "https://free.rpc.fastnear.com" - ) - - payload = { - "jsonrpc": "2.0", - "id": "dontcare", - "method": "tx", - "params": { - "tx_hash": tx_hash, - "sender_account_id": sender_id, - "wait_until": "EXECUTED_OPTIMISTIC", - }, - } - - response = requests.post(rpc_url, json=payload, timeout=30) - result = response.json() - - if "error" in result: - raise Exception(f"RPC error fetching tx: {result['error']}") - - return result.get("result") - - -def parse_donation_from_tx(tx_result: dict) -> dict: - """ - Parse donation data from transaction execution result. - Looks through receipts_outcome to find the SuccessValue containing donation data. - """ - receipts_outcome = tx_result.get("receipts_outcome", []) - - for outcome in receipts_outcome: - status = outcome.get("outcome", {}).get("status", {}) - if isinstance(status, dict) and "SuccessValue" in status: - success_value = status["SuccessValue"] - if success_value: - try: - decoded = base64.b64decode(success_value).decode() - data = json.loads(decoded) - # Check if this looks like direct donation data - if isinstance(data, dict) and "donor_id" in data and "recipient_id" in data: - return data - except (json.JSONDecodeError, UnicodeDecodeError): - continue - - return None - - -class DirectDonationSyncAPI(APIView): - """ - Sync a direct donation from blockchain to database. - - Called by frontend after a user makes a direct donation. - Frontend passes the transaction hash, backend parses the donation from tx result. - """ - - @extend_schema( - summary="Sync a direct donation", - description="Sync a single direct donation using the transaction hash from the donation response.", - parameters=[ - OpenApiParameter( - name="tx_hash", - description="Transaction hash from the donation transaction", - required=True, - type=str, - ), - OpenApiParameter( - name="sender_id", - description="Account ID of the transaction sender (donor)", - required=True, - type=str, - ), - ], - responses={ - 200: OpenApiResponse(description="Donation synced"), - 400: OpenApiResponse(description="Missing required parameters"), - 404: OpenApiResponse(description="Donation not found in transaction"), - 502: OpenApiResponse(description="RPC failed"), - }, - ) - def post(self, request): - try: - # Get required parameters - tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") - sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") - - if not tx_hash or not sender_id: - return Response( - {"error": "tx_hash and sender_id are required"}, - status=400, - ) - - # Fetch transaction result and parse donation data - tx_result = fetch_tx_result(tx_hash, sender_id) - if not tx_result: - return Response({"error": "Transaction not found"}, status=404) - - donation_data = parse_donation_from_tx(tx_result) - if not donation_data: - return Response( - {"error": "Could not parse donation from transaction result"}, - status=404, - ) - - # Upsert accounts - donor, _ = Account.objects.get_or_create( - defaults={"chain_id": 1}, id=donation_data["donor_id"] - ) - recipient, _ = Account.objects.get_or_create( - defaults={"chain_id": 1}, id=donation_data["recipient_id"] - ) - - referrer = None - if donation_data.get("referrer_id"): - referrer, _ = Account.objects.get_or_create( - defaults={"chain_id": 1}, id=donation_data["referrer_id"] - ) - - # Get or create token - token_id = donation_data.get("ft_id") or "near" - token_acct, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=token_id) - token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) - - # Parse timestamp - donated_at = datetime.fromtimestamp( - donation_data["donated_at_ms"] / 1000, tz=timezone.utc - ) - - # Calculate net_amount if not provided (total - protocol_fee - referrer_fee) - total_amount = int(donation_data["total_amount"]) - protocol_fee = int(donation_data.get("protocol_fee", 0)) - referrer_fee = int(donation_data.get("referrer_fee", 0) or 0) - net_amount = donation_data.get("net_amount") - if net_amount is None: - net_amount = total_amount - protocol_fee - referrer_fee - - # Create or update donation - donation_defaults = { - "donor": donor, - "recipient": recipient, - "token": token, - "total_amount": str(total_amount), - "net_amount": str(net_amount), - "message": donation_data.get("message"), - "donated_at": donated_at, - "protocol_fee": str(protocol_fee), - "referrer": referrer, - "referrer_fee": str(referrer_fee) if referrer_fee else None, - "matching_pool": False, - "tx_hash": tx_hash, - } - - donation, created = Donation.objects.update_or_create( - on_chain_id=donation_data["id"], - pot__isnull=True, # Direct donations have no pot - defaults=donation_defaults, - ) - - return Response( - { - "success": True, - "message": "Donation synced", - "donation_id": donation.on_chain_id, - "created": created, - } - ) - - except Exception as e: - logger.error(f"Error syncing direct donation: {e}") - return Response({"error": str(e)}, status=502) From e3c1086fd73bf78ba53c8f9483d47acd5ef35a3d Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Wed, 11 Feb 2026 00:41:21 +0500 Subject: [PATCH 431/454] Move broken reclaim_python_sdk import to actual method to prevent full app crashing --- base/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/api.py b/base/api.py index e0b1662..9af00c9 100644 --- a/base/api.py +++ b/base/api.py @@ -13,7 +13,6 @@ from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView -from reclaim_python_sdk import ReclaimProofRequest from accounts.models import Account from donations.models import Donation from pots.models import PotPayout @@ -115,6 +114,8 @@ class ReclaimProofRequestView(APIView): } ) def post(self, request: Request, *args, **kwargs): + from reclaim_python_sdk import ReclaimProofRequest + APP_ID = settings.RECLAIM_APP_ID APP_SECRET = settings.RECLAIM_APP_SECRET PROVIDER_ID = settings.RECLAIM_TWITTER_PROVIDER_ID From 3f582dd385b1c5561c7513d2dc910686b4616bfc Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Wed, 11 Feb 2026 00:51:13 +0500 Subject: [PATCH 432/454] Re-add sync endpoints for accounts, lists and direct donations --- api/sync.py | 399 ++++++++++++++++++++++++++++++++++++++++++++++ api/urls.py | 35 ++++ donations/sync.py | 205 ++++++++++++++++++++++++ 3 files changed, 639 insertions(+) create mode 100644 api/sync.py create mode 100644 donations/sync.py diff --git a/api/sync.py b/api/sync.py new file mode 100644 index 0000000..537c5ee --- /dev/null +++ b/api/sync.py @@ -0,0 +1,399 @@ +""" +Sync endpoints - fetch data from blockchain RPC and store in database. + +Called by frontend after user creates/updates a list/registration or account profile. +Replaces the 24/7 indexer - now we fetch on-demand when user acts. + +Endpoints: + Lists: + POST /api/v1/lists/{list_id}/sync - Sync single list + POST /api/v1/lists/{list_id}/registrations/sync - Sync all registrations + POST /api/v1/lists/{list_id}/registrations/{registrant_id}/sync - Sync single registration + + Accounts: + POST /api/v1/accounts/{account_id}/sync - Sync account profile and recalculate stats +""" +import base64 +import json +import logging +from datetime import datetime + +import requests +from django.conf import settings +from drf_spectacular.utils import OpenApiResponse, extend_schema +from rest_framework.response import Response +from rest_framework.views import APIView + +from accounts.models import Account +from donations.models import Donation +from lists.models import List, ListRegistration + +logger = logging.getLogger(__name__) + +LISTS_CONTRACT = f"lists.{settings.POTLOCK_TLA}" + + +def fetch_from_rpc(method_name: str, args: dict = None, contract_id: str = None, timeout: int = 60): + """ + Fetch data from NEAR RPC with multiple fallbacks and retry logic. + + Order of attempts: + 1. Web4 RPC (most efficient) + 2. FastNear free RPC + 3. Official NEAR RPC (most reliable but rate-limited) + """ + from requests.adapters import HTTPAdapter + from urllib3.util.retry import Retry + + account_id = contract_id or LISTS_CONTRACT + + # Create session with retry logic + session = requests.Session() + retries = Retry(total=2, backoff_factor=0.5, status_forcelist=[502, 503, 504]) + session.mount("https://", HTTPAdapter(max_retries=retries)) + + # Try Web4 RPC first (skip for heavy methods that often fail) + heavy_methods = ["get_registrations_for_list"] + if method_name not in heavy_methods: + web4_url = f"{settings.FASTNEAR_RPC_URL}/account/{account_id}/view/{method_name}" + try: + response = session.post(web4_url, json=args or {}, timeout=timeout) + if response.status_code == 200: + return response.json() + logger.warning(f"web4 RPC returned status {response.status_code}") + except Exception as e: + logger.warning(f"web4 RPC failed: {e}") + + # RPC endpoints to try in order + rpc_endpoints = [ + "https://free.rpc.fastnear.com" if settings.ENVIRONMENT != "testnet" else "https://test.rpc.fastnear.com", + "https://rpc.mainnet.near.org" if settings.ENVIRONMENT != "testnet" else "https://rpc.testnet.near.org", + ] + + args_base64 = base64.b64encode(json.dumps(args or {}).encode()).decode() + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "query", + "params": { + "request_type": "call_function", + "account_id": account_id, + "method_name": method_name, + "args_base64": args_base64, + "finality": "optimistic" + } + } + + last_error = None + for rpc_url in rpc_endpoints: + try: + logger.info(f"Trying RPC: {rpc_url} for {method_name}") + response = session.post(rpc_url, json=payload, timeout=timeout) + result = response.json() + + if "error" in result: + logger.warning(f"RPC error from {rpc_url}: {result['error']}") + last_error = result["error"] + continue + + if "error" in result.get("result", {}): + logger.warning(f"Contract error from {rpc_url}: {result['result']['error']}") + last_error = result["result"]["error"] + continue + + # Decode result bytes to JSON + if "result" not in result.get("result", {}): + return None + result_bytes = bytes(result["result"]["result"]) + return json.loads(result_bytes.decode()) + + except requests.exceptions.Timeout: + logger.warning(f"RPC {rpc_url} timed out after {timeout}s") + last_error = f"Timeout after {timeout}s" + except Exception as e: + logger.warning(f"RPC {rpc_url} failed: {e}") + last_error = str(e) + + raise Exception(f"All RPC endpoints failed. Last error: {last_error}") + + +class ListSyncAPI(APIView): + """ + Sync a list from blockchain to database. + + Called by frontend after user creates a list. + Fetches current state from RPC, creates/updates in DB. + """ + + @extend_schema( + summary="Sync list from blockchain", + responses={ + 200: OpenApiResponse(description="List synced"), + 404: OpenApiResponse(description="List not found on chain"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, list_id: int): + try: + # Fetch from RPC + data = fetch_from_rpc("get_list", {"list_id": int(list_id)}) + + if not data: + return Response({"error": "List not found on chain"}, status=404) + + # Check if already exists + existing_list = List.objects.filter(on_chain_id=int(list_id)).first() + + if existing_list: + # Update existing list + existing_list.name = data["name"] + existing_list.description = data.get("description", "") + existing_list.cover_image_url = data.get("cover_image_url") + existing_list.admin_only_registrations = data.get("admin_only_registrations", False) + existing_list.default_registration_status = data.get("default_registration_status", "Pending") + existing_list.updated_at = datetime.fromtimestamp(data["updated_at"] / 1000) + existing_list.save() + + # Update admins + existing_list.admins.clear() + for admin_id in data.get("admins", []): + admin, _ = Account.objects.get_or_create(id=admin_id) + existing_list.admins.add(admin) + + return Response({ + "success": True, + "message": "List updated", + "on_chain_id": list_id + }) + + # Create list (on_chain_id is the blockchain ID, id is auto-generated) + list_obj = List.objects.create( + on_chain_id=data["id"], + owner_id=data["owner"], + name=data["name"], + description=data.get("description", ""), + cover_image_url=data.get("cover_image_url"), + admin_only_registrations=data.get("admin_only_registrations", False), + default_registration_status=data.get("default_registration_status", "Pending"), + created_at=datetime.fromtimestamp(data["created_at"] / 1000), + updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), + ) + + # Create owner account + Account.objects.get_or_create(id=data["owner"]) + + # Add admins + for admin_id in data.get("admins", []): + admin, _ = Account.objects.get_or_create(id=admin_id) + list_obj.admins.add(admin) + + return Response({ + "success": True, + "message": "List created", + "on_chain_id": list_obj.on_chain_id + }) + + except Exception as e: + logger.error(f"Error syncing list {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class ListRegistrationsSyncAPI(APIView): + """ + Sync all registrations for a list. + + Called after user registers to a list. + """ + + @extend_schema( + summary="Sync all registrations for a list", + responses={ + 200: OpenApiResponse(description="Registrations synced"), + 404: OpenApiResponse(description="List not found"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, list_id: int): + try: + # Ensure list exists + try: + list_obj = List.objects.get(on_chain_id=int(list_id)) + except List.DoesNotExist: + # Sync list first + list_sync = ListSyncAPI() + resp = list_sync.post(request, list_id) + if resp.status_code != 200: + return Response({"error": "List not found"}, status=404) + list_obj = List.objects.get(on_chain_id=int(list_id)) + + # Fetch registrations from RPC (use longer timeout for large lists) + registrations = fetch_from_rpc("get_registrations_for_list", {"list_id": int(list_id)}, timeout=120) + + if not registrations: + registrations = [] + + synced = 0 + for reg in registrations: + # Create accounts + Account.objects.get_or_create(id=reg["registrant_id"]) + Account.objects.get_or_create(id=reg.get("registered_by", reg["registrant_id"])) + + # Create/update registration (id is AutoField, use list+registrant as unique key) + ListRegistration.objects.update_or_create( + list=list_obj, + registrant_id=reg["registrant_id"], + defaults={ + "registered_by_id": reg.get("registered_by", reg["registrant_id"]), + "status": reg.get("status", "Pending"), + "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000), + "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), + "admin_notes": reg.get("admin_notes"), + "registrant_notes": reg.get("registrant_notes"), + } + ) + synced += 1 + + return Response({ + "success": True, + "message": f"Synced {synced} registrations", + "synced_count": synced + }) + + except Exception as e: + logger.error(f"Error syncing registrations for list {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class SingleRegistrationSyncAPI(APIView): + """ + Sync a single registration. + + More efficient than syncing all when you know the registrant. + """ + + @extend_schema( + summary="Sync single registration", + responses={ + 200: OpenApiResponse(description="Registration synced"), + 404: OpenApiResponse(description="Not found"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, list_id: int, registrant_id: str): + try: + # Ensure list exists + try: + list_obj = List.objects.get(on_chain_id=int(list_id)) + except List.DoesNotExist: + list_sync = ListSyncAPI() + resp = list_sync.post(request, list_id) + if resp.status_code != 200: + return Response({"error": "List not found"}, status=404) + list_obj = List.objects.get(on_chain_id=int(list_id)) + + # Fetch all registrations and filter (contract doesn't have single-registration lookup by registrant_id) + registrations = fetch_from_rpc("get_registrations_for_list", {"list_id": int(list_id)}, timeout=120) + + if not registrations: + return Response({"error": "No registrations found for list"}, status=404) + + # Find the specific registration + reg = next((r for r in registrations if r.get("registrant_id") == registrant_id), None) + + if not reg: + return Response({"error": "Registration not found"}, status=404) + + # Create accounts + Account.objects.get_or_create(id=reg["registrant_id"]) + Account.objects.get_or_create(id=reg.get("registered_by", reg["registrant_id"])) + + # Create/update registration (id is AutoField, use list+registrant as unique key) + registration, created = ListRegistration.objects.update_or_create( + list=list_obj, + registrant_id=reg["registrant_id"], + defaults={ + "registered_by_id": reg.get("registered_by", reg["registrant_id"]), + "status": reg.get("status", "Pending"), + "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000), + "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), + "admin_notes": reg.get("admin_notes"), + "registrant_notes": reg.get("registrant_notes"), + } + ) + + return Response({ + "success": True, + "message": "Registration synced", + "registrant_id": registrant_id, + "status": registration.status + }) + + except Exception as e: + logger.error(f"Error syncing registration: {e}") + return Response({"error": str(e)}, status=502) + + +class AccountSyncAPI(APIView): + """ + Sync account data and recalculate donation stats. + + Called by frontend after actions that affect an account's totals. + Fetches NEAR Social profile and recalculates donation totals from DB. + """ + + @extend_schema( + summary="Sync account and recalculate stats", + responses={ + 200: OpenApiResponse(description="Account synced"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, account_id: str): + from django.db.models import Sum, Count + + try: + account, created = Account.objects.get_or_create(id=account_id) + + # Fetch NEAR Social profile + try: + profile_data = fetch_from_rpc( + "get", + {"keys": [f"{account_id}/profile/**"]}, + contract_id="social.near" if settings.ENVIRONMENT != "testnet" else "v1.social08.testnet" + ) + if profile_data and account_id in profile_data: + account.near_social_profile_data = profile_data[account_id].get("profile", {}) + except Exception as e: + logger.warning(f"Failed to fetch social profile for {account_id}: {e}") + + # Recalculate donation totals from existing DB records + donations_received = Donation.objects.filter(recipient_id=account_id).aggregate( + total_usd=Sum('total_amount_usd'), + count=Count('id') + ) + donations_sent = Donation.objects.filter(donor_id=account_id).aggregate( + total_usd=Sum('total_amount_usd'), + count=Count('id') + ) + + account.total_donations_in_usd = donations_received['total_usd'] or 0 + account.total_donations_out_usd = donations_sent['total_usd'] or 0 + account.donors_count = Donation.objects.filter( + recipient_id=account_id + ).values('donor').distinct().count() + + account.save() + + return Response({ + "success": True, + "message": "Account synced", + "account_id": account_id, + "created": created, + "total_donations_in_usd": str(account.total_donations_in_usd), + "total_donations_out_usd": str(account.total_donations_out_usd), + "donors_count": account.donors_count + }) + + except Exception as e: + logger.error(f"Error syncing account {account_id}: {e}") + return Response({"error": str(e)}, status=502) diff --git a/api/urls.py b/api/urls.py index dfb3d44..3069476 100644 --- a/api/urls.py +++ b/api/urls.py @@ -17,8 +17,15 @@ AccountsListAPI, DonorsAPI, ) +from api.sync import ( + AccountSyncAPI, + ListSyncAPI, + ListRegistrationsSyncAPI, + SingleRegistrationSyncAPI, +) from base.api import StatsAPI, ReclaimProofRequestView from donations.api import DonationContractConfigAPI +from donations.sync import DirectDonationSyncAPI from grantpicks.api import AccountProjectListAPI, ProjectListAPI, ProjectRoundVotesAPI, ProjectStatsAPI, RoundApplicationsAPI, RoundDetailAPI, RoundsListAPI from lists.api import ( ListDetailAPI, @@ -168,4 +175,32 @@ MpdaoVoterDetailAPI.as_view(), name="mpdao_voter_detail", ), + # sync endpoints (for on-demand data fetching from blockchain) + path( + "v1/lists//sync", + ListSyncAPI.as_view(), + name="list_sync_api", + ), + path( + "v1/lists//registrations/sync", + ListRegistrationsSyncAPI.as_view(), + name="list_registrations_sync_api", + ), + path( + "v1/lists//registrations//sync", + SingleRegistrationSyncAPI.as_view(), + name="single_registration_sync_api", + ), + # direct donation sync + path( + "v1/donations/sync", + DirectDonationSyncAPI.as_view(), + name="direct_donation_sync_api", + ), + # account sync + path( + "v1/accounts//sync", + AccountSyncAPI.as_view(), + name="account_sync_api", + ), ] diff --git a/donations/sync.py b/donations/sync.py new file mode 100644 index 0000000..aaff2cf --- /dev/null +++ b/donations/sync.py @@ -0,0 +1,205 @@ +""" +Sync endpoints for direct donations - fetch data from blockchain RPC and store in database. + +Called by frontend after user makes a direct donation. + +Endpoints: + POST /api/v1/donations/sync - Sync single donation via tx_hash +""" +import base64 +import json +import logging +from datetime import datetime, timezone + +import requests +from django.conf import settings +from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema +from rest_framework.response import Response +from rest_framework.views import APIView + +from accounts.models import Account +from donations.models import Donation +from tokens.models import Token + +logger = logging.getLogger(__name__) + +DONATION_CONTRACT = f"donate.{settings.POTLOCK_TLA}" + + +def fetch_tx_result(tx_hash: str, sender_id: str): + """ + Fetch transaction result from NEAR RPC. + Returns the parsed result from the transaction execution. + """ + rpc_url = ( + "https://test.rpc.fastnear.com" + if settings.ENVIRONMENT == "testnet" + else "https://free.rpc.fastnear.com" + ) + + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "tx", + "params": { + "tx_hash": tx_hash, + "sender_account_id": sender_id, + "wait_until": "EXECUTED_OPTIMISTIC", + }, + } + + response = requests.post(rpc_url, json=payload, timeout=30) + result = response.json() + + if "error" in result: + raise Exception(f"RPC error fetching tx: {result['error']}") + + return result.get("result") + + +def parse_donation_from_tx(tx_result: dict) -> dict: + """ + Parse donation data from transaction execution result. + Looks through receipts_outcome to find the SuccessValue containing donation data. + """ + receipts_outcome = tx_result.get("receipts_outcome", []) + + for outcome in receipts_outcome: + status = outcome.get("outcome", {}).get("status", {}) + if isinstance(status, dict) and "SuccessValue" in status: + success_value = status["SuccessValue"] + if success_value: + try: + decoded = base64.b64decode(success_value).decode() + data = json.loads(decoded) + # Check if this looks like direct donation data + if isinstance(data, dict) and "donor_id" in data and "recipient_id" in data: + return data + except (json.JSONDecodeError, UnicodeDecodeError): + continue + + return None + + +class DirectDonationSyncAPI(APIView): + """ + Sync a direct donation from blockchain to database. + + Called by frontend after a user makes a direct donation. + Frontend passes the transaction hash, backend parses the donation from tx result. + """ + + @extend_schema( + summary="Sync a direct donation", + description="Sync a single direct donation using the transaction hash from the donation response.", + parameters=[ + OpenApiParameter( + name="tx_hash", + description="Transaction hash from the donation transaction", + required=True, + type=str, + ), + OpenApiParameter( + name="sender_id", + description="Account ID of the transaction sender (donor)", + required=True, + type=str, + ), + ], + responses={ + 200: OpenApiResponse(description="Donation synced"), + 400: OpenApiResponse(description="Missing required parameters"), + 404: OpenApiResponse(description="Donation not found in transaction"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request): + try: + # Get required parameters + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + + # Fetch transaction result and parse donation data + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) + + donation_data = parse_donation_from_tx(tx_result) + if not donation_data: + return Response( + {"error": "Could not parse donation from transaction result"}, + status=404, + ) + + # Upsert accounts + donor, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, id=donation_data["donor_id"] + ) + recipient, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, id=donation_data["recipient_id"] + ) + + referrer = None + if donation_data.get("referrer_id"): + referrer, _ = Account.objects.get_or_create( + defaults={"chain_id": 1}, id=donation_data["referrer_id"] + ) + + # Get or create token + token_id = donation_data.get("ft_id") or "near" + token_acct, _ = Account.objects.get_or_create(defaults={"chain_id": 1}, id=token_id) + token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) + + # Parse timestamp + donated_at = datetime.fromtimestamp( + donation_data["donated_at_ms"] / 1000, tz=timezone.utc + ) + + # Calculate net_amount if not provided (total - protocol_fee - referrer_fee) + total_amount = int(donation_data["total_amount"]) + protocol_fee = int(donation_data.get("protocol_fee", 0)) + referrer_fee = int(donation_data.get("referrer_fee", 0) or 0) + net_amount = donation_data.get("net_amount") + if net_amount is None: + net_amount = total_amount - protocol_fee - referrer_fee + + # Create or update donation + donation_defaults = { + "donor": donor, + "recipient": recipient, + "token": token, + "total_amount": str(total_amount), + "net_amount": str(net_amount), + "message": donation_data.get("message"), + "donated_at": donated_at, + "protocol_fee": str(protocol_fee), + "referrer": referrer, + "referrer_fee": str(referrer_fee) if referrer_fee else None, + "matching_pool": False, + "tx_hash": tx_hash, + } + + donation, created = Donation.objects.update_or_create( + on_chain_id=donation_data["id"], + pot__isnull=True, # Direct donations have no pot + defaults=donation_defaults, + ) + + return Response( + { + "success": True, + "message": "Donation synced", + "donation_id": donation.on_chain_id, + "created": created, + } + ) + + except Exception as e: + logger.error(f"Error syncing direct donation: {e}") + return Response({"error": str(e)}, status=502) From 8de6a3d451bd9dd0da6e03a0a3bd439bf0a50de9 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Wed, 11 Feb 2026 18:58:02 +0500 Subject: [PATCH 433/454] Added pots sync endpoints --- api/sync.py | 514 +++++++++++++++++++++++++++++++++++++++++++++++++++- api/urls.py | 31 ++++ 2 files changed, 543 insertions(+), 2 deletions(-) diff --git a/api/sync.py b/api/sync.py index 537c5ee..e8fb8fe 100644 --- a/api/sync.py +++ b/api/sync.py @@ -20,13 +20,15 @@ import requests from django.conf import settings -from drf_spectacular.utils import OpenApiResponse, extend_schema +from drf_spectacular.utils import OpenApiExample, OpenApiResponse, extend_schema from rest_framework.response import Response from rest_framework.views import APIView from accounts.models import Account from donations.models import Donation from lists.models import List, ListRegistration +from pots.models import Pot, PotApplication, PotApplicationReview, PotFactory, PotPayout, PotPayoutChallenge, PotPayoutChallengeAdminResponse +from tokens.models import Token logger = logging.getLogger(__name__) @@ -53,7 +55,7 @@ def fetch_from_rpc(method_name: str, args: dict = None, contract_id: str = None, session.mount("https://", HTTPAdapter(max_retries=retries)) # Try Web4 RPC first (skip for heavy methods that often fail) - heavy_methods = ["get_registrations_for_list"] + heavy_methods = ["get_registrations_for_list", "get_donations", "get_applications", "get_payouts_challenges"] if method_name not in heavy_methods: web4_url = f"{settings.FASTNEAR_RPC_URL}/account/{account_id}/view/{method_name}" try: @@ -333,6 +335,514 @@ def post(self, request, list_id: int, registrant_id: str): return Response({"error": str(e)}, status=502) +class PotSyncAPI(APIView): + """ + Sync a pot (campaign) from blockchain to database. + + Called by frontend after user deploys or updates a pot. + Fetches current config from RPC via get_config, creates/updates in DB. + """ + + @extend_schema( + summary="Sync pot config from blockchain", + responses={ + 200: OpenApiResponse(description="Pot synced"), + 404: OpenApiResponse(description="Pot not found on chain"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, pot_id: str): + try: + # Fetch pot config from RPC (each pot is its own contract) + config = fetch_from_rpc("get_config", contract_id=pot_id) + + if not config: + return Response({"error": "Pot not found on chain"}, status=404) + + # Upsert accounts + Account.objects.get_or_create(id=pot_id) + owner, _ = Account.objects.get_or_create(id=config["owner"]) + + chef = None + if config.get("chef"): + chef, _ = Account.objects.get_or_create(id=config["chef"]) + + # Derive pot factory from pot account ID (e.g. mypot.v1.potfactory.potlock.near -> v1.potfactory.potlock.near) + factory_id = ".".join(pot_id.split(".")[1:]) if "." in pot_id else pot_id + factory_account, _ = Account.objects.get_or_create(id=factory_id) + + # Check if pot already exists + existing_pot = Pot.objects.filter(account_id=pot_id).first() + + if existing_pot: + # Update existing pot config + existing_pot.owner = owner + existing_pot.chef = chef + existing_pot.name = config.get("pot_name", "") + existing_pot.description = config.get("pot_description", "") + existing_pot.max_approved_applicants = config.get("max_projects", 0) + existing_pot.base_currency = config.get("base_currency", "near") + existing_pot.application_start = datetime.fromtimestamp(config["application_start_ms"] / 1000) + existing_pot.application_end = datetime.fromtimestamp(config["application_end_ms"] / 1000) + existing_pot.matching_round_start = datetime.fromtimestamp(config["public_round_start_ms"] / 1000) + existing_pot.matching_round_end = datetime.fromtimestamp(config["public_round_end_ms"] / 1000) + existing_pot.registry_provider = config.get("registry_provider") + existing_pot.min_matching_pool_donation_amount = config.get("min_matching_pool_donation_amount", "0") + existing_pot.sybil_wrapper_provider = config.get("sybil_wrapper_provider") + existing_pot.custom_sybil_checks = config.get("custom_sybil_checks") + existing_pot.custom_min_threshold_score = config.get("custom_min_threshold_score") + existing_pot.referral_fee_matching_pool_basis_points = config["referral_fee_matching_pool_basis_points"] + existing_pot.referral_fee_public_round_basis_points = config["referral_fee_public_round_basis_points"] + existing_pot.chef_fee_basis_points = config["chef_fee_basis_points"] + if config.get("cooldown_end_ms"): + existing_pot.cooldown_end = datetime.fromtimestamp(config["cooldown_end_ms"] / 1000) + existing_pot.all_paid_out = config.get("all_paid_out", False) + existing_pot.protocol_config_provider = config.get("protocol_config_provider") + existing_pot.save() + + # Update admins + existing_pot.admins.clear() + for admin_id in config.get("admins", []): + admin, _ = Account.objects.get_or_create(id=admin_id) + existing_pot.admins.add(admin) + + return Response({ + "success": True, + "message": "Pot config updated", + "pot_id": pot_id + }) + else: + # Ensure pot factory exists + if not PotFactory.objects.filter(account_id=factory_id).exists(): + return Response({ + "error": f"Pot factory {factory_id} not found in database. Sync the factory first." + }, status=404) + + # Create new pot + pot = Pot.objects.create( + account_id=pot_id, + pot_factory_id=factory_id, + deployer=owner, + deployed_at=datetime.now(), + source_metadata=config.get("source_metadata", {}), + owner=owner, + chef=chef, + name=config.get("pot_name", ""), + description=config.get("pot_description", ""), + max_approved_applicants=config.get("max_projects", 0), + base_currency=config.get("base_currency", "near"), + application_start=datetime.fromtimestamp(config["application_start_ms"] / 1000), + application_end=datetime.fromtimestamp(config["application_end_ms"] / 1000), + matching_round_start=datetime.fromtimestamp(config["public_round_start_ms"] / 1000), + matching_round_end=datetime.fromtimestamp(config["public_round_end_ms"] / 1000), + registry_provider=config.get("registry_provider"), + min_matching_pool_donation_amount=config.get("min_matching_pool_donation_amount", "0"), + sybil_wrapper_provider=config.get("sybil_wrapper_provider"), + custom_sybil_checks=config.get("custom_sybil_checks"), + custom_min_threshold_score=config.get("custom_min_threshold_score"), + referral_fee_matching_pool_basis_points=config["referral_fee_matching_pool_basis_points"], + referral_fee_public_round_basis_points=config["referral_fee_public_round_basis_points"], + chef_fee_basis_points=config["chef_fee_basis_points"], + total_matching_pool=config.get("matching_pool_balance", "0"), + total_matching_pool_usd=None, + matching_pool_balance=config.get("matching_pool_balance", "0"), + matching_pool_donations_count=config.get("matching_pool_donations_count", 0), + total_public_donations=config.get("total_public_donations", "0"), + public_donations_count=config.get("public_donations_count", 0), + cooldown_period_ms=config.get("cooldown_period_ms"), + all_paid_out=config.get("all_paid_out", False), + protocol_config_provider=config.get("protocol_config_provider"), + ) + + # Add admins + for admin_id in config.get("admins", []): + admin, _ = Account.objects.get_or_create(id=admin_id) + pot.admins.add(admin) + + return Response({ + "success": True, + "message": "Pot created", + "pot_id": pot_id + }) + + except Exception as e: + logger.error(f"Error syncing pot {pot_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class PotDonationsSyncAPI(APIView): + """ + Sync all donations for a pot from blockchain. + + Called by frontend after a user donates to a pot. + Fetches donations via get_donations RPC call. + """ + + @extend_schema( + summary="Sync all donations for a pot", + responses={ + 200: OpenApiResponse(description="Donations synced"), + 404: OpenApiResponse(description="Pot not found"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, pot_id: str): + try: + # Ensure pot exists in DB + pot = Pot.objects.filter(account_id=pot_id).first() + if not pot: + pot_sync = PotSyncAPI() + resp = pot_sync.post(request, pot_id) + if resp.status_code != 200: + return Response({"error": "Pot not found"}, status=404) + pot = Pot.objects.get(account_id=pot_id) + + # Fetch donations from RPC (use longer timeout for pots with many donations) + donations = fetch_from_rpc("get_donations", contract_id=pot_id, timeout=120) + + if not donations: + donations = [] + + # Get or create NEAR token + near_account, _ = Account.objects.get_or_create(id="near") + near_token, _ = Token.objects.get_or_create( + account=near_account, + defaults={"name": "NEAR", "symbol": "NEAR", "decimals": 24} + ) + + synced = 0 + for don in donations: + # Upsert accounts + donor, _ = Account.objects.get_or_create(id=don["donor_id"]) + + recipient = None + if don.get("project_id"): + recipient, _ = Account.objects.get_or_create(id=don["project_id"]) + elif don.get("recipient_id"): + recipient, _ = Account.objects.get_or_create(id=don["recipient_id"]) + + referrer = None + if don.get("referrer_id"): + referrer, _ = Account.objects.get_or_create(id=don["referrer_id"]) + + chef = None + if don.get("chef_id"): + chef, _ = Account.objects.get_or_create(id=don["chef_id"]) + + # Calculate net_amount if not present + total_amount = don.get("total_amount", "0") + net_amount = don.get("net_amount") + if not net_amount or net_amount == "0": + protocol_fee = int(don.get("protocol_fee", "0")) + referrer_fee = int(don.get("referrer_fee") or "0") + chef_fee = int(don.get("chef_fee") or "0") + net_amount = str(int(total_amount) - protocol_fee - referrer_fee - chef_fee) + + # Determine token + ft_id = don.get("ft_id", "near") + if ft_id == "near": + token = near_token + else: + ft_account, _ = Account.objects.get_or_create(id=ft_id) + token, _ = Token.objects.get_or_create( + account=ft_account, + defaults={"name": ft_id, "decimals": 24} + ) + + donated_at = datetime.fromtimestamp( + (don.get("donated_at") or don.get("donated_at_ms", 0)) / 1000 + ) + + Donation.objects.update_or_create( + on_chain_id=don["id"], + pot=pot, + defaults={ + "donor": donor, + "total_amount": total_amount, + "net_amount": net_amount, + "token": token, + "matching_pool": don.get("matching_pool", False), + "message": don.get("message"), + "donated_at": donated_at, + "recipient": recipient, + "protocol_fee": don.get("protocol_fee", "0"), + "referrer": referrer, + "referrer_fee": don.get("referrer_fee"), + "chef": chef, + "chef_fee": don.get("chef_fee"), + } + ) + synced += 1 + + return Response({ + "success": True, + "message": f"Synced {synced} donations", + "synced_count": synced + }) + + except Exception as e: + logger.error(f"Error syncing donations for pot {pot_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class PotApplicationsSyncAPI(APIView): + """ + Sync all applications for a pot from blockchain. + + Called by frontend after a user applies to a pot or an application is reviewed. + Fetches applications via get_applications RPC call. + """ + + @extend_schema( + summary="Sync all applications for a pot", + responses={ + 200: OpenApiResponse(description="Applications synced"), + 404: OpenApiResponse(description="Pot not found"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, pot_id: str): + try: + # Ensure pot exists + pot = Pot.objects.filter(account_id=pot_id).first() + if not pot: + pot_sync = PotSyncAPI() + resp = pot_sync.post(request, pot_id) + if resp.status_code != 200: + return Response({"error": "Pot not found"}, status=404) + pot = Pot.objects.get(account_id=pot_id) + + # Fetch applications from RPC + applications = fetch_from_rpc("get_applications", contract_id=pot_id, timeout=120) + + if not applications: + applications = [] + + synced = 0 + for app in applications: + # Upsert applicant account + applicant, _ = Account.objects.get_or_create(id=app["project_id"]) + + submitted_at = datetime.fromtimestamp(app["submitted_at"] / 1000) + updated_at = datetime.fromtimestamp(app["updated_at"] / 1000) if app.get("updated_at") else submitted_at + + application, created = PotApplication.objects.update_or_create( + pot=pot, + applicant=applicant, + defaults={ + "message": app.get("message", ""), + "status": app.get("status", "Pending"), + "submitted_at": submitted_at, + "updated_at": updated_at, + } + ) + + # Sync review notes if present + if app.get("review_notes") and app.get("status") != "Pending": + reviewer_id = app.get("reviewed_by", pot.owner_id) + reviewer, _ = Account.objects.get_or_create(id=reviewer_id) + PotApplicationReview.objects.update_or_create( + application=application, + reviewer=reviewer, + reviewed_at=updated_at, + defaults={ + "notes": app.get("review_notes"), + "status": app["status"], + } + ) + + synced += 1 + + return Response({ + "success": True, + "message": f"Synced {synced} applications", + "synced_count": synced + }) + + except Exception as e: + logger.error(f"Error syncing applications for pot {pot_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class PotPayoutsSyncAPI(APIView): + """ + Sync all payouts for a pot from blockchain. + + Called by frontend after chef sets payouts or payouts are transferred. + Payouts are included in the get_config response. + """ + + @extend_schema( + summary="Sync all payouts for a pot", + responses={ + 200: OpenApiResponse(description="Payouts synced"), + 404: OpenApiResponse(description="Pot not found"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, pot_id: str): + try: + # Ensure pot exists + pot = Pot.objects.filter(account_id=pot_id).first() + if not pot: + pot_sync = PotSyncAPI() + resp = pot_sync.post(request, pot_id) + if resp.status_code != 200: + return Response({"error": "Pot not found"}, status=404) + pot = Pot.objects.get(account_id=pot_id) + + # Fetch config from RPC (payouts are included in config) + config = fetch_from_rpc("get_config", contract_id=pot_id) + + if not config: + return Response({"error": "Pot config not found on chain"}, status=404) + + # Get payouts from config + payouts = config.get("payouts", []) + + if not payouts: + return Response({ + "success": True, + "message": "No payouts set yet", + "synced_count": 0 + }) + + # Get or create NEAR token + near_account, _ = Account.objects.get_or_create(id="near") + near_token, _ = Token.objects.get_or_create( + account=near_account, + defaults={"name": "NEAR", "symbol": "NEAR", "decimals": 24} + ) + + # Also update pot's cooldown_end and all_paid_out from config + if config.get("cooldown_end_ms"): + pot.cooldown_end = datetime.fromtimestamp(config["cooldown_end_ms"] / 1000) + pot.all_paid_out = config.get("all_paid_out", False) + pot.save() + + synced = 0 + for payout in payouts: + # Upsert recipient account + recipient, _ = Account.objects.get_or_create(id=payout["project_id"]) + + # Parse paid_at if present + paid_at = None + if payout.get("paid_at"): + paid_at = datetime.fromtimestamp(payout["paid_at"] / 1000) + + PotPayout.objects.update_or_create( + pot=pot, + recipient=recipient, + defaults={ + "amount": payout["amount"], + "token": near_token, + "paid_at": paid_at, + } + ) + synced += 1 + + return Response({ + "success": True, + "message": f"Synced {synced} payouts", + "synced_count": synced, + "all_paid_out": pot.all_paid_out + }) + + except Exception as e: + logger.error(f"Error syncing payouts for pot {pot_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class PotPayoutChallengesSyncAPI(APIView): + """ + Sync payout challenges for a pot from blockchain. + + Called by frontend after a user challenges payouts. + Fetches challenges via get_payouts_challenges RPC call. + """ + + @extend_schema( + summary="Sync payout challenges for a pot", + responses={ + 200: OpenApiResponse(description="Challenges synced"), + 404: OpenApiResponse(description="Pot not found"), + 502: OpenApiResponse(description="RPC failed"), + } + ) + def post(self, request, pot_id: str): + try: + # Ensure pot exists + pot = Pot.objects.filter(account_id=pot_id).first() + if not pot: + pot_sync = PotSyncAPI() + resp = pot_sync.post(request, pot_id) + if resp.status_code != 200: + return Response({"error": "Pot not found"}, status=404) + pot = Pot.objects.get(account_id=pot_id) + + # Fetch challenges from RPC (paginated) + all_challenges = [] + from_index = 0 + limit = 300 + + while True: + challenges = fetch_from_rpc( + "get_payouts_challenges", + {"from_index": from_index, "limit": limit}, + contract_id=pot_id, + timeout=120 + ) + + if not challenges: + break + + all_challenges.extend(challenges) + + if len(challenges) < limit: + break + from_index += limit + + synced = 0 + for c in all_challenges: + challenger, _ = Account.objects.get_or_create(id=c["challenger_id"]) + + created_at = datetime.fromtimestamp(c["created_at"] / 1000) + + challenge, _ = PotPayoutChallenge.objects.update_or_create( + pot=pot, + challenger=challenger, + defaults={ + "message": c.get("reason", ""), + "created_at": created_at, + } + ) + + # If there's an admin response, create it + if c.get("admin_notes") or c.get("resolved"): + PotPayoutChallengeAdminResponse.objects.update_or_create( + challenger=challenger, + pot=pot, + defaults={ + "admin": pot.owner, + "created_at": created_at, + "message": c.get("admin_notes", ""), + "resolved": c.get("resolved", False), + } + ) + + synced += 1 + + return Response({ + "success": True, + "message": f"Synced {synced} payout challenges", + "synced_count": synced + }) + + except Exception as e: + logger.error(f"Error syncing payout challenges for pot {pot_id}: {e}") + return Response({"error": str(e)}, status=502) + + class AccountSyncAPI(APIView): """ Sync account data and recalculate donation stats. diff --git a/api/urls.py b/api/urls.py index 3069476..0dff287 100644 --- a/api/urls.py +++ b/api/urls.py @@ -22,6 +22,11 @@ ListSyncAPI, ListRegistrationsSyncAPI, SingleRegistrationSyncAPI, + PotSyncAPI, + PotDonationsSyncAPI, + PotApplicationsSyncAPI, + PotPayoutsSyncAPI, + PotPayoutChallengesSyncAPI, ) from base.api import StatsAPI, ReclaimProofRequestView from donations.api import DonationContractConfigAPI @@ -197,6 +202,32 @@ DirectDonationSyncAPI.as_view(), name="direct_donation_sync_api", ), + # pot sync endpoints + path( + "v1/pots//sync", + PotSyncAPI.as_view(), + name="pot_sync_api", + ), + path( + "v1/pots//donations/sync", + PotDonationsSyncAPI.as_view(), + name="pot_donations_sync_api", + ), + path( + "v1/pots//applications/sync", + PotApplicationsSyncAPI.as_view(), + name="pot_applications_sync_api", + ), + path( + "v1/pots//payouts/sync", + PotPayoutsSyncAPI.as_view(), + name="pot_payouts_sync_api", + ), + path( + "v1/pots//challenges/sync", + PotPayoutChallengesSyncAPI.as_view(), + name="pot_challenges_sync_api", + ), # account sync path( "v1/accounts//sync", From 9fac5e0ba2c892991ba070eee92b860732cd7738 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Wed, 18 Feb 2026 23:34:29 +0500 Subject: [PATCH 434/454] Added campaign deletion,refund and unescrow sync endpoints --- api/urls.py | 18 ++++ campaigns/sync.py | 252 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 269 insertions(+), 1 deletion(-) diff --git a/api/urls.py b/api/urls.py index f49d5df..4fc6c26 100644 --- a/api/urls.py +++ b/api/urls.py @@ -39,6 +39,9 @@ from campaigns.sync import ( CampaignSyncAPI, CampaignDonationSyncAPI, + CampaignDeleteSyncAPI, + CampaignRefundSyncAPI, + CampaignUnescrowSyncAPI, ) from donations.api import DonationContractConfigAPI from donations.sync import DirectDonationSyncAPI @@ -164,6 +167,21 @@ CampaignDonationSyncAPI.as_view(), name="campaign_donation_sync_api", ), + path( + "v1/campaigns//delete/sync", + CampaignDeleteSyncAPI.as_view(), + name="campaign_delete_sync_api", + ), + path( + "v1/campaigns//refunds/sync", + CampaignRefundSyncAPI.as_view(), + name="campaign_refund_sync_api", + ), + path( + "v1/campaigns//unescrow/sync", + CampaignUnescrowSyncAPI.as_view(), + name="campaign_unescrow_sync_api", + ), # donors path("v1/donors", DonorsAPI.as_view(), name="donors_api"), # lists diff --git a/campaigns/sync.py b/campaigns/sync.py index e1ad42a..b6f651d 100644 --- a/campaigns/sync.py +++ b/campaigns/sync.py @@ -1,12 +1,16 @@ """ Sync endpoints - fetch data from blockchain RPC and store in database. -Called by frontend after user creates/updates a campaign or donates. +Called by frontend after user creates/updates/deletes a campaign, donates, +or processes refunds/escrowed donations. Replaces the 24/7 indexer - now we fetch on-demand when user acts. Endpoints: POST /api/v1/campaigns/{campaign_id}/sync - Sync single campaign POST /api/v1/campaigns/{campaign_id}/donations/sync - Sync single donation via tx_hash + POST /api/v1/campaigns/{campaign_id}/delete/sync - Sync campaign deletion via tx_hash + POST /api/v1/campaigns/{campaign_id}/refunds/sync - Sync donation refunds via tx_hash + POST /api/v1/campaigns/{campaign_id}/unescrow/sync - Sync donation unescrow via tx_hash """ import base64 import json @@ -106,6 +110,27 @@ def fetch_tx_result(tx_hash: str, sender_id: str): return result.get("result") +def parse_events_from_tx(tx_result: dict, event_name: str) -> list[dict]: + + events = [] + receipts_outcome = tx_result.get("receipts_outcome", []) + + for outcome in receipts_outcome: + logs = outcome.get("outcome", {}).get("logs", []) + for log in logs: + if not log.startswith("EVENT_JSON:"): + continue + try: + parsed = json.loads(log[len("EVENT_JSON:"):]) + if parsed.get("event") == event_name: + for data_item in parsed.get("data", []): + events.append(data_item) + except (json.JSONDecodeError, KeyError): + continue + + return events + + def parse_donation_from_tx(tx_result: dict) -> dict: """ Parse donation data from transaction execution result. @@ -397,3 +422,228 @@ def post(self, request, campaign_id: int): except Exception as e: logger.error(f"Error syncing donation for campaign {campaign_id}: {e}") return Response({"error": str(e)}, status=502) + + +class CampaignDeleteSyncAPI(APIView): + + + @extend_schema( + summary="Sync campaign deletion from blockchain via tx_hash", + parameters=[ + OpenApiParameter(name="tx_hash", required=True, type=str), + OpenApiParameter(name="sender_id", required=True, type=str), + ], + responses={ + 200: OpenApiResponse(description="Campaign deleted from DB"), + 400: OpenApiResponse(description="Missing parameters or no delete event found"), + 404: OpenApiResponse(description="Campaign not found in DB"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request, campaign_id: int): + try: + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + + # Fetch transaction and parse campaign_delete events + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) + + delete_events = parse_events_from_tx(tx_result, "campaign_delete") + + # Find the delete event for this specific campaign + matching_event = None + for event in delete_events: + if event.get("campaign_id") == int(campaign_id): + matching_event = event + break + + if not matching_event: + return Response( + {"error": f"No campaign_delete event found for campaign {campaign_id} in this transaction"}, + status=400, + ) + + # Event verified — delete from DB + deleted_count, _ = Campaign.objects.filter(on_chain_id=int(campaign_id)).delete() + + if deleted_count > 0: + logger.info(f"Campaign {campaign_id} deleted from DB (verified via tx {tx_hash})") + return Response( + { + "success": True, + "message": "Campaign deleted", + "on_chain_id": campaign_id, + } + ) + + return Response({"error": "Campaign not found in database"}, status=404) + + except Exception as e: + logger.error(f"Error syncing campaign deletion {campaign_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class CampaignRefundSyncAPI(APIView): + + @extend_schema( + summary="Sync donation refunds from blockchain via tx_hash", + parameters=[ + OpenApiParameter(name="tx_hash", required=True, type=str), + OpenApiParameter(name="sender_id", required=True, type=str), + ], + responses={ + 200: OpenApiResponse(description="Refunds synced"), + 400: OpenApiResponse(description="Missing parameters or no refund event found"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request, campaign_id: int): + try: + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) + + refund_events = parse_events_from_tx(tx_result, "escrow_refund") + + # Find refund events for this campaign + matching_events = [ + e for e in refund_events if e.get("campaign_id") == int(campaign_id) + ] + + if not matching_events: + return Response( + {"error": f"No escrow_refund event found for campaign {campaign_id} in this transaction"}, + status=400, + ) + + total_refunded = 0 + now = datetime.now(tz=timezone.utc) + + for event_data in matching_events: + donation_ids = event_data.get("donations", []) + + # Mark donations as refunded (mirrors handle_campaign_donation_refund) + updated_count = CampaignDonation.objects.filter( + on_chain_id__in=donation_ids, campaign__on_chain_id=int(campaign_id) + ).update(returned_at=now) + + total_refunded += updated_count + + # Update campaign escrow balance and totals + try: + campaign = Campaign.objects.get(on_chain_id=int(campaign_id)) + escrow_balance = event_data.get("escrow_balance", "0") + campaign.escrow_balance = str( + int(campaign.escrow_balance) - int(escrow_balance) + ) + + refunded_donations = CampaignDonation.objects.filter( + on_chain_id__in=donation_ids, campaign__on_chain_id=int(campaign_id) + ).values_list("total_amount", "net_amount") + + total_amount_refunded = sum(int(d[0]) for d in refunded_donations) + net_amount_refunded = sum(int(d[1]) for d in refunded_donations) + + campaign.total_raised_amount = str( + int(campaign.total_raised_amount) - total_amount_refunded + ) + campaign.net_raised_amount = str( + int(campaign.net_raised_amount) - net_amount_refunded + ) + campaign.save() + + except Campaign.DoesNotExist: + logger.error(f"Campaign {campaign_id} not found for refund update") + + logger.info(f"Synced {total_refunded} refunds for campaign {campaign_id} (tx {tx_hash})") + return Response( + { + "success": True, + "message": f"{total_refunded} donation(s) marked as refunded", + "refunded_count": total_refunded, + } + ) + + except Exception as e: + logger.error(f"Error syncing refunds for campaign {campaign_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class CampaignUnescrowSyncAPI(APIView): + + @extend_schema( + summary="Sync donation unescrow from blockchain via tx_hash", + parameters=[ + OpenApiParameter(name="tx_hash", required=True, type=str), + OpenApiParameter(name="sender_id", required=True, type=str), + ], + responses={ + 200: OpenApiResponse(description="Unescrow synced"), + 400: OpenApiResponse(description="Missing parameters or no unescrow event found"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request, campaign_id: int): + try: + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) + + unescrow_events = parse_events_from_tx(tx_result, "escrow_process") + + if not unescrow_events: + return Response( + {"error": "No escrow_process event found in this transaction"}, + status=400, + ) + + total_unescrowed = 0 + + for event_data in unescrow_events: + donation_ids = event_data.get("donation_ids", []) + + # Mark donations as unescrowed (mirrors handle_campaign_donation_unescrowed) + updated_count = CampaignDonation.objects.filter( + on_chain_id__in=donation_ids + ).update(escrowed=False) + + total_unescrowed += updated_count + + logger.info(f"Synced {total_unescrowed} unescrows for campaign {campaign_id} (tx {tx_hash})") + return Response( + { + "success": True, + "message": f"{total_unescrowed} donation(s) marked as unescrowed", + "unescrowed_count": total_unescrowed, + } + ) + + except Exception as e: + logger.error(f"Error syncing unescrow for campaign {campaign_id}: {e}") + return Response({"error": str(e)}, status=502) From 88004f34841b046950644c3c163be246799bc41d Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Wed, 11 Feb 2026 18:58:02 +0500 Subject: [PATCH 435/454] Added pots sync endpoints --- api/urls.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/api/urls.py b/api/urls.py index 4fc6c26..b42c5b1 100644 --- a/api/urls.py +++ b/api/urls.py @@ -290,6 +290,32 @@ PotPayoutChallengesSyncAPI.as_view(), name="pot_challenges_sync_api", ), + # pot sync endpoints + path( + "v1/pots//sync", + PotSyncAPI.as_view(), + name="pot_sync_api", + ), + path( + "v1/pots//donations/sync", + PotDonationsSyncAPI.as_view(), + name="pot_donations_sync_api", + ), + path( + "v1/pots//applications/sync", + PotApplicationsSyncAPI.as_view(), + name="pot_applications_sync_api", + ), + path( + "v1/pots//payouts/sync", + PotPayoutsSyncAPI.as_view(), + name="pot_payouts_sync_api", + ), + path( + "v1/pots//challenges/sync", + PotPayoutChallengesSyncAPI.as_view(), + name="pot_challenges_sync_api", + ), # account sync path( "v1/accounts//sync", From 2a71801664cb9510026d2de2d642b10176db0c09 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Fri, 20 Feb 2026 10:58:25 +0500 Subject: [PATCH 436/454] Added list deletion and upvote/remove upvote sync endpoints --- api/sync.py | 272 +++++++++++++++++++++++++++++++++++++++++++++++++++- api/urls.py | 18 ++++ 2 files changed, 288 insertions(+), 2 deletions(-) diff --git a/api/sync.py b/api/sync.py index e8fb8fe..23c2b00 100644 --- a/api/sync.py +++ b/api/sync.py @@ -9,6 +9,9 @@ POST /api/v1/lists/{list_id}/sync - Sync single list POST /api/v1/lists/{list_id}/registrations/sync - Sync all registrations POST /api/v1/lists/{list_id}/registrations/{registrant_id}/sync - Sync single registration + POST /api/v1/lists/{list_id}/delete/sync - Sync list deletion via tx_hash + POST /api/v1/lists/{list_id}/upvote/sync - Sync list upvote via tx_hash + POST /api/v1/lists/{list_id}/remove-upvote/sync - Sync list remove-upvote via tx_hash Accounts: POST /api/v1/accounts/{account_id}/sync - Sync account profile and recalculate stats @@ -20,13 +23,14 @@ import requests from django.conf import settings -from drf_spectacular.utils import OpenApiExample, OpenApiResponse, extend_schema +from drf_spectacular.utils import OpenApiExample, OpenApiParameter, OpenApiResponse, extend_schema from rest_framework.response import Response from rest_framework.views import APIView from accounts.models import Account +from activities.models import Activity from donations.models import Donation -from lists.models import List, ListRegistration +from lists.models import List, ListRegistration, ListUpvote from pots.models import Pot, PotApplication, PotApplicationReview, PotFactory, PotPayout, PotPayoutChallenge, PotPayoutChallengeAdminResponse from tokens.models import Token @@ -119,6 +123,58 @@ def fetch_from_rpc(method_name: str, args: dict = None, contract_id: str = None, raise Exception(f"All RPC endpoints failed. Last error: {last_error}") +def fetch_tx_result(tx_hash: str, sender_id: str): + """ + Fetch transaction result from NEAR RPC. + Returns the parsed result from the transaction execution. + """ + rpc_url = ( + "https://test.rpc.fastnear.com" + if settings.ENVIRONMENT == "testnet" + else "https://free.rpc.fastnear.com" + ) + + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "tx", + "params": { + "tx_hash": tx_hash, + "sender_account_id": sender_id, + "wait_until": "EXECUTED_OPTIMISTIC", + }, + } + + response = requests.post(rpc_url, json=payload, timeout=30) + result = response.json() + + if "error" in result: + raise Exception(f"RPC error fetching tx: {result['error']}") + + return result.get("result") + + +def parse_events_from_tx(tx_result: dict, event_name: str) -> list[dict]: + """Parse EVENT_JSON logs from transaction receipts matching a specific event name.""" + events = [] + receipts_outcome = tx_result.get("receipts_outcome", []) + + for outcome in receipts_outcome: + logs = outcome.get("outcome", {}).get("logs", []) + for log in logs: + if not log.startswith("EVENT_JSON:"): + continue + try: + parsed = json.loads(log[len("EVENT_JSON:"):]) + if parsed.get("event") == event_name: + for data_item in parsed.get("data", []): + events.append(data_item) + except (json.JSONDecodeError, KeyError): + continue + + return events + + class ListSyncAPI(APIView): """ Sync a list from blockchain to database. @@ -843,6 +899,218 @@ def post(self, request, pot_id: str): return Response({"error": str(e)}, status=502) +class ListDeleteSyncAPI(APIView): + """ + Sync a list deletion from blockchain via tx_hash. + + Called by frontend after user deletes a list on-chain. + Fetches the transaction, parses the delete_list EVENT_JSON, and deletes from DB. + """ + + @extend_schema( + summary="Sync list deletion from blockchain via tx_hash", + parameters=[ + OpenApiParameter(name="tx_hash", required=True, type=str), + OpenApiParameter(name="sender_id", required=True, type=str), + ], + responses={ + 200: OpenApiResponse(description="List deleted from DB"), + 400: OpenApiResponse(description="Missing parameters or no delete event found"), + 404: OpenApiResponse(description="List not found in DB"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request, list_id: int): + try: + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + + # Fetch transaction and parse delete_list events + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) + + delete_events = parse_events_from_tx(tx_result, "delete_list") + + # Find the delete event for this specific list + matching_event = None + for event in delete_events: + if event.get("list_id") == int(list_id): + matching_event = event + break + + if not matching_event: + return Response( + {"error": f"No delete_list event found for list {list_id} in this transaction"}, + status=400, + ) + + # Event verified — delete from DB + deleted_count, _ = List.objects.filter(on_chain_id=int(list_id)).delete() + + if deleted_count > 0: + logger.info(f"List {list_id} deleted from DB (verified via tx {tx_hash})") + return Response( + { + "success": True, + "message": "List deleted", + "on_chain_id": list_id, + } + ) + + return Response({"error": "List not found in database"}, status=404) + + except Exception as e: + logger.error(f"Error syncing list deletion {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class ListUpvoteSyncAPI(APIView): + """ + Sync a list upvote from blockchain via tx_hash. + + Called by frontend after user upvotes a list on-chain. + Fetches the transaction to verify it succeeded, then creates the upvote record. + """ + + @extend_schema( + summary="Sync list upvote from blockchain via tx_hash", + parameters=[ + OpenApiParameter(name="tx_hash", required=True, type=str), + OpenApiParameter(name="sender_id", required=True, type=str), + ], + responses={ + 200: OpenApiResponse(description="Upvote synced"), + 400: OpenApiResponse(description="Missing parameters"), + 404: OpenApiResponse(description="List not found"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request, list_id: int): + try: + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + + # Fetch transaction to verify it succeeded + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) + + # Find the list + try: + list_obj = List.objects.get(on_chain_id=int(list_id)) + except List.DoesNotExist: + return Response({"error": "List not found in database"}, status=404) + + # Create account if needed + account, _ = Account.objects.get_or_create(id=sender_id) + + # Create or update the upvote + now = datetime.now() + ListUpvote.objects.update_or_create( + list=list_obj, + account=account, + defaults={"created_at": now}, + ) + + # Create activity record + Activity.objects.update_or_create( + action_result={"list_id": int(list_id)}, + type="Upvote", + defaults={ + "signer": account, + "receiver_id": LISTS_CONTRACT, + "timestamp": now, + "tx_hash": tx_hash, + }, + ) + + logger.info(f"List {list_id} upvoted by {sender_id} (verified via tx {tx_hash})") + return Response( + { + "success": True, + "message": "Upvote synced", + "on_chain_id": list_id, + } + ) + + except Exception as e: + logger.error(f"Error syncing list upvote {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class ListRemoveUpvoteSyncAPI(APIView): + """ + Sync a list remove-upvote from blockchain via tx_hash. + + Called by frontend after user removes their upvote from a list on-chain. + Fetches the transaction to verify it succeeded, then deletes the upvote record. + """ + + @extend_schema( + summary="Sync list remove-upvote from blockchain via tx_hash", + parameters=[ + OpenApiParameter(name="tx_hash", required=True, type=str), + OpenApiParameter(name="sender_id", required=True, type=str), + ], + responses={ + 200: OpenApiResponse(description="Upvote removed"), + 400: OpenApiResponse(description="Missing parameters"), + 404: OpenApiResponse(description="List not found"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request, list_id: int): + try: + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + + # Fetch transaction to verify it succeeded + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) + + # Find the list + try: + list_obj = List.objects.get(on_chain_id=int(list_id)) + except List.DoesNotExist: + return Response({"error": "List not found in database"}, status=404) + + # Delete the upvote + ListUpvote.objects.filter(list=list_obj, account_id=sender_id).delete() + + logger.info(f"Upvote removed from list {list_id} by {sender_id} (verified via tx {tx_hash})") + return Response( + { + "success": True, + "message": "Upvote removed", + "on_chain_id": list_id, + } + ) + + except Exception as e: + logger.error(f"Error syncing list remove-upvote {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + class AccountSyncAPI(APIView): """ Sync account data and recalculate donation stats. diff --git a/api/urls.py b/api/urls.py index b42c5b1..a7bd348 100644 --- a/api/urls.py +++ b/api/urls.py @@ -22,6 +22,9 @@ ListSyncAPI, ListRegistrationsSyncAPI, SingleRegistrationSyncAPI, + ListDeleteSyncAPI, + ListUpvoteSyncAPI, + ListRemoveUpvoteSyncAPI, PotSyncAPI, PotDonationsSyncAPI, PotApplicationsSyncAPI, @@ -290,6 +293,21 @@ PotPayoutChallengesSyncAPI.as_view(), name="pot_challenges_sync_api", ), + path( + "v1/lists//delete/sync", + ListDeleteSyncAPI.as_view(), + name="list_delete_sync_api", + ), + path( + "v1/lists//upvote/sync", + ListUpvoteSyncAPI.as_view(), + name="list_upvote_sync_api", + ), + path( + "v1/lists//remove-upvote/sync", + ListRemoveUpvoteSyncAPI.as_view(), + name="list_remove_upvote_sync_api", + ), # pot sync endpoints path( "v1/pots//sync", From de462bec44aa9a18e0178922260c67a1b58f1e44 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Fri, 20 Feb 2026 10:58:25 +0500 Subject: [PATCH 437/454] Added list deletion and upvote/remove upvote sync endpoints --- api/sync.py | 272 +++++++++++++++++++++++++++++++++++++++++++++++++++- api/urls.py | 18 ++++ 2 files changed, 288 insertions(+), 2 deletions(-) diff --git a/api/sync.py b/api/sync.py index e8fb8fe..23c2b00 100644 --- a/api/sync.py +++ b/api/sync.py @@ -9,6 +9,9 @@ POST /api/v1/lists/{list_id}/sync - Sync single list POST /api/v1/lists/{list_id}/registrations/sync - Sync all registrations POST /api/v1/lists/{list_id}/registrations/{registrant_id}/sync - Sync single registration + POST /api/v1/lists/{list_id}/delete/sync - Sync list deletion via tx_hash + POST /api/v1/lists/{list_id}/upvote/sync - Sync list upvote via tx_hash + POST /api/v1/lists/{list_id}/remove-upvote/sync - Sync list remove-upvote via tx_hash Accounts: POST /api/v1/accounts/{account_id}/sync - Sync account profile and recalculate stats @@ -20,13 +23,14 @@ import requests from django.conf import settings -from drf_spectacular.utils import OpenApiExample, OpenApiResponse, extend_schema +from drf_spectacular.utils import OpenApiExample, OpenApiParameter, OpenApiResponse, extend_schema from rest_framework.response import Response from rest_framework.views import APIView from accounts.models import Account +from activities.models import Activity from donations.models import Donation -from lists.models import List, ListRegistration +from lists.models import List, ListRegistration, ListUpvote from pots.models import Pot, PotApplication, PotApplicationReview, PotFactory, PotPayout, PotPayoutChallenge, PotPayoutChallengeAdminResponse from tokens.models import Token @@ -119,6 +123,58 @@ def fetch_from_rpc(method_name: str, args: dict = None, contract_id: str = None, raise Exception(f"All RPC endpoints failed. Last error: {last_error}") +def fetch_tx_result(tx_hash: str, sender_id: str): + """ + Fetch transaction result from NEAR RPC. + Returns the parsed result from the transaction execution. + """ + rpc_url = ( + "https://test.rpc.fastnear.com" + if settings.ENVIRONMENT == "testnet" + else "https://free.rpc.fastnear.com" + ) + + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "tx", + "params": { + "tx_hash": tx_hash, + "sender_account_id": sender_id, + "wait_until": "EXECUTED_OPTIMISTIC", + }, + } + + response = requests.post(rpc_url, json=payload, timeout=30) + result = response.json() + + if "error" in result: + raise Exception(f"RPC error fetching tx: {result['error']}") + + return result.get("result") + + +def parse_events_from_tx(tx_result: dict, event_name: str) -> list[dict]: + """Parse EVENT_JSON logs from transaction receipts matching a specific event name.""" + events = [] + receipts_outcome = tx_result.get("receipts_outcome", []) + + for outcome in receipts_outcome: + logs = outcome.get("outcome", {}).get("logs", []) + for log in logs: + if not log.startswith("EVENT_JSON:"): + continue + try: + parsed = json.loads(log[len("EVENT_JSON:"):]) + if parsed.get("event") == event_name: + for data_item in parsed.get("data", []): + events.append(data_item) + except (json.JSONDecodeError, KeyError): + continue + + return events + + class ListSyncAPI(APIView): """ Sync a list from blockchain to database. @@ -843,6 +899,218 @@ def post(self, request, pot_id: str): return Response({"error": str(e)}, status=502) +class ListDeleteSyncAPI(APIView): + """ + Sync a list deletion from blockchain via tx_hash. + + Called by frontend after user deletes a list on-chain. + Fetches the transaction, parses the delete_list EVENT_JSON, and deletes from DB. + """ + + @extend_schema( + summary="Sync list deletion from blockchain via tx_hash", + parameters=[ + OpenApiParameter(name="tx_hash", required=True, type=str), + OpenApiParameter(name="sender_id", required=True, type=str), + ], + responses={ + 200: OpenApiResponse(description="List deleted from DB"), + 400: OpenApiResponse(description="Missing parameters or no delete event found"), + 404: OpenApiResponse(description="List not found in DB"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request, list_id: int): + try: + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + + # Fetch transaction and parse delete_list events + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) + + delete_events = parse_events_from_tx(tx_result, "delete_list") + + # Find the delete event for this specific list + matching_event = None + for event in delete_events: + if event.get("list_id") == int(list_id): + matching_event = event + break + + if not matching_event: + return Response( + {"error": f"No delete_list event found for list {list_id} in this transaction"}, + status=400, + ) + + # Event verified — delete from DB + deleted_count, _ = List.objects.filter(on_chain_id=int(list_id)).delete() + + if deleted_count > 0: + logger.info(f"List {list_id} deleted from DB (verified via tx {tx_hash})") + return Response( + { + "success": True, + "message": "List deleted", + "on_chain_id": list_id, + } + ) + + return Response({"error": "List not found in database"}, status=404) + + except Exception as e: + logger.error(f"Error syncing list deletion {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class ListUpvoteSyncAPI(APIView): + """ + Sync a list upvote from blockchain via tx_hash. + + Called by frontend after user upvotes a list on-chain. + Fetches the transaction to verify it succeeded, then creates the upvote record. + """ + + @extend_schema( + summary="Sync list upvote from blockchain via tx_hash", + parameters=[ + OpenApiParameter(name="tx_hash", required=True, type=str), + OpenApiParameter(name="sender_id", required=True, type=str), + ], + responses={ + 200: OpenApiResponse(description="Upvote synced"), + 400: OpenApiResponse(description="Missing parameters"), + 404: OpenApiResponse(description="List not found"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request, list_id: int): + try: + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + + # Fetch transaction to verify it succeeded + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) + + # Find the list + try: + list_obj = List.objects.get(on_chain_id=int(list_id)) + except List.DoesNotExist: + return Response({"error": "List not found in database"}, status=404) + + # Create account if needed + account, _ = Account.objects.get_or_create(id=sender_id) + + # Create or update the upvote + now = datetime.now() + ListUpvote.objects.update_or_create( + list=list_obj, + account=account, + defaults={"created_at": now}, + ) + + # Create activity record + Activity.objects.update_or_create( + action_result={"list_id": int(list_id)}, + type="Upvote", + defaults={ + "signer": account, + "receiver_id": LISTS_CONTRACT, + "timestamp": now, + "tx_hash": tx_hash, + }, + ) + + logger.info(f"List {list_id} upvoted by {sender_id} (verified via tx {tx_hash})") + return Response( + { + "success": True, + "message": "Upvote synced", + "on_chain_id": list_id, + } + ) + + except Exception as e: + logger.error(f"Error syncing list upvote {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class ListRemoveUpvoteSyncAPI(APIView): + """ + Sync a list remove-upvote from blockchain via tx_hash. + + Called by frontend after user removes their upvote from a list on-chain. + Fetches the transaction to verify it succeeded, then deletes the upvote record. + """ + + @extend_schema( + summary="Sync list remove-upvote from blockchain via tx_hash", + parameters=[ + OpenApiParameter(name="tx_hash", required=True, type=str), + OpenApiParameter(name="sender_id", required=True, type=str), + ], + responses={ + 200: OpenApiResponse(description="Upvote removed"), + 400: OpenApiResponse(description="Missing parameters"), + 404: OpenApiResponse(description="List not found"), + 502: OpenApiResponse(description="RPC failed"), + }, + ) + def post(self, request, list_id: int): + try: + tx_hash = request.data.get("tx_hash") or request.query_params.get("tx_hash") + sender_id = request.data.get("sender_id") or request.query_params.get("sender_id") + + if not tx_hash or not sender_id: + return Response( + {"error": "tx_hash and sender_id are required"}, + status=400, + ) + + # Fetch transaction to verify it succeeded + tx_result = fetch_tx_result(tx_hash, sender_id) + if not tx_result: + return Response({"error": "Transaction not found"}, status=404) + + # Find the list + try: + list_obj = List.objects.get(on_chain_id=int(list_id)) + except List.DoesNotExist: + return Response({"error": "List not found in database"}, status=404) + + # Delete the upvote + ListUpvote.objects.filter(list=list_obj, account_id=sender_id).delete() + + logger.info(f"Upvote removed from list {list_id} by {sender_id} (verified via tx {tx_hash})") + return Response( + { + "success": True, + "message": "Upvote removed", + "on_chain_id": list_id, + } + ) + + except Exception as e: + logger.error(f"Error syncing list remove-upvote {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + class AccountSyncAPI(APIView): """ Sync account data and recalculate donation stats. diff --git a/api/urls.py b/api/urls.py index 0dff287..03425a4 100644 --- a/api/urls.py +++ b/api/urls.py @@ -22,6 +22,9 @@ ListSyncAPI, ListRegistrationsSyncAPI, SingleRegistrationSyncAPI, + ListDeleteSyncAPI, + ListUpvoteSyncAPI, + ListRemoveUpvoteSyncAPI, PotSyncAPI, PotDonationsSyncAPI, PotApplicationsSyncAPI, @@ -202,6 +205,21 @@ DirectDonationSyncAPI.as_view(), name="direct_donation_sync_api", ), + path( + "v1/lists//delete/sync", + ListDeleteSyncAPI.as_view(), + name="list_delete_sync_api", + ), + path( + "v1/lists//upvote/sync", + ListUpvoteSyncAPI.as_view(), + name="list_upvote_sync_api", + ), + path( + "v1/lists//remove-upvote/sync", + ListRemoveUpvoteSyncAPI.as_view(), + name="list_remove_upvote_sync_api", + ), # pot sync endpoints path( "v1/pots//sync", From 7ce5c6991aec092e5b29c70d87bb274d7f6f6c9f Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Sun, 8 Mar 2026 21:19:40 +0500 Subject: [PATCH 438/454] Implemented all grantpicks sync endpoints --- api/urls.py | 23 +++ grantpicks/sync.py | 367 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 390 insertions(+) create mode 100644 grantpicks/sync.py diff --git a/api/urls.py b/api/urls.py index 03425a4..8ba0958 100644 --- a/api/urls.py +++ b/api/urls.py @@ -35,6 +35,18 @@ from donations.api import DonationContractConfigAPI from donations.sync import DirectDonationSyncAPI from grantpicks.api import AccountProjectListAPI, ProjectListAPI, ProjectRoundVotesAPI, ProjectStatsAPI, RoundApplicationsAPI, RoundDetailAPI, RoundsListAPI +from grantpicks.sync import ( + ProjectSyncAPI, + ProjectsListSyncAPI, + RoundSyncAPI, + RoundsListSyncAPI, + RoundApplicationsSyncAPI, + ApplicationReviewSyncAPI, + ApprovedProjectsSyncAPI, + RoundDepositsSyncAPI, + RoundVotesSyncAPI, + RoundPayoutsSyncAPI, +) from lists.api import ( ListDetailAPI, ListRandomRegistrationAPI, @@ -172,6 +184,17 @@ ), path("v1//projects", AccountProjectListAPI.as_view(), name="user_projects_api"), path("v1//project-stats", ProjectStatsAPI.as_view(), name="projects_stat__api"), + # grantpicks sync + path("v1/projects//sync", ProjectSyncAPI.as_view(), name="project_sync_api"), + path("v1/projects/sync", ProjectsListSyncAPI.as_view(), name="projects_list_sync_api"), + path("v1/rounds//sync", RoundSyncAPI.as_view(), name="round_sync_api"), + path("v1/rounds/sync", RoundsListSyncAPI.as_view(), name="rounds_list_sync_api"), + path("v1/rounds//applications/sync", RoundApplicationsSyncAPI.as_view(), name="round_applications_sync_api"), + path("v1/rounds//applications/review/sync", ApplicationReviewSyncAPI.as_view(), name="application_review_sync_api"), + path("v1/rounds//approved-projects/sync", ApprovedProjectsSyncAPI.as_view(), name="approved_projects_sync_api"), + path("v1/rounds//deposits/sync", RoundDepositsSyncAPI.as_view(), name="round_deposits_sync_api"), + path("v1/rounds//votes/sync", RoundVotesSyncAPI.as_view(), name="round_votes_sync_api"), + path("v1/rounds//payouts/sync", RoundPayoutsSyncAPI.as_view(), name="round_payouts_sync_api"), path( "v1/mpdao/voters", diff --git a/grantpicks/sync.py b/grantpicks/sync.py new file mode 100644 index 0000000..3768fdf --- /dev/null +++ b/grantpicks/sync.py @@ -0,0 +1,367 @@ +""" +Sync endpoints for grantpicks - fetch data from Stellar RPC and store in database. + +Called by frontend after user performs on-chain actions (create/update project, round, application). + +Endpoints: + POST /api/v1/projects/{project_id}/sync - Sync single project from chain + POST /api/v1/projects/sync - Sync multiple projects from chain + POST /api/v1/rounds/{round_id}/sync - Sync single round from chain + POST /api/v1/rounds/sync - Sync multiple rounds from chain + POST /api/v1/rounds/{round_id}/applications/sync - Sync applications for a round from chain + POST /api/v1/rounds/{round_id}/applications/review/sync - Sync application review from chain (requires applicant_id + reviewer_id) + POST /api/v1/rounds/{round_id}/approved-projects/sync - Sync approved projects for a round from chain + POST /api/v1/rounds/{round_id}/deposits/sync - Sync deposits for a round from chain + POST /api/v1/rounds/{round_id}/votes/sync - Sync votes for a round from chain + POST /api/v1/rounds/{round_id}/payouts/sync - Sync payouts for a round from chain +""" +import json +import logging +from datetime import datetime + +import stellar_sdk +from django.conf import settings +from rest_framework.response import Response +from rest_framework.views import APIView + +from indexer_app.tasks import address_to_string +from indexer_app.utils import ( + create_or_update_round, + create_round_application, + create_round_payout, + process_project_event, + process_rounds_deposit_event, + process_vote_event, + update_application, + update_approved_projects, + update_round_payout, +) + +logger = logging.getLogger(__name__) + +# Contract IDs for sync endpoints only — does not affect Celery worker or other code +_STELLAR_ROUND_CONTRACT = { + "testnet": "CA7A2776JYIOYXPAJFONDC7BVPDWWLZU524UTGIJIQH6HWWOR6LKYQQT", + "dev": "CDCRTJQ3SP2LJEOT5EA2B6WTGUUI6BVYDJXKYRJCKRSPSYJHXV5V6X3O", +}.get(settings.ENVIRONMENT, "") + +_STELLAR_PROJECT_CONTRACT = { + "testnet": "CA56XSY7YEZ7CJ5FYG7YODQIWE3JNRGZ5S7E7VJAQ675KDS4BLZJ5NJH", + "dev": "CD6X5JVK6ITAZGOMIUBVJUHFMK34YW2ZEWQ2BDLV6XFRFGNV56A4L3RC", +}.get(settings.ENVIRONMENT, "") + + +def fetch_from_stellar_rpc(contract_id, function_name, parameters): + """Call a Stellar contract view method via simulate_transaction.""" + server = stellar_sdk.SorobanServer( + "https://soroban-testnet.stellar.org" + if settings.ENVIRONMENT == "testnet" + else "https://rpc.ankr.com/stellar_soroban" + ) + public_key = "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7" + acct = server.load_account(public_key) + result = server.simulate_transaction( + transaction_envelope=stellar_sdk.TransactionBuilder( + source_account=acct, + ) + .append_invoke_contract_function_op(contract_id, function_name, parameters) + .set_timeout(30) + .build() + ) + if result.results: + xdr = result.results[0].xdr + data = stellar_sdk.scval.to_native(xdr) + data = json.loads(json.dumps(data, default=address_to_string)) + return data + return None + + +def _fill_project_defaults(data): + """Fill in fields the shared handler expects but RPC data may not have.""" + if not data.get("payout_address"): + data["payout_address"] = data.get("owner", "") + if not data.get("video_url"): + data["video_url"] = "" + if not data.get("updated_ms"): + data["updated_ms"] = data.get("submited_ms", 0) + return data + + +class ProjectSyncAPI(APIView): + """POST /api/v1/projects/{project_id}/sync - Sync a single project from chain.""" + + def post(self, request, project_id: int): + data = fetch_from_stellar_rpc( + _STELLAR_PROJECT_CONTRACT, + "get_project_by_id", + [stellar_sdk.scval.to_uint128(project_id)], + ) + if not data: + return Response({"error": "Project not found on chain"}, status=404) + + result = process_project_event(_fill_project_defaults(data)) + if result: + return Response({"success": True, "message": "Project synced"}) + return Response({"error": "Failed to process project"}, status=502) + + +class ProjectsListSyncAPI(APIView): + """POST /api/v1/projects/sync - Sync multiple projects from chain.""" + + def post(self, request): + skip = int(request.data.get("skip", 0)) + limit = int(request.data.get("limit", 100)) + + data = fetch_from_stellar_rpc( + _STELLAR_PROJECT_CONTRACT, + "get_projects", + [ + stellar_sdk.scval.to_uint64(skip), + stellar_sdk.scval.to_uint64(limit), + ], + ) + if not data: + return Response({"error": "No projects found"}, status=404) + + synced = 0 + for project_data in data: + if process_project_event(_fill_project_defaults(project_data)): + synced += 1 + + return Response({"success": True, "synced": synced, "total": len(data)}) + + +class RoundSyncAPI(APIView): + """POST /api/v1/round/{round_id}/sync - Sync a single round from chain.""" + + def post(self, request, round_id: int): + data = fetch_from_stellar_rpc( + _STELLAR_ROUND_CONTRACT, + "get_round", + [stellar_sdk.scval.to_uint128(round_id)], + ) + if not data: + return Response({"error": "Round not found on chain"}, status=404) + + timestamp = datetime.fromtimestamp(data.get("deployed_at", 0) / 1000) if data.get("deployed_at") else datetime.now() + result = create_or_update_round(data, _STELLAR_ROUND_CONTRACT, timestamp) + if result: + return Response({"success": True, "message": "Round synced"}) + return Response({"error": "Failed to process round"}, status=502) + + +class RoundsListSyncAPI(APIView): + """POST /api/v1/rounds/sync - Sync multiple rounds from chain.""" + + def post(self, request): + skip = int(request.data.get("skip", 0)) + limit = int(request.data.get("limit", 100)) + + data = fetch_from_stellar_rpc( + _STELLAR_ROUND_CONTRACT, + "get_rounds", + [ + stellar_sdk.scval.to_uint64(skip), + stellar_sdk.scval.to_uint64(limit), + ], + ) + if not data: + return Response({"error": "No rounds found"}, status=404) + + synced = 0 + for round_data in data: + timestamp = datetime.fromtimestamp(round_data.get("deployed_at", 0) / 1000) if round_data.get("deployed_at") else datetime.now() + if create_or_update_round(round_data, _STELLAR_ROUND_CONTRACT, timestamp): + synced += 1 + + return Response({"success": True, "synced": synced, "total": len(data)}) + + +class RoundApplicationsSyncAPI(APIView): + """POST /api/v1/round/{round_id}/applications/sync - Sync applications for a round from chain.""" + + def post(self, request, round_id: int): + skip = int(request.data.get("skip", 0)) + limit = int(request.data.get("limit", 100)) + + data = fetch_from_stellar_rpc( + _STELLAR_ROUND_CONTRACT, + "get_applications_for_round", + [ + stellar_sdk.scval.to_uint128(round_id), + stellar_sdk.scval.to_uint64(skip), + stellar_sdk.scval.to_uint64(limit), + ], + ) + if not data: + return Response({"error": "No applications found for this round"}, status=404) + + synced = 0 + for app_data in data: + if create_round_application([round_id, app_data], tx_hash=None): + synced += 1 + + return Response({"success": True, "synced": synced, "total": len(data)}) + + +class ApplicationReviewSyncAPI(APIView): + """POST /api/v1/rounds/{round_id}/applications/review/sync - Sync application review. + + Fetches the application from Stellar RPC, then updates the DB. + Only requires applicant_id (to look up the application on-chain) and + reviewer_id (not stored in the contract's RoundApplication struct). + + Expected request body: + { + "applicant_id": "GXYZ...", + "reviewer_id": "GABC...", + "tx_hash": "abc123..." // optional + } + """ + + def post(self, request, round_id: int): + data = request.data + required_fields = ["applicant_id", "reviewer_id"] + missing = [f for f in required_fields if f not in data] + if missing: + return Response({"error": f"Missing required fields: {missing}"}, status=400) + + applicant_id = data["applicant_id"] + reviewer_id = data["reviewer_id"] + tx_hash = data.get("tx_hash") + + # Fetch application from Stellar RPC + application_data = fetch_from_stellar_rpc( + _STELLAR_ROUND_CONTRACT, + "get_application", + [ + stellar_sdk.scval.to_uint128(round_id), + stellar_sdk.scval.to_address(applicant_id), + ], + ) + if not application_data: + return Response({"error": "Application not found on chain"}, status=404) + + # Format as [round_id, application_data, reviewer_id] — exactly how Celery worker passes it + event_data = [round_id, application_data, reviewer_id] + result = update_application(event_data, tx_hash) + if result: + return Response({"success": True, "message": "Application review synced"}) + return Response({"error": "Failed to sync application review"}, status=502) + + +class ApprovedProjectsSyncAPI(APIView): + """POST /api/v1/rounds/{round_id}/approved-projects/sync - Sync approved projects. + + Fetches approved project IDs from Stellar RPC, then updates the DB. + No request body needed. + """ + + def post(self, request, round_id: int): + # Fetch approved projects from Stellar RPC + project_ids = fetch_from_stellar_rpc( + _STELLAR_ROUND_CONTRACT, + "get_approved_projects", + [stellar_sdk.scval.to_uint128(round_id)], + ) + if project_ids is None: + return Response({"error": "Could not fetch approved projects from chain"}, status=502) + if not project_ids: + return Response({"success": True, "message": "No approved projects on chain", "synced": 0}) + + # Format as [round_id, [project_ids]] — exactly how Celery worker passes it + event_data = [round_id, project_ids] + result = update_approved_projects(event_data) + if result: + return Response({"success": True, "message": "Approved projects synced", "synced": len(project_ids)}) + return Response({"error": "Failed to sync approved projects"}, status=502) + + +class RoundDepositsSyncAPI(APIView): + """POST /api/v1/rounds/{round_id}/deposits/sync - Sync deposits for a round from chain.""" + + def post(self, request, round_id: int): + skip = int(request.data.get("skip", 0)) + limit = int(request.data.get("limit", 100)) + + data = fetch_from_stellar_rpc( + _STELLAR_ROUND_CONTRACT, + "get_deposits_for_round", + [ + stellar_sdk.scval.to_uint128(round_id), + stellar_sdk.scval.to_uint64(skip), + stellar_sdk.scval.to_uint64(limit), + ], + ) + if not data: + return Response({"error": "No deposits found for this round"}, status=404) + + synced = 0 + for deposit_data in data: + if process_rounds_deposit_event([round_id, deposit_data], tx_hash=None): + synced += 1 + + return Response({"success": True, "synced": synced, "total": len(data)}) + + +class RoundVotesSyncAPI(APIView): + """POST /api/v1/rounds/{round_id}/votes/sync - Sync votes for a round from chain.""" + + def post(self, request, round_id: int): + skip = int(request.data.get("skip", 0)) + limit = int(request.data.get("limit", 100)) + + data = fetch_from_stellar_rpc( + _STELLAR_ROUND_CONTRACT, + "get_votes_for_round", + [ + stellar_sdk.scval.to_uint128(round_id), + stellar_sdk.scval.to_uint64(skip), + stellar_sdk.scval.to_uint64(limit), + ], + ) + if not data: + return Response({"error": "No votes found for this round"}, status=404) + + synced = 0 + for vote_data in data: + if process_vote_event([round_id, vote_data], tx_hash=None): + synced += 1 + + return Response({"success": True, "synced": synced, "total": len(data)}) + + +class RoundPayoutsSyncAPI(APIView): + """POST /api/v1/rounds/{round_id}/payouts/sync - Sync payouts for a round from chain. + + Fetches all payouts from the contract. Creates new payouts and updates existing ones + (e.g. when paid_at_ms is set after processing). + """ + + def post(self, request, round_id: int): + skip = int(request.data.get("skip", 0)) + limit = int(request.data.get("limit", 100)) + + data = fetch_from_stellar_rpc( + _STELLAR_ROUND_CONTRACT, + "get_payouts_for_round", + [ + stellar_sdk.scval.to_uint128(round_id), + stellar_sdk.scval.to_uint64(skip), + stellar_sdk.scval.to_uint64(limit), + ], + ) + if not data: + return Response({"error": "No payouts found for this round"}, status=404) + + synced = 0 + for payout_data in data: + event = [round_id, payout_data] + # Try update first (payout may already exist in DB); if not found, create then update + if not update_round_payout(event, tx_hash=None): + create_round_payout(event, tx_hash=None) + if payout_data.get("paid_at_ms"): + update_round_payout(event, tx_hash=None) + synced += 1 + + return Response({"success": True, "synced": synced, "total": len(data)}) From 629360c7dd208d1a974a702c49b50bb965377b0a Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Sun, 8 Mar 2026 22:19:24 +0500 Subject: [PATCH 439/454] Added grantpicks list sync endpoints --- api/urls.py | 34 ++++++- grantpicks/sync.py | 248 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 277 insertions(+), 5 deletions(-) diff --git a/api/urls.py b/api/urls.py index 8ba0958..5277bb9 100644 --- a/api/urls.py +++ b/api/urls.py @@ -46,6 +46,10 @@ RoundDepositsSyncAPI, RoundVotesSyncAPI, RoundPayoutsSyncAPI, + StellarListSyncAPI, + StellarListRegistrationsSyncAPI, + StellarSingleRegistrationSyncAPI, + StellarListDeleteSyncAPI, ) from lists.api import ( ListDetailAPI, @@ -207,19 +211,36 @@ name="mpdao_voter_detail", ), # sync endpoints (for on-demand data fetching from blockchain) + # NEAR list sync endpoints (kept for NEAR/potlock frontend) path( - "v1/lists//sync", + "v1/near/lists//sync", ListSyncAPI.as_view(), + name="near_list_sync_api", + ), + path( + "v1/near/lists//registrations/sync", + ListRegistrationsSyncAPI.as_view(), + name="near_list_registrations_sync_api", + ), + path( + "v1/near/lists//registrations//sync", + SingleRegistrationSyncAPI.as_view(), + name="near_single_registration_sync_api", + ), + # Stellar list sync endpoints (grantpicks frontend) + path( + "v1/lists//sync", + StellarListSyncAPI.as_view(), name="list_sync_api", ), path( "v1/lists//registrations/sync", - ListRegistrationsSyncAPI.as_view(), + StellarListRegistrationsSyncAPI.as_view(), name="list_registrations_sync_api", ), path( "v1/lists//registrations//sync", - SingleRegistrationSyncAPI.as_view(), + StellarSingleRegistrationSyncAPI.as_view(), name="single_registration_sync_api", ), # direct donation sync @@ -229,8 +250,13 @@ name="direct_donation_sync_api", ), path( - "v1/lists//delete/sync", + "v1/near/lists//delete/sync", ListDeleteSyncAPI.as_view(), + name="near_list_delete_sync_api", + ), + path( + "v1/lists//delete/sync", + StellarListDeleteSyncAPI.as_view(), name="list_delete_sync_api", ), path( diff --git a/grantpicks/sync.py b/grantpicks/sync.py index 3768fdf..21f00a3 100644 --- a/grantpicks/sync.py +++ b/grantpicks/sync.py @@ -1,7 +1,7 @@ """ Sync endpoints for grantpicks - fetch data from Stellar RPC and store in database. -Called by frontend after user performs on-chain actions (create/update project, round, application). +Called by frontend after user performs on-chain actions (create/update project, round, application, list). Endpoints: POST /api/v1/projects/{project_id}/sync - Sync single project from chain @@ -14,6 +14,10 @@ POST /api/v1/rounds/{round_id}/deposits/sync - Sync deposits for a round from chain POST /api/v1/rounds/{round_id}/votes/sync - Sync votes for a round from chain POST /api/v1/rounds/{round_id}/payouts/sync - Sync payouts for a round from chain + POST /api/v1/lists/{list_id}/sync - Sync single list from chain + POST /api/v1/lists/{list_id}/registrations/sync - Sync registrations for a list from chain + POST /api/v1/lists/{list_id}/registrations/{registrant_id}/sync - Sync single registration from chain + POST /api/v1/lists/{list_id}/delete/sync - Delete list from DB after on-chain deletion """ import json import logging @@ -24,6 +28,9 @@ from rest_framework.response import Response from rest_framework.views import APIView +from accounts.models import Account +from lists.models import List, ListRegistration + from indexer_app.tasks import address_to_string from indexer_app.utils import ( create_or_update_round, @@ -50,6 +57,11 @@ "dev": "CD6X5JVK6ITAZGOMIUBVJUHFMK34YW2ZEWQ2BDLV6XFRFGNV56A4L3RC", }.get(settings.ENVIRONMENT, "") +_STELLAR_LISTS_CONTRACT = { + "testnet": "CCLXSELRRF67M3K5JJYNT6HRTJN26JDJKZYKR5QTZAEZU2TSCF6OGFZT", + "dev": "CAIYXP5CNFB5WUBAWEPBZHIKYZGP3IEFXILFMDUT37FZBKNGFJGNJPNT", +}.get(settings.ENVIRONMENT, "") + def fetch_from_stellar_rpc(contract_id, function_name, parameters): """Call a Stellar contract view method via simulate_transaction.""" @@ -365,3 +377,237 @@ def post(self, request, round_id: int): synced += 1 return Response({"success": True, "synced": synced, "total": len(data)}) + + +class StellarListSyncAPI(APIView): + """POST /api/v1/lists/{list_id}/sync - Sync a single list from Stellar chain.""" + + def post(self, request, list_id: int): + try: + data = fetch_from_stellar_rpc( + _STELLAR_LISTS_CONTRACT, + "get_list", + [stellar_sdk.scval.to_uint128(list_id)], + ) + if not data: + return Response({"error": "List not found on chain"}, status=404) + + # Map Stellar field names to DB fields + owner_id = data.get("owner", "") + Account.objects.get_or_create(id=owner_id) + + # default_registration_status comes as array like ["Pending"] from Stellar + default_status = data.get("default_registration_status", "Pending") + if isinstance(default_status, list): + default_status = default_status[0] if default_status else "Pending" + + created_at = datetime.fromtimestamp(data.get("created_ms", 0) / 1000) if data.get("created_ms") else datetime.now() + updated_at = datetime.fromtimestamp(data.get("updated_ms", 0) / 1000) if data.get("updated_ms") else datetime.now() + + existing_list = List.objects.filter(on_chain_id=int(list_id)).first() + + if existing_list: + existing_list.name = data.get("name", "") + existing_list.description = data.get("description", "") + existing_list.cover_image_url = data.get("cover_img_url") or data.get("cover_image_url") + existing_list.admin_only_registrations = data.get("admin_only_registrations", False) + existing_list.default_registration_status = default_status + existing_list.updated_at = updated_at + existing_list.save() + + existing_list.admins.clear() + for admin_id in data.get("admins", []): + admin, _ = Account.objects.get_or_create(id=admin_id) + existing_list.admins.add(admin) + + return Response({"success": True, "message": "List updated", "on_chain_id": list_id}) + + list_obj = List.objects.create( + on_chain_id=data.get("id", list_id), + owner_id=owner_id, + name=data.get("name", ""), + description=data.get("description", ""), + cover_image_url=data.get("cover_img_url") or data.get("cover_image_url"), + admin_only_registrations=data.get("admin_only_registrations", False), + default_registration_status=default_status, + created_at=created_at, + updated_at=updated_at, + ) + + for admin_id in data.get("admins", []): + admin, _ = Account.objects.get_or_create(id=admin_id) + list_obj.admins.add(admin) + + return Response({"success": True, "message": "List created", "on_chain_id": list_obj.on_chain_id}) + + except Exception as e: + logger.error(f"Error syncing list {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class StellarListRegistrationsSyncAPI(APIView): + """POST /api/v1/lists/{list_id}/registrations/sync - Sync registrations for a list from Stellar chain.""" + + def post(self, request, list_id: int): + try: + # Ensure list exists in DB + try: + list_obj = List.objects.get(on_chain_id=int(list_id)) + except List.DoesNotExist: + sync = StellarListSyncAPI() + resp = sync.post(request, list_id) + if resp.status_code != 200: + return Response({"error": "List not found"}, status=404) + list_obj = List.objects.get(on_chain_id=int(list_id)) + + # Fetch all registrations (no status filter — pass None) + registrations = fetch_from_stellar_rpc( + _STELLAR_LISTS_CONTRACT, + "get_registrations_for_list", + [ + stellar_sdk.scval.to_uint128(list_id), + stellar_sdk.scval.to_void(), + stellar_sdk.scval.to_uint64(0), + stellar_sdk.scval.to_uint64(100), + ], + ) + + if not registrations: + registrations = [] + + synced = 0 + for reg in registrations: + registrant_id = reg.get("registrant_id", "") + registered_by = reg.get("registered_by", registrant_id) + Account.objects.get_or_create(id=registrant_id) + Account.objects.get_or_create(id=registered_by) + + status = reg.get("status", "Pending") + if isinstance(status, list): + status = status[0] if status else "Pending" + + submitted_at = datetime.fromtimestamp(reg.get("submitted_ms", 0) / 1000) if reg.get("submitted_ms") else datetime.now() + updated_at = datetime.fromtimestamp(reg.get("updated_ms", 0) / 1000) if reg.get("updated_ms") else datetime.now() + + ListRegistration.objects.update_or_create( + list=list_obj, + registrant_id=registrant_id, + defaults={ + "registered_by_id": registered_by, + "status": status, + "submitted_at": submitted_at, + "updated_at": updated_at, + "admin_notes": reg.get("admin_notes"), + "registrant_notes": reg.get("registrant_notes"), + } + ) + synced += 1 + + return Response({"success": True, "message": f"Synced {synced} registrations", "synced_count": synced}) + + except Exception as e: + logger.error(f"Error syncing registrations for list {list_id}: {e}") + return Response({"error": str(e)}, status=502) + + +class StellarSingleRegistrationSyncAPI(APIView): + """POST /api/v1/lists/{list_id}/registrations/{registrant_id}/sync - Sync single registration from Stellar chain.""" + + def post(self, request, list_id: int, registrant_id: str): + try: + # Ensure list exists in DB + try: + list_obj = List.objects.get(on_chain_id=int(list_id)) + except List.DoesNotExist: + sync = StellarListSyncAPI() + resp = sync.post(request, list_id) + if resp.status_code != 200: + return Response({"error": "List not found"}, status=404) + list_obj = List.objects.get(on_chain_id=int(list_id)) + + # Fetch all registrations and find the one we need + registrations = fetch_from_stellar_rpc( + _STELLAR_LISTS_CONTRACT, + "get_registrations_for_list", + [ + stellar_sdk.scval.to_uint128(list_id), + stellar_sdk.scval.to_void(), + stellar_sdk.scval.to_uint64(0), + stellar_sdk.scval.to_uint64(100), + ], + ) + + if not registrations: + return Response({"error": "No registrations found for list"}, status=404) + + reg = next((r for r in registrations if r.get("registrant_id") == registrant_id), None) + if not reg: + return Response({"error": "Registration not found"}, status=404) + + Account.objects.get_or_create(id=reg["registrant_id"]) + registered_by = reg.get("registered_by", reg["registrant_id"]) + Account.objects.get_or_create(id=registered_by) + + status = reg.get("status", "Pending") + if isinstance(status, list): + status = status[0] if status else "Pending" + + submitted_at = datetime.fromtimestamp(reg.get("submitted_ms", 0) / 1000) if reg.get("submitted_ms") else datetime.now() + updated_at = datetime.fromtimestamp(reg.get("updated_ms", 0) / 1000) if reg.get("updated_ms") else datetime.now() + + registration, created = ListRegistration.objects.update_or_create( + list=list_obj, + registrant_id=reg["registrant_id"], + defaults={ + "registered_by_id": registered_by, + "status": status, + "submitted_at": submitted_at, + "updated_at": updated_at, + "admin_notes": reg.get("admin_notes"), + "registrant_notes": reg.get("registrant_notes"), + } + ) + + return Response({"success": True, "message": "Registration synced", "registrant_id": registrant_id, "status": registration.status}) + + except Exception as e: + logger.error(f"Error syncing registration: {e}") + return Response({"error": str(e)}, status=502) + + +class StellarListDeleteSyncAPI(APIView): + """POST /api/v1/lists/{list_id}/delete/sync - Delete list from DB after on-chain deletion.""" + + def post(self, request, list_id: int): + try: + # Verify list no longer exists on chain + data = fetch_from_stellar_rpc( + _STELLAR_LISTS_CONTRACT, + "get_list", + [stellar_sdk.scval.to_uint128(list_id)], + ) + + if data: + return Response({"error": "List still exists on chain — not deleted"}, status=400) + + # List doesn't exist on chain — safe to delete from DB + deleted_count, _ = List.objects.filter(on_chain_id=int(list_id)).delete() + + if deleted_count > 0: + logger.info(f"List {list_id} deleted from DB (verified not on chain)") + return Response({"success": True, "message": "List deleted", "on_chain_id": list_id}) + + return Response({"error": "List not found in database"}, status=404) + + except Exception as e: + # If RPC call fails (e.g. contract panics with "List does not exist"), that confirms deletion + error_str = str(e) + if "does not exist" in error_str or "not found" in error_str.lower(): + deleted_count, _ = List.objects.filter(on_chain_id=int(list_id)).delete() + if deleted_count > 0: + logger.info(f"List {list_id} deleted from DB (RPC confirmed not on chain)") + return Response({"success": True, "message": "List deleted", "on_chain_id": list_id}) + return Response({"error": "List not found in database"}, status=404) + + logger.error(f"Error syncing list deletion {list_id}: {e}") + return Response({"error": str(e)}, status=502) From 6d9bcae49dce023e911c63d8be0da5af70758c20 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Sun, 8 Mar 2026 22:57:58 +0500 Subject: [PATCH 440/454] Seperated grantpicks endpoints for clarity --- api/urls.py | 203 ++++++++++------------------------------------------ 1 file changed, 39 insertions(+), 164 deletions(-) diff --git a/api/urls.py b/api/urls.py index 0e02f89..5942bc5 100644 --- a/api/urls.py +++ b/api/urls.py @@ -256,18 +256,6 @@ ), path("v1//projects", AccountProjectListAPI.as_view(), name="user_projects_api"), path("v1//project-stats", ProjectStatsAPI.as_view(), name="projects_stat__api"), - # grantpicks sync - path("v1/projects//sync", ProjectSyncAPI.as_view(), name="project_sync_api"), - path("v1/projects/sync", ProjectsListSyncAPI.as_view(), name="projects_list_sync_api"), - path("v1/rounds//sync", RoundSyncAPI.as_view(), name="round_sync_api"), - path("v1/rounds/sync", RoundsListSyncAPI.as_view(), name="rounds_list_sync_api"), - path("v1/rounds//applications/sync", RoundApplicationsSyncAPI.as_view(), name="round_applications_sync_api"), - path("v1/rounds//applications/review/sync", ApplicationReviewSyncAPI.as_view(), name="application_review_sync_api"), - path("v1/rounds//approved-projects/sync", ApprovedProjectsSyncAPI.as_view(), name="approved_projects_sync_api"), - path("v1/rounds//deposits/sync", RoundDepositsSyncAPI.as_view(), name="round_deposits_sync_api"), - path("v1/rounds//votes/sync", RoundVotesSyncAPI.as_view(), name="round_votes_sync_api"), - path("v1/rounds//payouts/sync", RoundPayoutsSyncAPI.as_view(), name="round_payouts_sync_api"), - path( "v1/mpdao/voters", MpdaoVotersListAPI.as_view(), @@ -278,156 +266,43 @@ MpdaoVoterDetailAPI.as_view(), name="mpdao_voter_detail", ), - # sync endpoints (for on-demand data fetching from blockchain) - # NEAR list sync endpoints (kept for NEAR/potlock frontend) - path( - "v1/near/lists//sync", - ListSyncAPI.as_view(), - name="near_list_sync_api", - ), - path( - "v1/near/lists//registrations/sync", - ListRegistrationsSyncAPI.as_view(), - name="near_list_registrations_sync_api", - ), - path( - "v1/near/lists//registrations//sync", - SingleRegistrationSyncAPI.as_view(), - name="near_single_registration_sync_api", - ), - # Stellar list sync endpoints (grantpicks frontend) - path( - "v1/lists//sync", - StellarListSyncAPI.as_view(), - name="list_sync_api", - ), - path( - "v1/lists//registrations/sync", - StellarListRegistrationsSyncAPI.as_view(), - name="list_registrations_sync_api", - ), - path( - "v1/lists//registrations//sync", - StellarSingleRegistrationSyncAPI.as_view(), - name="single_registration_sync_api", - ), - # pot sync endpoints - path( - "v1/pots//sync", - PotSyncAPI.as_view(), - name="pot_sync_api", - ), - path( - "v1/pots//donations/sync", - PotDonationsSyncAPI.as_view(), - name="pot_donations_sync_api", - ), - path( - "v1/pots//applications/sync", - PotApplicationsSyncAPI.as_view(), - name="pot_applications_sync_api", - ), - path( - "v1/pots//payouts/sync", - PotPayoutsSyncAPI.as_view(), - name="pot_payouts_sync_api", - ), - path( - "v1/pots//challenges/sync", - PotPayoutChallengesSyncAPI.as_view(), - name="pot_challenges_sync_api", - ), - path( - "v1/lists//delete/sync", - ListDeleteSyncAPI.as_view(), - name="list_delete_sync_api", - ), - path( - "v1/lists//upvote/sync", - ListUpvoteSyncAPI.as_view(), - name="list_upvote_sync_api", - ), - path( - "v1/lists//remove-upvote/sync", - ListRemoveUpvoteSyncAPI.as_view(), - name="list_remove_upvote_sync_api", - ), - # pot sync endpoints - path( - "v1/pots//sync", - PotSyncAPI.as_view(), - name="pot_sync_api", - ), - path( - "v1/pots//donations/sync", - PotDonationsSyncAPI.as_view(), - name="pot_donations_sync_api", - ), - path( - "v1/pots//applications/sync", - PotApplicationsSyncAPI.as_view(), - name="pot_applications_sync_api", - ), - path( - "v1/pots//payouts/sync", - PotPayoutsSyncAPI.as_view(), - name="pot_payouts_sync_api", - ), - path( - "v1/pots//challenges/sync", - PotPayoutChallengesSyncAPI.as_view(), - name="pot_challenges_sync_api", - ), - path( - "v1/near/lists//delete/sync", - ListDeleteSyncAPI.as_view(), - name="near_list_delete_sync_api", - ), - path( - "v1/lists//delete/sync", - StellarListDeleteSyncAPI.as_view(), - name="list_delete_sync_api", - ), - path( - "v1/lists//upvote/sync", - ListUpvoteSyncAPI.as_view(), - name="list_upvote_sync_api", - ), - path( - "v1/lists//remove-upvote/sync", - ListRemoveUpvoteSyncAPI.as_view(), - name="list_remove_upvote_sync_api", - ), - # pot sync endpoints - path( - "v1/pots//sync", - PotSyncAPI.as_view(), - name="pot_sync_api", - ), - path( - "v1/pots//donations/sync", - PotDonationsSyncAPI.as_view(), - name="pot_donations_sync_api", - ), - path( - "v1/pots//applications/sync", - PotApplicationsSyncAPI.as_view(), - name="pot_applications_sync_api", - ), - path( - "v1/pots//payouts/sync", - PotPayoutsSyncAPI.as_view(), - name="pot_payouts_sync_api", - ), - path( - "v1/pots//challenges/sync", - PotPayoutChallengesSyncAPI.as_view(), - name="pot_challenges_sync_api", - ), - # account sync - path( - "v1/accounts//sync", - AccountSyncAPI.as_view(), - name="account_sync_api", - ), + # ========================================================================= + # GRANTPICKS sync endpoints (Stellar chain — grantpicks frontend) + # All prefixed with v1/grantpicks/ to avoid conflicts with Potlock/NEAR + # ========================================================================= + # Projects + path("v1/grantpicks/projects//sync", ProjectSyncAPI.as_view(), name="grantpicks_project_sync_api"), + path("v1/grantpicks/projects/sync", ProjectsListSyncAPI.as_view(), name="grantpicks_projects_list_sync_api"), + # Rounds + path("v1/grantpicks/rounds//sync", RoundSyncAPI.as_view(), name="grantpicks_round_sync_api"), + path("v1/grantpicks/rounds/sync", RoundsListSyncAPI.as_view(), name="grantpicks_rounds_list_sync_api"), + path("v1/grantpicks/rounds//applications/sync", RoundApplicationsSyncAPI.as_view(), name="grantpicks_round_applications_sync_api"), + path("v1/grantpicks/rounds//applications/review/sync", ApplicationReviewSyncAPI.as_view(), name="grantpicks_application_review_sync_api"), + path("v1/grantpicks/rounds//approved-projects/sync", ApprovedProjectsSyncAPI.as_view(), name="grantpicks_approved_projects_sync_api"), + path("v1/grantpicks/rounds//deposits/sync", RoundDepositsSyncAPI.as_view(), name="grantpicks_round_deposits_sync_api"), + path("v1/grantpicks/rounds//votes/sync", RoundVotesSyncAPI.as_view(), name="grantpicks_round_votes_sync_api"), + path("v1/grantpicks/rounds//payouts/sync", RoundPayoutsSyncAPI.as_view(), name="grantpicks_round_payouts_sync_api"), + # Lists + path("v1/grantpicks/lists//sync", StellarListSyncAPI.as_view(), name="grantpicks_list_sync_api"), + path("v1/grantpicks/lists//registrations/sync", StellarListRegistrationsSyncAPI.as_view(), name="grantpicks_list_registrations_sync_api"), + path("v1/grantpicks/lists//registrations//sync", StellarSingleRegistrationSyncAPI.as_view(), name="grantpicks_single_registration_sync_api"), + path("v1/grantpicks/lists//delete/sync", StellarListDeleteSyncAPI.as_view(), name="grantpicks_list_delete_sync_api"), + # ========================================================================= + # POTLOCK sync endpoints (NEAR chain — potlock frontend) + # ========================================================================= + # Lists + path("v1/lists//sync", ListSyncAPI.as_view(), name="list_sync_api"), + path("v1/lists//registrations/sync", ListRegistrationsSyncAPI.as_view(), name="list_registrations_sync_api"), + path("v1/lists//registrations//sync", SingleRegistrationSyncAPI.as_view(), name="single_registration_sync_api"), + path("v1/lists//delete/sync", ListDeleteSyncAPI.as_view(), name="list_delete_sync_api"), + path("v1/lists//upvote/sync", ListUpvoteSyncAPI.as_view(), name="list_upvote_sync_api"), + path("v1/lists//remove-upvote/sync", ListRemoveUpvoteSyncAPI.as_view(), name="list_remove_upvote_sync_api"), + # Pots + path("v1/pots//sync", PotSyncAPI.as_view(), name="pot_sync_api"), + path("v1/pots//donations/sync", PotDonationsSyncAPI.as_view(), name="pot_donations_sync_api"), + path("v1/pots//applications/sync", PotApplicationsSyncAPI.as_view(), name="pot_applications_sync_api"), + path("v1/pots//payouts/sync", PotPayoutsSyncAPI.as_view(), name="pot_payouts_sync_api"), + path("v1/pots//challenges/sync", PotPayoutChallengesSyncAPI.as_view(), name="pot_challenges_sync_api"), + # Accounts + path("v1/accounts//sync", AccountSyncAPI.as_view(), name="account_sync_api"), ] From 1f50f722796dda118a14eb97961c48509474ed65 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Thu, 26 Mar 2026 00:09:15 +0500 Subject: [PATCH 441/454] Added 501c3 verification --- api/urls.py | 7 + base/settings.py | 1 + tax_verification/__init__.py | 0 tax_verification/admin.py | 44 +++++ tax_verification/api.py | 181 ++++++++++++++++++++ tax_verification/apps.py | 6 + tax_verification/migrations/0001_initial.py | 162 ++++++++++++++++++ tax_verification/migrations/__init__.py | 0 tax_verification/models.py | 108 ++++++++++++ tax_verification/serializers.py | 39 +++++ 10 files changed, 548 insertions(+) create mode 100644 tax_verification/__init__.py create mode 100644 tax_verification/admin.py create mode 100644 tax_verification/api.py create mode 100644 tax_verification/apps.py create mode 100644 tax_verification/migrations/0001_initial.py create mode 100644 tax_verification/migrations/__init__.py create mode 100644 tax_verification/models.py create mode 100644 tax_verification/serializers.py diff --git a/api/urls.py b/api/urls.py index 5942bc5..7973710 100644 --- a/api/urls.py +++ b/api/urls.py @@ -71,6 +71,10 @@ ListRegistrationsAPI, ListsListAPI, ) +from tax_verification.api import ( + OrgVerificationSubmitAPI, + OrgVerificationDetailAPI, +) from pots.api import ( MpdaoVotersListAPI, MpdaoVoterDetailAPI, @@ -305,4 +309,7 @@ path("v1/pots//challenges/sync", PotPayoutChallengesSyncAPI.as_view(), name="pot_challenges_sync_api"), # Accounts path("v1/accounts//sync", AccountSyncAPI.as_view(), name="account_sync_api"), + # tax verification + path("v1/tax-verification/org-verification", OrgVerificationSubmitAPI.as_view(), name="org_verification_submit"), + path("v1/tax-verification/org-verification/", OrgVerificationDetailAPI.as_view(), name="org_verification_detail"), ] diff --git a/base/settings.py b/base/settings.py index 0b928fd..8109349 100644 --- a/base/settings.py +++ b/base/settings.py @@ -154,6 +154,7 @@ "chains", "grantpicks", "campaigns", + "tax_verification", ] DEFAULT_PAGE_SIZE = 30 diff --git a/tax_verification/__init__.py b/tax_verification/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tax_verification/admin.py b/tax_verification/admin.py new file mode 100644 index 0000000..397ccd2 --- /dev/null +++ b/tax_verification/admin.py @@ -0,0 +1,44 @@ +from django.contrib import admin + +from .models import OrganizationVerification + + +@admin.register(OrganizationVerification) +class OrganizationVerificationAdmin(admin.ModelAdmin): + list_display = ( + "id", + "account", + "legal_name", + "ein", + "subsection_code", + "status", + "submitted_at", + ) + list_filter = ("status", "submitted_at") + search_fields = ("account__id", "legal_name", "ein") + readonly_fields = ( + "id", + "account", + "ein", + "legal_name", + "address", + "city", + "state", + "zip_code", + "subsection_code", + "ntee_code", + "ruling_date", + "status", + "rejection_reason", + "submitted_at", + "updated_at", + ) + + def has_add_permission(self, request): + return False + + def has_delete_permission(self, request, obj=None): + return False + + def has_change_permission(self, request, obj=None): + return False diff --git a/tax_verification/api.py b/tax_verification/api.py new file mode 100644 index 0000000..cf22843 --- /dev/null +++ b/tax_verification/api.py @@ -0,0 +1,181 @@ +import logging +import re + +import requests +from drf_spectacular.utils import extend_schema +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from accounts.models import Account + +from .models import OrganizationVerification, VerificationStatus +from .serializers import ( + OrganizationVerificationSerializer, + OrgVerificationSubmitSerializer, +) + +logger = logging.getLogger(__name__) + +PROPUBLICA_API_URL = ( + "https://projects.propublica.org/nonprofits/api/v2/organizations/{ein}.json" +) + + +def verify_ein_with_propublica(ein: str) -> dict: + """ + Call ProPublica Nonprofit Explorer API to verify an EIN. + Returns dict with org data if found, or raises ValueError if not found / not 501(c)(3). + """ + # Strip dashes for the API call + clean_ein = ein.replace("-", "") + url = PROPUBLICA_API_URL.format(ein=clean_ein) + + response = requests.get(url, timeout=15) + if response.status_code != 200: + raise ValueError("Could not verify EIN with IRS database.") + + data = response.json() + org = data.get("organization", {}) + + # ProPublica returns "Unknown Organization" for non-existent EINs + if not org or org.get("name", "").strip() == "Unknown Organization": + raise ValueError( + f"EIN {ein} was not found in IRS records. " + "Please check the number and try again." + ) + + return org + + +class OrgVerificationSubmitAPI(APIView): + @extend_schema( + request=OrgVerificationSubmitSerializer, + responses={200: OrganizationVerificationSerializer}, + summary="Submit 501(c)(3) verification", + description=( + "Submit an EIN for automated 501(c)(3) verification against IRS records " + "via ProPublica. Auto-approves if subsection_code is 3 (501(c)(3)), " + "otherwise rejects." + ), + ) + def post(self, request): + serializer = OrgVerificationSubmitSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + account_id = serializer.validated_data["account_id"] + ein = serializer.validated_data["ein"] + + # Validate EIN format (XX-XXXXXXX) + if not re.match(r"^\d{2}-?\d{7}$", ein): + return Response( + {"detail": "Invalid EIN format. Expected XX-XXXXXXX."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Check if already approved + existing = OrganizationVerification.objects.filter( + account_id=account_id + ).first() + if existing and existing.status == VerificationStatus.APPROVED: + return Response( + {"detail": "This account is already verified."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Verify with ProPublica + try: + org_data = verify_ein_with_propublica(ein) + except ValueError as e: + # If existing record, update it to rejected + if existing: + existing.ein = ein + existing.status = VerificationStatus.REJECTED + existing.rejection_reason = str(e) + existing.save() + return Response( + OrganizationVerificationSerializer(existing).data, + status=status.HTTP_200_OK, + ) + return Response( + {"detail": str(e)}, + status=status.HTTP_400_BAD_REQUEST, + ) + except requests.RequestException: + logger.exception("ProPublica API request failed") + return Response( + {"detail": "IRS verification service is temporarily unavailable."}, + status=status.HTTP_503_SERVICE_UNAVAILABLE, + ) + + # Determine approval based on subsection code + subsection_code = org_data.get("subsection_code") + is_501c3 = subsection_code == 3 + + # Get or create account + account, _ = Account.objects.get_or_create(id=account_id) + + # Normalize EIN to XX-XXXXXXX format + clean_ein = ein.replace("-", "") + formatted_ein = f"{clean_ein[:2]}-{clean_ein[2:]}" + + # Build verification data from ProPublica response + verification_data = { + "ein": formatted_ein, + "legal_name": org_data.get("name", ""), + "address": org_data.get("address", ""), + "city": org_data.get("city", ""), + "state": org_data.get("state", ""), + "zip_code": org_data.get("zipcode", ""), + "subsection_code": subsection_code, + "ntee_code": org_data.get("ntee_code", ""), + "ruling_date": org_data.get("ruling_date", ""), + "status": ( + VerificationStatus.APPROVED + if is_501c3 + else VerificationStatus.REJECTED + ), + "rejection_reason": ( + None + if is_501c3 + else f"Organization is not a 501(c)(3). Subsection code: {subsection_code}." + ), + } + + if existing: + for key, value in verification_data.items(): + setattr(existing, key, value) + existing.save() + verification = existing + else: + verification = OrganizationVerification.objects.create( + account=account, **verification_data + ) + + return Response( + OrganizationVerificationSerializer(verification).data, + status=status.HTTP_200_OK, + ) + + +class OrgVerificationDetailAPI(APIView): + @extend_schema( + responses={200: OrganizationVerificationSerializer}, + summary="Get organization verification status", + description="Retrieve the 501(c)(3) verification status for a given account.", + ) + def get(self, request, account_id): + try: + verification = OrganizationVerification.objects.get( + account_id=account_id + ) + except OrganizationVerification.DoesNotExist: + return Response( + {"detail": "No verification found for this account."}, + status=status.HTTP_404_NOT_FOUND, + ) + + return Response( + OrganizationVerificationSerializer(verification).data, + status=status.HTTP_200_OK, + ) diff --git a/tax_verification/apps.py b/tax_verification/apps.py new file mode 100644 index 0000000..9712571 --- /dev/null +++ b/tax_verification/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TaxVerificationConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "tax_verification" diff --git a/tax_verification/migrations/0001_initial.py b/tax_verification/migrations/0001_initial.py new file mode 100644 index 0000000..c67a60e --- /dev/null +++ b/tax_verification/migrations/0001_initial.py @@ -0,0 +1,162 @@ +# Generated by Django 5.0.4 on 2026-03-25 00:00 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("accounts", "0006_alter_account_near_social_profile_data"), + ] + + operations = [ + migrations.CreateModel( + name="OrganizationVerification", + fields=[ + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="verification id", + ), + ), + ( + "ein", + models.CharField( + help_text="Employer Identification Number (XX-XXXXXXX).", + max_length=10, + verbose_name="EIN", + ), + ), + ( + "legal_name", + models.CharField( + help_text="Organization legal name from IRS records.", + max_length=255, + verbose_name="legal name", + ), + ), + ( + "address", + models.CharField( + blank=True, + max_length=255, + null=True, + verbose_name="address", + ), + ), + ( + "city", + models.CharField( + blank=True, + max_length=100, + null=True, + verbose_name="city", + ), + ), + ( + "state", + models.CharField( + blank=True, + help_text="US state code (e.g. CA, NY).", + max_length=2, + null=True, + verbose_name="state", + ), + ), + ( + "zip_code", + models.CharField( + blank=True, + max_length=10, + null=True, + verbose_name="zip code", + ), + ), + ( + "subsection_code", + models.IntegerField( + blank=True, + help_text="IRS subsection code. 3 = 501(c)(3).", + null=True, + verbose_name="subsection code", + ), + ), + ( + "ntee_code", + models.CharField( + blank=True, + help_text="National Taxonomy of Exempt Entities code.", + max_length=10, + null=True, + verbose_name="NTEE code", + ), + ), + ( + "ruling_date", + models.CharField( + blank=True, + help_text="Date IRS granted tax-exempt status.", + max_length=20, + null=True, + verbose_name="ruling date", + ), + ), + ( + "status", + models.CharField( + choices=[ + ("Pending", "Pending"), + ("Approved", "Approved"), + ("Rejected", "Rejected"), + ], + db_index=True, + default="Pending", + max_length=32, + verbose_name="verification status", + ), + ), + ( + "rejection_reason", + models.CharField( + blank=True, + help_text="Reason for rejection if verification failed.", + max_length=255, + null=True, + verbose_name="rejection reason", + ), + ), + ( + "submitted_at", + models.DateTimeField( + auto_now_add=True, + verbose_name="submitted at", + ), + ), + ( + "updated_at", + models.DateTimeField( + auto_now=True, + verbose_name="updated at", + ), + ), + ( + "account", + models.OneToOneField( + help_text="NEAR account for this organization.", + on_delete=django.db.models.deletion.CASCADE, + related_name="org_verification", + to="accounts.account", + ), + ), + ], + options={ + "verbose_name": "Organization Verification", + "verbose_name_plural": "Organization Verifications", + }, + ), + ] diff --git a/tax_verification/migrations/__init__.py b/tax_verification/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tax_verification/models.py b/tax_verification/models.py new file mode 100644 index 0000000..789530b --- /dev/null +++ b/tax_verification/models.py @@ -0,0 +1,108 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from accounts.models import Account + + +class VerificationStatus(models.TextChoices): + PENDING = "Pending", "Pending" + APPROVED = "Approved", "Approved" + REJECTED = "Rejected", "Rejected" + + +class OrganizationVerification(models.Model): + id = models.AutoField( + _("verification id"), + primary_key=True, + ) + account = models.OneToOneField( + Account, + on_delete=models.CASCADE, + related_name="org_verification", + help_text=_("NEAR account for this organization."), + ) + ein = models.CharField( + _("EIN"), + max_length=10, + help_text=_("Employer Identification Number (XX-XXXXXXX)."), + ) + # Fields auto-populated from IRS data via ProPublica + legal_name = models.CharField( + _("legal name"), + max_length=255, + help_text=_("Organization legal name from IRS records."), + ) + address = models.CharField( + _("address"), + max_length=255, + null=True, + blank=True, + ) + city = models.CharField( + _("city"), + max_length=100, + null=True, + blank=True, + ) + state = models.CharField( + _("state"), + max_length=2, + null=True, + blank=True, + help_text=_("US state code (e.g. CA, NY)."), + ) + zip_code = models.CharField( + _("zip code"), + max_length=10, + null=True, + blank=True, + ) + subsection_code = models.IntegerField( + _("subsection code"), + null=True, + blank=True, + help_text=_("IRS subsection code. 3 = 501(c)(3)."), + ) + ntee_code = models.CharField( + _("NTEE code"), + max_length=10, + null=True, + blank=True, + help_text=_("National Taxonomy of Exempt Entities code."), + ) + ruling_date = models.CharField( + _("ruling date"), + max_length=20, + null=True, + blank=True, + help_text=_("Date IRS granted tax-exempt status."), + ) + status = models.CharField( + _("verification status"), + max_length=32, + choices=VerificationStatus.choices, + default=VerificationStatus.PENDING, + db_index=True, + ) + rejection_reason = models.CharField( + _("rejection reason"), + max_length=255, + null=True, + blank=True, + help_text=_("Reason for rejection if verification failed."), + ) + submitted_at = models.DateTimeField( + _("submitted at"), + auto_now_add=True, + ) + updated_at = models.DateTimeField( + _("updated at"), + auto_now=True, + ) + + class Meta: + verbose_name = "Organization Verification" + verbose_name_plural = "Organization Verifications" + + def __str__(self): + return f"{self.legal_name} ({self.account_id}) - {self.status}" diff --git a/tax_verification/serializers.py b/tax_verification/serializers.py new file mode 100644 index 0000000..e4fb4c6 --- /dev/null +++ b/tax_verification/serializers.py @@ -0,0 +1,39 @@ +from rest_framework import serializers +from rest_framework.serializers import ModelSerializer + +from accounts.serializers import AccountSerializer + +from .models import OrganizationVerification + + +class OrgVerificationSubmitSerializer(serializers.Serializer): + account_id = serializers.CharField( + required=True, help_text="NEAR account ID" + ) + ein = serializers.CharField( + max_length=10, required=True, help_text="EIN in XX-XXXXXXX format" + ) + + +class OrganizationVerificationSerializer(ModelSerializer): + account = AccountSerializer() + + class Meta: + model = OrganizationVerification + fields = [ + "id", + "account", + "ein", + "legal_name", + "address", + "city", + "state", + "zip_code", + "subsection_code", + "ntee_code", + "ruling_date", + "status", + "rejection_reason", + "submitted_at", + "updated_at", + ] From 7930c5952cf01d2dd2d8f29e58ac9f14415cab9c Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Sun, 5 Apr 2026 19:41:53 +0500 Subject: [PATCH 442/454] Replace ProPublica with local IRS data and monthly update task --- base/celery.py | 6 + tax_verification/admin.py | 19 +- tax_verification/api.py | 61 ++-- tax_verification/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/refresh_eo_bmf.py | 295 ++++++++++++++++++ .../migrations/0002_nonprofitregistry.py | 68 ++++ tax_verification/models.py | 44 +++ tax_verification/tasks.py | 17 + 9 files changed, 477 insertions(+), 33 deletions(-) create mode 100644 tax_verification/management/__init__.py create mode 100644 tax_verification/management/commands/__init__.py create mode 100644 tax_verification/management/commands/refresh_eo_bmf.py create mode 100644 tax_verification/migrations/0002_nonprofitregistry.py create mode 100644 tax_verification/tasks.py diff --git a/base/celery.py b/base/celery.py index fdc2044..1116bc0 100644 --- a/base/celery.py +++ b/base/celery.py @@ -50,6 +50,11 @@ "schedule": crontab(minute="*/1"), # Executes every 1 minutes "options": {"queue": "beat_tasks"}, }, + "refresh_eo_bmf_monthly": { + "task": "tax_verification.tasks.refresh_eo_bmf", + "schedule": crontab(day_of_month="5", hour="4", minute="0"), # 5th of each month, 4 AM UTC + "options": {"queue": "beat_tasks"}, + }, } app.conf.task_routes = { @@ -58,6 +63,7 @@ "indexer_app.tasks.update_pot_statistics": {"queue": "beat_tasks"}, "indexer_app.tasks.stellar_event_indexer": {"queue": "beat_tasks"}, "indexer_app.tasks.process_stellar_events": {"queue": "beat_tasks"}, + "tax_verification.tasks.refresh_eo_bmf": {"queue": "beat_tasks"}, } SPOT_INDEXER_QUEUE_NAME = "spot_indexing" diff --git a/tax_verification/admin.py b/tax_verification/admin.py index 397ccd2..91f9cb4 100644 --- a/tax_verification/admin.py +++ b/tax_verification/admin.py @@ -1,6 +1,23 @@ from django.contrib import admin -from .models import OrganizationVerification +from .models import NonprofitRegistry, OrganizationVerification + + +@admin.register(NonprofitRegistry) +class NonprofitRegistryAdmin(admin.ModelAdmin): + list_display = ("ein", "name", "city", "state", "subsection", "deductibility", "status") + list_filter = ("subsection", "deductibility", "status", "state") + search_fields = ("ein", "name", "city") + readonly_fields = [f.name for f in NonprofitRegistry._meta.get_fields()] + + def has_add_permission(self, request): + return False + + def has_delete_permission(self, request, obj=None): + return False + + def has_change_permission(self, request, obj=None): + return False @admin.register(OrganizationVerification) diff --git a/tax_verification/api.py b/tax_verification/api.py index cf22843..2b47677 100644 --- a/tax_verification/api.py +++ b/tax_verification/api.py @@ -1,7 +1,6 @@ import logging import re -import requests from drf_spectacular.utils import extend_schema from rest_framework import status from rest_framework.response import Response @@ -9,7 +8,7 @@ from accounts.models import Account -from .models import OrganizationVerification, VerificationStatus +from .models import NonprofitRegistry, OrganizationVerification, VerificationStatus from .serializers import ( OrganizationVerificationSerializer, OrgVerificationSubmitSerializer, @@ -17,35 +16,39 @@ logger = logging.getLogger(__name__) -PROPUBLICA_API_URL = ( - "https://projects.propublica.org/nonprofits/api/v2/organizations/{ein}.json" -) - -def verify_ein_with_propublica(ein: str) -> dict: +def verify_ein_with_registry(ein: str) -> dict: """ - Call ProPublica Nonprofit Explorer API to verify an EIN. - Returns dict with org data if found, or raises ValueError if not found / not 501(c)(3). + Look up an EIN in the local NonprofitRegistry (populated from IRS EO BMF). + Returns dict with org data if found, or raises ValueError if not found. """ - # Strip dashes for the API call + # Strip dashes — registry stores 9-digit EINs without dashes clean_ein = ein.replace("-", "") - url = PROPUBLICA_API_URL.format(ein=clean_ein) - - response = requests.get(url, timeout=15) - if response.status_code != 200: - raise ValueError("Could not verify EIN with IRS database.") - - data = response.json() - org = data.get("organization", {}) - # ProPublica returns "Unknown Organization" for non-existent EINs - if not org or org.get("name", "").strip() == "Unknown Organization": + try: + record = NonprofitRegistry.objects.get(ein=clean_ein) + except NonprofitRegistry.DoesNotExist: raise ValueError( f"EIN {ein} was not found in IRS records. " "Please check the number and try again." ) - return org + # Convert subsection string ("03") to int (3) to match previous ProPublica response shape + try: + subsection_code = int(record.subsection) if record.subsection else None + except ValueError: + subsection_code = None + + return { + "name": record.name, + "address": record.street, + "city": record.city, + "state": record.state, + "zipcode": record.zip, + "subsection_code": subsection_code, + "ntee_code": record.ntee_cd, + "ruling_date": record.ruling, + } class OrgVerificationSubmitAPI(APIView): @@ -54,8 +57,8 @@ class OrgVerificationSubmitAPI(APIView): responses={200: OrganizationVerificationSerializer}, summary="Submit 501(c)(3) verification", description=( - "Submit an EIN for automated 501(c)(3) verification against IRS records " - "via ProPublica. Auto-approves if subsection_code is 3 (501(c)(3)), " + "Submit an EIN for automated 501(c)(3) verification against local IRS " + "registry. Auto-approves if subsection_code is 3 (501(c)(3)), " "otherwise rejects." ), ) @@ -83,9 +86,9 @@ def post(self, request): status=status.HTTP_400_BAD_REQUEST, ) - # Verify with ProPublica + # Verify against local IRS registry try: - org_data = verify_ein_with_propublica(ein) + org_data = verify_ein_with_registry(ein) except ValueError as e: # If existing record, update it to rejected if existing: @@ -101,12 +104,6 @@ def post(self, request): {"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST, ) - except requests.RequestException: - logger.exception("ProPublica API request failed") - return Response( - {"detail": "IRS verification service is temporarily unavailable."}, - status=status.HTTP_503_SERVICE_UNAVAILABLE, - ) # Determine approval based on subsection code subsection_code = org_data.get("subsection_code") @@ -119,7 +116,7 @@ def post(self, request): clean_ein = ein.replace("-", "") formatted_ein = f"{clean_ein[:2]}-{clean_ein[2:]}" - # Build verification data from ProPublica response + # Build verification data from registry lookup verification_data = { "ein": formatted_ein, "legal_name": org_data.get("name", ""), diff --git a/tax_verification/management/__init__.py b/tax_verification/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tax_verification/management/commands/__init__.py b/tax_verification/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tax_verification/management/commands/refresh_eo_bmf.py b/tax_verification/management/commands/refresh_eo_bmf.py new file mode 100644 index 0000000..a0554c6 --- /dev/null +++ b/tax_verification/management/commands/refresh_eo_bmf.py @@ -0,0 +1,295 @@ +import csv +import hashlib +import logging +import os +import tempfile +import time + +import requests +from django.core.cache import cache +from django.core.management.base import BaseCommand +from django.utils import timezone + +from tax_verification.models import NonprofitRegistry + +logger = logging.getLogger("jobs") + +EO_BMF_URLS = [ + "https://www.irs.gov/pub/irs-soi/eo1.csv", + "https://www.irs.gov/pub/irs-soi/eo2.csv", + "https://www.irs.gov/pub/irs-soi/eo3.csv", + "https://www.irs.gov/pub/irs-soi/eo4.csv", +] + +EXPECTED_HEADERS = [ + "EIN", "NAME", "ICO", "STREET", "CITY", "STATE", "ZIP", "GROUP", + "SUBSECTION", "AFFILIATION", "CLASSIFICATION", "RULING", "DEDUCTIBILITY", + "FOUNDATION", "ACTIVITY", "ORGANIZATION", "STATUS", "TAX_PERIOD", + "ASSET_CD", "INCOME_CD", "FILING_REQ_CD", "PF_FILING_REQ_CD", "ACCT_PD", + "ASSET_AMT", "INCOME_AMT", "REVENUE_AMT", "NTEE_CD", "SORT_NAME", +] + +CHUNK_SIZE = 5000 +LOCK_KEY = "eo_bmf_refresh_lock" +LOCK_TIMEOUT = 45 * 60 # 45 minutes + + +def compute_row_hash(row): + key_fields = "|".join([ + row.get("EIN", ""), + row.get("NAME", ""), + row.get("SUBSECTION", ""), + row.get("DEDUCTIBILITY", ""), + row.get("STATUS", ""), + row.get("CITY", ""), + row.get("STATE", ""), + row.get("RULING", ""), + row.get("NTEE_CD", ""), + row.get("FOUNDATION", ""), + row.get("ASSET_AMT", ""), + row.get("INCOME_AMT", ""), + row.get("REVENUE_AMT", ""), + ]) + return hashlib.md5(key_fields.encode("latin-1", errors="replace")).hexdigest() + + +def download_file(url, dest_path, max_retries=3): + for attempt in range(max_retries): + try: + logger.info(f"Downloading {url} (attempt {attempt + 1})...") + resp = requests.get(url, stream=True, timeout=120) + resp.raise_for_status() + with open(dest_path, "wb") as f: + for chunk in resp.iter_content(chunk_size=8192): + f.write(chunk) + file_size = os.path.getsize(dest_path) + logger.info(f"Downloaded {url} ({file_size / 1024 / 1024:.1f} MB)") + return True + except Exception as e: + wait = 5 * (3 ** attempt) # 5s, 15s, 45s + logger.warning(f"Download failed for {url}: {e}. Retrying in {wait}s...") + time.sleep(wait) + logger.error(f"All {max_retries} download attempts failed for {url}") + return False + + +def validate_csv_headers(file_path): + with open(file_path, "r", encoding="latin-1") as f: + reader = csv.reader(f) + headers = next(reader) + headers = [h.strip() for h in headers] + if headers != EXPECTED_HEADERS: + logger.error(f"Header mismatch in {file_path}. Got: {headers}") + return False + return True + + +def row_to_model_kwargs(row): + return { + "ein": row.get("EIN", "").strip(), + "name": row.get("NAME", "").strip(), + "ico": row.get("ICO", "").strip(), + "street": row.get("STREET", "").strip(), + "city": row.get("CITY", "").strip(), + "state": row.get("STATE", "").strip(), + "zip": row.get("ZIP", "").strip(), + "group": row.get("GROUP", "").strip(), + "subsection": row.get("SUBSECTION", "").strip(), + "affiliation": row.get("AFFILIATION", "").strip(), + "classification": row.get("CLASSIFICATION", "").strip(), + "ruling": row.get("RULING", "").strip(), + "deductibility": row.get("DEDUCTIBILITY", "").strip(), + "foundation": row.get("FOUNDATION", "").strip(), + "activity": row.get("ACTIVITY", "").strip(), + "organization": row.get("ORGANIZATION", "").strip(), + "status": row.get("STATUS", "").strip(), + "tax_period": row.get("TAX_PERIOD", "").strip(), + "asset_cd": row.get("ASSET_CD", "").strip(), + "income_cd": row.get("INCOME_CD", "").strip(), + "filing_req_cd": row.get("FILING_REQ_CD", "").strip(), + "pf_filing_req_cd": row.get("PF_FILING_REQ_CD", "").strip(), + "acct_pd": row.get("ACCT_PD", "").strip(), + "asset_amt": row.get("ASSET_AMT", "").strip(), + "income_amt": row.get("INCOME_AMT", "").strip(), + "revenue_amt": row.get("REVENUE_AMT", "").strip(), + "ntee_cd": row.get("NTEE_CD", "").strip(), + "sort_name": row.get("SORT_NAME", "").strip(), + } + + +class Command(BaseCommand): + help = "Download and import IRS EO BMF data into NonprofitRegistry" + + def add_arguments(self, parser): + parser.add_argument( + "--limit", + type=int, + default=None, + help="Limit total rows processed per file (for testing)", + ) + parser.add_argument( + "--files", + type=str, + default=None, + help="Comma-separated file numbers to download (e.g. '4' or '1,2'). Default: all 4.", + ) + + def handle(self, *args, **options): + limit = options.get("limit") + files_arg = options.get("files") + if files_arg: + file_nums = [int(x.strip()) for x in files_arg.split(",")] + urls = [u for u in EO_BMF_URLS if int(u.split("eo")[-1].split(".")[0]) in file_nums] + else: + urls = EO_BMF_URLS + # Acquire distributed lock + if not cache.add(LOCK_KEY, "running", LOCK_TIMEOUT): + logger.warning("EO BMF refresh already running (lock held). Exiting.") + self.stdout.write("Another refresh is already running.") + return + + start_time = time.time() + logger.info("Starting IRS EO BMF refresh...") + + stats = {"inserted": 0, "updated": 0, "unchanged": 0, "skipped": 0, "deleted": 0} + seen_eins = set() + tmp_dir = tempfile.mkdtemp() + + try: + # Step 1: Download all 4 files + file_paths = [] + for url in urls: + filename = url.split("/")[-1] + dest = os.path.join(tmp_dir, filename) + if not download_file(url, dest): + logger.error(f"Failed to download {url}. Aborting entire refresh.") + return + if not validate_csv_headers(dest): + logger.error(f"Invalid headers in {dest}. Aborting entire refresh.") + return + file_paths.append(dest) + + # Load existing hashes for comparison + logger.info("Loading existing EIN hashes from database...") + existing_hashes = dict( + NonprofitRegistry.objects.values_list("ein", "data_hash") + ) + logger.info(f"Loaded {len(existing_hashes)} existing records.") + + # Step 2-3: Process each file in chunks + for file_path in file_paths: + file_start = time.time() + filename = os.path.basename(file_path) + file_rows = 0 + file_errors = 0 + chunk = [] + + with open(file_path, "r", encoding="latin-1") as f: + reader = csv.DictReader(f) + for line_num, row in enumerate(reader, start=2): + if limit is not None and file_rows >= limit: + break + # Validate row has EIN + ein = row.get("EIN", "").strip() + if not ein: + file_errors += 1 + logger.warning(f"{filename}:{line_num} - Missing EIN, skipping.") + stats["skipped"] += 1 + continue + + seen_eins.add(ein) + file_rows += 1 + data_hash = compute_row_hash(row) + + # Check if record exists and hash matches + if ein in existing_hashes and existing_hashes[ein] == data_hash: + stats["unchanged"] += 1 + continue + + kwargs = row_to_model_kwargs(row) + kwargs["data_hash"] = data_hash + chunk.append(NonprofitRegistry(**kwargs)) + + if ein in existing_hashes: + stats["updated"] += 1 + else: + stats["inserted"] += 1 + + if len(chunk) >= CHUNK_SIZE: + self._upsert_chunk(chunk) + chunk = [] + + # Flush remaining chunk + if chunk: + self._upsert_chunk(chunk) + + # Check for high error rate + if file_rows > 0 and file_errors / file_rows > 0.01: + logger.error( + f"{filename}: {file_errors}/{file_rows} rows malformed (>{1}%). Aborting." + ) + return + + file_duration = time.time() - file_start + logger.info( + f"{filename}: {file_rows} rows processed in {file_duration:.1f}s, " + f"{file_errors} errors" + ) + + # Step 4: Delete orgs no longer in IRS data (only on full runs) + is_full_run = limit is None and len(urls) == len(EO_BMF_URLS) + if is_full_run and seen_eins: + deleted_count, _ = NonprofitRegistry.objects.exclude( + ein__in=seen_eins + ).delete() + stats["deleted"] = deleted_count + elif not is_full_run: + logger.info("Partial run (--limit or --files used); skipping delete step.") + + duration = time.time() - start_time + logger.info( + f"EO BMF refresh complete in {duration:.1f}s. " + f"Inserted: {stats['inserted']}, Updated: {stats['updated']}, " + f"Unchanged: {stats['unchanged']}, Deleted: {stats['deleted']}, " + f"Skipped: {stats['skipped']}" + ) + self.stdout.write( + self.style.SUCCESS( + f"EO BMF refresh complete. " + f"Inserted: {stats['inserted']}, Updated: {stats['updated']}, " + f"Unchanged: {stats['unchanged']}, Deleted: {stats['deleted']}" + ) + ) + + except Exception as e: + logger.error(f"EO BMF refresh failed: {e}") + raise + finally: + # Cleanup temp files + for url in urls: + filename = url.split("/")[-1] + path = os.path.join(tmp_dir, filename) + if os.path.exists(path): + os.remove(path) + try: + os.rmdir(tmp_dir) + except OSError: + pass + # Release lock + cache.delete(LOCK_KEY) + + def _upsert_chunk(self, chunk): + update_fields = [ + "name", "ico", "street", "city", "state", "zip", "group", + "subsection", "affiliation", "classification", "ruling", + "deductibility", "foundation", "activity", "organization", + "status", "tax_period", "asset_cd", "income_cd", "filing_req_cd", + "pf_filing_req_cd", "acct_pd", "asset_amt", "income_amt", + "revenue_amt", "ntee_cd", "sort_name", "data_hash", "last_updated", + ] + NonprofitRegistry.objects.bulk_create( + chunk, + update_conflicts=True, + unique_fields=["ein"], + update_fields=update_fields, + ) diff --git a/tax_verification/migrations/0002_nonprofitregistry.py b/tax_verification/migrations/0002_nonprofitregistry.py new file mode 100644 index 0000000..2b3848a --- /dev/null +++ b/tax_verification/migrations/0002_nonprofitregistry.py @@ -0,0 +1,68 @@ +# Generated manually + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("tax_verification", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="NonprofitRegistry", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("ein", models.CharField(db_index=True, max_length=9, unique=True)), + ("name", models.CharField(max_length=255)), + ("ico", models.CharField(blank=True, max_length=255)), + ("street", models.CharField(blank=True, max_length=255)), + ("city", models.CharField(blank=True, max_length=100)), + ("state", models.CharField(blank=True, max_length=2)), + ("zip", models.CharField(blank=True, max_length=10)), + ("group", models.CharField(blank=True, max_length=4)), + ("subsection", models.CharField(blank=True, max_length=2)), + ("affiliation", models.CharField(blank=True, max_length=1)), + ("classification", models.CharField(blank=True, max_length=10)), + ("ruling", models.CharField(blank=True, max_length=6)), + ("deductibility", models.CharField(blank=True, max_length=1)), + ("foundation", models.CharField(blank=True, max_length=2)), + ("activity", models.CharField(blank=True, max_length=9)), + ("organization", models.CharField(blank=True, max_length=1)), + ("status", models.CharField(blank=True, max_length=2)), + ("tax_period", models.CharField(blank=True, max_length=6)), + ("asset_cd", models.CharField(blank=True, max_length=1)), + ("income_cd", models.CharField(blank=True, max_length=1)), + ("filing_req_cd", models.CharField(blank=True, max_length=2)), + ("pf_filing_req_cd", models.CharField(blank=True, max_length=1)), + ("acct_pd", models.CharField(blank=True, max_length=2)), + ("asset_amt", models.CharField(blank=True, max_length=20)), + ("income_amt", models.CharField(blank=True, max_length=20)), + ("revenue_amt", models.CharField(blank=True, max_length=20)), + ("ntee_cd", models.CharField(blank=True, max_length=4)), + ("sort_name", models.CharField(blank=True, max_length=255)), + ("data_hash", models.CharField(blank=True, max_length=32)), + ("last_updated", models.DateTimeField(auto_now=True)), + ], + options={ + "verbose_name": "Nonprofit Registry Entry", + "verbose_name_plural": "Nonprofit Registry", + "db_table": "nonprofit_registry", + "indexes": [ + models.Index( + fields=["subsection", "deductibility"], + name="nonprofit_re_subsect_idx", + ), + ], + }, + ), + ] diff --git a/tax_verification/models.py b/tax_verification/models.py index 789530b..7a8160f 100644 --- a/tax_verification/models.py +++ b/tax_verification/models.py @@ -106,3 +106,47 @@ class Meta: def __str__(self): return f"{self.legal_name} ({self.account_id}) - {self.status}" + + +class NonprofitRegistry(models.Model): + ein = models.CharField(max_length=9, unique=True, db_index=True) + name = models.CharField(max_length=255) + ico = models.CharField(max_length=255, blank=True) + street = models.CharField(max_length=255, blank=True) + city = models.CharField(max_length=100, blank=True) + state = models.CharField(max_length=2, blank=True) + zip = models.CharField(max_length=10, blank=True) + group = models.CharField(max_length=4, blank=True) + subsection = models.CharField(max_length=2, blank=True) + affiliation = models.CharField(max_length=1, blank=True) + classification = models.CharField(max_length=10, blank=True) + ruling = models.CharField(max_length=6, blank=True) + deductibility = models.CharField(max_length=1, blank=True) + foundation = models.CharField(max_length=2, blank=True) + activity = models.CharField(max_length=9, blank=True) + organization = models.CharField(max_length=1, blank=True) + status = models.CharField(max_length=2, blank=True) + tax_period = models.CharField(max_length=6, blank=True) + asset_cd = models.CharField(max_length=1, blank=True) + income_cd = models.CharField(max_length=1, blank=True) + filing_req_cd = models.CharField(max_length=2, blank=True) + pf_filing_req_cd = models.CharField(max_length=1, blank=True) + acct_pd = models.CharField(max_length=2, blank=True) + asset_amt = models.CharField(max_length=20, blank=True) + income_amt = models.CharField(max_length=20, blank=True) + revenue_amt = models.CharField(max_length=20, blank=True) + ntee_cd = models.CharField(max_length=4, blank=True) + sort_name = models.CharField(max_length=255, blank=True) + data_hash = models.CharField(max_length=32, blank=True) + last_updated = models.DateTimeField(auto_now=True) + + class Meta: + db_table = "nonprofit_registry" + verbose_name = "Nonprofit Registry Entry" + verbose_name_plural = "Nonprofit Registry" + indexes = [ + models.Index(fields=["subsection", "deductibility"]), + ] + + def __str__(self): + return f"{self.ein} - {self.name}" diff --git a/tax_verification/tasks.py b/tax_verification/tasks.py new file mode 100644 index 0000000..111ec10 --- /dev/null +++ b/tax_verification/tasks.py @@ -0,0 +1,17 @@ +import logging + +from celery import shared_task +from django.core.management import call_command + +jobs_logger = logging.getLogger("jobs") + + +@shared_task +def refresh_eo_bmf(): + jobs_logger.info("Starting scheduled EO BMF refresh...") + try: + call_command("refresh_eo_bmf") + jobs_logger.info("Scheduled EO BMF refresh completed.") + except Exception as e: + jobs_logger.error(f"Scheduled EO BMF refresh failed: {e}") + raise From 38ba0063c4c237574a0c8c2d81b19f2f5af05a48 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Wed, 8 Apr 2026 22:07:52 +0500 Subject: [PATCH 443/454] Add bi-weekly backfill task --- base/celery.py | 6 + .../management/commands/run_backfill.py | 33 + indexer_app/tasks.py | 577 ++++++++++++++++++ 3 files changed, 616 insertions(+) create mode 100644 indexer_app/management/commands/run_backfill.py diff --git a/base/celery.py b/base/celery.py index 1116bc0..567f612 100644 --- a/base/celery.py +++ b/base/celery.py @@ -55,6 +55,11 @@ "schedule": crontab(day_of_month="5", hour="4", minute="0"), # 5th of each month, 4 AM UTC "options": {"queue": "beat_tasks"}, }, + "backfill_missing_data_biweekly": { + "task": "indexer_app.tasks.backfill_missing_data", + "schedule": crontab(day_of_week="0", hour="3", minute="0"), # Every Sunday 3 AM UTC; task self-skips odd weeks for biweekly + "options": {"queue": "beat_tasks"}, + }, } app.conf.task_routes = { @@ -64,6 +69,7 @@ "indexer_app.tasks.stellar_event_indexer": {"queue": "beat_tasks"}, "indexer_app.tasks.process_stellar_events": {"queue": "beat_tasks"}, "tax_verification.tasks.refresh_eo_bmf": {"queue": "beat_tasks"}, + "indexer_app.tasks.backfill_missing_data": {"queue": "beat_tasks"}, } SPOT_INDEXER_QUEUE_NAME = "spot_indexing" diff --git a/indexer_app/management/commands/run_backfill.py b/indexer_app/management/commands/run_backfill.py new file mode 100644 index 0000000..4d4ddb9 --- /dev/null +++ b/indexer_app/management/commands/run_backfill.py @@ -0,0 +1,33 @@ +""" +Manually trigger the biweekly backfill task (bypasses the week parity check). + +Usage: + python manage.py run_backfill # run synchronously (no celery needed) + python manage.py run_backfill --async # dispatch to celery worker +""" + +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = "Manually trigger the biweekly backfill task." + + def add_arguments(self, parser): + parser.add_argument( + "--async", + action="store_true", + default=False, + dest="run_async", + help="Dispatch to celery worker instead of running synchronously.", + ) + + def handle(self, *args, **options): + from indexer_app.tasks import backfill_missing_data + + if options["run_async"]: + backfill_missing_data.delay() + self.stdout.write(self.style.SUCCESS("Backfill task dispatched to celery worker.")) + else: + self.stdout.write(self.style.WARNING("Running backfill synchronously (bypassing week check)...")) + backfill_missing_data(force=True) + self.stdout.write(self.style.SUCCESS("Backfill complete.")) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index dc6ac49..cb00224 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -509,6 +509,583 @@ def process_stellar_events(): jobs_logger.info(f"Finished processing Stellar events.") +@shared_task +def backfill_missing_data(force=False): + """ + Biweekly backfill task: compare on-chain state with DB and fill any gaps. + + Catches data that was missed when on-demand sync API calls failed but the + transaction went through on-chain. Handles: accounts, lists, registrations, + donations, pots (applications & payouts), and campaigns (if app is installed). + + Scheduled every Sunday at 3 AM UTC, but only runs on even ISO weeks + to achieve a biweekly (~every 14 days) cadence. + + Args: + force: If True, skip the week parity check (for manual runs). + """ + import base64 + import json + import time as _time + from datetime import datetime, timezone + + import requests + from django.apps import apps + from django.conf import settings + from django.db import connection + + from accounts.models import Account + from donations.models import Donation + from lists.models import List, ListRegistration + from pots.models import Pot, PotApplication, PotPayout + from tokens.models import Token + + # Only run on even ISO weeks for biweekly cadence (skip check if forced) + now = datetime.now(tz=timezone.utc) + if not force and now.isocalendar().week % 2 != 0: + jobs_logger.info("Backfill skipped — odd week (runs biweekly on even weeks).") + return + + LISTS_CONTRACT = f"lists.{settings.POTLOCK_TLA}" + DONATE_CONTRACT = f"donate.{settings.POTLOCK_TLA}" + + def rpc_call(contract_id, method_name, args=None, timeout=60): + rpc_endpoints = [ + "https://free.rpc.fastnear.com" if settings.ENVIRONMENT != "testnet" else "https://test.rpc.fastnear.com", + "https://rpc.mainnet.near.org" if settings.ENVIRONMENT != "testnet" else "https://rpc.testnet.near.org", + ] + args_base64 = base64.b64encode(json.dumps(args or {}).encode()).decode() + payload = { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "query", + "params": { + "request_type": "call_function", + "account_id": contract_id, + "method_name": method_name, + "args_base64": args_base64, + "finality": "optimistic", + }, + } + for rpc_url in rpc_endpoints: + try: + response = requests.post(rpc_url, json=payload, timeout=timeout) + result = response.json() + if "error" in result: + continue + if "error" in result.get("result", {}): + continue + result_field = result.get("result", {}) + if "result" not in result_field: + return None + result_bytes = bytes(result_field["result"]) + return json.loads(result_bytes.decode()) + except Exception: + continue + return None + + def rpc_call_paginated(contract_id, method_name, base_args, page_size=300, delay=0.5): + all_results = [] + page = 0 + while True: + args = {**base_args, "from_index": page * page_size, "limit": page_size} + results = rpc_call(contract_id, method_name, args, timeout=120) + if results is None: + break + all_results.extend(results) + if len(results) < page_size: + break + page += 1 + _time.sleep(delay) + return all_results + + def fix_sequences(): + tables = [ + ("lists_listregistration", "id"), + ("lists_list", "id"), + ("donations_donation", "id"), + ] + with connection.cursor() as cursor: + for table, col in tables: + try: + seq_name = f"{table}_{col}_seq" + cursor.execute( + f"SELECT setval('{seq_name}', COALESCE((SELECT MAX({col}) FROM {table}), 0) + 1, false)" + ) + except Exception as e: + jobs_logger.warning(f"Could not reset sequence for {table}: {e}") + + def backfill_accounts(): + """Backfill accounts from list registrations on-chain.""" + jobs_logger.info("Backfill: checking accounts...") + db_lists = List.objects.all() + if not db_lists.exists(): + return 0 + + missing_count = 0 + for list_obj in db_lists: + on_chain_regs = rpc_call_paginated( + LISTS_CONTRACT, + "get_registrations_for_list", + {"list_id": list_obj.on_chain_id}, + page_size=300, + ) + if on_chain_regs is None: + continue + + # Collect all account IDs referenced in registrations + account_ids = set() + for reg in on_chain_regs: + account_ids.add(reg["registrant_id"]) + if reg.get("registered_by"): + account_ids.add(reg["registered_by"]) + + # Also add list owner and admins from on-chain data + on_chain_list = rpc_call(LISTS_CONTRACT, "get_list", {"list_id": list_obj.on_chain_id}) + if on_chain_list: + account_ids.add(on_chain_list["owner"]) + for admin_id in on_chain_list.get("admins", []): + account_ids.add(admin_id) + + existing_ids = set(Account.objects.filter(id__in=account_ids).values_list("id", flat=True)) + new_ids = account_ids - existing_ids + + if new_ids: + Account.objects.bulk_create( + [Account(id=aid) for aid in new_ids], + ignore_conflicts=True, + ) + jobs_logger.info(f" Created {len(new_ids)} missing accounts from list {list_obj.on_chain_id}") + missing_count += len(new_ids) + + _time.sleep(0.3) + + jobs_logger.info(f"Backfill accounts: {missing_count} created.") + return missing_count + + def backfill_lists(): + jobs_logger.info("Backfill: checking lists...") + on_chain_lists = rpc_call(LISTS_CONTRACT, "get_lists") + if not on_chain_lists: + jobs_logger.error("Failed to fetch lists from contract") + return 0 + + db_list_ids = set(List.objects.values_list("on_chain_id", flat=True)) + on_chain_ids = {l["id"] for l in on_chain_lists} + missing_ids = on_chain_ids - db_list_ids + + if not missing_ids: + jobs_logger.info("Backfill lists: all in sync.") + return 0 + + missing_count = 0 + for l in on_chain_lists: + if l["id"] not in missing_ids: + continue + owner, _ = Account.objects.get_or_create(id=l["owner"]) + list_obj, _ = List.objects.update_or_create( + on_chain_id=l["id"], + defaults={ + "owner": owner, + "name": l["name"], + "description": l.get("description", ""), + "cover_image_url": l.get("cover_image_url"), + "admin_only_registrations": l.get("admin_only_registrations", False), + "default_registration_status": l.get("default_registration_status", "Pending"), + "created_at": datetime.fromtimestamp(l["created_at"] / 1000, tz=timezone.utc), + "updated_at": datetime.fromtimestamp(l["updated_at"] / 1000, tz=timezone.utc), + }, + ) + for admin_id in l.get("admins", []): + admin, _ = Account.objects.get_or_create(id=admin_id) + list_obj.admins.add(admin) + missing_count += 1 + + jobs_logger.info(f"Backfill lists: {missing_count} created.") + return missing_count + + def backfill_registrations(): + jobs_logger.info("Backfill: checking registrations...") + db_lists = List.objects.all() + if not db_lists.exists(): + return 0 + + total_missing = 0 + for list_obj in db_lists: + on_chain_regs = rpc_call_paginated( + LISTS_CONTRACT, + "get_registrations_for_list", + {"list_id": list_obj.on_chain_id}, + page_size=300, + ) + if on_chain_regs is None: + continue + + db_registrant_ids = set( + ListRegistration.objects.filter(list=list_obj).values_list("registrant_id", flat=True) + ) + on_chain_registrant_ids = {r["registrant_id"] for r in on_chain_regs} + missing = on_chain_registrant_ids - db_registrant_ids + + if not missing: + continue + + for reg in on_chain_regs: + if reg["registrant_id"] not in missing: + continue + registrant, _ = Account.objects.get_or_create(id=reg["registrant_id"]) + registered_by, _ = Account.objects.get_or_create( + id=reg.get("registered_by", reg["registrant_id"]) + ) + ListRegistration.objects.update_or_create( + list=list_obj, + registrant=registrant, + defaults={ + "registered_by": registered_by, + "status": reg["status"], + "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000, tz=timezone.utc), + "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000, tz=timezone.utc), + "admin_notes": reg.get("admin_notes"), + "registrant_notes": reg.get("registrant_notes"), + "tx_hash": None, + }, + ) + total_missing += 1 + + _time.sleep(0.5) + + jobs_logger.info(f"Backfill registrations: {total_missing} created.") + return total_missing + + def backfill_donations(): + jobs_logger.info("Backfill: checking direct donations...") + on_chain_donations = rpc_call_paginated( + DONATE_CONTRACT, "get_donations", {}, page_size=300, + ) + if on_chain_donations is None: + jobs_logger.error("Failed to fetch donations from contract") + return 0 + + db_donation_ids = set( + Donation.objects.filter(pot__isnull=True).values_list("on_chain_id", flat=True) + ) + on_chain_ids = {d["id"] for d in on_chain_donations} + missing_ids = on_chain_ids - db_donation_ids + + if not missing_ids: + jobs_logger.info("Backfill donations: all in sync.") + return 0 + + missing_count = 0 + for d in on_chain_donations: + if d["id"] not in missing_ids: + continue + + donor, _ = Account.objects.get_or_create(id=d.get("donor_id", "")) + recipient, _ = Account.objects.get_or_create(id=d.get("recipient_id", "")) + ft_id = d.get("ft_id", "near") + token_id = ft_id if ft_id != "near" else "near" + token, _ = Token.objects.get_or_create(id=token_id) + + referrer = None + if d.get("referrer_id"): + referrer, _ = Account.objects.get_or_create(id=d["referrer_id"]) + + chef = None + if d.get("chef_id"): + chef, _ = Account.objects.get_or_create(id=d["chef_id"]) + + Donation.objects.update_or_create( + on_chain_id=d["id"], + pot__isnull=True, + defaults={ + "donor": donor, + "total_amount": d.get("total_amount", "0"), + "net_amount": d.get("net_amount", "0"), + "token": token, + "matching_pool": d.get("matching_pool", False), + "message": d.get("message"), + "donated_at": datetime.fromtimestamp(d["donated_at_ms"] / 1000, tz=timezone.utc), + "recipient": recipient, + "protocol_fee": d.get("protocol_fee", "0"), + "referrer_fee": d.get("referrer_fee"), + "referrer": referrer, + "chef": chef, + "chef_fee": d.get("chef_fee"), + "tx_hash": None, + }, + ) + missing_count += 1 + + jobs_logger.info(f"Backfill donations: {missing_count} created.") + return missing_count + + def backfill_pots(): + jobs_logger.info("Backfill: checking pots (applications & payouts)...") + db_pots = Pot.objects.all() + if not db_pots.exists(): + return 0 + + total_missing = 0 + for pot in db_pots: + pot_contract = str(pot.account_id) + if not pot_contract or "." not in pot_contract: + continue + + # Applications + on_chain_apps = rpc_call_paginated(pot_contract, "get_applications", {}, page_size=300) + if on_chain_apps is not None: + db_app_ids = set( + PotApplication.objects.filter(pot=pot).values_list("applicant_id", flat=True) + ) + on_chain_app_ids = {a.get("project_id", a.get("applicant_id", "")) for a in on_chain_apps} + missing_apps = on_chain_app_ids - db_app_ids + + if missing_apps: + for a in on_chain_apps: + applicant_id = a.get("project_id", a.get("applicant_id", "")) + if applicant_id not in missing_apps: + continue + applicant, _ = Account.objects.get_or_create(id=applicant_id) + PotApplication.objects.update_or_create( + pot=pot, + applicant=applicant, + defaults={ + "message": a.get("message", ""), + "status": a.get("status", "Pending"), + "submitted_at": datetime.fromtimestamp( + a["submitted_at"] / 1000, tz=timezone.utc + ) if a.get("submitted_at") else now, + "updated_at": datetime.fromtimestamp( + a["updated_at"] / 1000, tz=timezone.utc + ) if a.get("updated_at") else None, + "tx_hash": None, + }, + ) + total_missing += len(missing_apps) + + # Payouts + on_chain_payouts = rpc_call_paginated(pot_contract, "get_payouts", {}, page_size=300) + if on_chain_payouts is not None: + db_payout_recipients = set( + PotPayout.objects.filter(pot=pot).values_list("recipient_id", flat=True) + ) + on_chain_payout_recipients = { + p.get("project_id", p.get("recipient_id", "")) for p in on_chain_payouts + } + missing_payouts = on_chain_payout_recipients - db_payout_recipients + if missing_payouts: + for p in on_chain_payouts: + recipient_id = p.get("project_id", p.get("recipient_id", "")) + if recipient_id not in missing_payouts: + continue + recipient, _ = Account.objects.get_or_create(id=recipient_id) + token = None + if p.get("ft_id"): + token, _ = Token.objects.get_or_create(id=p["ft_id"]) + PotPayout.objects.update_or_create( + pot=pot, + recipient=recipient, + defaults={ + "amount": p.get("amount", "0"), + "token": token, + "paid_at": datetime.fromtimestamp( + p["paid_at"] / 1000, tz=timezone.utc + ) if p.get("paid_at") else None, + "tx_hash": None, + }, + ) + total_missing += len(missing_payouts) + + _time.sleep(0.5) + + jobs_logger.info(f"Backfill pots: {total_missing} missing apps/payouts created.") + return total_missing + + def backfill_campaigns(): + """Backfill campaigns and campaign donations from on-chain data.""" + if not apps.is_installed("campaigns"): + jobs_logger.info("Backfill campaigns: app not installed, skipping.") + return 0 + + from campaigns.models import Campaign, CampaignDonation + + CAMPAIGNS_CONTRACT = ( + f"v1.campaign.{settings.POTLOCK_TLA}" + if settings.ENVIRONMENT == "testnet" + else f"v1.campaigns.{settings.POTLOCK_TLA}" + ) + + jobs_logger.info("Backfill: checking campaigns...") + + # Fetch all campaigns by iterating with get_campaign until we get None + # The contract uses sequential IDs starting from 1 + on_chain_campaigns = [] + campaign_id = 1 + consecutive_failures = 0 + while consecutive_failures < 3: + data = rpc_call(CAMPAIGNS_CONTRACT, "get_campaign", {"campaign_id": campaign_id}) + if data is None: + consecutive_failures += 1 + campaign_id += 1 + continue + consecutive_failures = 0 + data["_chain_id"] = campaign_id + on_chain_campaigns.append(data) + campaign_id += 1 + _time.sleep(0.3) + + if not on_chain_campaigns: + jobs_logger.info("Backfill campaigns: no campaigns found on chain.") + return 0 + + db_campaign_ids = set(Campaign.objects.values_list("on_chain_id", flat=True)) + total_missing = 0 + + for data in on_chain_campaigns: + cid = data.get("id", data["_chain_id"]) + if cid in db_campaign_ids: + # Still update existing campaigns to sync latest state (e.g. raised amounts) + try: + campaign = Campaign.objects.get(on_chain_id=cid) + campaign.total_raised_amount = str(data.get("total_raised_amount", "0")) + campaign.net_raised_amount = str(data.get("net_raised_amount", "0")) + campaign.escrow_balance = str(data.get("escrow_balance", "0")) + campaign.save(update_fields=["total_raised_amount", "net_raised_amount", "escrow_balance"]) + except Campaign.DoesNotExist: + pass + continue + + # Create missing campaign + owner, _ = Account.objects.get_or_create(id=data["owner"]) + recipient, _ = Account.objects.get_or_create(id=data["recipient"]) + + token = None + token_id = data.get("ft_id") + if token_id: + token_acct, _ = Account.objects.get_or_create(id=token_id) + token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) + + start_at = datetime.fromtimestamp(data["start_ms"] / 1000, tz=timezone.utc) + end_at = ( + datetime.fromtimestamp(data["end_ms"] / 1000, tz=timezone.utc) + if data.get("end_ms") else None + ) + created_at = ( + datetime.fromtimestamp(data["created_ms"] / 1000, tz=timezone.utc) + if data.get("created_ms") else now + ) + + Campaign.objects.update_or_create( + on_chain_id=cid, + defaults={ + "owner": owner, + "name": data["name"], + "description": data.get("description"), + "cover_image_url": data.get("cover_image_url"), + "recipient": recipient, + "token": token, + "start_at": start_at, + "end_at": end_at, + "created_at": created_at, + "target_amount": str(data["target_amount"]), + "min_amount": str(data["min_amount"]) if data.get("min_amount") else None, + "max_amount": str(data["max_amount"]) if data.get("max_amount") else None, + "total_raised_amount": str(data.get("total_raised_amount", "0")), + "net_raised_amount": str(data.get("net_raised_amount", "0")), + "escrow_balance": str(data.get("escrow_balance", "0")), + "referral_fee_basis_points": data["referral_fee_basis_points"], + "creator_fee_basis_points": data["creator_fee_basis_points"], + "allow_fee_avoidance": data.get("allow_fee_avoidance", False), + }, + ) + total_missing += 1 + + # Backfill campaign donations for all campaigns + for campaign in Campaign.objects.all(): + on_chain_donations = rpc_call_paginated( + CAMPAIGNS_CONTRACT, + "get_donations_for_campaign", + {"campaign_id": campaign.on_chain_id}, + page_size=300, + ) + if on_chain_donations is None: + continue + + db_donation_ids = set( + CampaignDonation.objects.filter(campaign=campaign).values_list("on_chain_id", flat=True) + ) + on_chain_ids = {d["id"] for d in on_chain_donations} + missing = on_chain_ids - db_donation_ids + + if not missing: + continue + + for d in on_chain_donations: + if d["id"] not in missing: + continue + + donor, _ = Account.objects.get_or_create(id=d["donor_id"]) + referrer = None + if d.get("referrer_id"): + referrer, _ = Account.objects.get_or_create(id=d["referrer_id"]) + + don_token = campaign.token + if d.get("ft_id"): + token_acct, _ = Account.objects.get_or_create(id=d["ft_id"]) + don_token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) + + donated_at = datetime.fromtimestamp(d["donated_at_ms"] / 1000, tz=timezone.utc) + returned_at = ( + datetime.fromtimestamp(d["returned_at_ms"] / 1000, tz=timezone.utc) + if d.get("returned_at_ms") else None + ) + + CampaignDonation.objects.update_or_create( + on_chain_id=d["id"], + campaign=campaign, + donor=donor, + defaults={ + "token": don_token, + "total_amount": str(d["total_amount"]), + "net_amount": str(d["net_amount"]), + "message": d.get("message"), + "donated_at": donated_at, + "protocol_fee": str(d["protocol_fee"]), + "referrer": referrer, + "referrer_fee": str(d["referrer_fee"]) if d.get("referrer_fee") else None, + "creator_fee": str(d["creator_fee"]), + "returned_at": returned_at, + "escrowed": d.get("is_in_escrow", False), + "tx_hash": None, + }, + ) + total_missing += 1 + + _time.sleep(0.5) + + jobs_logger.info(f"Backfill campaigns: {total_missing} missing entries created.") + return total_missing + + # === Main backfill execution === + jobs_logger.info(f"=== Biweekly Backfill Starting (ISO week {now.isocalendar().week}) ===") + + fix_sequences() + + total = 0 + total += backfill_accounts() + total += backfill_lists() + total += backfill_registrations() + total += backfill_donations() + total += backfill_pots() + total += backfill_campaigns() + + if total == 0: + jobs_logger.info("=== Backfill complete: everything in sync! ===") + else: + jobs_logger.info(f"=== Backfill complete: {total} missing entries created. ===") + + @task_revoked.connect def on_task_revoked(request, terminated, signum, expired, **kwargs): logger.info( From f24fda16e38fe0870b370c58768557b9baea4027 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Wed, 8 Apr 2026 22:23:59 +0500 Subject: [PATCH 444/454] Made fixes to backfill file --- indexer_app/tasks.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index cb00224..70edbf3 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -650,11 +650,9 @@ def backfill_accounts(): existing_ids = set(Account.objects.filter(id__in=account_ids).values_list("id", flat=True)) new_ids = account_ids - existing_ids + for aid in new_ids: + Account.objects.get_or_create(id=aid) if new_ids: - Account.objects.bulk_create( - [Account(id=aid) for aid in new_ids], - ignore_conflicts=True, - ) jobs_logger.info(f" Created {len(new_ids)} missing accounts from list {list_obj.on_chain_id}") missing_count += len(new_ids) @@ -785,7 +783,8 @@ def backfill_donations(): recipient, _ = Account.objects.get_or_create(id=d.get("recipient_id", "")) ft_id = d.get("ft_id", "near") token_id = ft_id if ft_id != "near" else "near" - token, _ = Token.objects.get_or_create(id=token_id) + token_acct, _ = Account.objects.get_or_create(id=token_id) + token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) referrer = None if d.get("referrer_id"): @@ -882,7 +881,8 @@ def backfill_pots(): recipient, _ = Account.objects.get_or_create(id=recipient_id) token = None if p.get("ft_id"): - token, _ = Token.objects.get_or_create(id=p["ft_id"]) + token_acct, _ = Account.objects.get_or_create(id=p["ft_id"]) + token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) PotPayout.objects.update_or_create( pot=pot, recipient=recipient, From a02ef39f072c3fb0311a3a5479bb45eefcd90b8a Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Sun, 12 Apr 2026 16:59:21 +0500 Subject: [PATCH 445/454] Fix backfill script --- api/sync.py | 107 +++++++++----- campaigns/sync.py | 3 +- indexer_app/tasks.py | 340 +++++++++++++++++++++++++++---------------- 3 files changed, 290 insertions(+), 160 deletions(-) diff --git a/api/sync.py b/api/sync.py index 23c2b00..45d7806 100644 --- a/api/sync.py +++ b/api/sync.py @@ -19,7 +19,7 @@ import base64 import json import logging -from datetime import datetime +from datetime import datetime, timezone import requests from django.conf import settings @@ -209,7 +209,7 @@ def post(self, request, list_id: int): existing_list.cover_image_url = data.get("cover_image_url") existing_list.admin_only_registrations = data.get("admin_only_registrations", False) existing_list.default_registration_status = data.get("default_registration_status", "Pending") - existing_list.updated_at = datetime.fromtimestamp(data["updated_at"] / 1000) + existing_list.updated_at = datetime.fromtimestamp(data["updated_at"] / 1000, tz=timezone.utc) existing_list.save() # Update admins @@ -233,8 +233,8 @@ def post(self, request, list_id: int): cover_image_url=data.get("cover_image_url"), admin_only_registrations=data.get("admin_only_registrations", False), default_registration_status=data.get("default_registration_status", "Pending"), - created_at=datetime.fromtimestamp(data["created_at"] / 1000), - updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), + created_at=datetime.fromtimestamp(data["created_at"] / 1000, tz=timezone.utc), + updated_at=datetime.fromtimestamp(data["updated_at"] / 1000, tz=timezone.utc), ) # Create owner account @@ -303,8 +303,8 @@ def post(self, request, list_id: int): defaults={ "registered_by_id": reg.get("registered_by", reg["registrant_id"]), "status": reg.get("status", "Pending"), - "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000), - "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), + "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000, tz=timezone.utc), + "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000, tz=timezone.utc), "admin_notes": reg.get("admin_notes"), "registrant_notes": reg.get("registrant_notes"), } @@ -372,8 +372,8 @@ def post(self, request, list_id: int, registrant_id: str): defaults={ "registered_by_id": reg.get("registered_by", reg["registrant_id"]), "status": reg.get("status", "Pending"), - "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000), - "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000), + "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000, tz=timezone.utc), + "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000, tz=timezone.utc), "admin_notes": reg.get("admin_notes"), "registrant_notes": reg.get("registrant_notes"), } @@ -438,10 +438,10 @@ def post(self, request, pot_id: str): existing_pot.description = config.get("pot_description", "") existing_pot.max_approved_applicants = config.get("max_projects", 0) existing_pot.base_currency = config.get("base_currency", "near") - existing_pot.application_start = datetime.fromtimestamp(config["application_start_ms"] / 1000) - existing_pot.application_end = datetime.fromtimestamp(config["application_end_ms"] / 1000) - existing_pot.matching_round_start = datetime.fromtimestamp(config["public_round_start_ms"] / 1000) - existing_pot.matching_round_end = datetime.fromtimestamp(config["public_round_end_ms"] / 1000) + existing_pot.application_start = datetime.fromtimestamp(config["application_start_ms"] / 1000, tz=timezone.utc) + existing_pot.application_end = datetime.fromtimestamp(config["application_end_ms"] / 1000, tz=timezone.utc) + existing_pot.matching_round_start = datetime.fromtimestamp(config["public_round_start_ms"] / 1000, tz=timezone.utc) + existing_pot.matching_round_end = datetime.fromtimestamp(config["public_round_end_ms"] / 1000, tz=timezone.utc) existing_pot.registry_provider = config.get("registry_provider") existing_pot.min_matching_pool_donation_amount = config.get("min_matching_pool_donation_amount", "0") existing_pot.sybil_wrapper_provider = config.get("sybil_wrapper_provider") @@ -451,7 +451,7 @@ def post(self, request, pot_id: str): existing_pot.referral_fee_public_round_basis_points = config["referral_fee_public_round_basis_points"] existing_pot.chef_fee_basis_points = config["chef_fee_basis_points"] if config.get("cooldown_end_ms"): - existing_pot.cooldown_end = datetime.fromtimestamp(config["cooldown_end_ms"] / 1000) + existing_pot.cooldown_end = datetime.fromtimestamp(config["cooldown_end_ms"] / 1000, tz=timezone.utc) existing_pot.all_paid_out = config.get("all_paid_out", False) existing_pot.protocol_config_provider = config.get("protocol_config_provider") existing_pot.save() @@ -468,18 +468,39 @@ def post(self, request, pot_id: str): "pot_id": pot_id }) else: - # Ensure pot factory exists + # Auto-create pot factory if missing if not PotFactory.objects.filter(account_id=factory_id).exists(): - return Response({ - "error": f"Pot factory {factory_id} not found in database. Sync the factory first." - }, status=404) + try: + factory_config = fetch_from_rpc("get_config", contract_id=factory_id) + if not factory_config: + return Response({ + "error": f"Pot factory {factory_id} not found on chain." + }, status=404) + factory_owner, _ = Account.objects.get_or_create(id=factory_config.get("owner", factory_id)) + protocol_fee_recipient, _ = Account.objects.get_or_create( + id=factory_config.get("protocol_fee_recipient_account", factory_config.get("owner", factory_id)) + ) + PotFactory.objects.create( + account_id=factory_id, + owner=factory_owner, + deployed_at=datetime.now(tz=timezone.utc), + protocol_fee_basis_points=factory_config.get("protocol_fee_basis_points", 0), + protocol_fee_recipient=protocol_fee_recipient, + require_whitelist=factory_config.get("require_whitelist", False), + ) + logger.info(f"Auto-created PotFactory {factory_id}") + except Exception as e: + logger.error(f"Failed to auto-create PotFactory {factory_id}: {e}") + return Response({ + "error": f"Pot factory {factory_id} not found and auto-creation failed: {e}" + }, status=404) # Create new pot pot = Pot.objects.create( account_id=pot_id, pot_factory_id=factory_id, deployer=owner, - deployed_at=datetime.now(), + deployed_at=datetime.now(tz=timezone.utc), source_metadata=config.get("source_metadata", {}), owner=owner, chef=chef, @@ -487,10 +508,10 @@ def post(self, request, pot_id: str): description=config.get("pot_description", ""), max_approved_applicants=config.get("max_projects", 0), base_currency=config.get("base_currency", "near"), - application_start=datetime.fromtimestamp(config["application_start_ms"] / 1000), - application_end=datetime.fromtimestamp(config["application_end_ms"] / 1000), - matching_round_start=datetime.fromtimestamp(config["public_round_start_ms"] / 1000), - matching_round_end=datetime.fromtimestamp(config["public_round_end_ms"] / 1000), + application_start=datetime.fromtimestamp(config["application_start_ms"] / 1000, tz=timezone.utc), + application_end=datetime.fromtimestamp(config["application_end_ms"] / 1000, tz=timezone.utc), + matching_round_start=datetime.fromtimestamp(config["public_round_start_ms"] / 1000, tz=timezone.utc), + matching_round_end=datetime.fromtimestamp(config["public_round_end_ms"] / 1000, tz=timezone.utc), registry_provider=config.get("registry_provider"), min_matching_pool_donation_amount=config.get("min_matching_pool_donation_amount", "0"), sybil_wrapper_provider=config.get("sybil_wrapper_provider"), @@ -606,7 +627,8 @@ def post(self, request, pot_id: str): ) donated_at = datetime.fromtimestamp( - (don.get("donated_at") or don.get("donated_at_ms", 0)) / 1000 + (don.get("donated_at") or don.get("donated_at_ms", 0)) / 1000, + tz=timezone.utc, ) Donation.objects.update_or_create( @@ -679,8 +701,8 @@ def post(self, request, pot_id: str): # Upsert applicant account applicant, _ = Account.objects.get_or_create(id=app["project_id"]) - submitted_at = datetime.fromtimestamp(app["submitted_at"] / 1000) - updated_at = datetime.fromtimestamp(app["updated_at"] / 1000) if app.get("updated_at") else submitted_at + submitted_at = datetime.fromtimestamp(app["submitted_at"] / 1000, tz=timezone.utc) + updated_at = datetime.fromtimestamp(app["updated_at"] / 1000, tz=timezone.utc) if app.get("updated_at") else submitted_at application, created = PotApplication.objects.update_or_create( pot=pot, @@ -772,7 +794,7 @@ def post(self, request, pot_id: str): # Also update pot's cooldown_end and all_paid_out from config if config.get("cooldown_end_ms"): - pot.cooldown_end = datetime.fromtimestamp(config["cooldown_end_ms"] / 1000) + pot.cooldown_end = datetime.fromtimestamp(config["cooldown_end_ms"] / 1000, tz=timezone.utc) pot.all_paid_out = config.get("all_paid_out", False) pot.save() @@ -784,17 +806,28 @@ def post(self, request, pot_id: str): # Parse paid_at if present paid_at = None if payout.get("paid_at"): - paid_at = datetime.fromtimestamp(payout["paid_at"] / 1000) + paid_at = datetime.fromtimestamp(payout["paid_at"] / 1000, tz=timezone.utc) - PotPayout.objects.update_or_create( - pot=pot, - recipient=recipient, - defaults={ - "amount": payout["amount"], - "token": near_token, - "paid_at": paid_at, - } - ) + payout_defaults = { + "amount": payout["amount"], + "token": near_token, + "paid_at": paid_at, + } + + # Use on_chain_id as lookup if available, otherwise fall back to (pot, recipient) + if payout.get("id") is not None: + payout_defaults["pot"] = pot + payout_defaults["recipient"] = recipient + PotPayout.objects.update_or_create( + on_chain_id=payout["id"], + defaults=payout_defaults, + ) + else: + PotPayout.objects.update_or_create( + pot=pot, + recipient=recipient, + defaults=payout_defaults, + ) synced += 1 return Response({ @@ -862,7 +895,7 @@ def post(self, request, pot_id: str): for c in all_challenges: challenger, _ = Account.objects.get_or_create(id=c["challenger_id"]) - created_at = datetime.fromtimestamp(c["created_at"] / 1000) + created_at = datetime.fromtimestamp(c["created_at"] / 1000, tz=timezone.utc) challenge, _ = PotPayoutChallenge.objects.update_or_create( pot=pot, diff --git a/campaigns/sync.py b/campaigns/sync.py index b6f651d..f0fad90 100644 --- a/campaigns/sync.py +++ b/campaigns/sync.py @@ -272,10 +272,11 @@ def sync_donation_from_data(campaign: Campaign, donation_data: dict, tx_hash: st if tx_hash: donation_defaults["tx_hash"] = tx_hash + donation_defaults["donor"] = donor + donation, created = CampaignDonation.objects.update_or_create( on_chain_id=donation_data["id"], campaign=campaign, - donor=donor, defaults=donation_defaults, ) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 70edbf3..162156a 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -516,7 +516,8 @@ def backfill_missing_data(force=False): Catches data that was missed when on-demand sync API calls failed but the transaction went through on-chain. Handles: accounts, lists, registrations, - donations, pots (applications & payouts), and campaigns (if app is installed). + donations (direct + pot), pots (applications & payouts), and campaigns + (if app is installed). Scheduled every Sunday at 3 AM UTC, but only runs on even ISO weeks to achieve a biweekly (~every 14 days) cadence. @@ -535,6 +536,7 @@ def backfill_missing_data(force=False): from django.db import connection from accounts.models import Account + from chains.models import Chain from donations.models import Donation from lists.models import List, ListRegistration from pots.models import Pot, PotApplication, PotPayout @@ -549,6 +551,8 @@ def backfill_missing_data(force=False): LISTS_CONTRACT = f"lists.{settings.POTLOCK_TLA}" DONATE_CONTRACT = f"donate.{settings.POTLOCK_TLA}" + # --- Helpers --- + def rpc_call(contract_id, method_name, args=None, timeout=60): rpc_endpoints = [ "https://free.rpc.fastnear.com" if settings.ENVIRONMENT != "testnet" else "https://test.rpc.fastnear.com", @@ -599,6 +603,39 @@ def rpc_call_paginated(contract_id, method_name, base_args, page_size=300, delay _time.sleep(delay) return all_results + def ensure_accounts_exist(account_ids): + """Bulk-create missing Account records, bypassing Account.save() to avoid + triggering NEAR Social profile fetches for every new account.""" + if not account_ids: + return + account_ids = {aid for aid in account_ids if aid} + existing = set(Account.objects.filter(id__in=account_ids).values_list("id", flat=True)) + missing = account_ids - existing + if missing: + near_chain = Chain.objects.get(name="NEAR") + Account.objects.bulk_create( + [Account(id=aid, chain=near_chain) for aid in missing], + ignore_conflicts=True, + ) + + def get_near_token(): + """Get or create the NEAR token.""" + near_acct, _ = Account.objects.get_or_create(id="near") + near_token, _ = Token.objects.get_or_create( + account=near_acct, + defaults={"name": "NEAR", "symbol": "NEAR", "decimals": 24}, + ) + return near_token + + def get_token_for_ft(ft_id, default_token): + """Get the Token for an ft_id, falling back to default_token.""" + if not ft_id or ft_id == "near": + return default_token + ensure_accounts_exist({ft_id}) + token_acct = Account.objects.get(id=ft_id) + token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) + return token + def fix_sequences(): tables = [ ("lists_listregistration", "id"), @@ -615,51 +652,7 @@ def fix_sequences(): except Exception as e: jobs_logger.warning(f"Could not reset sequence for {table}: {e}") - def backfill_accounts(): - """Backfill accounts from list registrations on-chain.""" - jobs_logger.info("Backfill: checking accounts...") - db_lists = List.objects.all() - if not db_lists.exists(): - return 0 - - missing_count = 0 - for list_obj in db_lists: - on_chain_regs = rpc_call_paginated( - LISTS_CONTRACT, - "get_registrations_for_list", - {"list_id": list_obj.on_chain_id}, - page_size=300, - ) - if on_chain_regs is None: - continue - - # Collect all account IDs referenced in registrations - account_ids = set() - for reg in on_chain_regs: - account_ids.add(reg["registrant_id"]) - if reg.get("registered_by"): - account_ids.add(reg["registered_by"]) - - # Also add list owner and admins from on-chain data - on_chain_list = rpc_call(LISTS_CONTRACT, "get_list", {"list_id": list_obj.on_chain_id}) - if on_chain_list: - account_ids.add(on_chain_list["owner"]) - for admin_id in on_chain_list.get("admins", []): - account_ids.add(admin_id) - - existing_ids = set(Account.objects.filter(id__in=account_ids).values_list("id", flat=True)) - new_ids = account_ids - existing_ids - - for aid in new_ids: - Account.objects.get_or_create(id=aid) - if new_ids: - jobs_logger.info(f" Created {len(new_ids)} missing accounts from list {list_obj.on_chain_id}") - missing_count += len(new_ids) - - _time.sleep(0.3) - - jobs_logger.info(f"Backfill accounts: {missing_count} created.") - return missing_count + # --- Backfill functions --- def backfill_lists(): jobs_logger.info("Backfill: checking lists...") @@ -676,15 +669,23 @@ def backfill_lists(): jobs_logger.info("Backfill lists: all in sync.") return 0 + # Bulk-create all accounts referenced in missing lists + all_account_ids = set() + for l in on_chain_lists: + if l["id"] not in missing_ids: + continue + all_account_ids.add(l["owner"]) + all_account_ids.update(l.get("admins", [])) + ensure_accounts_exist(all_account_ids) + missing_count = 0 for l in on_chain_lists: if l["id"] not in missing_ids: continue - owner, _ = Account.objects.get_or_create(id=l["owner"]) list_obj, _ = List.objects.update_or_create( on_chain_id=l["id"], defaults={ - "owner": owner, + "owner_id": l["owner"], "name": l["name"], "description": l.get("description", ""), "cover_image_url": l.get("cover_image_url"), @@ -695,8 +696,7 @@ def backfill_lists(): }, ) for admin_id in l.get("admins", []): - admin, _ = Account.objects.get_or_create(id=admin_id) - list_obj.admins.add(admin) + list_obj.admins.add(Account.objects.get(id=admin_id)) missing_count += 1 jobs_logger.info(f"Backfill lists: {missing_count} created.") @@ -728,18 +728,23 @@ def backfill_registrations(): if not missing: continue + # Bulk-create accounts for this list's missing registrations + account_ids = set() + for reg in on_chain_regs: + if reg["registrant_id"] not in missing: + continue + account_ids.add(reg["registrant_id"]) + account_ids.add(reg.get("registered_by", reg["registrant_id"])) + ensure_accounts_exist(account_ids) + for reg in on_chain_regs: if reg["registrant_id"] not in missing: continue - registrant, _ = Account.objects.get_or_create(id=reg["registrant_id"]) - registered_by, _ = Account.objects.get_or_create( - id=reg.get("registered_by", reg["registrant_id"]) - ) ListRegistration.objects.update_or_create( list=list_obj, - registrant=registrant, + registrant_id=reg["registrant_id"], defaults={ - "registered_by": registered_by, + "registered_by_id": reg.get("registered_by", reg["registrant_id"]), "status": reg["status"], "submitted_at": datetime.fromtimestamp(reg["submitted_ms"] / 1000, tz=timezone.utc), "updated_at": datetime.fromtimestamp(reg["updated_ms"] / 1000, tz=timezone.utc), @@ -774,42 +779,45 @@ def backfill_donations(): jobs_logger.info("Backfill donations: all in sync.") return 0 - missing_count = 0 + # Bulk-create all accounts + account_ids = set() for d in on_chain_donations: if d["id"] not in missing_ids: continue - - donor, _ = Account.objects.get_or_create(id=d.get("donor_id", "")) - recipient, _ = Account.objects.get_or_create(id=d.get("recipient_id", "")) + account_ids.add(d.get("donor_id", "")) + account_ids.add(d.get("recipient_id", "")) + if d.get("referrer_id"): + account_ids.add(d["referrer_id"]) + if d.get("chef_id"): + account_ids.add(d["chef_id"]) ft_id = d.get("ft_id", "near") - token_id = ft_id if ft_id != "near" else "near" - token_acct, _ = Account.objects.get_or_create(id=token_id) - token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) + account_ids.add(ft_id if ft_id != "near" else "near") + ensure_accounts_exist(account_ids) - referrer = None - if d.get("referrer_id"): - referrer, _ = Account.objects.get_or_create(id=d["referrer_id"]) + near_token = get_near_token() + missing_count = 0 + for d in on_chain_donations: + if d["id"] not in missing_ids: + continue - chef = None - if d.get("chef_id"): - chef, _ = Account.objects.get_or_create(id=d["chef_id"]) + token = get_token_for_ft(d.get("ft_id", "near"), near_token) Donation.objects.update_or_create( on_chain_id=d["id"], pot__isnull=True, defaults={ - "donor": donor, + "donor_id": d.get("donor_id", ""), "total_amount": d.get("total_amount", "0"), "net_amount": d.get("net_amount", "0"), "token": token, "matching_pool": d.get("matching_pool", False), "message": d.get("message"), "donated_at": datetime.fromtimestamp(d["donated_at_ms"] / 1000, tz=timezone.utc), - "recipient": recipient, + "recipient_id": d.get("recipient_id", ""), "protocol_fee": d.get("protocol_fee", "0"), "referrer_fee": d.get("referrer_fee"), - "referrer": referrer, - "chef": chef, + "referrer_id": d.get("referrer_id"), + "chef_id": d.get("chef_id"), "chef_fee": d.get("chef_fee"), "tx_hash": None, }, @@ -820,18 +828,20 @@ def backfill_donations(): return missing_count def backfill_pots(): - jobs_logger.info("Backfill: checking pots (applications & payouts)...") + jobs_logger.info("Backfill: checking pots (applications, payouts & donations)...") db_pots = Pot.objects.all() if not db_pots.exists(): return 0 + near_token = get_near_token() total_missing = 0 + for pot in db_pots: pot_contract = str(pot.account_id) if not pot_contract or "." not in pot_contract: continue - # Applications + # --- Applications --- on_chain_apps = rpc_call_paginated(pot_contract, "get_applications", {}, page_size=300) if on_chain_apps is not None: db_app_ids = set( @@ -841,14 +851,14 @@ def backfill_pots(): missing_apps = on_chain_app_ids - db_app_ids if missing_apps: + ensure_accounts_exist(missing_apps) for a in on_chain_apps: applicant_id = a.get("project_id", a.get("applicant_id", "")) if applicant_id not in missing_apps: continue - applicant, _ = Account.objects.get_or_create(id=applicant_id) PotApplication.objects.update_or_create( pot=pot, - applicant=applicant, + applicant_id=applicant_id, defaults={ "message": a.get("message", ""), "status": a.get("status", "Pending"), @@ -863,30 +873,33 @@ def backfill_pots(): ) total_missing += len(missing_apps) - # Payouts + # --- Payouts --- on_chain_payouts = rpc_call_paginated(pot_contract, "get_payouts", {}, page_size=300) if on_chain_payouts is not None: - db_payout_recipients = set( - PotPayout.objects.filter(pot=pot).values_list("recipient_id", flat=True) + db_payout_ids = set( + PotPayout.objects.filter(pot=pot).values_list("on_chain_id", flat=True) ) - on_chain_payout_recipients = { - p.get("project_id", p.get("recipient_id", "")) for p in on_chain_payouts - } - missing_payouts = on_chain_payout_recipients - db_payout_recipients - if missing_payouts: + on_chain_payout_ids = {p["id"] for p in on_chain_payouts if "id" in p} + missing_payout_ids = on_chain_payout_ids - db_payout_ids + + if missing_payout_ids: + # Bulk-create recipient accounts + recipient_ids = { + p.get("project_id", p.get("recipient_id", "")) + for p in on_chain_payouts if p.get("id") in missing_payout_ids + } + ensure_accounts_exist(recipient_ids) + for p in on_chain_payouts: - recipient_id = p.get("project_id", p.get("recipient_id", "")) - if recipient_id not in missing_payouts: + if p.get("id") not in missing_payout_ids: continue - recipient, _ = Account.objects.get_or_create(id=recipient_id) - token = None - if p.get("ft_id"): - token_acct, _ = Account.objects.get_or_create(id=p["ft_id"]) - token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) + recipient_id = p.get("project_id", p.get("recipient_id", "")) + token = get_token_for_ft(p.get("ft_id"), near_token) PotPayout.objects.update_or_create( - pot=pot, - recipient=recipient, + on_chain_id=p["id"], defaults={ + "pot": pot, + "recipient_id": recipient_id, "amount": p.get("amount", "0"), "token": token, "paid_at": datetime.fromtimestamp( @@ -895,11 +908,80 @@ def backfill_pots(): "tx_hash": None, }, ) - total_missing += len(missing_payouts) + total_missing += len(missing_payout_ids) + + # --- Pot Donations --- + on_chain_donations = rpc_call_paginated(pot_contract, "get_donations", {}, page_size=300) + if on_chain_donations is not None: + db_donation_ids = set( + Donation.objects.filter(pot=pot).values_list("on_chain_id", flat=True) + ) + on_chain_don_ids = {d["id"] for d in on_chain_donations} + missing_don_ids = on_chain_don_ids - db_donation_ids + + if missing_don_ids: + # Bulk-create accounts + account_ids = set() + for d in on_chain_donations: + if d["id"] not in missing_don_ids: + continue + account_ids.add(d["donor_id"]) + if d.get("project_id"): + account_ids.add(d["project_id"]) + elif d.get("recipient_id"): + account_ids.add(d["recipient_id"]) + if d.get("referrer_id"): + account_ids.add(d["referrer_id"]) + if d.get("chef_id"): + account_ids.add(d["chef_id"]) + ensure_accounts_exist(account_ids) + + for d in on_chain_donations: + if d["id"] not in missing_don_ids: + continue + + recipient_id = d.get("project_id") or d.get("recipient_id") + token = get_token_for_ft(d.get("ft_id", "near"), near_token) + + # Calculate net_amount if missing + total_amount = d.get("total_amount", "0") + net_amount = d.get("net_amount") + if not net_amount or net_amount == "0": + protocol_fee = int(d.get("protocol_fee", "0")) + referrer_fee = int(d.get("referrer_fee") or "0") + chef_fee = int(d.get("chef_fee") or "0") + net_amount = str(int(total_amount) - protocol_fee - referrer_fee - chef_fee) + + donated_at = datetime.fromtimestamp( + (d.get("donated_at") or d.get("donated_at_ms", 0)) / 1000, + tz=timezone.utc, + ) + + Donation.objects.update_or_create( + on_chain_id=d["id"], + pot=pot, + defaults={ + "donor_id": d["donor_id"], + "total_amount": total_amount, + "net_amount": net_amount, + "token": token, + "matching_pool": d.get("matching_pool", False), + "message": d.get("message"), + "donated_at": donated_at, + "recipient_id": recipient_id, + "protocol_fee": d.get("protocol_fee", "0"), + "referrer_fee": d.get("referrer_fee"), + "referrer_id": d.get("referrer_id"), + "chef_id": d.get("chef_id"), + "chef_fee": d.get("chef_fee"), + "tx_hash": None, + }, + ) + total_missing += len(missing_don_ids) _time.sleep(0.5) - jobs_logger.info(f"Backfill pots: {total_missing} missing apps/payouts created.") + jobs_logger.info(f"Backfill pots: {total_missing} missing apps/payouts/donations created.") return total_missing def backfill_campaigns(): @@ -939,6 +1021,16 @@ def backfill_campaigns(): jobs_logger.info("Backfill campaigns: no campaigns found on chain.") return 0 + # Bulk-create all accounts from campaigns + account_ids = set() + for data in on_chain_campaigns: + account_ids.add(data["owner"]) + account_ids.add(data["recipient"]) + if data.get("ft_id"): + account_ids.add(data["ft_id"]) + ensure_accounts_exist(account_ids) + + near_token = get_near_token() db_campaign_ids = set(Campaign.objects.values_list("on_chain_id", flat=True)) total_missing = 0 @@ -957,14 +1049,7 @@ def backfill_campaigns(): continue # Create missing campaign - owner, _ = Account.objects.get_or_create(id=data["owner"]) - recipient, _ = Account.objects.get_or_create(id=data["recipient"]) - - token = None - token_id = data.get("ft_id") - if token_id: - token_acct, _ = Account.objects.get_or_create(id=token_id) - token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) + token = get_token_for_ft(data.get("ft_id"), near_token) start_at = datetime.fromtimestamp(data["start_ms"] / 1000, tz=timezone.utc) end_at = ( @@ -979,11 +1064,11 @@ def backfill_campaigns(): Campaign.objects.update_or_create( on_chain_id=cid, defaults={ - "owner": owner, + "owner_id": data["owner"], "name": data["name"], "description": data.get("description"), "cover_image_url": data.get("cover_image_url"), - "recipient": recipient, + "recipient_id": data["recipient"], "token": token, "start_at": start_at, "end_at": end_at, @@ -1021,19 +1106,23 @@ def backfill_campaigns(): if not missing: continue + # Bulk-create accounts for missing donations + don_account_ids = set() for d in on_chain_donations: if d["id"] not in missing: continue - - donor, _ = Account.objects.get_or_create(id=d["donor_id"]) - referrer = None + don_account_ids.add(d["donor_id"]) if d.get("referrer_id"): - referrer, _ = Account.objects.get_or_create(id=d["referrer_id"]) - - don_token = campaign.token + don_account_ids.add(d["referrer_id"]) if d.get("ft_id"): - token_acct, _ = Account.objects.get_or_create(id=d["ft_id"]) - don_token, _ = Token.objects.get_or_create(account=token_acct, defaults={"decimals": 24}) + don_account_ids.add(d["ft_id"]) + ensure_accounts_exist(don_account_ids) + + for d in on_chain_donations: + if d["id"] not in missing: + continue + + don_token = get_token_for_ft(d.get("ft_id"), campaign.token) donated_at = datetime.fromtimestamp(d["donated_at_ms"] / 1000, tz=timezone.utc) returned_at = ( @@ -1044,15 +1133,15 @@ def backfill_campaigns(): CampaignDonation.objects.update_or_create( on_chain_id=d["id"], campaign=campaign, - donor=donor, defaults={ + "donor_id": d["donor_id"], "token": don_token, "total_amount": str(d["total_amount"]), "net_amount": str(d["net_amount"]), "message": d.get("message"), "donated_at": donated_at, "protocol_fee": str(d["protocol_fee"]), - "referrer": referrer, + "referrer_id": d.get("referrer_id"), "referrer_fee": str(d["referrer_fee"]) if d.get("referrer_fee") else None, "creator_fee": str(d["creator_fee"]), "returned_at": returned_at, @@ -1072,13 +1161,20 @@ def backfill_campaigns(): fix_sequences() + backfill_funcs = [ + ("lists", backfill_lists), + ("registrations", backfill_registrations), + ("direct donations", backfill_donations), + ("pots", backfill_pots), + ("campaigns", backfill_campaigns), + ] + total = 0 - total += backfill_accounts() - total += backfill_lists() - total += backfill_registrations() - total += backfill_donations() - total += backfill_pots() - total += backfill_campaigns() + for name, func in backfill_funcs: + try: + total += func() + except Exception as e: + jobs_logger.error(f"Backfill {name} failed: {e}", exc_info=True) if total == 0: jobs_logger.info("=== Backfill complete: everything in sync! ===") From 9ea7e9de402764d73724ba31282e62802457a6d9 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Sun, 12 Apr 2026 22:47:28 +0500 Subject: [PATCH 446/454] fix backfill incorecctly touching stellar lists --- indexer_app/tasks.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 162156a..5a88432 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -550,6 +550,10 @@ def backfill_missing_data(force=False): LISTS_CONTRACT = f"lists.{settings.POTLOCK_TLA}" DONATE_CONTRACT = f"donate.{settings.POTLOCK_TLA}" + near_chain = Chain.objects.get(name="NEAR") + + # Check if List model has chain field (dev has it, prod doesn't) + list_has_chain = hasattr(List, "chain") # --- Helpers --- @@ -612,7 +616,6 @@ def ensure_accounts_exist(account_ids): existing = set(Account.objects.filter(id__in=account_ids).values_list("id", flat=True)) missing = account_ids - existing if missing: - near_chain = Chain.objects.get(name="NEAR") Account.objects.bulk_create( [Account(id=aid, chain=near_chain) for aid in missing], ignore_conflicts=True, @@ -661,7 +664,8 @@ def backfill_lists(): jobs_logger.error("Failed to fetch lists from contract") return 0 - db_list_ids = set(List.objects.values_list("on_chain_id", flat=True)) + list_qs = List.objects.filter(chain=near_chain) if list_has_chain else List.objects.all() + db_list_ids = set(list_qs.values_list("on_chain_id", flat=True)) on_chain_ids = {l["id"] for l in on_chain_lists} missing_ids = on_chain_ids - db_list_ids @@ -682,19 +686,20 @@ def backfill_lists(): for l in on_chain_lists: if l["id"] not in missing_ids: continue - list_obj, _ = List.objects.update_or_create( - on_chain_id=l["id"], - defaults={ - "owner_id": l["owner"], - "name": l["name"], - "description": l.get("description", ""), - "cover_image_url": l.get("cover_image_url"), - "admin_only_registrations": l.get("admin_only_registrations", False), - "default_registration_status": l.get("default_registration_status", "Pending"), - "created_at": datetime.fromtimestamp(l["created_at"] / 1000, tz=timezone.utc), - "updated_at": datetime.fromtimestamp(l["updated_at"] / 1000, tz=timezone.utc), - }, - ) + lookup = {"on_chain_id": l["id"]} + if list_has_chain: + lookup["chain"] = near_chain + list_defaults = { + "owner_id": l["owner"], + "name": l["name"], + "description": l.get("description", ""), + "cover_image_url": l.get("cover_image_url"), + "admin_only_registrations": l.get("admin_only_registrations", False), + "default_registration_status": l.get("default_registration_status", "Pending"), + "created_at": datetime.fromtimestamp(l["created_at"] / 1000, tz=timezone.utc), + "updated_at": datetime.fromtimestamp(l["updated_at"] / 1000, tz=timezone.utc), + } + list_obj, _ = List.objects.update_or_create(**lookup, defaults=list_defaults) for admin_id in l.get("admins", []): list_obj.admins.add(Account.objects.get(id=admin_id)) missing_count += 1 @@ -704,7 +709,7 @@ def backfill_lists(): def backfill_registrations(): jobs_logger.info("Backfill: checking registrations...") - db_lists = List.objects.all() + db_lists = List.objects.filter(chain=near_chain) if list_has_chain else List.objects.all() if not db_lists.exists(): return 0 From b4f17ac25fcec00fbe158f15a66f1437de45d6db Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Mon, 25 May 2026 22:21:53 +0500 Subject: [PATCH 447/454] Add global daily metrics for POTLOCK --- api/urls.py | 3 +- base/api.py | 178 ++++++++++++++++++++++++++++++++++++++++++- base/celery.py | 6 ++ indexer_app/tasks.py | 34 +++++++++ 4 files changed, 217 insertions(+), 4 deletions(-) diff --git a/api/urls.py b/api/urls.py index 7973710..d76d63c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -31,7 +31,7 @@ PotPayoutsSyncAPI, PotPayoutChallengesSyncAPI, ) -from base.api import StatsAPI, ReclaimProofRequestView +from base.api import DailyStatsAPI, StatsAPI, ReclaimProofRequestView from campaigns.api import ( AllCampaignDonationsAPI, CampaignContractConfigAPI, @@ -246,6 +246,7 @@ ), # stats path("v1/stats", StatsAPI.as_view(), name="stats_api"), + path("v1/stats/daily", DailyStatsAPI.as_view(), name="daily_stats_api"), path("v1/reclaim/generate-request", ReclaimProofRequestView.as_view(), name="stats_api"), # grantpicks diff --git a/base/api.py b/base/api.py index 9af00c9..454d54b 100644 --- a/base/api.py +++ b/base/api.py @@ -1,9 +1,13 @@ -from django.db.models import Sum -from django.http import JsonResponse +from datetime import timedelta + +from django.db.models import Count, Sum +from django.http import HttpResponse, JsonResponse +from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from drf_spectacular.utils import ( OpenApiExample, + OpenApiParameter, OpenApiResponse, extend_schema, ) @@ -15,7 +19,12 @@ from rest_framework.views import APIView from accounts.models import Account from donations.models import Donation -from pots.models import PotPayout +from pots.models import Pot, PotPayout + +try: + from campaigns.models import Campaign +except ImportError: + Campaign = None class StatsResponseSerializer(serializers.Serializer): @@ -91,6 +100,169 @@ def get(self, request: Request, *args, **kwargs): ) +def _money(value) -> str: + return f"${(value or 0):,.2f}" + + +def _aggregate_donations(qs): + agg = qs.aggregate( + volume=Sum("total_amount_usd"), + protocol_fees=Sum("protocol_fee_usd"), + referrer_fees=Sum("referrer_fee_usd"), + chef_fees=Sum("chef_fee_usd"), + count=Count("id"), + donors=Count("donor", distinct=True), + recipients=Count("recipient", distinct=True), + ) + matching_pool_volume = ( + qs.filter(matching_pool=True).aggregate(s=Sum("total_amount_usd"))["s"] or 0 + ) + direct_volume = (agg["volume"] or 0) - matching_pool_volume + return { + "volume": agg["volume"] or 0, + "direct_volume": direct_volume, + "matching_pool_volume": matching_pool_volume, + "protocol_fees": agg["protocol_fees"] or 0, + "referrer_fees": agg["referrer_fees"] or 0, + "chef_fees": agg["chef_fees"] or 0, + "count": agg["count"] or 0, + "donors": agg["donors"] or 0, + "recipients": agg["recipients"] or 0, + } + + +def _payout_stats(qs): + paid = qs.filter(paid_at__isnull=False) + pending = qs.filter(paid_at__isnull=True) + return { + "paid_usd": paid.aggregate(s=Sum("amount_paid_usd"))["s"] or 0, + "paid_count": paid.count(), + "pending_count": pending.count(), + } + + +def _campaign_stats(window_filter=None): + if Campaign is None: + return None + qs = Campaign.objects.all() if window_filter is None else Campaign.objects.filter(**window_filter) + return { + "count": qs.count(), + "raised_usd": qs.aggregate(s=Sum("total_raised_amount_usd"))["s"] or 0, + } + + +def _build_daily_stats(): + now = timezone.now() + today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) + seven_days_ago = now - timedelta(days=7) + + donations_today = _aggregate_donations(Donation.objects.filter(donated_at__gte=today_start)) + donations_7d = _aggregate_donations(Donation.objects.filter(donated_at__gte=seven_days_ago)) + donations_all = _aggregate_donations(Donation.objects.all()) + + payouts_today = _payout_stats(PotPayout.objects.filter(paid_at__gte=today_start)) + payouts_7d = _payout_stats(PotPayout.objects.filter(paid_at__gte=seven_days_ago)) + payouts_all = _payout_stats(PotPayout.objects.all()) + + pots_today = Pot.objects.filter(deployed_at__gte=today_start).count() + pots_7d = Pot.objects.filter(deployed_at__gte=seven_days_ago).count() + pots_all = Pot.objects.count() + + campaigns_today = _campaign_stats({"created_at__gte": today_start}) + campaigns_7d = _campaign_stats({"created_at__gte": seven_days_ago}) + campaigns_all = _campaign_stats() + + return { + "date": now.strftime("%Y-%m-%d"), + "today": { + "donations": donations_today, + "payouts": payouts_today, + "new_pots": pots_today, + "campaigns": campaigns_today, + }, + "last_7_days": { + "donations": donations_7d, + "payouts": payouts_7d, + "new_pots": pots_7d, + "campaigns": campaigns_7d, + }, + "all_time": { + "donations": donations_all, + "payouts": payouts_all, + "pots": pots_all, + "campaigns": campaigns_all, + }, + } + + +def _format_window(label: str, window: dict, *, include_new_label: bool, all_time: bool = False) -> str: + d = window["donations"] + p = window["payouts"] + lines = [ + label, + f"Volume: {_money(d['volume'])} (direct {_money(d['direct_volume'])} + matching {_money(d['matching_pool_volume'])})", + f"Fees: {_money(d['protocol_fees'] + d['referrer_fees'] + d['chef_fees'])}" + f" (protocol {_money(d['protocol_fees'])} + referrer {_money(d['referrer_fees'])} + chef {_money(d['chef_fees'])})", + f"Donations: {d['count']}", + f"Unique donors: {d['donors']}", + f"Unique recipients: {d['recipients']}", + f"Payouts: {_money(p['paid_usd'])} ({p['paid_count']})", + f"Pending payouts: {p['pending_count']}", + ] + pot_label = "Pots" if all_time else ("New pots" if include_new_label else "Pots") + if all_time: + lines.append(f"{pot_label}: {window['pots']}") + else: + lines.append(f"{pot_label}: {window['new_pots']}") + + campaigns = window["campaigns"] + if campaigns is not None: + camp_label = "Campaigns" if all_time else ("New campaigns" if include_new_label else "Campaigns") + lines.append(f"{camp_label}: {campaigns['count']} (raised {_money(campaigns['raised_usd'])})") + return "\n".join(lines) + + +def format_daily_stats_text(stats: dict) -> str: + divider = "-" * 20 + parts = [ + "POTLOCK metrics - Global", + f"Date: {stats['date']}", + divider, + _format_window("TODAY", stats["today"], include_new_label=True), + divider, + _format_window("LAST 7 DAYS", stats["last_7_days"], include_new_label=True), + divider, + _format_window("ALL-TIME", stats["all_time"], include_new_label=False, all_time=True), + ] + return "\n".join(parts) + + +class DailyStatsAPI(APIView): + """Daily aggregated stats for POTLOCK. Default response is pre-formatted text + suitable for posting directly to Signal. Pass `?format=json` for raw numbers.""" + + @method_decorator(cache_page(60 * 5)) + @extend_schema( + parameters=[ + OpenApiParameter( + name="format", + description="`text` (default) or `json`", + required=False, + type=str, + ), + ], + responses={ + 200: OpenApiResponse(description="Daily stats (text/plain or application/json)"), + 500: OpenApiResponse(description="Internal server error"), + }, + ) + def get(self, request: Request, *args, **kwargs): + stats = _build_daily_stats() + if request.query_params.get("format", "text").lower() == "json": + return Response(stats) + return HttpResponse(format_daily_stats_text(stats), content_type="text/plain; charset=utf-8") + + class ReclaimProofRequestView(APIView): @extend_schema( diff --git a/base/celery.py b/base/celery.py index 567f612..b9b682f 100644 --- a/base/celery.py +++ b/base/celery.py @@ -60,6 +60,11 @@ "schedule": crontab(day_of_week="0", hour="3", minute="0"), # Every Sunday 3 AM UTC; task self-skips odd weeks for biweekly "options": {"queue": "beat_tasks"}, }, + "post_daily_stats_to_signal": { + "task": "indexer_app.tasks.post_daily_stats_to_signal", + "schedule": crontab(hour="12", minute="0"), # Daily at 12:00 UTC + "options": {"queue": "beat_tasks"}, + }, } app.conf.task_routes = { @@ -70,6 +75,7 @@ "indexer_app.tasks.process_stellar_events": {"queue": "beat_tasks"}, "tax_verification.tasks.refresh_eo_bmf": {"queue": "beat_tasks"}, "indexer_app.tasks.backfill_missing_data": {"queue": "beat_tasks"}, + "indexer_app.tasks.post_daily_stats_to_signal": {"queue": "beat_tasks"}, } SPOT_INDEXER_QUEUE_NAME = "spot_indexing" diff --git a/indexer_app/tasks.py b/indexer_app/tasks.py index 5a88432..f0978eb 100644 --- a/indexer_app/tasks.py +++ b/indexer_app/tasks.py @@ -1187,6 +1187,40 @@ def backfill_campaigns(): jobs_logger.info(f"=== Backfill complete: {total} missing entries created. ===") +@shared_task +def post_daily_stats_to_signal(): + """Build daily POTLOCK stats and POST them to signal-cli-rest-api. + + Required env vars: + SIGNAL_API_URL base URL of signal-cli-rest-api (e.g. http://localhost:8080) + SIGNAL_SENDER_NUMBER registered Signal sender (E.164) + SIGNAL_RECIPIENT group id or phone number to send to + """ + import os + + from base.api import _build_daily_stats, format_daily_stats_text + + api_url = os.environ.get("SIGNAL_API_URL") + sender = os.environ.get("SIGNAL_SENDER_NUMBER") + recipient = os.environ.get("SIGNAL_RECIPIENT") + + if not (api_url and sender and recipient): + jobs_logger.warning( + "post_daily_stats_to_signal skipped: missing one of " + "SIGNAL_API_URL / SIGNAL_SENDER_NUMBER / SIGNAL_RECIPIENT" + ) + return + + message = format_daily_stats_text(_build_daily_stats()) + resp = requests.post( + f"{api_url.rstrip('/')}/v2/send", + json={"number": sender, "recipients": [recipient], "message": message}, + timeout=30, + ) + resp.raise_for_status() + jobs_logger.info("Posted daily stats to Signal (%d chars).", len(message)) + + @task_revoked.connect def on_task_revoked(request, terminated, signum, expired, **kwargs): logger.info( From f47b5c5e23b8e0f40d5ce68ae3b68554d742bde6 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Mon, 15 Jun 2026 21:04:02 +0500 Subject: [PATCH 448/454] Add /api/v1/stats/campaigns endpoint --- api/urls.py | 3 ++- base/api.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/api/urls.py b/api/urls.py index 7973710..e6f8ace 100644 --- a/api/urls.py +++ b/api/urls.py @@ -31,7 +31,7 @@ PotPayoutsSyncAPI, PotPayoutChallengesSyncAPI, ) -from base.api import StatsAPI, ReclaimProofRequestView +from base.api import StatsAPI, CampaignStatsAPI, ReclaimProofRequestView from campaigns.api import ( AllCampaignDonationsAPI, CampaignContractConfigAPI, @@ -246,6 +246,7 @@ ), # stats path("v1/stats", StatsAPI.as_view(), name="stats_api"), + path("v1/stats/campaigns", CampaignStatsAPI.as_view(), name="campaign_stats_api"), path("v1/reclaim/generate-request", ReclaimProofRequestView.as_view(), name="stats_api"), # grantpicks diff --git a/base/api.py b/base/api.py index 9af00c9..3857464 100644 --- a/base/api.py +++ b/base/api.py @@ -1,5 +1,8 @@ +from datetime import timedelta + from django.db.models import Sum from django.http import JsonResponse +from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from drf_spectacular.utils import ( @@ -14,6 +17,7 @@ from rest_framework.response import Response from rest_framework.views import APIView from accounts.models import Account +from campaigns.models import Campaign from donations.models import Donation from pots.models import PotPayout @@ -91,6 +95,47 @@ def get(self, request: Request, *args, **kwargs): ) +def _campaign_window_stats(window_filter=None): + qs = Campaign.objects.all() if window_filter is None else Campaign.objects.filter(**window_filter) + return { + "count": qs.count(), + "raised_usd": float(qs.aggregate(s=Sum("total_raised_amount_usd"))["s"] or 0), + } + + +def _build_campaign_stats(): + """Campaign aggregates for today / last 7 days / all-time. + + Windows are based on `Campaign.created_at`; `raised_usd` sums each campaign's + `total_raised_amount_usd`. Consumed by the prod deployment's daily Signal + message (prod has no campaigns app, so it pulls this over HTTP).""" + now = timezone.now() + today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) + seven_days_ago = now - timedelta(days=7) + return { + "today": _campaign_window_stats({"created_at__gte": today_start}), + "last_7_days": _campaign_window_stats({"created_at__gte": seven_days_ago}), + "all_time": _campaign_window_stats(), + } + + +class CampaignStatsAPI(APIView): + """Campaign aggregates (count + raised USD) for today / last 7 days / all-time. + + Used by the prod deployment to merge campaign data into its daily stats + message, since the campaigns app only lives on this (dev) deployment.""" + + @method_decorator(cache_page(60 * 5)) + @extend_schema( + responses={ + 200: OpenApiResponse(description="Campaign stats per window"), + 500: OpenApiResponse(description="Internal server error"), + } + ) + def get(self, request: Request, *args, **kwargs): + return Response(_build_campaign_stats()) + + class ReclaimProofRequestView(APIView): @extend_schema( From e18398e955a918c9553d9674eb55d0aaa7fdb7b9 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Mon, 15 Jun 2026 21:51:57 +0500 Subject: [PATCH 449/454] Compute campaign raised USD on the fly in stats endpoint --- base/api.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/base/api.py b/base/api.py index 5895ed0..fcb486c 100644 --- a/base/api.py +++ b/base/api.py @@ -263,18 +263,46 @@ def get(self, request: Request, *args, **kwargs): return HttpResponse(format_daily_stats_text(stats), content_type="text/plain; charset=utf-8") +def _campaign_raised_usd(campaign): + """USD raised for a single campaign. Uses the stored value if present, + otherwise converts the raw on-chain amount using the token's historical + price at the campaign's creation date (cached in TokenHistoricalPrice).""" + if campaign.total_raised_amount_usd is not None: + return campaign.total_raised_amount_usd + token = campaign.token + if not token or not campaign.total_raised_amount: + return 0 + try: + price_usd = token.fetch_usd_prices_common(campaign.created_at) + if not price_usd: + return 0 + return token.format_price(campaign.total_raised_amount) * price_usd + except Exception: + return 0 + + +def _campaign_window_stats(window_filter=None): + qs = Campaign.objects.all() if window_filter is None else Campaign.objects.filter(**window_filter) + qs = qs.select_related("token", "token__account") + return { + "count": qs.count(), + "raised_usd": float(sum(_campaign_raised_usd(c) for c in qs)), + } + + def _build_campaign_stats(): """Campaign aggregates (count + raised USD) for today / last 7 days / all-time. Consumed by the prod deployment's daily Signal message: prod has no campaigns - app, so it pulls this over HTTP and merges it into the same message.""" + app, so it pulls this over HTTP and merges it into the same message. USD is + computed on the fly (the periodic price task is disabled on this deployment).""" now = timezone.now() today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) seven_days_ago = now - timedelta(days=7) return { - "today": _campaign_stats({"created_at__gte": today_start}), - "last_7_days": _campaign_stats({"created_at__gte": seven_days_ago}), - "all_time": _campaign_stats(), + "today": _campaign_window_stats({"created_at__gte": today_start}), + "last_7_days": _campaign_window_stats({"created_at__gte": seven_days_ago}), + "all_time": _campaign_window_stats(), } From 62b46d871af28ddd75dc1ee9cb60c0a83ed163e4 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Tue, 16 Jun 2026 21:11:17 +0500 Subject: [PATCH 450/454] Add campaign donation count + USD to campaign-stats endpoint --- base/api.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/base/api.py b/base/api.py index fcb486c..d85d8e2 100644 --- a/base/api.py +++ b/base/api.py @@ -22,9 +22,10 @@ from pots.models import Pot, PotPayout try: - from campaigns.models import Campaign + from campaigns.models import Campaign, CampaignDonation except ImportError: Campaign = None + CampaignDonation = None class StatsResponseSerializer(serializers.Serializer): @@ -281,17 +282,31 @@ def _campaign_raised_usd(campaign): return 0 -def _campaign_window_stats(window_filter=None): - qs = Campaign.objects.all() if window_filter is None else Campaign.objects.filter(**window_filter) - qs = qs.select_related("token", "token__account") +def _campaign_window_stats(start=None): + """Per-window campaign aggregates: new campaigns (count + raised USD) plus the + donations made into campaigns (count + USD, windowed by donated_at). `start` + is a datetime, or None for all-time.""" + cqs = Campaign.objects.all() if start is None else Campaign.objects.filter(created_at__gte=start) + cqs = cqs.select_related("token", "token__account") + + dqs = ( + CampaignDonation.objects.all() + if start is None + else CampaignDonation.objects.filter(donated_at__gte=start) + ) + donations = dqs.aggregate(usd=Sum("total_amount_usd"), count=Count("id")) + return { - "count": qs.count(), - "raised_usd": float(sum(_campaign_raised_usd(c) for c in qs)), + "count": cqs.count(), + "raised_usd": float(sum(_campaign_raised_usd(c) for c in cqs)), + "donation_count": donations["count"] or 0, + "donation_usd": float(donations["usd"] or 0), } def _build_campaign_stats(): - """Campaign aggregates (count + raised USD) for today / last 7 days / all-time. + """Campaign aggregates for today / last 7 days / all-time: new-campaign count + + raised USD, plus donation count + USD into campaigns. Consumed by the prod deployment's daily Signal message: prod has no campaigns app, so it pulls this over HTTP and merges it into the same message. USD is @@ -300,8 +315,8 @@ def _build_campaign_stats(): today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) seven_days_ago = now - timedelta(days=7) return { - "today": _campaign_window_stats({"created_at__gte": today_start}), - "last_7_days": _campaign_window_stats({"created_at__gte": seven_days_ago}), + "today": _campaign_window_stats(today_start), + "last_7_days": _campaign_window_stats(seven_days_ago), "all_time": _campaign_window_stats(), } From f9fb8858e317db860d5157bdb289a657eea5f45e Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Tue, 16 Jun 2026 22:03:08 +0500 Subject: [PATCH 451/454] Add NEAR amount + approx USD for campaign donations to stats endpoint --- base/api.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/base/api.py b/base/api.py index d85d8e2..6b5bc5d 100644 --- a/base/api.py +++ b/base/api.py @@ -1,4 +1,5 @@ from datetime import timedelta +from decimal import Decimal from django.db.models import Count, Sum from django.http import HttpResponse, JsonResponse @@ -20,6 +21,7 @@ from accounts.models import Account from donations.models import Donation from pots.models import Pot, PotPayout +from tokens.models import Token try: from campaigns.models import Campaign, CampaignDonation @@ -282,6 +284,19 @@ def _campaign_raised_usd(campaign): return 0 +def _near_usd_price(): + """Current NEAR/USD price (approx) via the 'near' Token, mirroring the + frontend's CoinGecko conversion. Returns 0.0 if unavailable.""" + token = Token.objects.filter(account_id="near").first() + if not token: + return 0.0 + try: + price = token.fetch_usd_prices_common(timezone.now()) + return float(price) if price else 0.0 + except Exception: + return 0.0 + + def _campaign_window_stats(start=None): """Per-window campaign aggregates: new campaigns (count + raised USD) plus the donations made into campaigns (count + USD, windowed by donated_at). `start` @@ -296,11 +311,23 @@ def _campaign_window_stats(start=None): ) donations = dqs.aggregate(usd=Sum("total_amount_usd"), count=Count("id")) + # Raw NEAR donated to campaigns. NEAR donations have a null token and no USD + # computed, so report the on-chain amount (yoctoNEAR, 24 decimals) as NEAR. + near_yocto = sum( + int(a) for a in dqs.filter(token__isnull=True).values_list("total_amount", flat=True) if a + ) + donation_near = float(Decimal(near_yocto) / Decimal(10**24)) + # Approx USD for the NEAR amount using the current NEAR price (like the + # frontend's live CoinGecko conversion), since these donations have no stored USD. + donation_near_usd = round(donation_near * _near_usd_price(), 2) if donation_near else 0.0 + return { "count": cqs.count(), "raised_usd": float(sum(_campaign_raised_usd(c) for c in cqs)), "donation_count": donations["count"] or 0, "donation_usd": float(donations["usd"] or 0), + "donation_near": donation_near, + "donation_near_usd": donation_near_usd, } From af29782e62ff057566878e5add8f46d27769efe6 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Tue, 16 Jun 2026 22:10:52 +0500 Subject: [PATCH 452/454] Fix NEAR campaign donation detection (near token, not null) and avoid USD double-count --- base/api.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/base/api.py b/base/api.py index 6b5bc5d..395596a 100644 --- a/base/api.py +++ b/base/api.py @@ -1,7 +1,7 @@ from datetime import timedelta from decimal import Decimal -from django.db.models import Count, Sum +from django.db.models import Count, Q, Sum from django.http import HttpResponse, JsonResponse from django.utils import timezone from django.utils.decorators import method_decorator @@ -309,23 +309,25 @@ def _campaign_window_stats(start=None): if start is None else CampaignDonation.objects.filter(donated_at__gte=start) ) - donations = dqs.aggregate(usd=Sum("total_amount_usd"), count=Count("id")) - - # Raw NEAR donated to campaigns. NEAR donations have a null token and no USD - # computed, so report the on-chain amount (yoctoNEAR, 24 decimals) as NEAR. + # NEAR donations use the native "near" Token (or null) and have no USD + # computed, so report the on-chain amount (yoctoNEAR, 24 decimals) as NEAR + # plus an approx USD using the current NEAR price (like the frontend). + near_filter = Q(token__isnull=True) | Q(token__account_id="near") near_yocto = sum( - int(a) for a in dqs.filter(token__isnull=True).values_list("total_amount", flat=True) if a + int(a) for a in dqs.filter(near_filter).values_list("total_amount", flat=True) if a ) donation_near = float(Decimal(near_yocto) / Decimal(10**24)) - # Approx USD for the NEAR amount using the current NEAR price (like the - # frontend's live CoinGecko conversion), since these donations have no stored USD. donation_near_usd = round(donation_near * _near_usd_price(), 2) if donation_near else 0.0 + # Stored USD for non-NEAR (FT) donations only, so it doesn't double-count the + # NEAR amount already reported above as an approx USD. + ft_usd = dqs.exclude(near_filter).aggregate(s=Sum("total_amount_usd"))["s"] or 0 + return { "count": cqs.count(), "raised_usd": float(sum(_campaign_raised_usd(c) for c in cqs)), - "donation_count": donations["count"] or 0, - "donation_usd": float(donations["usd"] or 0), + "donation_count": dqs.count(), + "donation_usd": float(ft_usd), "donation_near": donation_near, "donation_near_usd": donation_near_usd, } From 5b8bbb4c798bcea286d9edebca4c9e664deed9d5 Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Tue, 16 Jun 2026 22:14:49 +0500 Subject: [PATCH 453/454] Use CoinGecko live simple-price for NEAR USD approximation in campaign stats --- base/api.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/base/api.py b/base/api.py index 395596a..7dd1572 100644 --- a/base/api.py +++ b/base/api.py @@ -1,6 +1,8 @@ +import logging from datetime import timedelta from decimal import Decimal +import requests from django.db.models import Count, Q, Sum from django.http import HttpResponse, JsonResponse from django.utils import timezone @@ -21,7 +23,6 @@ from accounts.models import Account from donations.models import Donation from pots.models import Pot, PotPayout -from tokens.models import Token try: from campaigns.models import Campaign, CampaignDonation @@ -29,6 +30,8 @@ Campaign = None CampaignDonation = None +logger = logging.getLogger("jobs") + class StatsResponseSerializer(serializers.Serializer): total_donations_usd = serializers.FloatField() @@ -285,19 +288,21 @@ def _campaign_raised_usd(campaign): def _near_usd_price(): - """Current NEAR/USD price (approx) via the 'near' Token, mirroring the - frontend's CoinGecko conversion. Returns 0.0 if unavailable.""" - token = Token.objects.filter(account_id="near").first() - if not token: - return 0.0 + """Current NEAR/USD price via CoinGecko's live simple-price endpoint, the same + source the frontend uses. Returns 0.0 if unavailable.""" try: - price = token.fetch_usd_prices_common(timezone.now()) - return float(price) if price else 0.0 - except Exception: + url = f"{settings.COINGECKO_URL}/simple/price?ids=near&vs_currencies=usd" + if settings.COINGECKO_API_KEY: + url += f"&x_cg_pro_api_key={settings.COINGECKO_API_KEY}" + resp = requests.get(url, timeout=10) + resp.raise_for_status() + return float(resp.json().get("near", {}).get("usd") or 0.0) + except Exception as e: + logger.warning("Failed to fetch NEAR price from CoinGecko: %s", e) return 0.0 -def _campaign_window_stats(start=None): +def _campaign_window_stats(start=None, near_price=0.0): """Per-window campaign aggregates: new campaigns (count + raised USD) plus the donations made into campaigns (count + USD, windowed by donated_at). `start` is a datetime, or None for all-time.""" @@ -317,7 +322,7 @@ def _campaign_window_stats(start=None): int(a) for a in dqs.filter(near_filter).values_list("total_amount", flat=True) if a ) donation_near = float(Decimal(near_yocto) / Decimal(10**24)) - donation_near_usd = round(donation_near * _near_usd_price(), 2) if donation_near else 0.0 + donation_near_usd = round(donation_near * near_price, 2) if donation_near else 0.0 # Stored USD for non-NEAR (FT) donations only, so it doesn't double-count the # NEAR amount already reported above as an approx USD. @@ -343,10 +348,11 @@ def _build_campaign_stats(): now = timezone.now() today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) seven_days_ago = now - timedelta(days=7) + near_price = _near_usd_price() return { - "today": _campaign_window_stats(today_start), - "last_7_days": _campaign_window_stats(seven_days_ago), - "all_time": _campaign_window_stats(), + "today": _campaign_window_stats(today_start, near_price), + "last_7_days": _campaign_window_stats(seven_days_ago, near_price), + "all_time": _campaign_window_stats(None, near_price), } From 85d110e4020246f4c1b24a694f94474b3b669e9c Mon Sep 17 00:00:00 2001 From: aunali8812 Date: Tue, 16 Jun 2026 22:38:06 +0500 Subject: [PATCH 454/454] Use CoinGecko public no-key endpoint for NEAR price (configured key is pro and 401s) --- base/api.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/base/api.py b/base/api.py index 7dd1572..6974fe4 100644 --- a/base/api.py +++ b/base/api.py @@ -288,13 +288,16 @@ def _campaign_raised_usd(campaign): def _near_usd_price(): - """Current NEAR/USD price via CoinGecko's live simple-price endpoint, the same - source the frontend uses. Returns 0.0 if unavailable.""" + """Current NEAR/USD price via CoinGecko's public live simple-price endpoint, + the same source the frontend uses. No API key: the configured key is a pro key + that returns 401 here, and this is only called a few times a day. Returns 0.0 + if unavailable.""" try: - url = f"{settings.COINGECKO_URL}/simple/price?ids=near&vs_currencies=usd" - if settings.COINGECKO_API_KEY: - url += f"&x_cg_pro_api_key={settings.COINGECKO_API_KEY}" - resp = requests.get(url, timeout=10) + resp = requests.get( + "https://api.coingecko.com/api/v3/simple/price", + params={"ids": "near", "vs_currencies": "usd"}, + timeout=10, + ) resp.raise_for_status() return float(resp.json().get("near", {}).get("usd") or 0.0) except Exception as e: