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
102 changes: 102 additions & 0 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# SaintDurbin Deployment Guide

This guide explains how to deploy the SaintDurbin contract with the correct SS58 public key configuration.

## Pre-Deployment Steps

### 1. Generate the Contract's SS58 Public Key

The contract requires its own SS58 public key (`thisSs58PublicKey`) to be passed as a constructor argument. This key MUST be pre-calculated based on the contract's future deployment address.

#### Using Standard CREATE Deployment:

1. First, determine your deployer address and current nonce:
```bash
# Get deployer address and nonce from your wallet or chain
DEPLOYER_ADDRESS="0x..." # Your deployer address
NONCE=5 # Your current nonce
```

2. Generate the SS58 key:
```bash
cd scripts
npm install # Install dependencies if not already done
node generate-ss58-key.js $DEPLOYER_ADDRESS $NONCE
```

This will output:
```
Deployer Address: 0x...
Nonce: 5
Predicted Contract Address: 0x...
SS58 Public Key (bytes32): 0x...

For deployment script:
export CONTRACT_SS58_KEY="0x..."
```

3. Copy the `CONTRACT_SS58_KEY` value for use in deployment.

#### Using CREATE2 Deployment:

If using CREATE2, you'll need to calculate the address differently and then convert it:
```bash
# If you know the CREATE2 address already:
node generate-ss58-key.js 0x<create2-address>
```

### 2. Set Environment Variables

Create a `.env` file or export the following environment variables:

```bash
# Contract configuration
export CONTRACT_SS58_KEY="0x..." # From step 1
export EMERGENCY_OPERATOR="0x..." # EVM address of emergency operator
export DRAIN_SS58_ADDRESS="0x..." # SS58 public key for emergency drain
export VALIDATOR_HOTKEY="0x..." # Initial validator's SS58 hotkey
export VALIDATOR_UID=123 # Initial validator's UID
export NETUID=1 # Subnet ID

# Recipients (SS58 public keys)
export RECIPIENT_SAM="0x..."
export RECIPIENT_WSL="0x..."
export RECIPIENT_PAPER="0x..."
export RECIPIENT_FLORIAN="0x..."
export RECIPIENT_4="0x..."
# ... continue for all 16 recipients
export RECIPIENT_15="0x..."
```

### 3. Deploy the Contract

```bash
# Deploy using Foundry
forge script script/DeploySaintDurbin.s.sol:DeploySaintDurbin \
--rpc-url $RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
--verify
```

## Important Notes

1. **SS58 Key Generation**: The `CONTRACT_SS58_KEY` MUST be generated from the contract's deployment address using the Blake2b-256 hash of `"evm:" + contract_address`. This is how the Bittensor precompiles identify the contract.

2. **Address Types**:
- `EMERGENCY_OPERATOR`: Standard EVM address (20 bytes)
- All other addresses: SS58 public keys (32 bytes)

3. **Immutability**: Once deployed, the contract configuration cannot be changed. Double-check all values before deployment.

## Verification

After deployment, verify:
1. The contract's `thisSs58PublicKey` matches your pre-calculated value
2. The contract can successfully call `getStakedBalance()`
3. All recipients are correctly configured

## Troubleshooting

- **"Precompile call failed: getStake"**: Likely means the SS58 key is incorrect. Verify you calculated it from the correct contract address.
- **Invalid recipient addresses**: Ensure all recipient coldkeys are 32-byte SS58 public keys, not EVM addresses.
14 changes: 13 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"@polkadot-api/descriptors": "^0.0.1"
}
}
59 changes: 59 additions & 0 deletions scripts/address-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Utility functions for converting between EVM and Substrate addresses
// These utilities implement the Frontier HashedAddressMapping logic

/**
* Calculates the Substrate-compatible public key (bytes32) from an EVM H160 address.
* This is used to determine the contract's own SS58 public key for precompile interactions.
* @param {string} ethAddress - The H160 EVM address (e.g., "0x123...").
* @returns {Promise<Uint8Array>} The 32-byte public key.
*/
export async function convertH160ToPublicKey(ethAddress) {
// Dynamic import for ESM modules
const { blake2AsU8a } = await import('@polkadot/util-crypto');
const { hexToU8a } = await import('@polkadot/util');

const prefix = "evm:";
const prefixBytes = new TextEncoder().encode(prefix);
const addressBytes = hexToU8a(
ethAddress.startsWith("0x") ? ethAddress : `0x${ethAddress}`
);
const combined = new Uint8Array(prefixBytes.length + addressBytes.length);

combined.set(prefixBytes);
combined.set(addressBytes, prefixBytes.length);

return blake2AsU8a(combined); // This is a 32-byte hash
}

