From 21b156914fc0158bdfd6227ac8bcc1e49d817851 Mon Sep 17 00:00:00 2001 From: Dera Okeke Date: Wed, 10 Jun 2026 12:06:31 +0100 Subject: [PATCH 1/9] evm updates --- .../evm-localnet-with-metamask-wallet.md | 21 ++-- .../evm-testnet-with-metamask-wallet.md | 2 +- docs/evm-tutorials/examples.md | 17 +++- docs/evm-tutorials/index.md | 8 +- docs/evm-tutorials/subnet-precompile.md | 95 ++----------------- docs/evm-tutorials/troubleshooting.md | 16 ++-- 6 files changed, 45 insertions(+), 114 deletions(-) diff --git a/docs/evm-tutorials/evm-localnet-with-metamask-wallet.md b/docs/evm-tutorials/evm-localnet-with-metamask-wallet.md index 60f4d959..fce8f56b 100644 --- a/docs/evm-tutorials/evm-localnet-with-metamask-wallet.md +++ b/docs/evm-tutorials/evm-localnet-with-metamask-wallet.md @@ -13,24 +13,25 @@ Consider first trying [EVM with Bittensor testnet](./evm-testnet-with-metamask-w Key values: -- **EVM Subtensor Mainnet Chain ID:**: `964` (UTF-8 encoded TAO symbol) -- **EVM Subtensor Testnet Chain ID:**: `945` (UTF-8 encoded alpha character) +- **EVM Subtensor Mainnet Chain ID:** `964` (UTF-8 encoded TAO symbol) +- **EVM Subtensor Testnet Chain ID:** `945` (UTF-8 encoded alpha character) - **Opentensor EVM-Bittensor GitHub repo with code examples:** https://github.com/opentensor/evm-bittensor/tree/main ## Step 1. Run EVM-enabled localnet -```bash -git clone https://github.com/opentensor/subtensor -./scripts/localnet.sh -``` +Before setting up EVM on a local chain, ensure that you have a local Subtensor instance running. + +To do this, follow the steps to start a local Subtensor instance. You can run it [using Docker](../local-build/deploy?local-chain=docker) or a [local source build](../local-build/deploy?local-chain=local). ## Step 2. Set Chain ID -The bare local network doesn't have the Chain ID setup and it needs to be configured with an admin extrinsic. Use [sudo section of Polkadot AppsUI](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/sudo) to call this extrinsic to set the ChainID to 945 (to simulate Testnet) or 964 (to simulate Mainnet): +A local Subtensor network does not have a Chain ID configured by default. You must set it using an admin extrinsic. Use the [sudo section of Polkadot Apps UI](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944&utm_source=chatgpt.com#/sudo) to call the appropriate extrinsic and set the Chain ID: -``` -adminUtils >> sudoSetEvmChainId -``` +On the **call: Call(RuntimeCall)** input: + +- Select the `adminUtils` pallet +- Then select the `sudoSetEvmChainId` extrinsic from list of pallet extrinsics +- Finally, set the chain ID to either `964` for mainnet or `945` for testnet ## Step 3. Create a Metamask wallet diff --git a/docs/evm-tutorials/evm-testnet-with-metamask-wallet.md b/docs/evm-tutorials/evm-testnet-with-metamask-wallet.md index 5728d20e..54bff4c2 100644 --- a/docs/evm-tutorials/evm-testnet-with-metamask-wallet.md +++ b/docs/evm-tutorials/evm-testnet-with-metamask-wallet.md @@ -61,7 +61,7 @@ Next, request testnet TAO in the Bittensor community [Discord](https://discord.c In this step you will copy the private key from your Metamask wallet account and paste it into the configuration file in the repo. This step will ensure that you are not prompted with password each and every step as you run these tutorials. -1. Navigate to the `examples` directory of the EVM-Bittensor repo: +1. Navigate to the `examples` directory of the [EVM-Bittensor repo](https://github.com/opentensor/evm-bittensor): ```bash cd examples diff --git a/docs/evm-tutorials/examples.md b/docs/evm-tutorials/examples.md index 1a1eaa70..10a39830 100644 --- a/docs/evm-tutorials/examples.md +++ b/docs/evm-tutorials/examples.md @@ -9,7 +9,7 @@ import { InstallPartial } from "./\_install.mdx"; ## Available Precompiles The following precompiled smart contracts are available on the Bittensor EVM. -The source code can be found [on GitHub](https://github.com/opentensor/subtensor/blob/main/precompiles). +The source code can be found [on GitHub](https://github.com/opentensor/subtensor/blob/main/precompiles/src). Code examples used throughout this section are provided by the _Opentensor Foundation_ (_OTF_), and come from [this repository](https://github.com/opentensor/evm-bittensor/tree/main/examples). @@ -31,8 +31,15 @@ Code examples used throughout this section are provided by the _Opentensor Found The following list consists of Bittensor-specific precompiles with links to their respective documentation: -- [`Ed25519Verify`](./ed25519-verify-precompile.md): Verify Ed25519 signatures +- `AddressMappingPrecompile`: Manage EVM and Substrate address conversions +- `AlphaPrecompile`: Manage alpha operations - [`BalanceTransfer`](./transfer-between-two-h160-accounts.md): Transfer TAO between accounts +- `CrowdloanPrecompile`: Manage crowdloan operations +- [`Ed25519Verify`](./ed25519-verify-precompile.md): Verify Ed25519 signatures +- `LeasingPrecompile`: Manage subnet leasing operations +- [`MetagraphPrecompile`](./metagraph-precompile.md): Interact with the metagraph +- [`NeuronPrecompile`](./neuron-precompile.md): Manage neuron operations +- `ProxyPrecompile`: Manage proxy operations - [`StakingPrecompile`](./staking-precompile.md): Manage staking operations - [`StakingPrecompileV2`](./staking-precompile.md) (0x805): Main staking operations including: - `addStake`: Add stake to a hotkey @@ -44,6 +51,8 @@ The following list consists of Bittensor-specific precompiles with links to thei - `getStake`: Get stake between specific hotkey and coldkey - `addProxy`: Add a proxy delegate - `removeProxy`: Remove a proxy delegate +- [`Sr25519Verify`](./ed25519-verify-precompile.md): Verify Sr25519 signatures +- `StorageQueryPrecompile`: Manages EVM contracts read access to Substrate chain storage. - [`SubnetPrecompile`](./subnet-precompile.md): Manage subnet operations -- [`MetagraphPrecompile`](./metagraph-precompile.md): Interact with the metagraph -- [`NeuronPrecompile`](./neuron-precompile.md): Manage neuron operations +- `UidLookupPrecompile`: Looks up registered neuron UIDs associated with a given EVM address on a subnet. +- `VotingPowerPrecompile`: Manages per-validator EMA voting power scores for on-chain governance logic. diff --git a/docs/evm-tutorials/index.md b/docs/evm-tutorials/index.md index e1f882a2..235c1662 100644 --- a/docs/evm-tutorials/index.md +++ b/docs/evm-tutorials/index.md @@ -68,26 +68,22 @@ On the Ethereum network, nodes such as full nodes, validator nodes and archive n Note that all operations performed by Bittensor EVM are executed solely on the Bittensor blockchain, not on the Ethereum blockchain. ::: - - - - - byte.toString(16).padStart(2, "0") + byte.toString(16).padStart(2, "0"), ).join(""); const signer = new ethers.Wallet(ethPrivateKey, provider); const ss58mirror = convertH160ToSS58(signer.address); let txSudoSetBalance = api.tx.sudo.sudo( - api.tx.balances.forceSetBalance(ss58mirror, BigInt(1e18).toString()) + api.tx.balances.forceSetBalance(ss58mirror, BigInt(1e18).toString()), ); await sendTransaction(api, txSudoSetBalance, account); const txSudoSetWhitelist = api.tx.sudo.sudo( - api.tx.evm.setWhitelist([signer.address]) + api.tx.evm.setWhitelist([signer.address]), ); await sendTransaction(api, txSudoSetWhitelist, account); @@ -944,7 +867,7 @@ async function createSubnetGetSetParameter() { const contractFactory = new ethers.ContractFactory( subnet_contract_abi, subnet_contract_bytecode, - signer + signer, ); const subnet_contract = await contractFactory.deploy(signer.address); @@ -955,8 +878,8 @@ async function createSubnetGetSetParameter() { txSudoSetBalance = api.tx.sudo.sudo( api.tx.balances.forceSetBalance( convertH160ToSS58(subnet_contract.target), - BigInt(1e16).toString() - ) + BigInt(1e16).toString(), + ), ); await sendTransaction(api, txSudoSetBalance, account); @@ -976,7 +899,7 @@ async function createSubnetGetSetParameter() { let tx = await subnet_contract.registerNetwork( encoder.encode("name"), encoder.encode("repo"), - encoder.encode("contact") + encoder.encode("contact"), ); await tx.wait(); @@ -993,7 +916,7 @@ async function createSubnetGetSetParameter() { // get parameter from chain let parameter = Number( - await api.query.subtensorModule.servingRateLimit(netuid) + await api.query.subtensorModule.servingRateLimit(netuid), ); assert(parameter == 255); @@ -1004,7 +927,7 @@ async function createSubnetGetSetParameter() { // check total networks after registration console.log( "total networks is ", - (await api.query.subtensorModule.totalNetworks()).toHuman() + (await api.query.subtensorModule.totalNetworks()).toHuman(), ); process.exit(0); diff --git a/docs/evm-tutorials/troubleshooting.md b/docs/evm-tutorials/troubleshooting.md index 67e4c1bb..891c30df 100644 --- a/docs/evm-tutorials/troubleshooting.md +++ b/docs/evm-tutorials/troubleshooting.md @@ -2,23 +2,25 @@ title: "Troubleshooting" --- -# Gas estimation failed +# Troubleshooting -Failure of `eth_estimateGas` may indicate a wide range of problems. To understand the root cause, it can help to understand the live cycle of EVM transaction first. When someone posts a transaction to the network, the first thing to happen is gas estimation. The signed transaction is sent to a chain node and the node checks the transaction for errors. In case of literally any error with the transaction (including inability to pay fees due to low sender balance or mis-formatted transaction, etc.) the gas estimation will fail. Failure may or may not indicate the issue with gas fees. If you get the gas estimation failure, please check the following (this list will be updated dynamically): +## Gas estimation failed + +Failure of `eth_estimateGas` may indicate a wide range of problems. To understand the root cause, it can help to understand the live cycle of EVM transaction first. + +When someone posts a transaction to the network, the first thing to happen is gas estimation. The signed transaction is sent to a chain node and the node checks the transaction for errors. In case of literally any error with the transaction (including inability to pay fees due to low sender balance or mis-formatted transaction, etc.) the gas estimation will fail. Failure may or may not indicate the issue with gas fees. If you get the gas estimation failure, please check the following (this list will be updated dynamically): 1. Sender address has sufficient balance to pay gas fees 2. If you're using local chain, it is properly setup: Chain ID is set, deployment white list is disabled - -# Gas estimation exceeds block limit (75000000) +## Gas estimation exceeds block limit (75000000) Select the correct Solidity/EVM version. It needs to be Cancun / 0.8.24 or below. -# InvalidCode(Opcode(94)) when calling a precompile from another contract +## InvalidCode(Opcode(94)) when calling a precompile from another contract Select the correct Solidity/EVM version. It needs to be Cancun / 0.8.24 or below. -# Transaction is pending for too long on localnet when posted through Metamask +## Transaction is pending for too long on localnet when posted through Metamask The issue may occur when Metamask is used to sign and post transactions onchain. The cause may be the nonce caching. Metamask caches address nonce, and doesn't revert it when the network is restarted. So, what may happen is Metamask will post the transaction with higher nonce, and it will get stuck in transaction pool, waiting for the address nonce to reach the expected value. To fix this, open Metamask setting, select "Advanced", and then click on "Clear activity tab data". - From 29f25305408c63817dcb91ca5906b8114cd81eed Mon Sep 17 00:00:00 2001 From: Dera Okeke Date: Wed, 10 Jun 2026 22:31:14 +0100 Subject: [PATCH 2/9] added precompiles --- docs/evm-tutorials/examples.md | 76 +++++++++++++----------- docs/evm-tutorials/staking-precompile.md | 22 +++---- docs/evm-tutorials/subnet-precompile.md | 62 ++----------------- 3 files changed, 54 insertions(+), 106 deletions(-) diff --git a/docs/evm-tutorials/examples.md b/docs/evm-tutorials/examples.md index 10a39830..afff43d7 100644 --- a/docs/evm-tutorials/examples.md +++ b/docs/evm-tutorials/examples.md @@ -15,44 +15,50 @@ Code examples used throughout this section are provided by the _Opentensor Found ## Examples -- [Convert Ethereum (H160) Address to Substrate (SS58)](./convert-h160-to-ss58): Learn how to convert between H160 and SS58 address formats +The following tutorials cover common EVM workflows on Bittensor, including account management, token bridging, and interaction with precompiles: + +- [Convert Ethereum (H160) Address to Substrate (SS58)](./convert-h160-to-ss58): Learn how to convert between H160 and SS58 address formats. +- [Bridging and wrapping vTAO](./vtao-bridge-tutorial.md): Move native TAO from the Bittensor Substrate layer to the Bittensor EVM and wrap it into vTAO. +- [Transfer TAO from Metamask to SS58 Address](./transfer-from-metamask-to-ss58.md): Learn how to transfer TAO from your Metamask wallet to your Bittensor SS58 address for a coldkey (wallet) or a hotkey. +- [Transfer Between Two H160 Accounts](./transfer-between-two-h160-accounts.md): Learn how to transfer TAO between two H160 accounts. ## Standard Ethereum Precompiles -- `ECRecover` (0x1): Recover the address associated with the public key from elliptic curve signature -- `Sha256` (0x2): SHA-256 hash function -- `Ripemd160` (0x3): RIPEMD-160 hash function -- `Identity` (0x4): Identity function (returns input data) -- `Modexp` (0x5): Modular exponentiation -- `Sha3FIPS256` (0x400): SHA3-256 hash function (FIPS variant) -- `ECRecoverPublicKey` (0x401): Recover the public key from an elliptic curve signature +The following table consists of standard Ethereum precompiles with their addresses and description: + +| Precompile | Address | Description | +| -------------------- | ------- | ---------------------------------------------------------------------------------------- | +| `ECRecover` | `0x1` | Recover the address associated with the public key from elliptic curve signature (ECDSA) | +| `Sha256` | `0x2` | SHA-256 hash function | +| `Ripemd160` | `0x3` | RIPEMD-160 hash function | +| `Identity` | `0x4` | Identity function (returns input data) | +| `Modexp` | `0x5` | Modular exponentiation | +| `Dispatch` | `0x6` | Dispatch a runtime call from EVM | +| `Bn128Mul` | `0x7` | BN128 scalar multiplication | +| `Bn128Pairing` | `0x8` | BN128 pairing check | +| `Bn128Add` | `0x9` | BN128 point addition | +| `Sha3FIPS256` | `0x400` | SHA3-256 hash function (FIPS variant) | +| `ECRecoverPublicKey` | `0x401` | Recover public key from an elliptic curve signature (ECDSA) | ## Bittensor-Specific Precompiles -The following list consists of Bittensor-specific precompiles with links to their respective documentation: - -- `AddressMappingPrecompile`: Manage EVM and Substrate address conversions -- `AlphaPrecompile`: Manage alpha operations -- [`BalanceTransfer`](./transfer-between-two-h160-accounts.md): Transfer TAO between accounts -- `CrowdloanPrecompile`: Manage crowdloan operations -- [`Ed25519Verify`](./ed25519-verify-precompile.md): Verify Ed25519 signatures -- `LeasingPrecompile`: Manage subnet leasing operations -- [`MetagraphPrecompile`](./metagraph-precompile.md): Interact with the metagraph -- [`NeuronPrecompile`](./neuron-precompile.md): Manage neuron operations -- `ProxyPrecompile`: Manage proxy operations -- [`StakingPrecompile`](./staking-precompile.md): Manage staking operations -- [`StakingPrecompileV2`](./staking-precompile.md) (0x805): Main staking operations including: - - `addStake`: Add stake to a hotkey - - `removeStake`: Remove stake from a hotkey - - `moveStake`: Move stake between hotkeys - - `transferStake`: Transfer stake between coldkeys - - `getTotalColdkeyStake`: Get total stake for a coldkey - - `getTotalHotkeyStake`: Get total stake for a hotkey - - `getStake`: Get stake between specific hotkey and coldkey - - `addProxy`: Add a proxy delegate - - `removeProxy`: Remove a proxy delegate -- [`Sr25519Verify`](./ed25519-verify-precompile.md): Verify Sr25519 signatures -- `StorageQueryPrecompile`: Manages EVM contracts read access to Substrate chain storage. -- [`SubnetPrecompile`](./subnet-precompile.md): Manage subnet operations -- `UidLookupPrecompile`: Looks up registered neuron UIDs associated with a given EVM address on a subnet. -- `VotingPowerPrecompile`: Manages per-validator EMA voting power scores for on-chain governance logic. +The following table lists of Bittensor-specific precompiles with their addresses, descriptions and links to their respective documentation: + +| Precompile | Address | Description | +| --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------- | +| `Ed25519Verify` | `0x402` | Verify Ed25519 signatures. See [Ed25519 Verify](./ed25519-verify-precompile.md) | +| `SR25519Verify` | `0x403` | Verify Substrate SR25519 signatures. See SR25519 Verify ./sr25519-verify-precompile.md | +| `BalanceTransferPrecompile` | `0x800` | Transfer TAO between H160 addresses. see [Transfer TAO](./transfer-between-two-h160-accounts.md) | +| `StakingPrecompile` | `0x801` | Staking via `msg.value` — **deprecated**, use V2. See [Staking Precompile](./staking-precompile.md) | +| `MetagraphPrecompile` | `0x802` | Interact with the metagraph (rank, trust, stake, axon). See [Metagraph Precompile](./metagraph-precompile.md) | +| `SubnetPrecompile` | `0x803` | Register subnets, get/set hyperparameters . See [Subnet Precompile](./subnet-precompile.md) | +| `NeuronPrecompile` | `0x804` | Register neurons, set weights, serve axon. See [Neuron Precompile](./neuron-precompile.md) | +| `StakingPrecompileV2` | `0x805` | Full staking with limits, allowances, moves . See | +| `UidLookupPrecompile` | `0x806` | Map EVM address → registered neuron UID. See | +| `StorageQueryPrecompile` | `0x807` | Manages EVM contracts read access to Substrate chain storage. | +| `AlphaPrecompile` | `0x808` | AMM pool state, prices, swap simulation. See | +| `CrowdloanPrecompile` | `0x809` | Create and manage crowdloan campaigns. See | +| `LeasingPrecompile` | `0x80A` | Create subnet lease crowdloans, terminate leases. See | +| `ProxyPrecompile` | `0x80B` | Add/remove/execute Substrate proxy relationships. See | +| `AddressMappingPrecompile` | `0x80C` | Convert H160 → Substrate AccountId32. See | +| `VotingPowerPrecompile` | `0x80D` | Query per-validator EMA voting power. See | diff --git a/docs/evm-tutorials/staking-precompile.md b/docs/evm-tutorials/staking-precompile.md index fe0f10a7..ed181a19 100644 --- a/docs/evm-tutorials/staking-precompile.md +++ b/docs/evm-tutorials/staking-precompile.md @@ -32,16 +32,9 @@ btcli subnet register --network ws://127.0.0.1:9944 3. Save the delegate hotkey address. You will use this in the staking pool use case below. -4. Disable staking rate limits by setting `targetStakesPerInterval` to 1000. Follow these below steps: - - Open the Polkadot JS app using [this link with encoded transaction](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/extrinsics/decode/0x0c00132fe803000000000000). - - Click on **Submission** tab. - - From the **using the selected account** field, select **ALICE**. - - Click on **Submit Transaction** at the bottom right. This will open the **authorize transaction** window. - - On this **authorize transaction** window, make sure the **sign and submit** toggle is ON and click on the **Sign and Submit** on the bottom right. - ## Staking V1 and V2 -There are two versions of staking precompile implemenation, V1 and V2. The contract address for V1 is `0x0000000000000000000000000000000000000801`. The address for V2 is `0x0000000000000000000000000000000000000805`. V1 is deprecated, but is kept for backwards-compatibility. The major difference between V1 and V2 is that the staking amount is fetched from the `msg.value` in V1. Then precompile transfers the token back to the caller. It is misleading and confuses solidity developers. In the V2 implementation, all amount parameters are defined as parameter of transaction. +There are two versions of staking precompile implemenation, V1 and V2. The contract address for V1 is `0x0000000000000000000000000000000000000801`. The address for V2 is `0x0000000000000000000000000000000000000805`. V1 is deprecated, but is kept for backwards-compatibility. The major difference between V1 and V2 is that the staking amount is fetched from the `msg.value` in V1. Then precompile transfers the token back to the caller. It is misleading and confuses solidity developers. In the V2 implementation, all amount parameters are defined as parameter of transaction. ## Call the staking precompile from another smart contract (staking pool use case) @@ -74,7 +67,6 @@ In this tutorial, you will interact directly with the staking precompile by usin 3. Remix IDE will find the precompile at the precompile address on the subtensor EVM and show it in the list of deployed contracts. Expand the contract, then expand the `addStake` method, and paste the public key of your delegate hotkey into the `hotkey` field. Then click **transact** and wait for the transaction to be completed. 4. Follow these steps to see that the stake record is updated in [Polkadot JS app](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/chainstate): - 1. Select **subtensorModule** + **stake** in the drop-down list. 2. Paste the delegate hotkey account ID in the first parameter. 3. Toggle **include option** OFF for the second parameter. @@ -82,9 +74,9 @@ In this tutorial, you will interact directly with the staking precompile by usin ## Notes: Calling the staking precompile from another smart contract - - The precompile takes the contract's address as the **coldkey**, since the precompile can't get the original caller. - - The **contract** (not the caller's coldkey) must have sufficient liquidity or the transaction will fail. - - The transaction must be *privileged* because the liquidity for `addStake` is subtracted from contract. - - - As the function parameter indicates, `amount` in `addStake` and `removeStake` are specified in TAO $\tau$. - - That when transferring liquidity to the contract, `msg.value` is in denominations of 1/1e18 TAO $\tau$ . The staking precompile, however, expects RAO, 1/1e9 TAO $\tau$. You must convert before calling it: **uint256 amount = msg.value / 1e9**. +- The precompile takes the contract's address as the **coldkey**, since the precompile can't get the original caller. +- The **contract** (not the caller's coldkey) must have sufficient liquidity or the transaction will fail. +- The transaction must be _privileged_ because the liquidity for `addStake` is subtracted from contract. + +- As the function parameter indicates, `amount` in `addStake` and `removeStake` are specified in TAO $\tau$. +- That when transferring liquidity to the contract, `msg.value` is in denominations of 1/1e18 TAO $\tau$ . The staking precompile, however, expects RAO, 1/1e9 TAO $\tau$. You must convert before calling it: **uint256 amount = msg.value / 1e9**. diff --git a/docs/evm-tutorials/subnet-precompile.md b/docs/evm-tutorials/subnet-precompile.md index e496a767..9181033f 100644 --- a/docs/evm-tutorials/subnet-precompile.md +++ b/docs/evm-tutorials/subnet-precompile.md @@ -140,7 +140,7 @@ Gets the weights set rate limit for a subnet. - `uint64`: The weights set rate limit value -#### `setWeightsSetRateLimit` ⚠️ **DEPRECATED** +#### `setWeightsSetRateLimit` ⚠️ **ROOT-ONLY** Sets the weights set rate limit for a subnet. **This function is deprecated. Subnet owners cannot set weight setting rate limits.** @@ -208,31 +208,6 @@ Sets the minimum allowed weights for a subnet. ### Consensus Parameters -#### `getAdjustmentAlpha` - -Gets the adjustment alpha parameter for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID - -**Returns:** - -- `uint64`: The adjustment alpha value - -#### `setAdjustmentAlpha` - -Sets the adjustment alpha parameter for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID -- `adjustmentAlpha` (uint64): The new adjustment alpha value - -**Returns:** - -- None (payable function) - #### `getKappa` Gets the kappa parameter for a subnet. @@ -258,31 +233,6 @@ Sets the kappa parameter for a subnet. - None (payable function) -#### `getRho` - -Gets the rho parameter for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID - -**Returns:** - -- `uint16`: The rho value - -#### `setRho` - -Sets the rho parameter for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID -- `rho` (uint16): The new rho value - -**Returns:** - -- None (payable function) - #### `getAlphaSigmoidSteepness` Gets the alpha sigmoid steepness parameter for a subnet. @@ -453,9 +403,9 @@ Gets the minimum burn amount for a subnet. - `uint64`: The minimum burn amount -#### `setMinBurn` ⚠️ **DEPRECATED** +#### `setMinBurn` -Sets the minimum burn amount for a subnet. **This function is deprecated. Subnet owners cannot set the minimum burn anymore.** +Sets the minimum burn amount for a subnet. **Parameters:** @@ -481,9 +431,9 @@ Gets the maximum burn amount for a subnet. - `uint64`: The maximum burn amount -#### `setMaxBurn` ⚠️ **DEPRECATED** +#### `setMaxBurn` -Sets the maximum burn amount for a subnet. **This function is deprecated. Subnet owners cannot set the maximum burn anymore.** +Sets the maximum burn amount for a subnet. **Parameters:** @@ -910,7 +860,7 @@ async function createSubnetGetSetParameter() { console.log("networkOwner is ", networkOwner); // Note: This example uses setHyperParameter which calls setServingRateLimit - // Some other functions like setMinBurn, setMaxBurn, setWeightsSetRateLimit are deprecated + // Some other functions like setWeightsSetRateLimit are root-only and cannot be set by the subnet owner tx = await subnet_contract.setHyperParameter(netuid, 255); await tx.wait(); From 118868c86b7346056851a9717bcbe313707a38ed Mon Sep 17 00:00:00 2001 From: Dera Okeke Date: Wed, 10 Jun 2026 23:42:38 +0100 Subject: [PATCH 3/9] alpha precompile doc --- docs/evm-tutorials/alpha-precompile.md | 75 ++ docs/evm-tutorials/metagraph-precompile.md | 1108 ++++++++++---------- docs/evm-tutorials/neuron-precompile.md | 630 +++++------ sidebars.js | 8 +- 4 files changed, 971 insertions(+), 850 deletions(-) create mode 100644 docs/evm-tutorials/alpha-precompile.md diff --git a/docs/evm-tutorials/alpha-precompile.md b/docs/evm-tutorials/alpha-precompile.md new file mode 100644 index 00000000..ce900889 --- /dev/null +++ b/docs/evm-tutorials/alpha-precompile.md @@ -0,0 +1,75 @@ +--- +title: "Alpha Precompile" +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Alpha Precompile + +The Alpha precompile exposes the state of every subnet's AMM pool to EVM smart contracts. It is a read-only interface for token prices, pool reserves, swap simulation, and emission rates. Any contract that needs to react to subnet token economics uses this precompile as its data source. + +**Address**: `0x0000000000000000000000000000000000000808` + +## Concepts + +### Spot price vs. moving price + +`getAlphaPrice` returns the current instantaneous price derived from the pool reserves (`TaoIn / AlphaIn`). This is the price you would get at this exact block. + +`getMovingAlphaPrice` returns an exponential moving average of the spot price, smoothed over `getEMAPriceHalvingBlocks` blocks. Use the moving price when you need a tamper-resistant value—it is significantly harder to manipulate through flash transactions than the spot price. + +### Pool reserves vs. outstanding supply + +The pool holds two reserves: + +- `TaoInPool` — TAO locked in the subnet's AMM pool. +- `AlphaInPool` — alpha tokens locked in the AMM pool. + +`AlphaOutPool` (also called `AlphaOut`) tracks the total alpha outstanding outside the pool — staked by validators, miners, and delegators. `AlphaIssuance` is the total ever minted. + +The ratio `AlphaInPool / (AlphaInPool + AlphaOutPool)` tells you what fraction of alpha supply is available for immediate trading. + +### Swap simulation + +`simSwapTaoForAlpha` and `simSwapAlphaForTao` compute expected output for a given input using the constant-product formula, including fees. Use these before a stake or unstake operation to estimate slippage and decide whether to use a limit-price variant. + +## Function reference + +### Prices and pool state + +| Function | Parameters | Returns | Description | +| ------------------------------------ | ---------- | ------------------------- | -------------------------------------- | +| `getAlphaPrice(uint16 netuid)` | netuid | `uint256` (RAO per alpha) | Spot price | +| `getMovingAlphaPrice(uint16 netuid)` | netuid | `uint256` (RAO per alpha) | EMA price | +| `getTaoInPool(uint16 netuid)` | netuid | `uint64` | TAO reserves in pool | +| `getAlphaInPool(uint16 netuid)` | netuid | `uint64` | Alpha reserves in pool | +| `getAlphaOutPool(uint16 netuid)` | netuid | `uint64` | Alpha outstanding outside pool | +| `getAlphaIssuance(uint16 netuid)` | netuid | `uint64` | Total alpha ever minted | +| `getTaoWeight()` | — | `uint256` | Global TAO weight scalar | +| `getSumAlphaPrice()` | — | `uint256` | Sum of alpha prices across all subnets | +| `getCKBurn()` | — | `uint256` | CK burn rate | + +### Swap simulation + +| Function | Parameters | Returns | +| ------------------------------------------------- | ------------------------- | ---------------------------------------- | +| `simSwapTaoForAlpha(uint16 netuid, uint64 tao)` | netuid, tao amount in RAO | Expected alpha received (`uint256`) | +| `simSwapAlphaForTao(uint16 netuid, uint64 alpha)` | netuid, alpha amount | Expected TAO received in RAO (`uint256`) | + +### Emission rates (per block) + +| Function | Parameters | Returns | Description | +| ------------------------------------ | ---------- | --------- | ----------------------------------------------------- | +| `getTaoInEmission(uint16 netuid)` | netuid | `uint256` | TAO flowing into the pool per block | +| `getAlphaInEmission(uint16 netuid)` | netuid | `uint256` | Alpha minted into the pool per block | +| `getAlphaOutEmission(uint16 netuid)` | netuid | `uint256` | Alpha emitted outside the pool (to stakers) per block | + +### Subnet metadata + +| Function | Parameters | Returns | Description | +| ----------------------------------------- | ---------- | ------------------------------ | ------------------------- | +| `getSubnetMechanism(uint16 netuid)` | netuid | `uint16` (0=Stable, 1=Dynamic) | AMM mechanism type | +| `getRootNetuid()` | — | `uint16` | Root subnet ID (always 0) | +| `getEMAPriceHalvingBlocks(uint16 netuid)` | netuid | `uint64` | Blocks for EMA half-life | +| `getSubnetVolume(uint16 netuid)` | netuid | `uint256` | Recent trading volume | diff --git a/docs/evm-tutorials/metagraph-precompile.md b/docs/evm-tutorials/metagraph-precompile.md index 611ec4aa..a10e8651 100644 --- a/docs/evm-tutorials/metagraph-precompile.md +++ b/docs/evm-tutorials/metagraph-precompile.md @@ -5,10 +5,9 @@ title: "Metagraph Precompile" import ThemedImage from '@theme/ThemedImage'; import useBaseUrl from '@docusaurus/useBaseUrl'; - # Metagraph Precompile -The metagraph precompile allows you to query information about neurons, their relationships, and network state in the Bittensor network. This precompile provides read-only access to the metagraph data through smart contracts at precompile address `2050`. +The metagraph precompile allows you to query information about neurons, their relationships, and network state in the Bittensor network. This precompile provides read-only access to the metagraph data through smart contracts at precompile address `0x802`. ## Overview @@ -30,11 +29,12 @@ All functions in this precompile are view-only operations that don't modify stat Returns the total number of UIDs (neurons) in a specific subnetwork. **Parameters:** + - `netuid` (uint16): The subnetwork ID to query **Returns:** -- `uint16`: Total count of neurons in the subnetwork +- `uint16`: Total count of neurons in the subnetwork ### Token and Consensus Metrics @@ -43,89 +43,95 @@ Returns the total number of UIDs (neurons) in a specific subnetwork. Retrieves the total stake amount for a specific neuron. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** + - `uint64`: Total stake amount for the neuron's hotkey **Errors:** -- Reverts with `InvalidRange` if the UID doesn't exist in the network - +- Reverts with `InvalidRange` if the UID doesn't exist in the network #### `getEmission` Gets the emission value for a specific neuron, representing its reward allocation. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** -- `uint64`: Emission value for the neuron +- `uint64`: Emission value for the neuron #### `getRank` Returns the rank score of a neuron, indicating its performance relative to others. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** -- `uint16`: Rank score of the neuron +- `uint16`: Rank score of the neuron #### `getTrust` Retrieves the trust score of a neuron, representing how much other neurons trust its outputs. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** -- `uint16`: Trust score of the neuron - +- `uint16`: Trust score of the neuron #### `getConsensus` Gets the consensus score of a neuron, indicating agreement with network consensus. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** -- `uint16`: Consensus score of the neuron +- `uint16`: Consensus score of the neuron #### `getIncentive` Returns the incentive score of a neuron, representing its contribution to the network. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** -- `uint16`: Incentive score of the neuron +- `uint16`: Incentive score of the neuron #### `getDividends` Retrieves the dividends score of a neuron, indicating its reward distribution. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** -- `uint16`: Dividends score of the neuron +- `uint16`: Dividends score of the neuron ### Validator-Specific Functions @@ -134,25 +140,26 @@ Retrieves the dividends score of a neuron, indicating its reward distribution. Gets the validator trust score for a neuron, specific to validator operations. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** -- `uint16`: Validator trust score - +- `uint16`: Validator trust score #### `getValidatorStatus` Checks if a neuron has validator permit status. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** -- `bool`: True if the neuron has validator permissions, false otherwise +- `bool`: True if the neuron has validator permissions, false otherwise ### Neuron State Information @@ -161,26 +168,26 @@ Checks if a neuron has validator permit status. Returns the block number of the last update for a neuron. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** -- `uint64`: Block number of the last update - +- `uint64`: Block number of the last update #### `getIsActive` Checks if a neuron is currently active in the network. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** -- `bool`: True if the neuron is active, false otherwise - +- `bool`: True if the neuron is active, false otherwise ### Network Connection Information @@ -189,13 +196,16 @@ Checks if a neuron is currently active in the network. Retrieves the axon information for a neuron, including network connection details. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** + - `AxonInfo`: Struct containing axon connection information **AxonInfo Structure:** + ```solidity struct AxonInfo { uint64 block; // Block number when axon was registered @@ -208,8 +218,8 @@ struct AxonInfo { ``` **Errors:** -- Reverts with "hotkey not found" if the neuron doesn't exist +- Reverts with "hotkey not found" if the neuron doesn't exist ### Key Management @@ -218,13 +228,16 @@ struct AxonInfo { Returns the hotkey (public key) associated with a neuron. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** + - `bytes32`: The hotkey as a 32-byte hash **Errors:** + - Reverts with `InvalidRange` if the UID doesn't exist #### `getColdkey` @@ -232,15 +245,17 @@ Returns the hotkey (public key) associated with a neuron. Returns the coldkey (owner key) associated with a neuron's hotkey. **Parameters:** + - `netuid` (uint16): The subnetwork ID - `uid` (uint16): The unique identifier of the neuron **Returns:** + - `bytes32`: The coldkey as a 32-byte hash **Errors:** -- Reverts with `InvalidRange` if the UID doesn't exist +- Reverts with `InvalidRange` if the UID doesn't exist ## Usage Examples @@ -250,7 +265,6 @@ First, set up your client to interact with the metagraph precompile: Fill in the RPC URL for the desired network: [EVM Network Details](./subtensor-networks). - Full source: https://github.com/opentensor/evm-bittensor/blob/main/examples/metagraph.js Cribbed from: https://github.com/opentensor/subtensor/blob/main/evm-tests/src/contracts/metagraph.ts @@ -263,409 +277,406 @@ const IMETAGRAPH_ADDRESS = "0x0000000000000000000000000000000000000802"; const publicClient = await getPublicClient("YOUR_RPC_URL"); const IMetagraphABI = [ - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getAxon", - outputs: [ - { - components: [ - { - internalType: "uint64", - name: "block", - type: "uint64", - }, - { - internalType: "uint32", - name: "version", - type: "uint32", - }, - { - internalType: "uint128", - name: "ip", - type: "uint128", - }, - { - internalType: "uint16", - name: "port", - type: "uint16", - }, - { - internalType: "uint8", - name: "ip_type", - type: "uint8", - }, - { - internalType: "uint8", - name: "protocol", - type: "uint8", - }, - ], - internalType: "struct AxonInfo", - name: "", - type: "tuple", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getColdkey", - outputs: [ - { - internalType: "bytes32", - name: "", - type: "bytes32", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getConsensus", - outputs: [ - { - internalType: "uint16", - name: "", - type: "uint16", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getDividends", - outputs: [ - { - internalType: "uint16", - name: "", - type: "uint16", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getEmission", - outputs: [ - { - internalType: "uint64", - name: "", - type: "uint64", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getHotkey", - outputs: [ - { - internalType: "bytes32", - name: "", - type: "bytes32", - }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getAxon", + outputs: [ + { + components: [ + { + internalType: "uint64", + name: "block", + type: "uint64", + }, + { + internalType: "uint32", + name: "version", + type: "uint32", + }, + { + internalType: "uint128", + name: "ip", + type: "uint128", + }, + { + internalType: "uint16", + name: "port", + type: "uint16", + }, + { + internalType: "uint8", + name: "ip_type", + type: "uint8", + }, + { + internalType: "uint8", + name: "protocol", + type: "uint8", + }, ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getIncentive", - outputs: [ - { - internalType: "uint16", - name: "", - type: "uint16", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getIsActive", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getLastUpdate", - outputs: [ - { - internalType: "uint64", - name: "", - type: "uint64", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getRank", - outputs: [ - { - internalType: "uint16", - name: "", - type: "uint16", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getStake", - outputs: [ - { - internalType: "uint64", - name: "", - type: "uint64", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getTrust", - outputs: [ - { - internalType: "uint16", - name: "", - type: "uint16", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - ], - name: "getUidCount", - outputs: [ - { - internalType: "uint16", - name: "", - type: "uint16", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getValidatorStatus", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16", - name: "uid", - type: "uint16", - }, - ], - name: "getVtrust", - outputs: [ - { - internalType: "uint16", - name: "", - type: "uint16", - }, - ], - stateMutability: "view", - type: "function", - }, + internalType: "struct AxonInfo", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getColdkey", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getConsensus", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getDividends", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getEmission", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getHotkey", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getIncentive", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getIsActive", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getLastUpdate", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getRank", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getStake", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getTrust", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getUidCount", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getValidatorStatus", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getVtrust", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, ]; - - ``` - ### Getting Network Information ```javascript // Get the total number of neurons in a subnetwork const subnetId = 1; // Example subnet ID const uidCount = await publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getUidCount", - args: [subnetId] + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getUidCount", + args: [subnetId], }); console.log(`Total neurons in subnet ${subnetId}: ${uidCount}`); @@ -679,29 +690,31 @@ const uid = 26; // Example neuron UID // Get stake amount const stake = await publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getStake", - args: [subnetId, uid] + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getStake", + args: [subnetId, uid], }); // Get emission value const emission = await publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getEmission", - args: [subnetId, uid] + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getEmission", + args: [subnetId, uid], }); // Get trust score const trust = await publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getTrust", - args: [subnetId, uid] + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getTrust", + args: [subnetId, uid], }); -console.log(`Neuron ${uid} - Stake: ${stake}, Emission: ${emission}, Trust: ${trust}`); +console.log( + `Neuron ${uid} - Stake: ${stake}, Emission: ${emission}, Trust: ${trust}`, +); ``` ### Getting Neuron Connection Information @@ -709,19 +722,19 @@ console.log(`Neuron ${uid} - Stake: ${stake}, Emission: ${emission}, Trust: ${tr ```javascript // Get axon information for network connectivity const axon = await publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getAxon", - args: [subnetId, uid] + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getAxon", + args: [subnetId, uid], }); console.log("Axon Info:", { - block: axon.block, - version: axon.version, - ip: axon.ip, - port: axon.port, - ipType: axon.ip_type, - protocol: axon.protocol + block: axon.block, + version: axon.version, + ip: axon.ip, + port: axon.port, + ipType: axon.ip_type, + protocol: axon.protocol, }); ``` @@ -730,18 +743,18 @@ console.log("Axon Info:", { ```javascript // Check if a neuron is a validator const isValidator = await publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getValidatorStatus", - args: [subnetId, uid] + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getValidatorStatus", + args: [subnetId, uid], }); // Get validator trust score const vtrust = await publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getVtrust", - args: [subnetId, uid] + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getVtrust", + args: [subnetId, uid], }); console.log(`Neuron ${uid} - Is Validator: ${isValidator}, VTrust: ${vtrust}`); @@ -752,17 +765,17 @@ console.log(`Neuron ${uid} - Is Validator: ${isValidator}, VTrust: ${vtrust}`); ```javascript // Get hotkey and coldkey for a neuron const hotkey = await publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getHotkey", - args: [subnetId, uid] + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getHotkey", + args: [subnetId, uid], }); const coldkey = await publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getColdkey", - args: [subnetId, uid] + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getColdkey", + args: [subnetId, uid], }); console.log(`Neuron ${uid} - Hotkey: ${hotkey}, Coldkey: ${coldkey}`); @@ -772,103 +785,102 @@ console.log(`Neuron ${uid} - Hotkey: ${hotkey}, Coldkey: ${coldkey}`); ```javascript async function analyzeNeuron(subnetId, uid) { - try { - // Get all key metrics for a neuron - const [ - stake, - emission, - rank, - trust, - consensus, - incentive, - dividends, - lastUpdate, - isActive, - validatorStatus - ] = await Promise.all([ - publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getStake", - args: [subnetId, uid] - }), - publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getEmission", - args: [subnetId, uid] - }), - publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getRank", - args: [subnetId, uid] - }), - publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getTrust", - args: [subnetId, uid] - }), - publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getConsensus", - args: [subnetId, uid] - }), - publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getIncentive", - args: [subnetId, uid] - }), - publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getDividends", - args: [subnetId, uid] - }), - publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getLastUpdate", - args: [subnetId, uid] - }), - publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getIsActive", - args: [subnetId, uid] - }), - publicClient.readContract({ - abi: IMetagraphABI, - address: IMETAGRAPH_ADDRESS, - functionName: "getValidatorStatus", - args: [subnetId, uid] - }) - ]); - - return { - uid, - stake, - emission, - rank, - trust, - consensus, - incentive, - dividends, - lastUpdate, - isActive, - isValidator: validatorStatus - }; - } catch (error) { - console.error(`Error analyzing neuron ${uid}:`, error); - throw error; - } + try { + // Get all key metrics for a neuron + const [ + stake, + emission, + rank, + trust, + consensus, + incentive, + dividends, + lastUpdate, + isActive, + validatorStatus, + ] = await Promise.all([ + publicClient.readContract({ + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getStake", + args: [subnetId, uid], + }), + publicClient.readContract({ + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getEmission", + args: [subnetId, uid], + }), + publicClient.readContract({ + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getRank", + args: [subnetId, uid], + }), + publicClient.readContract({ + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getTrust", + args: [subnetId, uid], + }), + publicClient.readContract({ + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getConsensus", + args: [subnetId, uid], + }), + publicClient.readContract({ + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getIncentive", + args: [subnetId, uid], + }), + publicClient.readContract({ + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getDividends", + args: [subnetId, uid], + }), + publicClient.readContract({ + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getLastUpdate", + args: [subnetId, uid], + }), + publicClient.readContract({ + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getIsActive", + args: [subnetId, uid], + }), + publicClient.readContract({ + abi: IMetagraphABI, + address: IMETAGRAPH_ADDRESS, + functionName: "getValidatorStatus", + args: [subnetId, uid], + }), + ]); + + return { + uid, + stake, + emission, + rank, + trust, + consensus, + incentive, + dividends, + lastUpdate, + isActive, + isValidator: validatorStatus, + }; + } catch (error) { + console.error(`Error analyzing neuron ${uid}:`, error); + throw error; + } } // Usage const neuronData = await analyzeNeuron(1, 0); console.log("Neuron Analysis:", neuronData); ``` - diff --git a/docs/evm-tutorials/neuron-precompile.md b/docs/evm-tutorials/neuron-precompile.md index be5dc1fe..63764e6f 100644 --- a/docs/evm-tutorials/neuron-precompile.md +++ b/docs/evm-tutorials/neuron-precompile.md @@ -7,8 +7,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; # Neuron Precompile - -This precompile enables full management of neurons (miner and validator nodes) through smart contracts, from registration to weight setting to service configuration. +This precompile enables full management of neurons (miner and validator nodes) through smart contracts, from registration to weight setting to service configuration. See [Understanding Neurons](../learn/neurons.md). @@ -27,9 +26,11 @@ The neuron precompile provides the following core functions for neuron managemen ### Weight Management #### `setWeights` + Set weights (rankings) for miners on the subnet. See [Requirements for validation](../validators/#requirements-for-validation) **Parameters:** + - `netuid` (uint16): The subnet ID where the neuron is registered - `dests` (uint16[]): Array of destination neuron UIDs to assign weights to - `weights` (uint16[]): Array of weight values corresponding to each destination UID @@ -39,9 +40,11 @@ Set weights (rankings) for miners on the subnet. See [Requirements for validatio This function allows a neuron to set weights on other neurons in the same subnet. The weights represent how much value or trust this neuron assigns to others, which is crucial for the Bittensor consensus mechanism. #### `commitWeights` + Commits weights using a hash commitment scheme for privacy and security. **Parameters:** + - `netuid` (uint16): The subnet ID where the neuron is registered - `commitHash` (bytes32): Hash commitment of the weights to be revealed later @@ -49,9 +52,11 @@ Commits weights using a hash commitment scheme for privacy and security. This function implements a commit-reveal scheme for setting weights. The neuron first commits a hash of their weights, then later reveals the actual weights. This prevents weight-copying. #### `revealWeights` + Reveals previously committed weights by providing the original data that produces the committed hash. **Parameters:** + - `netuid` (uint16): The subnet ID where the neuron is registered - `uids` (uint16[]): Array of neuron UIDs that weights are being set for - `values` (uint16[]): Array of weight values for each corresponding UID @@ -66,9 +71,11 @@ This function completes the commit-reveal process by revealing the actual weight Neuron registration is required for joining a subnet as a miner or validator #### `burnedRegister` + Registers a neuron in a subnet by burning TAO tokens. **Parameters:** + - `netuid` (uint16): The subnet ID to register the neuron in - `hotkey` (bytes32): The hotkey public key (32 bytes) of the neuron to register @@ -76,9 +83,11 @@ Registers a neuron in a subnet by burning TAO tokens. This function registers a new neuron in the specified subnet by burning TAO at the **current dynamic registration price**. The price **decays** over time (governed by `BurnHalfLife`) and **increases** after each successful registration (governed by `BurnIncreaseMult`), clamped by `MinBurn` and `MaxBurn`. The hotkey represents the neuron's identity on the network. #### `registerLimit` + Registers a neuron but **only if** the burn price does not exceed a caller-supplied cap (slippage protection). **Parameters:** + - `netuid` (uint16): The subnet ID to register the neuron in - `hotkey` (bytes32): The hotkey public key (32 bytes) of the neuron to register - `limitPrice` (uint64): Maximum acceptable burn price in the chain’s fixed-point balance representation—confirm encoding with the deployed precompile ABI and metadata @@ -89,9 +98,11 @@ Use this when you want the transaction to **fail** if the registration cost woul ### Axon Services #### `serveAxon` + Configures and serves an axon endpoint for the neuron. **Parameters:** + - `netuid` (uint16): The subnet ID where the neuron is serving - `version` (uint32): Version of the axon service - `ip` (uint128): IP address of the axon service (supports both IPv4 and IPv6) @@ -105,10 +116,11 @@ Configures and serves an axon endpoint for the neuron. This function allows a neuron to announce its axon service endpoint to the network. An axon is the service interface that other neurons can connect to for communication and inference requests using the dendrite-axon protocol. #### `serveAxonTls` -Configures and serves an axon endpoint with TLS/SSL security. +Configures and serves an axon endpoint with TLS/SSL security. **Parameters:** + - `netuid` (uint16): The subnet ID where the neuron is serving - `version` (uint32): Version of the axon service - `ip` (uint128): IP address of the axon service @@ -123,11 +135,11 @@ Configures and serves an axon endpoint with TLS/SSL security. Similar to `serveAxon`, but includes TLS certificate information for secure encrypted communication. This is recommended for production environments where data privacy and security are important. #### `servePrometheus` -Configures a Prometheus metrics endpoint for the neuron. - +Configures a Prometheus metrics endpoint for the neuron. **Parameters:** + - `netuid` (uint16): The subnet ID where the neuron is serving - `version` (uint32): Version of the Prometheus service - `ip` (uint128): IP address where Prometheus metrics are served @@ -155,237 +167,237 @@ import { ethers } from "ethers"; const INEURON_ADDRESS = "0x0000000000000000000000000000000000000804"; const INeuronABI = [ - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "bytes32", - name: "commitHash", - type: "bytes32", - }, - ], - name: "commitWeights", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16[]", - name: "uids", - type: "uint16[]", - }, - { - internalType: "uint16[]", - name: "values", - type: "uint16[]", - }, - { - internalType: "uint16[]", - name: "salt", - type: "uint16[]", - }, - { - internalType: "uint64", - name: "versionKey", - type: "uint64", - }, - ], - name: "revealWeights", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint16[]", - name: "dests", - type: "uint16[]", - }, - { - internalType: "uint16[]", - name: "weights", - type: "uint16[]", - }, - { - internalType: "uint64", - name: "versionKey", - type: "uint64", - }, - ], - name: "setWeights", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint32", - name: "version", - type: "uint32", - }, - { - internalType: "uint128", - name: "ip", - type: "uint128", - }, - { - internalType: "uint16", - name: "port", - type: "uint16", - }, - { - internalType: "uint8", - name: "ipType", - type: "uint8", - }, - { - internalType: "uint8", - name: "protocol", - type: "uint8", - }, - { - internalType: "uint8", - name: "placeholder1", - type: "uint8", - }, - { - internalType: "uint8", - name: "placeholder2", - type: "uint8", - }, - ], - name: "serveAxon", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint32", - name: "version", - type: "uint32", - }, - { - internalType: "uint128", - name: "ip", - type: "uint128", - }, - { - internalType: "uint16", - name: "port", - type: "uint16", - }, - { - internalType: "uint8", - name: "ipType", - type: "uint8", - }, - { - internalType: "uint8", - name: "protocol", - type: "uint8", - }, - { - internalType: "uint8", - name: "placeholder1", - type: "uint8", - }, - { - internalType: "uint8", - name: "placeholder2", - type: "uint8", - }, - { - internalType: "bytes", - name: "certificate", - type: "bytes", - }, - ], - name: "serveAxonTls", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "uint32", - name: "version", - type: "uint32", - }, - { - internalType: "uint128", - name: "ip", - type: "uint128", - }, - { - internalType: "uint16", - name: "port", - type: "uint16", - }, - { - internalType: "uint8", - name: "ipType", - type: "uint8", - }, - ], - name: "servePrometheus", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "uint16", - name: "netuid", - type: "uint16", - }, - { - internalType: "bytes32", - name: "hotkey", - type: "bytes32", - }, - ], - name: "burnedRegister", - outputs: [], - stateMutability: "payable", - type: "function", - }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "bytes32", + name: "commitHash", + type: "bytes32", + }, + ], + name: "commitWeights", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16[]", + name: "uids", + type: "uint16[]", + }, + { + internalType: "uint16[]", + name: "values", + type: "uint16[]", + }, + { + internalType: "uint16[]", + name: "salt", + type: "uint16[]", + }, + { + internalType: "uint64", + name: "versionKey", + type: "uint64", + }, + ], + name: "revealWeights", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16[]", + name: "dests", + type: "uint16[]", + }, + { + internalType: "uint16[]", + name: "weights", + type: "uint16[]", + }, + { + internalType: "uint64", + name: "versionKey", + type: "uint64", + }, + ], + name: "setWeights", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint32", + name: "version", + type: "uint32", + }, + { + internalType: "uint128", + name: "ip", + type: "uint128", + }, + { + internalType: "uint16", + name: "port", + type: "uint16", + }, + { + internalType: "uint8", + name: "ipType", + type: "uint8", + }, + { + internalType: "uint8", + name: "protocol", + type: "uint8", + }, + { + internalType: "uint8", + name: "placeholder1", + type: "uint8", + }, + { + internalType: "uint8", + name: "placeholder2", + type: "uint8", + }, + ], + name: "serveAxon", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint32", + name: "version", + type: "uint32", + }, + { + internalType: "uint128", + name: "ip", + type: "uint128", + }, + { + internalType: "uint16", + name: "port", + type: "uint16", + }, + { + internalType: "uint8", + name: "ipType", + type: "uint8", + }, + { + internalType: "uint8", + name: "protocol", + type: "uint8", + }, + { + internalType: "uint8", + name: "placeholder1", + type: "uint8", + }, + { + internalType: "uint8", + name: "placeholder2", + type: "uint8", + }, + { + internalType: "bytes", + name: "certificate", + type: "bytes", + }, + ], + name: "serveAxonTls", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint32", + name: "version", + type: "uint32", + }, + { + internalType: "uint128", + name: "ip", + type: "uint128", + }, + { + internalType: "uint16", + name: "port", + type: "uint16", + }, + { + internalType: "uint8", + name: "ipType", + type: "uint8", + }, + ], + name: "servePrometheus", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32", + }, + ], + name: "burnedRegister", + outputs: [], + stateMutability: "payable", + type: "function", + }, ]; // Initialize contract instance @@ -406,11 +418,12 @@ The following registration example is adapted from the test implementation in [n async function registerNeuron() { try { const netuid = 1; // Target subnet ID - const hotkey = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; // 32-byte hotkey - + const hotkey = + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; // 32-byte hotkey + const tx = await neuronContract.burnedRegister(netuid, hotkey); await tx.wait(); - + console.log("Neuron registered successfully!"); console.log("Transaction hash:", tx.hash); } catch (error) { @@ -434,10 +447,15 @@ async function setNeuronWeights() { const dests = [1, 2, 3]; // Target neuron UIDs const weights = [100, 200, 150]; // Weight values for each target const versionKey = 0; - - const tx = await neuronContract.setWeights(netuid, dests, weights, versionKey); + + const tx = await neuronContract.setWeights( + netuid, + dests, + weights, + versionKey, + ); await tx.wait(); - + console.log("Weights set successfully!"); console.log("Transaction hash:", tx.hash); } catch (error) { @@ -464,7 +482,7 @@ function convertH160ToPublicKey(ethAddress) { const prefix = "evm:"; const prefixBytes = new TextEncoder().encode(prefix); const addressBytes = hexToU8a( - ethAddress.startsWith("0x") ? ethAddress : `0x${ethAddress}` + ethAddress.startsWith("0x") ? ethAddress : `0x${ethAddress}`, ); const combined = new Uint8Array(prefixBytes.length + addressBytes.length); @@ -481,23 +499,23 @@ function convertH160ToPublicKey(ethAddress) { // Logic adapted from: https://github.com/opentensor/subtensor/blob/main/evm-tests/test/neuron.precompile.reveal-weights.test.ts function generateCommitHash(netuid, address, uids, values, salt, version_key) { const registry = new TypeRegistry(); - + // Convert Ethereum address to public key format (32 bytes) const publicKey = convertH160ToPublicKey(address); - + const tupleData = new Tuple( registry, [ VecFixed.with(u8, 32), // Public key - u16, // Network UID - Vec.with(u16), // UIDs - Vec.with(u16), // Values - Vec.with(u16), // Salt - u64, // Version key + u16, // Network UID + Vec.with(u16), // UIDs + Vec.with(u16), // Values + Vec.with(u16), // Salt + u64, // Version key ], - [publicKey, netuid, uids, values, salt, version_key] + [publicKey, netuid, uids, values, salt, version_key], ); - + return blake2AsU8a(tupleData.toU8a()); } @@ -509,16 +527,23 @@ async function commitWeights() { const values = [5]; const salt = [9]; // Random salt values const version_key = 0; - + // Generate commit hash - const commitHash = generateCommitHash(netuid, wallet.address, uids, values, salt, version_key); - + const commitHash = generateCommitHash( + netuid, + wallet.address, + uids, + values, + salt, + version_key, + ); + const tx = await neuronContract.commitWeights(netuid, commitHash); await tx.wait(); - + console.log("Weights committed successfully!"); console.log("Transaction hash:", tx.hash); - + // Store the reveal data for later use return { uids, values, salt, version_key }; } catch (error) { @@ -531,10 +556,16 @@ async function revealWeights(revealData) { try { const netuid = 1; const { uids, values, salt, version_key } = revealData; - - const tx = await neuronContract.revealWeights(netuid, uids, values, salt, version_key); + + const tx = await neuronContract.revealWeights( + netuid, + uids, + values, + salt, + version_key, + ); await tx.wait(); - + console.log("Weights revealed successfully!"); console.log("Transaction hash:", tx.hash); } catch (error) { @@ -545,10 +576,10 @@ async function revealWeights(revealData) { // Complete commit-reveal process async function commitRevealWeights() { const revealData = await commitWeights(); - + // Wait for the appropriate time (based on subnet parameters) // In production, you'd wait for the reveal period to begin - + await revealWeights(revealData); } ``` @@ -576,7 +607,7 @@ async function serveAxon() { const protocol = 0; const placeholder1 = 0; const placeholder2 = 0; - + const tx = await neuronContract.serveAxon( netuid, version, @@ -585,10 +616,10 @@ async function serveAxon() { ipType, protocol, placeholder1, - placeholder2 + placeholder2, ); await tx.wait(); - + console.log("Axon endpoint configured successfully!"); console.log("Transaction hash:", tx.hash); } catch (error) { @@ -614,15 +645,15 @@ async function serveAxonTls() { const protocol = 0; const placeholder1 = 0; const placeholder2 = 0; - + // Example TLS certificate (in practice, use your actual certificate) const certificate = new Uint8Array([ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, 62, 63, 64, 65, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, ]); - + const tx = await neuronContract.serveAxonTls( netuid, version, @@ -632,10 +663,10 @@ async function serveAxonTls() { protocol, placeholder1, placeholder2, - certificate + certificate, ); await tx.wait(); - + console.log("Axon TLS endpoint configured successfully!"); console.log("Transaction hash:", tx.hash); } catch (error) { @@ -658,16 +689,16 @@ async function servePrometheus() { const ip = 0x7f000001; // 127.0.0.1 in hex const port = 9090; // Prometheus default port const ipType = 4; // IPv4 - + const tx = await neuronContract.servePrometheus( netuid, version, ip, port, - ipType + ipType, ); await tx.wait(); - + console.log("Prometheus endpoint configured successfully!"); console.log("Transaction hash:", tx.hash); } catch (error) { @@ -678,32 +709,30 @@ async function servePrometheus() { ### Complete Neuron Setup Example - - ```javascript async function setupCompleteNeuron() { try { const netuid = 1; - const hotkey = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; - + const hotkey = + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; + // 1. Register the neuron console.log("Registering neuron..."); await registerNeuron(); - + // 2. Configure axon service console.log("Setting up axon service..."); await serveAxon(); - + // 3. Configure Prometheus metrics console.log("Setting up Prometheus metrics..."); await servePrometheus(); - + // 4. Set initial weights (if validator) console.log("Setting weights..."); await setNeuronWeights(); - + console.log("Neuron setup complete!"); - } catch (error) { console.error("Neuron setup failed:", error); } @@ -722,37 +751,42 @@ async function robustNeuronOperation() { const dests = [1, 2, 3]; const weights = [100, 200, 150]; const versionKey = 0; - + // Always check gas estimates before executing const gasEstimate = await neuronContract.setWeights.estimateGas( - netuid, dests, weights, versionKey + netuid, + dests, + weights, + versionKey, ); - + // Add buffer to gas limit - const gasLimit = gasEstimate * 120n / 100n; - + const gasLimit = (gasEstimate * 120n) / 100n; + const tx = await neuronContract.setWeights( - netuid, dests, weights, versionKey, - { gasLimit } + netuid, + dests, + weights, + versionKey, + { gasLimit }, ); - + // Wait for confirmation with timeout const receipt = await tx.wait(1); - + if (receipt.status === 1) { console.log("Operation successful!"); } else { console.error("Transaction failed"); } - } catch (error) { - if (error.code === 'INSUFFICIENT_FUNDS') { + if (error.code === "INSUFFICIENT_FUNDS") { console.error("Insufficient funds for transaction"); - } else if (error.code === 'NETWORK_ERROR') { + } else if (error.code === "NETWORK_ERROR") { console.error("Network connection issue"); } else { console.error("Unexpected error:", error.message); } } } -``` \ No newline at end of file +``` diff --git a/sidebars.js b/sidebars.js index 7446aa64..8739f97d 100644 --- a/sidebars.js +++ b/sidebars.js @@ -54,7 +54,7 @@ const sidebars = { "learn/emissions", "learn/ema", "learn/yuma-consensus", - "learn/yc3-blog", + "learn/yc3-blog", "concepts/weight-copying-in-bittensor", "learn/yuma3-migration-guide", "learn/fees", @@ -83,7 +83,7 @@ const sidebars = { collapsed: true, items: [ "keys/wallets", - { + { type: "category", label: "Bittensor Wallet", collapsible: true, @@ -112,7 +112,6 @@ const sidebars = { }, "keys/multisig", "keys/coldkey-swap", - ], }, @@ -223,7 +222,7 @@ const sidebars = { collapsible: true, collapsed: true, items: [ - "sdk/index", + "sdk/index", "getting-started/installation", "sdk/env-vars", "sdk/bt-api-ref", @@ -321,6 +320,7 @@ const sidebars = { "evm-tutorials/subnet-precompile", "evm-tutorials/metagraph-precompile", "evm-tutorials/neuron-precompile", + "evm-tutorials/alpha-precompile", ], }, // { From c4740bf1b9fb020f811ab254767f0dd3a2eb3ecf Mon Sep 17 00:00:00 2001 From: Dera Okeke Date: Thu, 11 Jun 2026 00:07:20 +0100 Subject: [PATCH 4/9] minor updates --- docs/evm-tutorials/alpha-precompile.md | 3 ++- docs/evm-tutorials/ed25519-verify-precompile.md | 3 +++ docs/evm-tutorials/metagraph-precompile.md | 5 ++++- docs/evm-tutorials/neuron-precompile.md | 5 ++--- docs/evm-tutorials/staking-precompile.md | 2 ++ docs/evm-tutorials/subnet-precompile.md | 10 +++------- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/evm-tutorials/alpha-precompile.md b/docs/evm-tutorials/alpha-precompile.md index ce900889..80373fc2 100644 --- a/docs/evm-tutorials/alpha-precompile.md +++ b/docs/evm-tutorials/alpha-precompile.md @@ -9,7 +9,8 @@ import TabItem from '@theme/TabItem'; The Alpha precompile exposes the state of every subnet's AMM pool to EVM smart contracts. It is a read-only interface for token prices, pool reserves, swap simulation, and emission rates. Any contract that needs to react to subnet token economics uses this precompile as its data source. -**Address**: `0x0000000000000000000000000000000000000808` +- **Address**: `0x0000000000000000000000000000000000000808` +- **Source code**: [AlphaPrecompile reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/alpha.rs) ## Concepts diff --git a/docs/evm-tutorials/ed25519-verify-precompile.md b/docs/evm-tutorials/ed25519-verify-precompile.md index b7799f80..782c92ab 100644 --- a/docs/evm-tutorials/ed25519-verify-precompile.md +++ b/docs/evm-tutorials/ed25519-verify-precompile.md @@ -9,6 +9,9 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; The Ed25519 Verify Precompile allows EVM smart contracts to verify Ed25519 signatures, which are commonly used in Substrate-based chains like Bittensor. This is essential for bridging identity and ownership between Substrate and EVM ecosystems. For example, you may want to verify coldkey ownership before transferring to someone. EVM functionality doesn't allow transferring directly to a `ss58` address—like the public key of a Bittensor coldkey—because EVM uses the H160 address schema. To bridge the gap, you can use this precompile to prove a claim of ownership. The owner of a coldkey can send an EVM transaction with a signed message, serving as proof of ownership of the coldkey's `ss58` address. +- **Address**: `0x0000000000000000000000000000000000000402` +- **Source code**: [Ed25519Verify reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/ed25519.rs) + ## Prerequisites - **Node.js** (v16 or later recommended) diff --git a/docs/evm-tutorials/metagraph-precompile.md b/docs/evm-tutorials/metagraph-precompile.md index a10e8651..629217f4 100644 --- a/docs/evm-tutorials/metagraph-precompile.md +++ b/docs/evm-tutorials/metagraph-precompile.md @@ -7,7 +7,10 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; # Metagraph Precompile -The metagraph precompile allows you to query information about neurons, their relationships, and network state in the Bittensor network. This precompile provides read-only access to the metagraph data through smart contracts at precompile address `0x802`. +The metagraph precompile allows you to query information about neurons, their relationships, and network state in the Bittensor network. This precompile provides read-only access to the metagraph data through smart contracts at precompile address. + +- **Address**: The subnet precompile is available at address `0x802`. +- **Source code**: [MetagraphPrecompile reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/metagraph.rs) ## Overview diff --git a/docs/evm-tutorials/neuron-precompile.md b/docs/evm-tutorials/neuron-precompile.md index 63764e6f..9cb28192 100644 --- a/docs/evm-tutorials/neuron-precompile.md +++ b/docs/evm-tutorials/neuron-precompile.md @@ -15,9 +15,8 @@ See [Understanding Neurons](../learn/neurons.md). Payable functions require tokens for execution ::: -## Precompile Address - -The neuron precompile is available at address `0x804` (2052 in decimal). +- **Address**: The neuron precompile is available at address `0x804` (2052 in decimal). +- **Source code**: [NeuronPrecompile reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/neuron.rs) ## Available Functions diff --git a/docs/evm-tutorials/staking-precompile.md b/docs/evm-tutorials/staking-precompile.md index ed181a19..24b52cd5 100644 --- a/docs/evm-tutorials/staking-precompile.md +++ b/docs/evm-tutorials/staking-precompile.md @@ -36,6 +36,8 @@ btcli subnet register --network ws://127.0.0.1:9944 There are two versions of staking precompile implemenation, V1 and V2. The contract address for V1 is `0x0000000000000000000000000000000000000801`. The address for V2 is `0x0000000000000000000000000000000000000805`. V1 is deprecated, but is kept for backwards-compatibility. The major difference between V1 and V2 is that the staking amount is fetched from the `msg.value` in V1. Then precompile transfers the token back to the caller. It is misleading and confuses solidity developers. In the V2 implementation, all amount parameters are defined as parameter of transaction. +- **Source code**: [StakingPrecompileV2 reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/staking.rs) + ## Call the staking precompile from another smart contract (staking pool use case) In this interaction you will compile [`stakeV2.sol`](https://github.com/opentensor/evm-bittensor/blob/main/solidity/stakeV2.sol), a Solidity smart contract code, and execute it on the Subtensor EVM. This `stakeV2.sol` will, in turn, call the staking precompile that is already deployed on the Subtensor EVM. diff --git a/docs/evm-tutorials/subnet-precompile.md b/docs/evm-tutorials/subnet-precompile.md index 9181033f..34a55e1a 100644 --- a/docs/evm-tutorials/subnet-precompile.md +++ b/docs/evm-tutorials/subnet-precompile.md @@ -9,14 +9,10 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; This precompile allows you to interact with Bittensor subnets through EVM smart contracts, affording functionality for registering networks, viewing and setting network parameters, and querying network state. -This page: +This page describes the precompile's [available functions](#available-functions) on the precompile and demonstrates the precompile's usage with [example scripts](#example-scripts). -- described the precompile's [available functions](#available-functions) on the precompile -- demonstrates the precompile's usage with [example scripts](#example-scripts). - -The subnet precompile is available at address `0x803` (2051 in decimal). - -View the [source on GitHub](https://github.com/opentensor/subtensor/blob/main/precompiles/src/subnet.rs) +- **Address**: The subnet precompile is available at address `0x803` (2051 in decimal). +- **Source code**: [SubnetPrecompile reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/subnet.rs) :::info permissions Subnet operations have distinct requirements! From 767693efce608ee595e6c3e7e2f6df9e387eddce Mon Sep 17 00:00:00 2001 From: Dera Okeke Date: Sun, 14 Jun 2026 03:22:20 +0100 Subject: [PATCH 5/9] proxy precompile --- docs/evm-tutorials/alpha-precompile.md | 43 +++++- docs/evm-tutorials/index.md | 30 +++- docs/evm-tutorials/proxy-precompile.md | 188 +++++++++++++++++++++++++ sidebars.js | 1 + 4 files changed, 256 insertions(+), 6 deletions(-) create mode 100644 docs/evm-tutorials/proxy-precompile.md diff --git a/docs/evm-tutorials/alpha-precompile.md b/docs/evm-tutorials/alpha-precompile.md index 80373fc2..2c4d7cfe 100644 --- a/docs/evm-tutorials/alpha-precompile.md +++ b/docs/evm-tutorials/alpha-precompile.md @@ -2,9 +2,6 @@ title: "Alpha Precompile" --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - # Alpha Precompile The Alpha precompile exposes the state of every subnet's AMM pool to EVM smart contracts. It is a read-only interface for token prices, pool reserves, swap simulation, and emission rates. Any contract that needs to react to subnet token economics uses this precompile as its data source. @@ -74,3 +71,43 @@ The ratio `AlphaInPool / (AlphaInPool + AlphaOutPool)` tells you what fraction o | `getRootNetuid()` | — | `uint16` | Root subnet ID (always 0) | | `getEMAPriceHalvingBlocks(uint16 netuid)` | netuid | `uint64` | Blocks for EMA half-life | | `getSubnetVolume(uint16 netuid)` | netuid | `uint256` | Recent trading volume | + +## Usage examples + +### ABI + +The canonical ABI is exported from [`contract-tests/src/contracts/alpha.ts`](https://github.com/opentensor/subtensor/blob/main/contract-tests/src/contracts/alpha.ts). You can import the ABI and contract address from a local copy of the source file as shown: + +```javascript +import { IAlphaABI, IALPHA_ADDRESS } from "./contracts/alpha"; +``` + +### Reading pool state + +```javascript +import { ethers } from "ethers"; +import { IAlphaABI, IALPHA_ADDRESS } from "./contracts/alpha"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, subSeed, rpcUrl, wsUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const alphaContract = new ethers.Contract(IALPHA_ADDRESS, IAlphaABI, provider); + +const netuid = 14; + +const spotPrice = await alphaContract.getAlphaPrice(netuid); +const movingPrice = await alphaContract.getMovingAlphaPrice(netuid); +const taoReserve = await alphaContract.getTaoInPool(netuid); +const alphaReserve = await alphaContract.getAlphaInPool(netuid); + +// Simulate a 1 TAO (1e9 RAO) stake +const ONE_TAO_RAO = 1_000_000_000n; +const expectedAlpha = await alphaContract.simSwapTaoForAlpha( + netuid, + ONE_TAO_RAO, +); + +console.log(`Spot price: ${spotPrice}`); +console.log(`Expected alpha for 1 TAO stake: ${expectedAlpha}`); +``` diff --git a/docs/evm-tutorials/index.md b/docs/evm-tutorials/index.md index 235c1662..0a064f72 100644 --- a/docs/evm-tutorials/index.md +++ b/docs/evm-tutorials/index.md @@ -55,10 +55,34 @@ Similarly, creating an Ethereum wallet gives you control of the h160 private key You can easily [convert an h160 address to an ss58 address](./convert-h160-to-ss58.md), or vice versa, but this does _not_ yield the corresponding private key. This means that if you create a wallet in Bittensor, you will not be able to sign Ethereum contracts with it, nor versa. ::: -Hence, in the context of Bittensor EVM we can distinguish between: +### The HashedAddressMapping -- 'Bittensor wallets': created using the Bittensor tool chain and therefore able to sign transactions using Bittensor transaction clients (BTCLI and the Bittensor SDK), but not EVM smart contracts, on the Bittensor blockchain. -- 'EVM wallets': created using an EVM client such as MetaMask and therefore able to sign EVM smart contracts, but not Subtensor extrinsics, on the Bittensor blockchain. +Every EVM call that touches Substrate state (staking, registering, setting weights) requires a Substrate Account—`AccountId32`. The runtime derives this from the H160 address using a one-way hash: + +``` +AccountId32 = Blake2b_256("evm:" ++ h160_bytes) +``` + +This is called **HashedAddressMapping**. It is deterministic — the same H160 always produces the same AccountId32 — but irreversible. You cannot reconstruct an H160 private key from the AccountId32. + +#### How it works + +When a smart contract calls a precompile function—for example, `addStake` on StakingV2, the contract's own H160 address is hashed to produce the coldkey for the stake position. The stake is held by the **contract** on-chain, not by the user who called the contract. + +Use the AddressMapping precompile (`0x80C`) to compute the Substrate coldkey for any EVM address. + +### Types of EVM wallets + +In the context of Bittensor EVM we can distinguish between the types of wallets available: + +| Type | Created with | Can sign | Used for | +| ---------------- | -------------------------------------- | ---------------------------------- | ----------------------------------------------------- | +| EVM wallet | MetaMask or any Ethereum key generator | EVM transactions, precompile calls | Interacting with smart contracts, calling precompiles | +| Bittensor wallet | `btcli wallet`, Bittensor SDK | Substrate extrinsics | Staking via btcli/SDK, key operations, governance | + +:::info Gas fees on Bittensor EVM +The Bittensor EVM uses the standard Ethereum gas model. Gas fees are paid in TAO (not a separate fee token). Always ensure that you have enough TAO in required the EVM wallet to cover the gas fees for each transaction. +::: ## Ethereum vs Bittensor EVM smart contract runtime diff --git a/docs/evm-tutorials/proxy-precompile.md b/docs/evm-tutorials/proxy-precompile.md new file mode 100644 index 00000000..c8f8ea27 --- /dev/null +++ b/docs/evm-tutorials/proxy-precompile.md @@ -0,0 +1,188 @@ +--- +title: "Proxy Precompile" +--- + +# Proxy Precompile + +The Proxy precompile exposes the Substrate proxy pallet to EVM contracts. Proxies let one account act on behalf of another for a limited set of operation types. + +- **Address**: `0x000000000000000000000000000000000000080b` +- **Source code**: [ProxyPrecompile reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/proxy.rs) + +See also: [Proxy Types and Permissions](../keys/proxies/index.md) for the full list of proxy types. + +## Functions + +| Function | Mutability | Description | +| ------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------- | +| `addProxy(bytes32 delegate, uint8 proxy_type, uint32 delay)` | nonpayable | Authorize `delegate` as a proxy of `proxy_type` with a `delay` in blocks. | +| `removeProxy(bytes32 delegate, uint8 proxy_type, uint32 delay)` | nonpayable | Remove a specific proxy relationship. | +| `removeProxies()` | nonpayable | Remove all proxies and recover the deposit. | +| `createPureProxy(uint8 proxy_type, uint32 delay, uint16 index)` | nonpayable | Create a keyless pure proxy account. Returns `bytes32 proxy`. | +| `killPureProxy(bytes32 spawner, uint8 proxy_type, uint16 index, uint32 height, uint32 ext_index)` | nonpayable | Destroy a pure proxy. `height` and `ext_index` identify the creation extrinsic. | +| `proxyCall(bytes32 real, uint8[] force_proxy_type, uint8[] call)` | nonpayable | Execute a SCALE-encoded call on behalf of `real`. | +| `pokeDeposit()` | nonpayable | Recalculate the proxy deposit for the caller. | +| `getProxies(bytes32 account)` | view | Returns all proxy relationships for `account`. | + +:::info Working with proxy accounts + +- **`delegate`** (`bytes32`) — the account to authorize as proxy. Pass the raw 32-byte public key: use `decodeAddress(ss58)` for Substrate wallets, or `h160ToPublicKey(evmAddress)` for EVM wallets. +- **`proxy_type`** (`uint256`) — numeric index controlling what operations the proxy can perform. `0` grants full access (`Any`). See the [proxy type index table](./proxy-precompile.md#proxy-type-values) for the full list. +- **`delay`** (`uint256`) — number of blocks the proxy must wait after announcing a call before executing it. Pass `0` for immediate execution. + ::: + +### Proxy type values + +Source: [`common/src/lib.rs`](https://github.com/opentensor/subtensor/blob/main/common/src/lib.rs) — `TryFrom for ProxyType`. + +| Value | Type | Notes | +| ----- | ------------------------ | ---------------------------- | +| 0 | `Any` | All operations | +| 1 | `Owner` | Subnet owner calls | +| 2 | `NonCritical` | | +| 3 | `NonTransfer` | All except balance transfers | +| 4 | `Senate` | Deprecated | +| 5 | `NonFungible` | Nothing involving moving TAO | +| 6 | `Triumvirate` | Deprecated | +| 7 | `Governance` | Deprecated | +| 8 | `Staking` | Staking operations | +| 9 | `Registration` | | +| 10 | `Transfer` | | +| 11 | `SmallTransfer` | Transfers up to limit | +| 12 | `RootWeights` | Deprecated | +| 13 | `ChildKeys` | | +| 14 | `SudoUncheckedSetCode` | | +| 15 | `SwapHotkey` | | +| 16 | `SubnetLeaseBeneficiary` | Operate a leased subnet | +| 17 | `RootClaim` | | + +Check [Proxy Types](../keys/proxies/index.md#proxytype) for the authoritative up-to-date list. + +## Usage examples + +### ABI + +The canonical ABI is exported from [`contract-tests/src/contracts/proxy.ts`](https://github.com/opentensor/subtensor/blob/main/contract-tests/src/contracts/proxy.ts). You can import the ABI and contract address from a local copy of the source file as shown: + +```javascript +import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; +``` + +### Add a proxy relationship on a Bittensor EVM address + +```js +import { ethers } from "ethers"; +import { decodeAddress } from "@polkadot/util-crypto"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); +import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, signer); + +const DELEGATE_SS58 = "5FJPvu5BGh2U3vrinRzDobr6Mtsrp..."; +const DELEGATE_PUBLIC_KEY = decodeAddress(DELEGATE_SS58); // decode SS58 → raw 32-byte public key +const PROXY_TYPE = 0; // Any — see proxy type index table in docs +const DELAY_BLOCKS = 0; + +const tx = await contract.addProxy( + DELEGATE_PUBLIC_KEY, + PROXY_TYPE, + DELAY_BLOCKS, + { gasLimit: 300_000n }, +); +const receipt = await tx.wait(); + +console.log(`Proxy added successfully`); +``` + +### Get all proxies for an EVM address + +```javascript +import { ethers } from "ethers"; +import { blake2AsU8a } from "@polkadot/util-crypto"; +import { hexToU8a } from "@polkadot/util"; +import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, subSeed, rpcUrl, wsUrl } = require("./config.js"); + +function h160ToPublicKey(evmAddress) { + const combined = new Uint8Array(24); + new TextEncoder().encodeInto("evm:", combined); + combined.set(hexToU8a(evmAddress), 4); + return blake2AsU8a(combined); +} + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, signer); + +const proxies = await contract.getProxies(h160ToPublicKey(signer.address)); +for (const p of proxies) { + console.log( + `Delegate: ${p.delegate}, type: ${p.proxy_type}, delay: ${p.delay}`, + ); +} +``` + +### Create a pure proxy with an EVM account + +```js +import { ethers } from "ethers"; +import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, signer); + +const PROXY_TYPE = 0; +const DELAY_BLOCKS = 0; +const DISAMBIGUATION_INDEX = 0; + +// Create pure proxy +const pureTx = await contract.createPureProxy( + PROXY_TYPE, + DELAY_BLOCKS, + DISAMBIGUATION_INDEX, + { + gasLimit: 300_000n, + }, +); +const pureReceipt = await pureTx.wait(); +console.log(`Pure proxy created in block ${pureReceipt.blockNumber}`); +``` + +### Remove a proxy relationship on an EVM account + +```js +import { ethers } from "ethers"; +import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; +import { decodeAddress } from "@polkadot/util-crypto"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, signer); + +const DELEGATE_SS58 = "5FJPvu5BGh2U3vrinRzDobr6Mtsrp..."; +const PROXY_TYPE = 0; +const DELAY_BLOCKS = 0; + +// Remove proxy +const tx = await contract.removeProxy( + decodeAddress(DELEGATE_SS58), // or h160ToPublicKey(evmAddress) for EVM wallets + PROXY_TYPE, + DELAY_BLOCKS, + { gasLimit: 300_000n }, +); +await tx.wait(); +console.log(`Proxy removed`); +``` diff --git a/sidebars.js b/sidebars.js index 8739f97d..35d34e59 100644 --- a/sidebars.js +++ b/sidebars.js @@ -321,6 +321,7 @@ const sidebars = { "evm-tutorials/metagraph-precompile", "evm-tutorials/neuron-precompile", "evm-tutorials/alpha-precompile", + "evm-tutorials/proxy-precompile", ], }, // { From 7b43b0ce0098b54163c3aed46538a42d8bec3ed9 Mon Sep 17 00:00:00 2001 From: Dera Okeke Date: Mon, 15 Jun 2026 22:12:30 +0100 Subject: [PATCH 6/9] updated proxy precompile --- docs/evm-tutorials/proxy-precompile.md | 75 ++++++++++++++++++++------ 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/docs/evm-tutorials/proxy-precompile.md b/docs/evm-tutorials/proxy-precompile.md index c8f8ea27..1e9f58a9 100644 --- a/docs/evm-tutorials/proxy-precompile.md +++ b/docs/evm-tutorials/proxy-precompile.md @@ -24,13 +24,6 @@ See also: [Proxy Types and Permissions](../keys/proxies/index.md) for the full l | `pokeDeposit()` | nonpayable | Recalculate the proxy deposit for the caller. | | `getProxies(bytes32 account)` | view | Returns all proxy relationships for `account`. | -:::info Working with proxy accounts - -- **`delegate`** (`bytes32`) — the account to authorize as proxy. Pass the raw 32-byte public key: use `decodeAddress(ss58)` for Substrate wallets, or `h160ToPublicKey(evmAddress)` for EVM wallets. -- **`proxy_type`** (`uint256`) — numeric index controlling what operations the proxy can perform. `0` grants full access (`Any`). See the [proxy type index table](./proxy-precompile.md#proxy-type-values) for the full list. -- **`delay`** (`uint256`) — number of blocks the proxy must wait after announcing a call before executing it. Pass `0` for immediate execution. - ::: - ### Proxy type values Source: [`common/src/lib.rs`](https://github.com/opentensor/subtensor/blob/main/common/src/lib.rs) — `TryFrom for ProxyType`. @@ -70,9 +63,10 @@ import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; ### Add a proxy relationship on a Bittensor EVM address +Use `addProxy` to authorize an account to act on behalf of the caller on-chain. The function takes three arguments: `delegate` (the account being authorized), `proxy_type` (the scope of permitted operations), and `delay` (the number of blocks the proxy must wait before executing a call after announcing). + ```js import { ethers } from "ethers"; -import { decodeAddress } from "@polkadot/util-crypto"; // PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE const { ethPrivateKey, rpcUrl } = require("./config.js"); @@ -82,9 +76,8 @@ const provider = new ethers.JsonRpcProvider(rpcUrl); const signer = new ethers.Wallet(ethPrivateKey, provider); const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, signer); -const DELEGATE_SS58 = "5FJPvu5BGh2U3vrinRzDobr6Mtsrp..."; -const DELEGATE_PUBLIC_KEY = decodeAddress(DELEGATE_SS58); // decode SS58 → raw 32-byte public key -const PROXY_TYPE = 0; // Any — see proxy type index table in docs +const DELEGATE_PUBLIC_KEY = "DELEGATE_COLDKEY_PUBLIC_KEY"; // raw 32-byte public key +const PROXY_TYPE = 0; // Any — see proxy type index table const DELAY_BLOCKS = 0; const tx = await contract.addProxy( @@ -98,8 +91,15 @@ const receipt = await tx.wait(); console.log(`Proxy added successfully`); ``` +:::info + +The `delegate` parameter expects a 32-byte public key. For Substrate wallets use `decodeAddress(ss58)` imported from `@polkadot/util-crypto`; for EVM wallets you must derive the corresponding public key for the H160 account before passing it. +::: + ### Get all proxies for an EVM address +Use `getProxies` to retrieve all active proxy relationships for a given account. It takes a single argument: the `bytes32` public key of the account whose proxies you want to look up. The function returns an array of `ProxyInfo` structs, each containing the delegate public key, proxy type, and delay. + ```javascript import { ethers } from "ethers"; import { blake2AsU8a } from "@polkadot/util-crypto"; @@ -128,8 +128,53 @@ for (const p of proxies) { } ``` +:::info +Passing `signer.address` to the `h160ToPublicKey` function applies HashedAddressMapping on the EVM's H160 account to derive the corresponding Substrate public key. +::: + +### Execute a proxy with an EVM account + +Use the `proxyCall` function to execute a call on behalf of another account through an existing proxy relationship. + +The `force_proxy_type` optionally restricts which proxy type is used for the call — pass `[0]` for `Any` or the relevant proxy type index to match the relationship. The `call` parameter is the SCALE-encoded runtime call to execute. Both of these parameters accept `uint8[]` arrays — convert the encoded call hex before passing it as shown: + +```js +import { createRequire } from "module"; +const require = createRequire(import.meta.url); +const { rpcUrl, delegateEthPrivateKey } = require("./config.cjs"); +import { ethers } from "ethers"; +import { blake2AsU8a } from "@polkadot/util-crypto"; +import { hexToU8a } from "@polkadot/util"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(delegateEthPrivateKey, provider); +const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, signer); + +const ENCODED_CALL = "0x050300fe65717dad0447d71...8a554d5860e0284d717"; + +const tx = await contract.proxyCall( + REAL_ADDRESS_PUBLIC_KEY, + [0], // force_proxy_type: Any + Array.from(ethers.getBytes(ENCODED_CALL)), + { gasLimit: 500_000n }, +); +const receipt = await tx.wait(); + +console.log(`Proxy call executed`); +``` + +:::info + +The `real` parameter expects a 32-byte public key. For Substrate wallets use `decodeAddress(ss58)` imported from `@polkadot/util-crypto`, for EVM wallets use `h160ToPublicKey(evmAddress)` to derive the corresponding public key before passing it. +::: + ### Create a pure proxy with an EVM account +Use `createPureProxy` to create a keyless proxy account controlled entirely by the caller. + ```js import { ethers } from "ethers"; import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; @@ -160,10 +205,11 @@ console.log(`Pure proxy created in block ${pureReceipt.blockNumber}`); ### Remove a proxy relationship on an EVM account +Use `removeProxy` to revoke a specific proxy relationship. The arguments must exactly match those used when the proxy was created — `delegate`, `proxy_type`, and `delay`. If any value differs, the call will fail. + ```js import { ethers } from "ethers"; import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; -import { decodeAddress } from "@polkadot/util-crypto"; // PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE const { ethPrivateKey, rpcUrl } = require("./config.js"); @@ -172,13 +218,12 @@ const provider = new ethers.JsonRpcProvider(rpcUrl); const signer = new ethers.Wallet(ethPrivateKey, provider); const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, signer); -const DELEGATE_SS58 = "5FJPvu5BGh2U3vrinRzDobr6Mtsrp..."; +const DELEGATE_PUBLIC_KEY = "DELEGATE_COLDKEY_PUBLIC_KEY"; // raw 32-byte public key const PROXY_TYPE = 0; const DELAY_BLOCKS = 0; -// Remove proxy const tx = await contract.removeProxy( - decodeAddress(DELEGATE_SS58), // or h160ToPublicKey(evmAddress) for EVM wallets + DELEGATE_PUBLIC_KEY, PROXY_TYPE, DELAY_BLOCKS, { gasLimit: 300_000n }, From c89222652da4c65142ade2545a5f4995f3adbdd4 Mon Sep 17 00:00:00 2001 From: Dera Okeke Date: Tue, 16 Jun 2026 23:04:03 +0100 Subject: [PATCH 7/9] update voting power precompile --- docs/evm-tutorials/alpha-precompile.md | 3 +- docs/evm-tutorials/proxy-precompile.md | 2 +- docs/evm-tutorials/voting-power-precompile.md | 66 +++++++++++++++++++ sidebars.js | 1 + 4 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 docs/evm-tutorials/voting-power-precompile.md diff --git a/docs/evm-tutorials/alpha-precompile.md b/docs/evm-tutorials/alpha-precompile.md index 2c4d7cfe..31ad9957 100644 --- a/docs/evm-tutorials/alpha-precompile.md +++ b/docs/evm-tutorials/alpha-precompile.md @@ -88,8 +88,7 @@ import { IAlphaABI, IALPHA_ADDRESS } from "./contracts/alpha"; import { ethers } from "ethers"; import { IAlphaABI, IALPHA_ADDRESS } from "./contracts/alpha"; -// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE -const { ethPrivateKey, subSeed, rpcUrl, wsUrl } = require("./config.js"); +const { rpcUrl } = require("./config.js"); const provider = new ethers.JsonRpcProvider(rpcUrl); const alphaContract = new ethers.Contract(IALPHA_ADDRESS, IAlphaABI, provider); diff --git a/docs/evm-tutorials/proxy-precompile.md b/docs/evm-tutorials/proxy-precompile.md index 1e9f58a9..c6b07319 100644 --- a/docs/evm-tutorials/proxy-precompile.md +++ b/docs/evm-tutorials/proxy-precompile.md @@ -107,7 +107,7 @@ import { hexToU8a } from "@polkadot/util"; import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; // PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE -const { ethPrivateKey, subSeed, rpcUrl, wsUrl } = require("./config.js"); +const { ethPrivateKey, rpcUrl } = require("./config.js"); function h160ToPublicKey(evmAddress) { const combined = new Uint8Array(24); diff --git a/docs/evm-tutorials/voting-power-precompile.md b/docs/evm-tutorials/voting-power-precompile.md new file mode 100644 index 00000000..8c40c25b --- /dev/null +++ b/docs/evm-tutorials/voting-power-precompile.md @@ -0,0 +1,66 @@ +--- +title: "Voting Power Precompile" +--- + +# Voting Power Precompile + +The VotingPower precompile gives smart contracts read-only access to the per-validator EMA voting power scores tracked on each subnet. Use it to build on-chain governance mechanisms — slashing conditions, quorum checks, reward weighting — that respond to relative validator influence. + +- **Address**: `0x000000000000000000000000000000000000080d` +- **Source code**: [VotingPowerPrecompile reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/voting_power.rs) + +## Functions + +| Function | Parameters | Returns | Description | +| ----------------------------------------------- | -------------- | --------- | ----------------------------------------------------------------------------------------- | +| `getVotingPower(uint16 netuid, bytes32 hotkey)` | netuid, hotkey | `uint256` | EMA voting power for a validator. Returns `0` if tracking is disabled or no entry exists. | +| `getTotalVotingPower(uint16 netuid)` | netuid | `uint256` | Sum of voting power across all validators on the subnet. | +| `isVotingPowerTrackingEnabled(uint16 netuid)` | netuid | `bool` | Whether tracking is currently active. | +| `getVotingPowerDisableAtBlock(uint16 netuid)` | netuid | `uint64` | Block at which tracking will auto-disable, or `0` if none scheduled. | +| `getVotingPowerEmaAlpha(uint16 netuid)` | netuid | `uint64` | EMA smoothing factor (18 decimal precision, `1.0 = 10^18`). | + +## Usage examples + +### ABI + +The canonical ABI is exported from [`contract-tests/src/contracts/votingPower.ts`](https://github.com/opentensor/subtensor/blob/main/contract-tests/src/contracts/votingPower.ts). You can import the ABI and contract address if you have a local copy of the source files as shown: + +```javascript +import { + IVotingPowerABI, + IVOTING_POWER_ADDRESS, +} from "./contracts/votingPower"; +``` + +### Quorum checks + +```javascript +import { ethers } from "ethers"; +import { + IVotingPowerABI, + IVOTING_POWER_ADDRESS, +} from "./contracts/votingPower"; + +const provider = new ethers.JsonRpcProvider("rpcUrl"); +const vp = new ethers.Contract( + IVOTING_POWER_ADDRESS, + IVotingPowerABI, + provider, +); + +const netuid = 1; +const hotkey = "0xabcd..."; // 32-byte hotkey + +const enabled = await vp.isVotingPowerTrackingEnabled(netuid); +const validatorVP = await vp.getVotingPower(netuid, hotkey); +const totalVP = await vp.getTotalVotingPower(netuid); + +console.log(`Tracking enabled: ${enabled}`); +console.log(`Validator has ${validatorVP} voting power`); +console.log(`Total voting power: ${totalVP}`); +``` + +:::info + +The `hotkey` parameter on the `getVotingPower` function expects a 32-byte public key. For Substrate wallets use `decodeAddress(ss58)` imported from `@polkadot/util-crypto`; for EVM wallets you must derive the corresponding public key for the H160 account before passing it. +::: diff --git a/sidebars.js b/sidebars.js index 35d34e59..2bd61ea2 100644 --- a/sidebars.js +++ b/sidebars.js @@ -322,6 +322,7 @@ const sidebars = { "evm-tutorials/neuron-precompile", "evm-tutorials/alpha-precompile", "evm-tutorials/proxy-precompile", + "evm-tutorials/voting-power-precompile", ], }, // { From ff4d740045d8951bbb61ea2453f750fd33c0e574 Mon Sep 17 00:00:00 2001 From: Dera Okeke Date: Wed, 17 Jun 2026 00:12:55 +0100 Subject: [PATCH 8/9] crowdloan precompile wip --- docs/evm-tutorials/crowdloan-precompile.md | 78 ++++++++++++++++++++++ sidebars.js | 1 + 2 files changed, 79 insertions(+) create mode 100644 docs/evm-tutorials/crowdloan-precompile.md diff --git a/docs/evm-tutorials/crowdloan-precompile.md b/docs/evm-tutorials/crowdloan-precompile.md new file mode 100644 index 00000000..506eccb5 --- /dev/null +++ b/docs/evm-tutorials/crowdloan-precompile.md @@ -0,0 +1,78 @@ +--- +title: "Crowdloan Precompile" +--- + +# Crowdloan Precompile + +The Crowdloan precompile lets EVM contracts create and manage crowdloans entirely on-chain. A campaign creator sets a funding cap and deadline, contributors deposit TAO, and when the cap is reached, the crowdloan can be finalized to execute either the stored call or a transfer to a target address. + +- **Address**: `0x0000000000000000000000000000000000000809` +- **Source code**: [CrowdloanPrecompile reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/crowdloan.rs) + +See [Crowdloans](../subnets/crowdloans/index.md) for the full concept and Substrate-side workflow. + +## Functions + +| Function | Mutability | Description | +| ----------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------- | +| `create(uint64 deposit, uint64 minContribution, uint64 cap, uint32 end, address targetAddress)` | payable | Create a new campaign. `end` is the block number at which contributions close. | +| `contribute(uint32 crowdloanId, uint64 amount)` | payable | Contribute to an active campaign. | +| `withdraw(uint32 crowdloanId)` | payable | Withdraw your contribution before finalization. | +| `finalize(uint32 crowdloanId)` | payable | Finalize a successful campaign and transfer funds to `targetAddress`. | +| `refund(uint32 crowdloanId)` | payable | Refund contributors of a failed campaign. Call repeatedly for large contributor sets. | +| `dissolve(uint32 crowdloanId)` | payable | Clean up storage after full refund. | +| `updateMinContribution(uint32 crowdloanId, uint64 newMinContribution)` | payable | Update minimum contribution (creator only). | +| `updateEnd(uint32 crowdloanId, uint32 newEnd)` | payable | Update end block (creator only). | +| `updateCap(uint32 crowdloanId, uint64 newCap)` | payable | Update funding cap (creator only). | +| `getCrowdloan(uint32 crowdloanId)` | view | Returns `CrowdloanInfo` struct. | +| `getContribution(uint32 crowdloanId, bytes32 coldkey)` | view | Returns contributor's total deposit in RAO. | + +## Usage examples + +### ABI + +The canonical ABI is exported from [`contract-tests/src/contracts/crowdloan.ts`](https://github.com/opentensor/subtensor/blob/main/contract-tests/src/contracts/crowdloan.ts). You can import the ABI and contract address if you have a local copy of the source files as shown: + +```javascript +import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; +``` + +### Creating a campaign + +```javascript +import { ethers } from "ethers"; +import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, signer); + +const depositRao = 1_000_000_000n; // 1 TAO + +const tx = await contract.create( + depositRao, // deposit + 100_000_000n, // minContribution: 0.1 TAO + 100_000_000_000n, // cap: 100 TAO + 57000, // end block + "0xYourTargetAddress", + { value: depositRao, gasLimit: 500_000n }, +); +await tx.wait(); +``` + +### Checking campaign progress + +```javascript +const info = await contract.getCrowdloan(0); +console.log(`Raised: ${info.raised} RAO of ${info.cap} RAO cap`); +console.log(`Finalized: ${info.finalized}`); +``` + +## Notes + +- All TAO amounts are in **RAO** (1 TAO = 1,000,000,000 RAO). +- `refund` processes a batch of contributors per call. For large campaigns, call it repeatedly before calling `dissolve`. +- The caller's EVM address maps to a Substrate coldkey via HashedAddressMapping. Use the same address for all operations on a given contribution. diff --git a/sidebars.js b/sidebars.js index 2bd61ea2..8fd8768f 100644 --- a/sidebars.js +++ b/sidebars.js @@ -322,6 +322,7 @@ const sidebars = { "evm-tutorials/neuron-precompile", "evm-tutorials/alpha-precompile", "evm-tutorials/proxy-precompile", + "evm-tutorials/crowdloan-precompile", "evm-tutorials/voting-power-precompile", ], }, From 02061c5b837be4197e3cdcb0dc37340b4d2706e4 Mon Sep 17 00:00:00 2001 From: Dera Okeke Date: Thu, 18 Jun 2026 00:49:35 +0100 Subject: [PATCH 9/9] complete crowdloan evm --- docs/evm-tutorials/crowdloan-precompile.md | 109 +++++++++++++++++++-- 1 file changed, 99 insertions(+), 10 deletions(-) diff --git a/docs/evm-tutorials/crowdloan-precompile.md b/docs/evm-tutorials/crowdloan-precompile.md index 506eccb5..8359602f 100644 --- a/docs/evm-tutorials/crowdloan-precompile.md +++ b/docs/evm-tutorials/crowdloan-precompile.md @@ -39,6 +39,10 @@ import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; ### Creating a campaign +Use `create` to launch a crowdloan campaign on-chain. The function takes the initial deposit amount, minimum contribution per contributor, funding cap, end block, and a target EVM address to receive the raised funds upon finalization. + +The target address is a H160 address type, not `bytes32`. You must pass it as a plain EVM address: + ```javascript import { ethers } from "ethers"; import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; @@ -50,29 +54,114 @@ const provider = new ethers.JsonRpcProvider(rpcUrl); const signer = new ethers.Wallet(ethPrivateKey, provider); const contract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, signer); -const depositRao = 1_000_000_000n; // 1 TAO +const DEPOSIT = 10_000_000_000n; // 10 TAO in RAO +const MIN_CONTRIBUTION = 1_000_000_000n; // 1 TAO in RAO +const CAP = 50_000_000_000n; // 50 TAO in RAO +const END_BLOCK = 8540500; const tx = await contract.create( - depositRao, // deposit - 100_000_000n, // minContribution: 0.1 TAO - 100_000_000_000n, // cap: 100 TAO - 57000, // end block + DEPOSIT, + MIN_CONTRIBUTION, + CAP, + END_BLOCK, "0xYourTargetAddress", - { value: depositRao, gasLimit: 500_000n }, + { gasLimit: 100_000n }, ); await tx.wait(); + +console.log(`Crowdloan created`); ``` ### Checking campaign progress +Use `getCrowdloan` to crowdloan by ID. + ```javascript +import { ethers } from "ethers"; +import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; +const { rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const contract = new ethers.Contract( + ICROWDLOAN_ADDRESS, + ICrowdloanABI, + provider, +); + const info = await contract.getCrowdloan(0); console.log(`Raised: ${info.raised} RAO of ${info.cap} RAO cap`); console.log(`Finalized: ${info.finalized}`); +console.log(`Cap: ${info.cap}`); +console.log(`End: ${info.end}`); ``` -## Notes +### Contribute to a crowdloan campaign + +Use `contribute` to add TAO to an active crowdloan campaign. The function takes the `crowdloanId` and the `amount` in RAO. The `amount` must be at least the campaign's `min_contribution`. + +```javascript +import { ethers } from "ethers"; +import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; -- All TAO amounts are in **RAO** (1 TAO = 1,000,000,000 RAO). -- `refund` processes a batch of contributors per call. For large campaigns, call it repeatedly before calling `dissolve`. -- The caller's EVM address maps to a Substrate coldkey via HashedAddressMapping. Use the same address for all operations on a given contribution. +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, signer); + +const CROWDLOAN_ID = 0; +const AMOUNT = 1_000_000_000n; // 1 TAO in RAO + +const tx = await contract.contribute(CROWDLOAN_ID, AMOUNT, { + gasLimit: 2_000_000n, +}); + +console.log(`Contributed ${AMOUNT} RAO to crowdloan ${CROWDLOAN_ID}`); +``` + +### Finalize a crowdloan campaign + +Use `finalize` to close a successfully funded campaign and transfer the raised TAO to the target address set at creation. The campaign must have reached its cap before the `finalize` function can be called. + +```javascript +import { ethers } from "ethers"; +import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, signer); + +const CROWDLOAN_ID = 0; + +const tx = await contract.finalize(CROWDLOAN_ID, { gasLimit: 2_000_000n }); +const receipt = await tx.wait(); +console.log(`Crowdloan ${CROWDLOAN_ID} finalized`); +``` + +### Dissolve a crowdloan campaign + +Use `dissolve` to permanently remove a crowdloan from storage. + +```javascript +import { ethers } from "ethers"; +import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, signer); + +const CROWDLOAN_ID = 0; + +const tx = await contract.dissolve(CROWDLOAN_ID, { gasLimit: 2_000_000n }); +const receipt = await tx.wait(); +console.log(`Crowdloan ${CROWDLOAN_ID} dissolved`); +console.log(` Block: ${receipt.blockNumber}`); +console.log(` Tx hash: ${receipt.hash}`); +```