Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions docs/evm-tutorials/alpha-precompile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
title: "Alpha Precompile"
---

# 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`
- **Source code**: [AlphaPrecompile reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/alpha.rs)

## 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 |

## 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";

const { rpcUrl } = 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}`);
```
167 changes: 167 additions & 0 deletions docs/evm-tutorials/crowdloan-precompile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
---
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

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";

// 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 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(
DEPOSIT,
MIN_CONTRIBUTION,
CAP,
END_BLOCK,
"0xYourTargetAddress",
{ 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}`);
```

### 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";

// 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}`);
```
3 changes: 3 additions & 0 deletions docs/evm-tutorials/ed25519-verify-precompile.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 11 additions & 10 deletions docs/evm-tutorials/evm-localnet-with-metamask-wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/evm-tutorials/evm-testnet-with-metamask-wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading