Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
33 changes: 33 additions & 0 deletions docs/miner_tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
- [4.1. Restrict ingress to validator IPs](#41-restrict-ingress-to-validator-ips)
- [4.2. Update and run the blacklist function](#42-update-and-run-the-blacklist-function)
- [4.3. Blacklist parameters](#43-blacklist-parameters)
- [5. Set up your public miner profile](#5-set-up-your-public-miner-profile)

### 1. Requirements

Expand Down Expand Up @@ -451,6 +452,38 @@ The new blacklist function applies a minimum of **65000** stake by default. This
- `--blacklist.validator_min_stake 65000` — gates requests by validator stake. The blacklist enforces a minimum of `65000`, even if you configure a lower value.
<sup>[Back to top ^][table-of-contents]</sup>

## 5. Set up your public miner profile

You can attach public identity metadata to your coldkey — a display name, an avatar image and social handles — shown on your miner profile page on the dashboard. Ownership is proven with an sr25519 signature made with your coldkey: no account or API key is needed.

Run the one-shot submission script on whatever machine holds your coldkey (the key never leaves the machine; only the signature is sent). It prompts for your wallet password to sign, then submits:

```shell
python scripts/submit_miner_profile.py \
--wallet_name my_wallet \
--display-name "My Mining Co" \
--avatar-url https://example.com/logo.png \
--twitter my_handle \
--discord my_handle \
--linkedin https://www.linkedin.com/in/my-handle
```

Every submission replaces all five fields, so pass all the values you want shown each time — an omitted field is cleared. To remove your metadata entirely:

```shell
python scripts/submit_miner_profile.py --wallet_name my_wallet --delete
```

Field constraints:

- `--display-name` — at most 40 printable characters.
- `--twitter` / `--discord` — bare handles (no URL), at most 64 characters from letters, digits and `_ . - #`.
- `--avatar-url` / `--linkedin` — `https://` URLs of at most 300 characters; the linkedin URL must be on `linkedin.com`.

Your coldkey must be (or have been) registered on the subnet, and the signed message expires after 5 minutes — the script signs and submits in one go, so this only matters if your machine's clock is badly off.

<sup>[Back to top ^][table-of-contents]</sup>

<!-- links -->

[table-of-contents]: #table-of-contents
124 changes: 124 additions & 0 deletions scripts/submit_miner_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""Set up (or clear) your public miner profile metadata.

Signs a metadata payload (display name, avatar, social handles) with your
coldkey — the sr25519 signature is the proof of ownership, no account
needed — and submits it to the miner dashboard API. The metadata is
rendered on your public miner profile page.

Run this one-shot script on whatever machine holds your coldkey (it never
leaves the machine; only the signature is sent). Each submission replaces
all five fields: an omitted field is cleared. Passing --delete removes
your metadata entirely.

Field constraints (enforced server-side):
- display name: at most 40 printable characters
- twitter / discord: bare handles (no URL), at most 64 characters from
letters, digits, ``_ . - #``
- avatar / linkedin: https:// URLs of at most 300 characters; the
linkedin URL must be on linkedin.com
"""

import argparse
import hashlib
import json
import time

import bittensor
import requests

# Hardcode or set the environment variable WALLET_PASS to the password for
# the wallet, to skip the interactive decrypt prompt
# environ["WALLET_PASS"] = ""
Comment thread
Copilot marked this conversation as resolved.
Outdated


def canonical_payload_hash(payload: dict) -> str:
canonical = json.dumps(
{key: value for key, value in payload.items() if value},
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
)
return hashlib.sha256(canonical.encode()).hexdigest()


def main(args):
if args.delete:
payload = {
"display_name": "",
"avatar_url": "",
"twitter": "",
"discord": "",
"linkedin": "",
}
else:
payload = {
"display_name": args.display_name or "",
"avatar_url": args.avatar_url or "",
"twitter": args.twitter or "",
"discord": args.discord or "",
"linkedin": args.linkedin or "",
}

Comment thread
Copilot marked this conversation as resolved.
Outdated
if args.wallet_path:
wallet = bittensor.Wallet(name=args.wallet_name, path=args.wallet_path)
else:
wallet = bittensor.Wallet(name=args.wallet_name)
keypair = wallet.coldkey

timestamp = int(time.time())
message = (
f"synth-profile-metadata|v1|{keypair.ss58_address}"
f"|{timestamp}|{canonical_payload_hash(payload)}"
)
signature = keypair.sign(data=message)

response = requests.put(
f"{args.api_url}/v1/profiles/{keypair.ss58_address}/metadata",
json={
"payload": payload,
"timestamp": timestamp,
"signature": signature.hex(),
},
timeout=30,
)
print(f"{response.status_code} {response.text}")
Comment thread
Thykof marked this conversation as resolved.


if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Sign and submit your public miner profile metadata"
)
parser.add_argument(
"--wallet_name", type=str, required=True, help="Name of the wallet"
)
parser.add_argument(
"--wallet_path",
type=str,
help="Path to the wallets directory (default ~/.bittensor/wallets)",
)
parser.add_argument("--display-name", type=str, help="Public display name")
parser.add_argument(
"--avatar-url", type=str, help="https:// URL of your avatar image"
)
parser.add_argument("--twitter", type=str, help="Twitter/X handle")
parser.add_argument("--discord", type=str, help="Discord handle")
parser.add_argument(
"--linkedin", type=str, help="https://www.linkedin.com/... URL"
)
parser.add_argument(
"--delete",
action="store_true",
help="Remove your profile metadata entirely",
)
parser.add_argument(
"--api-url",
type=str,
default="https://monitoring.synthdata.co",
help="Miner dashboard API base URL",
)
args = parser.parse_args()

main(args)

# Example usage:
# python scripts/submit_miner_profile.py --wallet_name my_wallet --display-name "My Mining Co" --avatar-url https://example.com/logo.png --twitter my_handle
Loading