Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
655efc3
init
ibraheem-abe Mar 14, 2025
6799497
add api examples
ibraheem-abe Mar 14, 2025
17cbe78
get_stake_fee added
ibraheem-abe Mar 14, 2025
3921aa1
Dynamic fee in stake remove
ibraheem-abe Mar 16, 2025
f5ef40f
Dynamic pricing in movements
ibraheem-abe Mar 16, 2025
76fae6a
Removes unused imports
ibraheem-abe Mar 16, 2025
3960c71
Adds dynamic fee to stake add
ibraheem-abe Mar 17, 2025
9ac84e4
Updates e2e tests to devnet ready
ibraheem-abe Mar 17, 2025
e9e0e70
Update remove slippage calc
ibraheem-abe Mar 17, 2025
ebecac0
Updates to devnet ready
ibraheem-abe Mar 17, 2025
cb2f25f
Merge pull request #390 from opentensor/update/e2e-test-dir
ibraheem-abe Mar 17, 2025
40b49a8
Merge branch 'staging' into feat/dynamic-staking-fee
ibraheem-abe Mar 17, 2025
e1a751f
Bumps async substrate
ibraheem-abe Mar 17, 2025
2195393
Adds instructions for pip installation for README.
thewhaleking Mar 17, 2025
8eac12c
Adds support for py 3.13
thewhaleking Mar 17, 2025
8b550ec
Merge pull request #392 from opentensor/feat/thewhaleking/allow-3.13-…
thewhaleking Mar 17, 2025
ae25df0
Merge pull request #391 from opentensor/feat/thewhaleking/pip-install…
thewhaleking Mar 17, 2025
ac1f249
Removes check for none
ibraheem-abe Mar 17, 2025
2ba6e5a
improve e2e tests' workflow
Mar 17, 2025
76da6a2
avoid cubit installation
Mar 17, 2025
2721b55
opps
Mar 17, 2025
63b7516
opps 2
Mar 17, 2025
00e0475
opps 3
Mar 17, 2025
7f21261
opps 4
Mar 17, 2025
bd305c1
try venv
Mar 17, 2025
b93e205
astral-sh wants python version
Mar 17, 2025
95eac6f
astral-sh wants python version
Mar 17, 2025
014c868
try without cubit deps
Mar 17, 2025
fe41fb8
comment cubit
Mar 17, 2025
c74931e
remove cubit
Mar 17, 2025
7061f65
try short name for tests in matrix
Mar 17, 2025
5db5bd2
update test's runners
Mar 18, 2025
d762c9a
Dummy commit
ibraheem-abe Mar 18, 2025
18f5eff
add dev deps
Mar 18, 2025
57b4c3d
improve + fix
Mar 18, 2025
4abd5be
Merge pull request #389 from opentensor/feat/dynamic-staking-fee
ibraheem-abe Mar 18, 2025
67b9196
check unstaking behavior
Mar 18, 2025
bc87b8d
add timeout to wait
Mar 18, 2025
3abaf62
bumps version and changelog
ibraheem-abe Mar 18, 2025
fe705bd
Merge pull request #394 from opentensor/changelog/9.2.0
ibraheem-abe Mar 18, 2025
8d3db7f
Merge branch 'staging' into feat/roman/improve-e2e-tests
Mar 18, 2025
fe10f98
CHANGELOG.md
Mar 18, 2025
67dd45f
Merge pull request #393 from opentensor/feat/roman/improve-e2e-tests
ibraheem-abe Mar 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 65 additions & 32 deletions .github/workflows/e2e-subtensor-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,48 +24,81 @@ env:
VERBOSE: ${{ github.event.inputs.verbose }}

jobs:
run-tests:
runs-on: SubtensorCI
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
timeout-minutes: 180
env:
RELEASE_NAME: development
RUSTV: stable
RUST_BACKTRACE: full
RUST_BIN_DIR: target/x86_64-unknown-linux-gnu
TARGET: x86_64-unknown-linux-gnu

find-tests:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
outputs:
test-files: ${{ steps.get-tests.outputs.test-files }}
steps:
- name: Check-out repository under $GITHUB_WORKSPACE
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Install dependencies
- name: Find test files
id: get-tests
run: |
sudo apt-get update &&
sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler
test_files=$(find tests/e2e_tests -name "test*.py" | jq -R -s -c 'split("\n") | map(select(. != ""))')
echo "::set-output name=test-files::$test_files"
shell: bash