/**
* Helper to get the SS58 public key as a hex string for Forge script environment variables
* @param {string} ethAddress - The H160 EVM address
* @returns {string} The SS58 public key as a hex string
*/
export async function convertH160ToPublicKeyHex(ethAddress) {
const pubKeyBytes = await convertH160ToPublicKey(ethAddress);
return '0x' + Buffer.from(pubKeyBytes).toString('hex');
}

/**
* Calculate the expected contract address for a standard CREATE deployment
* @param {string} deployerAddress - The deployer's address
* @param {number} nonce - The deployer's nonce
* @returns {Promise<string>} The predicted contract address
*/
export async function calculateContractAddress(deployerAddress, nonce) {
const { getCreateAddress } = await import('ethers');
return getCreateAddress({ from: deployerAddress, nonce });
}

/**
* Calculate the expected contract address for a CREATE2 deployment
* @param {string} factoryAddress - The factory contract address
* @param {string} salt - The salt for CREATE2
* @param {string} initCodeHash - The hash of the contract init code
* @returns {Promise<string>} The predicted contract address
*/
export async function calculateCreate2Address(factoryAddress, salt, initCodeHash) {
const { getCreate2Address } = await import('ethers');
return getCreate2Address(factoryAddress, salt, initCodeHash);
}
76 changes: 76 additions & 0 deletions scripts/generate-ss58-key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env node

// Standalone script to generate SS58 public key from an EVM address
// Usage: node generate-ss58-key.js <eth-address> [nonce]

import { blake2AsU8a } from '@polkadot/util-crypto';
import { hexToU8a } from '@polkadot/util';
import { getCreateAddress } from 'ethers';

/**
* Calculates the Substrate-compatible public key (bytes32) from an EVM H160 address.
* @param {string} ethAddress - The H160 EVM address (e.g., "0x123...").
* @returns {Uint8Array} The 32-byte public key.
*/
function convertH160ToPublicKey(ethAddress) {
const prefix = "evm:";
const prefixBytes = new TextEncoder().encode(prefix);
const addressBytes = hexToU8a(
ethAddress.startsWith("0x") ? ethAddress : `0x${ethAddress}`
);
const combined = new Uint8Array(prefixBytes.length + addressBytes.length);

combined.set(prefixBytes);
combined.set(addressBytes, prefixBytes.length);

return blake2AsU8a(combined); // This is a 32-byte hash
}

/**
* Helper to get the SS58 public key as a hex string
* @param {string} ethAddress - The H160 EVM address
* @returns {string} The SS58 public key as a hex string
*/
function convertH160ToPublicKeyHex(ethAddress) {
const pubKeyBytes = convertH160ToPublicKey(ethAddress);
return '0x' + Buffer.from(pubKeyBytes).toString('hex');
}

// Main execution
async function main() {
const args = process.argv.slice(2);

if (args.length === 0) {
console.error('Usage: node generate-ss58-key.js <eth-address-or-deployer> [nonce]');
console.error('Examples:');
console.error(' node generate-ss58-key.js 0x123...abc # Convert existing address');
console.error(' node generate-ss58-key.js 0x456...def 5 # Calculate from deployer + nonce');
process.exit(1);
}

let ethAddress = args[0];
const nonce = args[1] ? parseInt(args[1]) : null;

// If nonce is provided, calculate the contract address
if (nonce !== null) {
const deployerAddress = ethAddress;
ethAddress = getCreateAddress({ from: deployerAddress, nonce });
console.log(`Deployer Address: ${deployerAddress}`);
console.log(`Nonce: ${nonce}`);
console.log(`Predicted Contract Address: ${ethAddress}`);
} else {
console.log(`Contract Address: ${ethAddress}`);
}

const ss58PublicKeyHex = convertH160ToPublicKeyHex(ethAddress);
console.log(`SS58 Public Key (bytes32): ${ss58PublicKeyHex}`);

// Also output as environment variable format
console.log('\nFor deployment script:');
console.log(`export CONTRACT_SS58_KEY="${ss58PublicKeyHex}"`);
}

main().catch(error => {
console.error('Error:', error);
process.exit(1);
});
5 changes: 4 additions & 1 deletion scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "saintdurbin-distribution",
"version": "1.0.0",
"description": "SaintDurbin yield distribution cron job with automatic validator switching",
"type": "module",
"main": "distribute.js",
"scripts": {
"distribute": "node distribute.js",
Expand All @@ -12,7 +13,9 @@
},
"dependencies": {
"ethers": "^6.9.0",
"dotenv": "^16.3.1"
"dotenv": "^16.3.1",
"@polkadot/util": "^12.6.2",
"@polkadot/util-crypto": "^12.6.2"
},
"devDependencies": {
"mocha": "^10.2.0",
Expand Down
Loading
Loading