diff --git a/docs/miner_tutorial.md b/docs/miner_tutorial.md
index b65c95c0..327730df 100644
--- a/docs/miner_tutorial.md
+++ b/docs/miner_tutorial.md
@@ -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
@@ -451,6 +452,39 @@ 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.
[Back to top ^][table-of-contents]
+## 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 \
+ --website https://example.com
+```
+
+Every submission replaces all six 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` / `--website` / `--linkedin` — `https://` URLs of at most 300 characters; the linkedin URL must be on `linkedin.com` (the website can be any host).
+
+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.
+
+[Back to top ^][table-of-contents]
+
[table-of-contents]: #table-of-contents
diff --git a/scripts/submit_miner_profile.py b/scripts/submit_miner_profile.py
new file mode 100644
index 00000000..78b23d9c
--- /dev/null
+++ b/scripts/submit_miner_profile.py
@@ -0,0 +1,132 @@
+"""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 six 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 / website / linkedin: https:// URLs of at most 300 characters;
+ the linkedin URL must be on linkedin.com (website can be any host)
+"""
+
+import argparse
+import hashlib
+import json
+import time
+
+import bittensor
+import requests
+
+# To skip the interactive decrypt prompt, set WALLET_PASS in your environment, e.g.:
+# export WALLET_PASS='...'
+# Avoid hardcoding wallet passwords in this script.
+
+
+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": "",
+ "website": "",
+ }
+ 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 "",
+ "website": args.website or "",
+ }
+ if not args.delete and not any(payload.values()):
+ raise SystemExit(
+ "Refusing to submit empty metadata. Provide at least one field or use --delete."
+ )
+ 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}")
+
+
+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(
+ "--website", type=str, help="https:// URL of your website"
+ )
+ 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