pull-docker-image:
runs-on: ubuntu-latest
steps:
- name: Log in to GitHub Container Registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin

- name: Pull Docker Image
run: docker pull ghcr.io/opentensor/subtensor-localnet:latest

- name: Save Docker Image to Cache
run: docker save -o subtensor-localnet.tar ghcr.io/opentensor/subtensor-localnet:latest

- name: Install Rust ${{ env.RUSTV }}
uses: actions-rs/toolchain@v1.0.6
- name: Upload Docker Image as Artifact
uses: actions/upload-artifact@v4
with:
toolchain: ${{ env.RUSTV }}
components: rustfmt
profile: minimal
name: subtensor-localnet
path: subtensor-localnet.tar

- name: Add wasm32-unknown-unknown target
run: |
rustup target add wasm32-unknown-unknown --toolchain stable-x86_64-unknown-linux-gnu
rustup component add rust-src --toolchain stable-x86_64-unknown-linux-gnu
run-e2e-tests:
name: ${{ matrix.test-file }} / Python ${{ matrix.python-version }}
needs:
- find-tests
- pull-docker-image
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false # Allow other matrix jobs to run even if this job fails
max-parallel: 32 # Set the maximum number of parallel jobs (same as we have cores in SubtensorCI runner)
matrix:
os:
- ubuntu-latest
test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }}
# python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- name: Check-out repository
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
python-version: 3.13

- name: Clone subtensor repo
run: git clone https://github.com/opentensor/subtensor.git
- name: install dependencies
run: |
uv venv .venv
source .venv/bin/activate
uv pip install .[dev]
uv pip install pytest

- name: Setup subtensor repo
working-directory: ${{ github.workspace }}/subtensor
run: git checkout testnet
- name: Download Cached Docker Image
uses: actions/download-artifact@v4
with:
name: subtensor-localnet

- name: Install Python dependencies
run: python3 -m pip install -e . pytest
- name: Load Docker Image
run: docker load -i subtensor-localnet.tar

- name: Run all tests
- name: Run tests
run: |
LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" pytest tests/e2e_tests -s
source .venv/bin/activate
uv run pytest ${{ matrix.test-file }} -s
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 9.2.0 /2025-03-18

## What's Changed
* Improve e2e tests' workflow by @roman-opentensor in https://github.com/opentensor/btcli/pull/393
* Updates to E2E suubtensor tests to devnet ready by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/390
* Allow Py 3.13 install by @thewhaleking in https://github.com/opentensor/btcli/pull/392
* pip install readme by @thewhaleking in https://github.com/opentensor/btcli/pull/391
* Feat/dynamic staking fee by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/389

**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.1.4...v9.2.0

## 9.1.4 /2025-03-13

## What's Changed
Expand Down
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,20 @@ Installation steps are described below. For a full documentation on how to use `

## Install on macOS and Linux

You can install `btcli` on your local machine directly from source. **Make sure you verify your installation after you install**:
You can install `btcli` on your local machine directly from source, or from from PyPI. **Make sure you verify your installation after you install**:


### Install from PyPI

Run
```
pip install -U bittensor-cli
```

Alternatively, if you prefer to use [uv](https://pypi.org/project/uv/):
```
uv pip install bittensor-cli
```

### Install from source

Expand Down Expand Up @@ -69,6 +82,14 @@ cd btcli
pip3 install .
```

### Also install bittensor (SDK)

If you prefer to install the btcli alongside the bittensor SDK, you can do this in a single command with

```
pip install -U bittensor[cli]
```

---

## Install on Windows
Expand Down
66 changes: 66 additions & 0 deletions bittensor_cli/src/bittensor/subtensor_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -1435,3 +1435,69 @@ async def get_owned_hotkeys(
)

return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []]

async def get_stake_fee(
self,
origin_hotkey_ss58: Optional[str],
origin_netuid: Optional[int],
origin_coldkey_ss58: str,
destination_hotkey_ss58: Optional[str],
destination_netuid: Optional[int],
destination_coldkey_ss58: str,
amount: int,
block_hash: Optional[str] = None,
) -> Balance:
"""
Calculates the fee for a staking operation.

:param origin_hotkey_ss58: SS58 address of source hotkey (None for new stake)
:param origin_netuid: Netuid of source subnet (None for new stake)
:param origin_coldkey_ss58: SS58 address of source coldkey
:param destination_hotkey_ss58: SS58 address of destination hotkey (None for removing stake)
:param destination_netuid: Netuid of destination subnet (None for removing stake)
:param destination_coldkey_ss58: SS58 address of destination coldkey
:param amount: Amount of stake to transfer in RAO
:param block_hash: Optional block hash at which to perform the calculation

:return: The calculated stake fee as a Balance object

When to use None:

1. Adding new stake (default fee):
- origin_hotkey_ss58 = None
- origin_netuid = None
- All other fields required

2. Removing stake (default fee):
- destination_hotkey_ss58 = None
- destination_netuid = None
- All other fields required

For all other operations, no None values - provide all parameters:
3. Moving between subnets
4. Moving between hotkeys
5. Moving between coldkeys
"""

origin = None
if origin_hotkey_ss58 is not None and origin_netuid is not None:
origin = (origin_hotkey_ss58, origin_netuid)

destination = None
if destination_hotkey_ss58 is not None and destination_netuid is not None:
destination = (destination_hotkey_ss58, destination_netuid)

result = await self.query_runtime_api(
runtime_api="StakeInfoRuntimeApi",
method="get_stake_fee",
params=[
origin,
origin_coldkey_ss58,
destination,
destination_coldkey_ss58,
amount,
],
block_hash=block_hash,
)

return Balance.from_rao(result)
52 changes: 42 additions & 10 deletions bittensor_cli/src/commands/stake/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,24 @@ async def stake_extrinsic(
return False
remaining_wallet_balance -= amount_to_stake

# Calculate slippage
received_amount, slippage_pct, slippage_pct_float, rate = (
_calculate_slippage(subnet_info, amount_to_stake)
stake_fee = await subtensor.get_stake_fee(
origin_hotkey_ss58=None,
origin_netuid=None,
origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
destination_hotkey_ss58=hotkey[1],
destination_netuid=netuid,
destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
amount=amount_to_stake.rao,
)

# Calculate slippage
try:
received_amount, slippage_pct, slippage_pct_float, rate = (
_calculate_slippage(subnet_info, amount_to_stake, stake_fee)
)
except ValueError:
return False

max_slippage = max(slippage_pct_float, max_slippage)

# Add rows for the table
Expand All @@ -296,6 +310,7 @@ async def stake_extrinsic(
str(rate)
+ f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate
str(received_amount.set_unit(netuid)), # received
str(stake_fee), # fee
str(slippage_pct), # slippage
]

Expand Down Expand Up @@ -531,6 +546,11 @@ def _define_stake_table(
justify="center",
style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
)
table.add_column(
"Fee (τ)",
justify="center",
style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
)
table.add_column(
"Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]
)
Expand Down Expand Up @@ -585,29 +605,41 @@ def _print_table_and_slippage(table: Table, max_slippage: float, safe_staking: b


def _calculate_slippage(
subnet_info, amount: Balance
subnet_info, amount: Balance, stake_fee: Balance
) -> tuple[Balance, str, float, str]:
"""Calculate slippage when adding stake.

Args:
subnet_info: Subnet dynamic info
amount: Amount being staked
stake_fee: Transaction fee for the stake operation

Returns:
tuple containing:
- received_amount: Amount received after slippage
- received_amount: Amount received after slippage and fees
- slippage_str: Formatted slippage percentage string
- slippage_float: Raw slippage percentage value
- rate: Exchange rate string
"""
received_amount, _, slippage_pct_float = subnet_info.tao_to_alpha_with_slippage(
amount
)
amount_after_fee = amount - stake_fee

if amount_after_fee < 0:
print_error("You don't have enough balance to cover the stake fee.")
raise ValueError()

received_amount, _, _ = subnet_info.tao_to_alpha_with_slippage(amount_after_fee)

if subnet_info.is_dynamic:
ideal_amount = subnet_info.tao_to_alpha(amount)
total_slippage = ideal_amount - received_amount
slippage_pct_float = 100 * (total_slippage.tao / ideal_amount.tao)
slippage_str = f"{slippage_pct_float:.4f} %"
rate = f"{(1 / subnet_info.price.tao or 1):.4f}"
else:
slippage_pct_float = 0
slippage_str = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]N/A[/{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]"
slippage_pct_float = (
100 * float(stake_fee.tao) / float(amount.tao) if amount.tao != 0 else 0
)
slippage_str = f"{slippage_pct_float:.4f} %"
rate = "1"

return received_amount, slippage_str, slippage_pct_float, rate
Loading
Loading