hippius 0.2.48__tar.gz → 0.2.49__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {hippius-0.2.48 → hippius-0.2.49}/PKG-INFO +1 -1
  2. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/__init__.py +1 -1
  3. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/cli.py +47 -0
  4. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/cli_handlers.py +391 -0
  5. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/cli_parser.py +125 -0
  6. hippius-0.2.49/hippius_sdk/incentives.py +487 -0
  7. {hippius-0.2.48 → hippius-0.2.49}/pyproject.toml +1 -1
  8. {hippius-0.2.48 → hippius-0.2.49}/README.md +0 -0
  9. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/cli_assets.py +0 -0
  10. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/cli_rich.py +0 -0
  11. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/client.py +0 -0
  12. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/config.py +0 -0
  13. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/db/README.md +0 -0
  14. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/db/env.db.template +0 -0
  15. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/db/migrations/20241201000001_create_key_storage_tables.sql +0 -0
  16. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/db/migrations/20241202000001_switch_to_subaccount_encryption.sql +0 -0
  17. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/db/setup_database.sh +0 -0
  18. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/db_utils.py +0 -0
  19. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/errors.py +0 -0
  20. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/ipfs.py +0 -0
  21. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/ipfs_core.py +0 -0
  22. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/key_storage.py +0 -0
  23. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/substrate.py +0 -0
  24. {hippius-0.2.48 → hippius-0.2.49}/hippius_sdk/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hippius
3
- Version: 0.2.48
3
+ Version: 0.2.49
4
4
  Summary: Python SDK and CLI for Hippius blockchain storage
5
5
  Home-page: https://github.com/thenervelab/hippius-sdk
6
6
  Author: Dubs
@@ -26,7 +26,7 @@ from hippius_sdk.config import (
26
26
  from hippius_sdk.ipfs import IPFSClient, S3PublishResult, S3DownloadResult
27
27
  from hippius_sdk.utils import format_cid, format_size, hex_to_ipfs_cid
28
28
 
29
- __version__ = "0.2.48"
29
+ __version__ = "0.2.49"
30
30
  __all__ = [
31
31
  "HippiusClient",
32
32
  "IPFSClient",
@@ -409,6 +409,53 @@ def main():
409
409
  print_help_text(address_parser)
410
410
  return 1
411
411
 
412
+ # Handle miner commands
413
+ elif args.command == "miner":
414
+ if args.miner_action == "register-coldkey":
415
+ return run_async_handler(
416
+ cli_handlers.handle_register_coldkey,
417
+ client,
418
+ args.node_id,
419
+ args.node_priv_hex,
420
+ args.node_type,
421
+ ipfs_config=getattr(args, 'ipfs_config', None),
422
+ ipfs_priv_b64=getattr(args, 'ipfs_priv_b64', None),
423
+ ipfs_peer_id=getattr(args, 'ipfs_peer_id', None),
424
+ pay_in_credits=getattr(args, 'pay_in_credits', False),
425
+ expires_in=getattr(args, 'expires_in', 10),
426
+ block_width=getattr(args, 'block_width', "u32"),
427
+ domain=getattr(args, 'domain', "HIPPIUS::REGISTER::v1"),
428
+ nonce_hex=getattr(args, 'nonce_hex', None),
429
+ dry_run=getattr(args, 'dry_run', False),
430
+ )
431
+ elif args.miner_action == "register-hotkey":
432
+ return run_async_handler(
433
+ cli_handlers.handle_register_hotkey,
434
+ client,
435
+ args.coldkey,
436
+ args.node_id,
437
+ args.node_priv_hex,
438
+ args.node_type,
439
+ ipfs_config=getattr(args, 'ipfs_config', None),
440
+ ipfs_priv_b64=getattr(args, 'ipfs_priv_b64', None),
441
+ ipfs_peer_id=getattr(args, 'ipfs_peer_id', None),
442
+ pay_in_credits=getattr(args, 'pay_in_credits', False),
443
+ expires_in=getattr(args, 'expires_in', 10),
444
+ block_width=getattr(args, 'block_width', "u32"),
445
+ domain=getattr(args, 'domain', "HIPPIUS::REGISTER::v1"),
446
+ nonce_hex=getattr(args, 'nonce_hex', None),
447
+ dry_run=getattr(args, 'dry_run', False),
448
+ )
449
+ else:
450
+ # Display the Hippius logo banner with Rich formatting
451
+ console.print(HERO_TITLE, style="bold cyan")
452
+
453
+ miner_parser = get_subparser("miner")
454
+ from hippius_sdk.cli_rich import print_help_text
455
+
456
+ print_help_text(miner_parser)
457
+ return 1
458
+
412
459
  else:
413
460
  # Command not recognized
414
461
  error(f"Unknown command: [bold]{args.command}[/bold]")
@@ -3291,3 +3291,394 @@ def handle_default_address_clear() -> int:
3291
3291
  except Exception as e:
3292
3292
  error(f"Error clearing default address: {e}")
3293
3293
  return 1
3294
+
3295
+
3296
+ def handle_register_coldkey(
3297
+ client: HippiusClient,
3298
+ node_id: str,
3299
+ node_priv_hex: str,
3300
+ node_type: str,
3301
+ ipfs_config: str = None,
3302
+ ipfs_priv_b64: str = None,
3303
+ ipfs_peer_id: str = None,
3304
+ pay_in_credits: bool = False,
3305
+ expires_in: int = 10,
3306
+ block_width: str = "u32",
3307
+ domain: str = "HIPPIUS::REGISTER::v1",
3308
+ nonce_hex: str = None,
3309
+ dry_run: bool = False,
3310
+ ) -> int:
3311
+ """Handle miner register-coldkey command"""
3312
+ try:
3313
+ # Get current account info
3314
+ account = get_active_account()
3315
+ if not account:
3316
+ error("No active account. Please set up an account first with 'hippius account create' or 'hippius seed set'")
3317
+ return 1
3318
+
3319
+ account_address = get_account_address(account)
3320
+ if not account_address:
3321
+ error(f"Could not get address for account '{account}'")
3322
+ return 1
3323
+
3324
+ info(f"Registering node with coldkey using account: [bold cyan]{account}[/bold cyan] ({account_address})")
3325
+
3326
+ # Import and use incentives.py functionality
3327
+ from hippius_sdk.incentives import (
3328
+ load_ipfs_seed, load_main_seed, encode_account_id, verify_peer_id,
3329
+ blake2_256, manual_encode_challenge, get_peer_id_from_public_key,
3330
+ get_public_key_from_peer_id
3331
+ )
3332
+ from substrateinterface import SubstrateInterface, Keypair
3333
+ from nacl.signing import SigningKey
3334
+ import base58
3335
+ import secrets
3336
+ from binascii import hexlify
3337
+
3338
+ # Initialize SubstrateInterface
3339
+ substrate = SubstrateInterface(url=client.substrate_client.url)
3340
+
3341
+ # Get genesis hash and current block
3342
+ genesis_hash_hex = substrate.get_block_hash(0)
3343
+ genesis_hash = bytes.fromhex(genesis_hash_hex[2:])
3344
+ current_block_number = substrate.get_block_number(None)
3345
+
3346
+ log(f"Current block number: {current_block_number}")
3347
+
3348
+ # Process node_id
3349
+ if node_id.startswith("0x"):
3350
+ node_id_bytes = bytes.fromhex(node_id[2:])
3351
+ else:
3352
+ node_id_bytes = base58.b58decode(node_id)
3353
+
3354
+ # Load IPFS and main seeds
3355
+ ipfs_seed, peerid_from_config = load_ipfs_seed(ipfs_config, ipfs_priv_b64)
3356
+ main_seed = load_main_seed(node_priv_hex)
3357
+
3358
+ # Create signing keys
3359
+ main_sk = SigningKey(main_seed)
3360
+ main_pk = bytes(main_sk.verify_key)
3361
+ ipfs_sk = SigningKey(ipfs_seed)
3362
+ ipfs_pk = bytes(ipfs_sk.verify_key)
3363
+
3364
+ # Handle IPFS peer ID
3365
+ if ipfs_peer_id:
3366
+ ipfs_peer_id_bytes = base58.b58decode(ipfs_peer_id)
3367
+ elif peerid_from_config:
3368
+ ipfs_peer_id_bytes = base58.b58decode(peerid_from_config)
3369
+ else:
3370
+ if node_type in ["StorageMiner", "Validator"]:
3371
+ error("IPFS PeerID is required for StorageMiner and Validator node types")
3372
+ return 1
3373
+ ipfs_peer_id_bytes = b''
3374
+
3375
+ # Verify keys match node IDs
3376
+ if not verify_peer_id(main_pk, node_id_bytes, "Ed25519"):
3377
+ error("Main public key does not match node ID")
3378
+ return 1
3379
+
3380
+ if ipfs_peer_id_bytes and not verify_peer_id(ipfs_pk, ipfs_peer_id_bytes, "Ed25519"):
3381
+ error("IPFS public key does not match peer ID")
3382
+ return 1
3383
+
3384
+ # Create challenge data
3385
+ domain_bytes = domain.encode()
3386
+ domain24 = b"HIPPIUS::REGISTER::v1" + b"\x00"*3
3387
+ node_id_hash = blake2_256(node_id_bytes)
3388
+ ipfs_peer_id_hash = blake2_256(ipfs_peer_id_bytes)
3389
+
3390
+ nonce = (bytes.fromhex(nonce_hex[2:]) if nonce_hex and nonce_hex.startswith("0x")
3391
+ else bytes.fromhex(nonce_hex) if nonce_hex
3392
+ else secrets.token_bytes(32))
3393
+
3394
+ expires_at_block = current_block_number + expires_in
3395
+ account_bytes = encode_account_id(account_address)
3396
+
3397
+ challenge_data = {
3398
+ 'domain': domain24,
3399
+ 'genesis_hash': genesis_hash,
3400
+ 'account': account_bytes,
3401
+ 'node_id_hash': node_id_hash,
3402
+ 'ipfs_peer_id_hash': ipfs_peer_id_hash,
3403
+ 'block_number': current_block_number,
3404
+ 'nonce': nonce,
3405
+ 'expires_at': expires_at_block,
3406
+ }
3407
+
3408
+ challenge_bytes = manual_encode_challenge(challenge_data, block_width)
3409
+
3410
+ # Sign challenge
3411
+ main_sig = main_sk.sign(challenge_bytes).signature
3412
+ ipfs_sig = ipfs_sk.sign(challenge_bytes).signature
3413
+
3414
+ # Build call parameters
3415
+ call_params = {
3416
+ "node_type": node_type,
3417
+ "node_id": node_id,
3418
+ "node_id_hex": "0x"+hexlify(node_id_bytes).decode(),
3419
+ "pay_in_credits": pay_in_credits,
3420
+ "ipfs_node_id": ipfs_peer_id,
3421
+ "owner": account_address,
3422
+ "ipfs_peer_id": ipfs_peer_id,
3423
+ "ipfs_id_hex": "0x"+hexlify(ipfs_peer_id_bytes).decode(),
3424
+ "main_key_type": "Ed25519",
3425
+ "main_public_key": "0x"+main_pk.hex(),
3426
+ "main_sig": "0x"+main_sig.hex(),
3427
+ "ipfs_key_type": "Ed25519",
3428
+ "ipfs_public_key": "0x"+ipfs_pk.hex(),
3429
+ "ipfs_sig": "0x"+ipfs_sig.hex(),
3430
+ "challenge_bytes": "0x"+challenge_bytes.hex(),
3431
+ }
3432
+
3433
+ if dry_run:
3434
+ log("Dry run mode - printing payload without submitting")
3435
+ payload = {
3436
+ "genesis_hash_hex": "0x"+genesis_hash.hex(),
3437
+ "current_block_number": current_block_number,
3438
+ "challenge_bytes_hex": "0x"+challenge_bytes.hex(),
3439
+ "call_module": "Registration",
3440
+ "call_function": "register_node_with_coldkey",
3441
+ "call_params": call_params
3442
+ }
3443
+ console.print(json.dumps(payload, indent=2))
3444
+ return 0
3445
+
3446
+ # Get keypair for signing
3447
+ seed_phrase = client.substrate_client._get_seed_phrase()
3448
+ if not seed_phrase:
3449
+ error("No seed phrase available for signing transaction")
3450
+ return 1
3451
+
3452
+ kp = Keypair.create_from_uri(seed_phrase)
3453
+
3454
+ # Submit transaction
3455
+ log("Submitting registration transaction...")
3456
+ call = substrate.compose_call(
3457
+ call_module="Registration",
3458
+ call_function="register_node_with_coldkey",
3459
+ call_params=call_params
3460
+ )
3461
+ extrinsic = substrate.create_signed_extrinsic(call=call, keypair=kp)
3462
+ receipt = substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True)
3463
+
3464
+ result = {
3465
+ "extrinsic_hash": receipt.extrinsic_hash,
3466
+ "is_success": receipt.is_success,
3467
+ "error_message": receipt.error_message,
3468
+ "triggered_events": [str(event) for event in receipt.triggered_events]
3469
+ }
3470
+
3471
+ if receipt.is_success:
3472
+ success(f"Node registered successfully with coldkey!")
3473
+ success(f"Transaction hash: {receipt.extrinsic_hash}")
3474
+ else:
3475
+ error(f"Registration failed: {receipt.error_message}")
3476
+
3477
+ log("Full result:")
3478
+ console.print(json.dumps(result, indent=2))
3479
+
3480
+ return 0 if receipt.is_success else 1
3481
+
3482
+ except Exception as e:
3483
+ error(f"Error registering node with coldkey: {e}")
3484
+ if hasattr(e, '__traceback__'):
3485
+ import traceback
3486
+ traceback.print_exc()
3487
+ return 1
3488
+
3489
+
3490
+ def handle_register_hotkey(
3491
+ client: HippiusClient,
3492
+ coldkey: str,
3493
+ node_id: str,
3494
+ node_priv_hex: str,
3495
+ node_type: str,
3496
+ ipfs_config: str = None,
3497
+ ipfs_priv_b64: str = None,
3498
+ ipfs_peer_id: str = None,
3499
+ pay_in_credits: bool = False,
3500
+ expires_in: int = 10,
3501
+ block_width: str = "u32",
3502
+ domain: str = "HIPPIUS::REGISTER::v1",
3503
+ nonce_hex: str = None,
3504
+ dry_run: bool = False,
3505
+ ) -> int:
3506
+ """Handle miner register-hotkey command"""
3507
+ try:
3508
+ # Get current account info
3509
+ account = get_active_account()
3510
+ if not account:
3511
+ error("No active account. Please set up an account first with 'hippius account create' or 'hippius seed set'")
3512
+ return 1
3513
+
3514
+ account_address = get_account_address(account)
3515
+ if not account_address:
3516
+ error(f"Could not get address for account '{account}'")
3517
+ return 1
3518
+
3519
+ info(f"Registering node with hotkey using account: [bold cyan]{account}[/bold cyan] ({account_address})")
3520
+ info(f"Coldkey: [bold yellow]{coldkey}[/bold yellow]")
3521
+
3522
+ # Import and use incentives.py functionality
3523
+ from hippius_sdk.incentives import (
3524
+ load_ipfs_seed, load_main_seed, encode_account_id, verify_peer_id,
3525
+ blake2_256, manual_encode_challenge, get_peer_id_from_public_key,
3526
+ get_public_key_from_peer_id
3527
+ )
3528
+ from substrateinterface import SubstrateInterface, Keypair
3529
+ from nacl.signing import SigningKey
3530
+ import base58
3531
+ import secrets
3532
+ from binascii import hexlify
3533
+
3534
+ # Initialize SubstrateInterface
3535
+ substrate = SubstrateInterface(url=client.substrate_client.url)
3536
+
3537
+ # Get genesis hash and current block
3538
+ genesis_hash_hex = substrate.get_block_hash(0)
3539
+ genesis_hash = bytes.fromhex(genesis_hash_hex[2:])
3540
+ current_block_number = substrate.get_block_number(None)
3541
+
3542
+ log(f"Current block number: {current_block_number}")
3543
+
3544
+ # Process node_id
3545
+ if node_id.startswith("0x"):
3546
+ node_id_bytes = bytes.fromhex(node_id[2:])
3547
+ else:
3548
+ node_id_bytes = base58.b58decode(node_id)
3549
+
3550
+ # Load IPFS and main seeds
3551
+ ipfs_seed, peerid_from_config = load_ipfs_seed(ipfs_config, ipfs_priv_b64)
3552
+ main_seed = load_main_seed(node_priv_hex)
3553
+
3554
+ # Create signing keys
3555
+ main_sk = SigningKey(main_seed)
3556
+ main_pk = bytes(main_sk.verify_key)
3557
+ ipfs_sk = SigningKey(ipfs_seed)
3558
+ ipfs_pk = bytes(ipfs_sk.verify_key)
3559
+
3560
+ # Handle IPFS peer ID
3561
+ if ipfs_peer_id:
3562
+ ipfs_peer_id_bytes = base58.b58decode(ipfs_peer_id)
3563
+ elif peerid_from_config:
3564
+ ipfs_peer_id_bytes = base58.b58decode(peerid_from_config)
3565
+ else:
3566
+ if node_type in ["StorageMiner", "Validator"]:
3567
+ error("IPFS PeerID is required for StorageMiner and Validator node types")
3568
+ return 1
3569
+ ipfs_peer_id_bytes = b''
3570
+
3571
+ # Verify keys match node IDs
3572
+ if not verify_peer_id(main_pk, node_id_bytes, "Ed25519"):
3573
+ error("Main public key does not match node ID")
3574
+ return 1
3575
+
3576
+ if ipfs_peer_id_bytes and not verify_peer_id(ipfs_pk, ipfs_peer_id_bytes, "Ed25519"):
3577
+ error("IPFS public key does not match peer ID")
3578
+ return 1
3579
+
3580
+ # Create challenge data
3581
+ domain_bytes = domain.encode()
3582
+ domain24 = b"HIPPIUS::REGISTER::v1" + b"\x00"*3
3583
+ node_id_hash = blake2_256(node_id_bytes)
3584
+ ipfs_peer_id_hash = blake2_256(ipfs_peer_id_bytes)
3585
+
3586
+ nonce = (bytes.fromhex(nonce_hex[2:]) if nonce_hex and nonce_hex.startswith("0x")
3587
+ else bytes.fromhex(nonce_hex) if nonce_hex
3588
+ else secrets.token_bytes(32))
3589
+
3590
+ expires_at_block = current_block_number + expires_in
3591
+ account_bytes = encode_account_id(account_address)
3592
+
3593
+ challenge_data = {
3594
+ 'domain': domain24,
3595
+ 'genesis_hash': genesis_hash,
3596
+ 'account': account_bytes,
3597
+ 'node_id_hash': node_id_hash,
3598
+ 'ipfs_peer_id_hash': ipfs_peer_id_hash,
3599
+ 'block_number': current_block_number,
3600
+ 'nonce': nonce,
3601
+ 'expires_at': expires_at_block,
3602
+ }
3603
+
3604
+ challenge_bytes = manual_encode_challenge(challenge_data, block_width)
3605
+
3606
+ # Sign challenge
3607
+ main_sig = main_sk.sign(challenge_bytes).signature
3608
+ ipfs_sig = ipfs_sk.sign(challenge_bytes).signature
3609
+
3610
+ # Build call parameters
3611
+ call_params = {
3612
+ "coldkey": coldkey,
3613
+ "node_type": node_type,
3614
+ "node_id": node_id,
3615
+ "node_id_hex": "0x"+hexlify(node_id_bytes).decode(),
3616
+ "pay_in_credits": pay_in_credits,
3617
+ "ipfs_node_id": ipfs_peer_id,
3618
+ "ipfs_peer_id": ipfs_peer_id,
3619
+ "ipfs_id_hex": "0x"+hexlify(ipfs_peer_id_bytes).decode(),
3620
+ "owner": account_address,
3621
+ "main_key_type": "Ed25519",
3622
+ "main_public_key": "0x"+main_pk.hex(),
3623
+ "main_sig": "0x"+main_sig.hex(),
3624
+ "ipfs_key_type": "Ed25519",
3625
+ "ipfs_public_key": "0x"+ipfs_pk.hex(),
3626
+ "ipfs_sig": "0x"+ipfs_sig.hex(),
3627
+ "challenge_bytes": "0x"+challenge_bytes.hex(),
3628
+ }
3629
+
3630
+ if dry_run:
3631
+ log("Dry run mode - printing payload without submitting")
3632
+ payload = {
3633
+ "genesis_hash_hex": "0x"+genesis_hash.hex(),
3634
+ "current_block_number": current_block_number,
3635
+ "challenge_bytes_hex": "0x"+challenge_bytes.hex(),
3636
+ "call_module": "Registration",
3637
+ "call_function": "register_node_with_hotkey",
3638
+ "call_params": call_params
3639
+ }
3640
+ console.print(json.dumps(payload, indent=2))
3641
+ return 0
3642
+
3643
+ # Get keypair for signing
3644
+ seed_phrase = client.substrate_client._get_seed_phrase()
3645
+ if not seed_phrase:
3646
+ error("No seed phrase available for signing transaction")
3647
+ return 1
3648
+
3649
+ kp = Keypair.create_from_uri(seed_phrase)
3650
+
3651
+ # Submit transaction
3652
+ log("Submitting registration transaction...")
3653
+ call = substrate.compose_call(
3654
+ call_module="Registration",
3655
+ call_function="register_node_with_hotkey",
3656
+ call_params=call_params
3657
+ )
3658
+ extrinsic = substrate.create_signed_extrinsic(call=call, keypair=kp)
3659
+ receipt = substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True)
3660
+
3661
+ result = {
3662
+ "extrinsic_hash": receipt.extrinsic_hash,
3663
+ "is_success": receipt.is_success,
3664
+ "error_message": receipt.error_message,
3665
+ "triggered_events": [str(event) for event in receipt.triggered_events]
3666
+ }
3667
+
3668
+ if receipt.is_success:
3669
+ success(f"Node registered successfully with hotkey!")
3670
+ success(f"Transaction hash: {receipt.extrinsic_hash}")
3671
+ else:
3672
+ error(f"Registration failed: {receipt.error_message}")
3673
+
3674
+ log("Full result:")
3675
+ console.print(json.dumps(result, indent=2))
3676
+
3677
+ return 0 if receipt.is_success else 1
3678
+
3679
+ except Exception as e:
3680
+ error(f"Error registering node with hotkey: {e}")
3681
+ if hasattr(e, '__traceback__'):
3682
+ import traceback
3683
+ traceback.print_exc()
3684
+ return 1
@@ -171,6 +171,7 @@ examples:
171
171
  add_seed_commands(subparsers)
172
172
  add_account_commands(subparsers)
173
173
  add_address_commands(subparsers)
174
+ add_miner_commands(subparsers)
174
175
 
175
176
  return parser
176
177
 
@@ -654,6 +655,130 @@ def add_address_commands(subparsers):
654
655
  )
655
656
 
656
657
 
658
+ def add_miner_commands(subparsers):
659
+ """Add miner registration commands to the parser."""
660
+ # Miner command
661
+ miner_parser = subparsers.add_parser(
662
+ "miner", help="Miner node registration commands"
663
+ )
664
+ miner_subparsers = miner_parser.add_subparsers(
665
+ dest="miner_action", help="Miner action"
666
+ )
667
+
668
+ # Register coldkey command
669
+ register_coldkey_parser = miner_subparsers.add_parser(
670
+ "register-coldkey", help="Register a node with coldkey (current account becomes coldkey)"
671
+ )
672
+
673
+ # Required arguments for register-coldkey
674
+ register_coldkey_parser.add_argument(
675
+ "--node-id", required=True, help="Your main node_id (libp2p peer ID)"
676
+ )
677
+ register_coldkey_parser.add_argument(
678
+ "--node-priv-hex", required=True, help="Main libp2p ed25519 private key hex (32/64B)"
679
+ )
680
+ register_coldkey_parser.add_argument(
681
+ "--node-type", required=True,
682
+ choices=["StorageMiner", "Validator", "ComputeMiner", "StorageS3", "GpuMiner"],
683
+ help="Type of miner node"
684
+ )
685
+
686
+ # IPFS configuration (one required)
687
+ ipfs_group = register_coldkey_parser.add_mutually_exclusive_group(required=True)
688
+ ipfs_group.add_argument(
689
+ "--ipfs-config", help="Path to IPFS config file (e.g. ~/.ipfs/config)"
690
+ )
691
+ ipfs_group.add_argument(
692
+ "--ipfs-priv-b64", help="IPFS Identity.PrivKey base64 if not using --ipfs-config"
693
+ )
694
+
695
+ # Optional arguments
696
+ register_coldkey_parser.add_argument(
697
+ "--ipfs-peer-id", help="Optional override IPFS PeerID"
698
+ )
699
+ register_coldkey_parser.add_argument(
700
+ "--pay-in-credits", action="store_true",
701
+ help="Pay in credits for registration"
702
+ )
703
+ register_coldkey_parser.add_argument(
704
+ "--expires-in", type=int, default=10,
705
+ help="Challenge expiration in blocks (default: 10)"
706
+ )
707
+ register_coldkey_parser.add_argument(
708
+ "--block-width", choices=["u32", "u64"], default="u32",
709
+ help="Block number width (default: u32)"
710
+ )
711
+ register_coldkey_parser.add_argument(
712
+ "--domain", default="HIPPIUS::REGISTER::v1",
713
+ help="Domain for challenge (default: HIPPIUS::REGISTER::v1)"
714
+ )
715
+ register_coldkey_parser.add_argument(
716
+ "--nonce-hex", help="32-byte hex nonce (optional, random if not provided)"
717
+ )
718
+ register_coldkey_parser.add_argument(
719
+ "--dry-run", action="store_true",
720
+ help="Do not submit extrinsic; just print payload"
721
+ )
722
+
723
+ # Register hotkey command
724
+ register_hotkey_parser = miner_subparsers.add_parser(
725
+ "register-hotkey", help="Register a node with hotkey (current account becomes hotkey)"
726
+ )
727
+
728
+ # Required arguments for register-hotkey
729
+ register_hotkey_parser.add_argument(
730
+ "--coldkey", required=True, help="Coldkey SS58 address"
731
+ )
732
+ register_hotkey_parser.add_argument(
733
+ "--node-id", required=True, help="Your main node_id (libp2p peer ID)"
734
+ )
735
+ register_hotkey_parser.add_argument(
736
+ "--node-priv-hex", required=True, help="Main libp2p ed25519 private key hex (32/64B)"
737
+ )
738
+ register_hotkey_parser.add_argument(
739
+ "--node-type", required=True,
740
+ choices=["StorageMiner", "Validator", "ComputeMiner", "StorageS3", "GpuMiner"],
741
+ help="Type of miner node"
742
+ )
743
+
744
+ # IPFS configuration (one required)
745
+ ipfs_group_hotkey = register_hotkey_parser.add_mutually_exclusive_group(required=True)
746
+ ipfs_group_hotkey.add_argument(
747
+ "--ipfs-config", help="Path to IPFS config file (e.g. ~/.ipfs/config)"
748
+ )
749
+ ipfs_group_hotkey.add_argument(
750
+ "--ipfs-priv-b64", help="IPFS Identity.PrivKey base64 if not using --ipfs-config"
751
+ )
752
+
753
+ # Optional arguments
754
+ register_hotkey_parser.add_argument(
755
+ "--ipfs-peer-id", help="Optional override IPFS PeerID"
756
+ )
757
+ register_hotkey_parser.add_argument(
758
+ "--pay-in-credits", action="store_true",
759
+ help="Pay in credits for registration"
760
+ )
761
+ register_hotkey_parser.add_argument(
762
+ "--expires-in", type=int, default=10,
763
+ help="Challenge expiration in blocks (default: 10)"
764
+ )
765
+ register_hotkey_parser.add_argument(
766
+ "--block-width", choices=["u32", "u64"], default="u32",
767
+ help="Block number width (default: u32)"
768
+ )
769
+ register_hotkey_parser.add_argument(
770
+ "--domain", default="HIPPIUS::REGISTER::v1",
771
+ help="Domain for challenge (default: HIPPIUS::REGISTER::v1)"
772
+ )
773
+ register_hotkey_parser.add_argument(
774
+ "--nonce-hex", help="32-byte hex nonce (optional, random if not provided)"
775
+ )
776
+ register_hotkey_parser.add_argument(
777
+ "--dry-run", action="store_true",
778
+ help="Do not submit extrinsic; just print payload"
779
+ )
780
+
781
+
657
782
  def get_subparser(command: str) -> argparse.ArgumentParser:
658
783
  """Get a subparser for a specific command.
659
784
 
@@ -0,0 +1,487 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import json
4
+ import os
5
+ import base64
6
+ import secrets
7
+ from typing import Tuple
8
+ from nacl.signing import SigningKey
9
+ from binascii import unhexlify, hexlify
10
+ from hashlib import blake2b
11
+ import base58
12
+
13
+ from substrateinterface import SubstrateInterface, Keypair
14
+ from scalecodec.utils.ss58 import ss58_decode
15
+
16
+
17
+ # ---------- Helpers ----------
18
+ def blake2_256(b: bytes) -> bytes:
19
+ return blake2b(b, digest_size=32).digest()
20
+
21
+
22
+ def _read_varint(b: bytes, i: int) -> Tuple[int, int]:
23
+ x, s = 0, 0
24
+ while True:
25
+ if i >= len(b):
26
+ raise ValueError("truncated varint")
27
+ c = b[i]
28
+ i += 1
29
+ x |= (c & 0x7F) << s
30
+ if not (c & 0x80):
31
+ return x, i
32
+ s += 7
33
+
34
+
35
+ def decode_libp2p_privkey_b64(privkey_b64: str) -> bytes:
36
+ """Decode go-libp2p PrivateKey proto; return ed25519 32B seed."""
37
+ raw = base64.b64decode(privkey_b64)
38
+ i = 0
39
+ key_type = None
40
+ data = None
41
+ while i < len(raw):
42
+ key, i = _read_varint(raw, i)
43
+ fld, wtype = key >> 3, key & 0x7
44
+ if fld == 1:
45
+ if wtype != 0:
46
+ raise ValueError("bad wiretype for Type")
47
+ key_type, i = _read_varint(raw, i)
48
+ elif fld == 2:
49
+ if wtype != 2:
50
+ raise ValueError("bad wiretype for Data")
51
+ ln, j = _read_varint(raw, i)
52
+ data = raw[j : j + ln]
53
+ i = j + ln
54
+ else:
55
+ if wtype == 0:
56
+ _, i = _read_varint(raw, i)
57
+ elif wtype == 2:
58
+ ln, j = _read_varint(raw, i)
59
+ i = j + ln
60
+ else:
61
+ raise ValueError("unsupported wiretype")
62
+ if key_type != 1: # 1 = Ed25519
63
+ raise ValueError(f"Only Ed25519 supported (KeyType={key_type})")
64
+ if data is None:
65
+ raise ValueError("no Data")
66
+ if len(data) == 64:
67
+ return data[:32]
68
+ if len(data) == 32:
69
+ return data
70
+ raise ValueError(f"unexpected ed25519 Data length {len(data)}")
71
+
72
+
73
+ def get_peer_id_from_public_key(public_key: bytes) -> str:
74
+ """Convert a public key to a libp2p peer ID"""
75
+ prefix = bytes([0x00, 0x24, 0x08, 0x01, 0x12, 0x20])
76
+ peer_id_bytes = prefix + public_key
77
+ return base58.b58encode(peer_id_bytes).decode()
78
+
79
+
80
+ def get_public_key_from_peer_id(peer_id: str) -> bytes:
81
+ """Extract public key from a libp2p peer ID"""
82
+ peer_id_bytes = base58.b58decode(peer_id)
83
+
84
+ if len(peer_id_bytes) != 38:
85
+ raise ValueError(f"Invalid peer ID length: {len(peer_id_bytes)}")
86
+
87
+ prefix = bytes([0x00, 0x24, 0x08, 0x01, 0x12, 0x20])
88
+ if peer_id_bytes[:6] != prefix:
89
+ raise ValueError("Invalid peer ID prefix")
90
+
91
+ return peer_id_bytes[6:38]
92
+
93
+
94
+ def load_ipfs_seed(
95
+ ipfs_config: str = None, ipfs_priv_b64: str = None
96
+ ) -> Tuple[bytes, str]:
97
+ if ipfs_priv_b64:
98
+ try:
99
+ seed = decode_libp2p_privkey_b64(ipfs_priv_b64)
100
+ return seed, ""
101
+ except ValueError as e:
102
+ raise SystemExit(f"Error decoding IPFS private key: {e}")
103
+
104
+ if not ipfs_config:
105
+ raise SystemExit(
106
+ "Either --ipfs-config or --ipfs-priv-b64 is required for IPFS setup."
107
+ )
108
+
109
+ cfg_path = os.path.expanduser(ipfs_config)
110
+ if not os.path.exists(cfg_path):
111
+ raise SystemExit(f"IPFS config file not found at {cfg_path}")
112
+
113
+ try:
114
+ cfg = json.load(open(cfg_path))
115
+ except json.JSONDecodeError:
116
+ raise SystemExit(f"Invalid JSON in IPFS config file: {cfg_path}")
117
+
118
+ ident = cfg.get("Identity") or {}
119
+ if "PrivKey" not in ident:
120
+ raise SystemExit("IPFS config missing Identity.PrivKey")
121
+
122
+ seed = decode_libp2p_privkey_b64(ident["PrivKey"])
123
+ return seed, ident.get("PeerID", "")
124
+
125
+
126
+ def load_main_seed(node_priv_hex: str) -> bytes:
127
+ h = node_priv_hex.removeprefix("0x")
128
+ try:
129
+ b = unhexlify(h)
130
+ except Exception:
131
+ raise SystemExit("Invalid hex for node_priv_hex")
132
+ if len(b) == 32:
133
+ return b
134
+ if len(b) == 64:
135
+ return b[:32]
136
+ raise SystemExit("node_priv_hex must be 32 or 64 bytes")
137
+
138
+
139
+ def encode_account_id(ss58_address: str) -> bytes:
140
+ """Convert SS58 address to 32-byte public key (AccountId)"""
141
+ decoded = ss58_decode(ss58_address)
142
+ if isinstance(decoded, str):
143
+ decoded = bytes.fromhex(decoded)
144
+ elif isinstance(decoded, list):
145
+ decoded = bytes(decoded)
146
+ if len(decoded) != 32:
147
+ raise ValueError(f"Decoded AccountId must be 32 bytes, got {len(decoded)}")
148
+ return decoded
149
+
150
+
151
+ def verify_peer_id(public_key: bytes, peer_id: bytes, key_type: str) -> bool:
152
+ """Verify that a public key corresponds to a libp2p peer ID"""
153
+ if key_type != "Ed25519":
154
+ return False
155
+
156
+ if len(peer_id) != 38:
157
+ return False
158
+
159
+ expected_prefix = bytes([0x00, 0x24, 0x08, 0x01, 0x12, 0x20])
160
+ if peer_id[:6] != expected_prefix:
161
+ return False
162
+
163
+ return peer_id[6:38] == public_key
164
+
165
+
166
+ def manual_encode_challenge(challenge_data, block_width):
167
+ """Manually encode the challenge data using basic Scale encoding"""
168
+ encoded = b""
169
+
170
+ encoded += challenge_data["domain"]
171
+ encoded += challenge_data["genesis_hash"]
172
+ encoded += challenge_data["account"]
173
+ encoded += challenge_data["node_id_hash"]
174
+ encoded += challenge_data["ipfs_peer_id_hash"]
175
+
176
+ if block_width == "u32":
177
+ encoded += challenge_data["block_number"].to_bytes(4, "little")
178
+ else:
179
+ encoded += challenge_data["block_number"].to_bytes(8, "little")
180
+
181
+ encoded += challenge_data["nonce"]
182
+
183
+ if block_width == "u32":
184
+ encoded += challenge_data["expires_at"].to_bytes(4, "little")
185
+ else:
186
+ encoded += challenge_data["expires_at"].to_bytes(8, "little")
187
+
188
+ return encoded
189
+
190
+
191
+ # ---------- CLI ----------
192
+
193
+
194
+ def main():
195
+ ap = argparse.ArgumentParser(
196
+ description="Hippius v2 register: dual libp2p sigs + one-shot challenge"
197
+ )
198
+ ap.add_argument(
199
+ "--ws", required=True, help="Substrate WS endpoint (e.g. ws://127.0.0.1:9944)"
200
+ )
201
+ ap.add_argument("--module", default="Registration", help="Pallet name in metadata")
202
+ ap.add_argument(
203
+ "--function",
204
+ default="register_node_with_coldkey",
205
+ choices=[
206
+ "register_node_with_coldkey",
207
+ "register_node_with_hotkey",
208
+ "verify_existing_node",
209
+ "verify_existing_coldkey_node",
210
+ ],
211
+ help="Call function",
212
+ )
213
+
214
+ ap.add_argument(
215
+ "--owner-uri", required=True, help="Owner secret URI //path or mnemonic"
216
+ )
217
+ ap.add_argument("--owner-ss58", required=True, help="Owner SS58 address")
218
+ ap.add_argument("--owner-crypto", choices=["sr25519", "ed25519"], default="sr25519")
219
+
220
+ ap.add_argument("--node-id", required=True, help="Your main node_id (hex or utf8)")
221
+ ap.add_argument(
222
+ "--node-priv-hex",
223
+ required=True,
224
+ help="Main libp2p ed25519 private key hex (32/64B)",
225
+ )
226
+
227
+ ap.add_argument("--ipfs-config", help="~/.ipfs/config (reads PeerID & PrivKey)")
228
+ ap.add_argument(
229
+ "--ipfs-priv-b64", help="Identity.PrivKey base64 if not using --ipfs-config"
230
+ )
231
+ ap.add_argument("--ipfs-peer-id", help="Optional override PeerID (utf8)")
232
+ ap.add_argument("--expires-in", type=int, default=10)
233
+ ap.add_argument("--block-width", choices=["u32", "u64"], default="u32")
234
+ ap.add_argument("--domain", default="HIPPIUS::REGISTER::v1")
235
+ ap.add_argument("--nonce-hex", help="32-byte hex (optional)")
236
+ ap.add_argument(
237
+ "--node-type",
238
+ choices=["StorageMiner", "Validator", "ComputeMiner", "StorageS3", "GpuMiner"],
239
+ help="Required for registration functions",
240
+ )
241
+ ap.add_argument(
242
+ "--pay-in-credits", action="store_true", help="Pay in credits for registration"
243
+ )
244
+
245
+ ap.add_argument(
246
+ "--coldkey",
247
+ help="Coldkey SS58 address (required for register_node_with_hotkey)",
248
+ )
249
+
250
+ ap.add_argument(
251
+ "--dry-run",
252
+ action="store_true",
253
+ help="Do not submit extrinsic; just print payload",
254
+ )
255
+ args = ap.parse_args()
256
+
257
+ # Validate function-specific requirements
258
+ if args.function == "register_node_with_hotkey" and not args.coldkey:
259
+ raise SystemExit("--coldkey is required for register_node_with_hotkey function")
260
+ if (
261
+ args.function in ["register_node_with_coldkey", "register_node_with_hotkey"]
262
+ and not args.node_type
263
+ ):
264
+ raise SystemExit("--node-type is required for registration functions")
265
+
266
+ # Initialize SubstrateInterface
267
+ substrate = SubstrateInterface(url=args.ws)
268
+
269
+ # Connect & fetch genesis hash
270
+ genesis_hash_hex = substrate.get_block_hash(0)
271
+ genesis_hash = bytes.fromhex(genesis_hash_hex[2:])
272
+
273
+ # Get current block number from chain
274
+ try:
275
+ current_block_number = substrate.get_block_number(None)
276
+ print(f"Current block number: {current_block_number}")
277
+ except Exception as e:
278
+ raise SystemExit(f"Error fetching current block number: {e}")
279
+
280
+ # Ensure node_id_bytes is correctly formed
281
+ if args.node_id.startswith("0x"):
282
+ node_id_bytes = bytes.fromhex(args.node_id[2:])
283
+ else:
284
+ node_id_bytes = base58.b58decode(args.node_id)
285
+
286
+ ipfs_seed, peerid_from_config = load_ipfs_seed(args.ipfs_config, args.ipfs_priv_b64)
287
+ if args.ipfs_peer_id:
288
+ ipfs_peer_id_bytes = base58.b58decode(args.ipfs_peer_id)
289
+ elif peerid_from_config:
290
+ ipfs_peer_id_bytes = base58.b58decode(peerid_from_config)
291
+ else:
292
+ if args.node_type in ["StorageMiner", "Validator"] and args.function in [
293
+ "register_node_with_coldkey",
294
+ "register_node_with_hotkey",
295
+ ]:
296
+ raise SystemExit(
297
+ "IPFS PeerID is required for StorageMiner and Validator node WARRANTtypes."
298
+ )
299
+ ipfs_peer_id_bytes = b""
300
+
301
+ main_seed = load_main_seed(args.node_priv_hex)
302
+
303
+ # Keys & pubkeys
304
+ main_sk = SigningKey(main_seed)
305
+ main_pk = bytes(main_sk.verify_key)
306
+ ipfs_sk = SigningKey(ipfs_seed)
307
+ ipfs_pk = bytes(ipfs_sk.verify_key)
308
+
309
+ # DEBUG: Print what we're working with
310
+ print(f"Main public key: 0x{main_pk.hex()}")
311
+ print(f"IPFS public key: 0x{ipfs_pk.hex()}")
312
+
313
+ expected_main_peer_id = get_peer_id_from_public_key(main_pk)
314
+ expected_ipfs_peer_id = get_peer_id_from_public_key(ipfs_pk)
315
+ print(f"Expected main node ID from private key: {expected_main_peer_id}")
316
+ print(f"Expected IPFS peer ID from private key: {expected_ipfs_peer_id}")
317
+ print(f"Provided main node ID: {args.node_id}")
318
+ print(
319
+ f"Provided IPFS peer ID: {args.ipfs_peer_id if args.ipfs_peer_id else peerid_from_config}"
320
+ )
321
+
322
+ try:
323
+ expected_main_pk = get_public_key_from_peer_id(args.node_id)
324
+ print(f"Expected main public key from node ID: 0x{expected_main_pk.hex()}")
325
+ except Exception as e:
326
+ print(f"Error extracting public key from main node ID: {e}")
327
+
328
+ try:
329
+ provided_ipfs_peer_id = (
330
+ args.ipfs_peer_id if args.ipfs_peer_id else peerid_from_config
331
+ )
332
+ expected_ipfs_pk = get_public_key_from_peer_id(provided_ipfs_peer_id)
333
+ print(f"Expected IPFS public key from peer ID: 0x{expected_ipfs_pk.hex()}")
334
+ except Exception as e:
335
+ print(f"Error extracting public key from IPFS peer ID: {e}")
336
+
337
+ if not verify_peer_id(main_pk, node_id_bytes, "Ed25519"):
338
+ raise SystemExit("Main public key does not match node ID")
339
+
340
+ if ipfs_peer_id_bytes and not verify_peer_id(
341
+ ipfs_pk, ipfs_peer_id_bytes, "Ed25519"
342
+ ):
343
+ raise SystemExit("IPFS public key does not match peer ID")
344
+
345
+ # Challenge bytes
346
+ domain = args.domain.encode()
347
+ domain24 = b"HIPPIUS::REGISTER::v1" + b"\x00" * 3
348
+
349
+ node_id_hash = blake2_256(node_id_bytes)
350
+ ipfs_peer_id_hash = blake2_256(ipfs_peer_id_bytes)
351
+
352
+ nonce = (
353
+ bytes.fromhex(args.nonce_hex[2:])
354
+ if args.nonce_hex and args.nonce_hex.startswith("0x")
355
+ else bytes.fromhex(args.nonce_hex)
356
+ if args.nonce_hex
357
+ else secrets.token_bytes(32)
358
+ )
359
+
360
+ expires_at_block = current_block_number + args.expires_in
361
+
362
+ account_bytes = encode_account_id(args.owner_ss58)
363
+
364
+ challenge_data = {
365
+ "domain": domain24,
366
+ "genesis_hash": genesis_hash,
367
+ "account": account_bytes,
368
+ "node_id_hash": node_id_hash,
369
+ "ipfs_peer_id_hash": ipfs_peer_id_hash,
370
+ "block_number": current_block_number,
371
+ "nonce": nonce,
372
+ "expires_at": expires_at_block,
373
+ }
374
+
375
+ try:
376
+ challenge_bytes = manual_encode_challenge(challenge_data, args.block_width)
377
+ print(f"Successfully encoded challenge bytes: 0x{challenge_bytes.hex()}")
378
+ except Exception as e:
379
+ print(f"Error encoding challenge bytes: {e}")
380
+ raise
381
+
382
+ main_sig = main_sk.sign(challenge_bytes).signature
383
+ ipfs_sig = ipfs_sk.sign(challenge_bytes).signature
384
+
385
+ # Compose call params based on function
386
+ if args.function == "register_node_with_coldkey":
387
+ call_params = {
388
+ "node_type": args.node_type,
389
+ "node_id": args.node_id,
390
+ "node_id_hex": "0x" + hexlify(node_id_bytes).decode(),
391
+ "pay_in_credits": bool(args.pay_in_credits),
392
+ "ipfs_node_id": args.ipfs_peer_id,
393
+ "owner": args.owner_ss58,
394
+ "ipfs_peer_id": args.ipfs_peer_id,
395
+ "ipfs_id_hex": "0x" + hexlify(ipfs_peer_id_bytes).decode(),
396
+ "main_key_type": "Ed25519",
397
+ "main_public_key": "0x" + main_pk.hex(),
398
+ "main_sig": "0x" + main_sig.hex(),
399
+ "ipfs_key_type": "Ed25519",
400
+ "ipfs_public_key": "0x" + ipfs_pk.hex(),
401
+ "ipfs_sig": "0x" + ipfs_sig.hex(),
402
+ "challenge_bytes": "0x" + challenge_bytes.hex(),
403
+ }
404
+ elif args.function == "register_node_with_hotkey":
405
+ call_params = {
406
+ "coldkey": args.coldkey,
407
+ "node_type": args.node_type,
408
+ "node_id": args.node_id,
409
+ "node_id_hex": "0x" + hexlify(node_id_bytes).decode(),
410
+ "pay_in_credits": bool(args.pay_in_credits),
411
+ "ipfs_node_id": args.ipfs_peer_id,
412
+ "ipfs_peer_id": args.ipfs_peer_id,
413
+ "ipfs_id_hex": "0x" + hexlify(ipfs_peer_id_bytes).decode(),
414
+ "owner": args.owner_ss58,
415
+ "main_key_type": "Ed25519",
416
+ "main_public_key": "0x" + main_pk.hex(),
417
+ "main_sig": "0x" + main_sig.hex(),
418
+ "ipfs_key_type": "Ed25519",
419
+ "ipfs_public_key": "0x" + ipfs_pk.hex(),
420
+ "ipfs_sig": "0x" + ipfs_sig.hex(),
421
+ "challenge_bytes": "0x" + challenge_bytes.hex(),
422
+ }
423
+ elif args.function in ["verify_existing_node", "verify_existing_coldkey_node"]:
424
+ call_params = {
425
+ "node_id": args.node_id,
426
+ "node_id_hex": "0x" + hexlify(node_id_bytes).decode(),
427
+ "ipfs_id_hex": "0x" + hexlify(ipfs_peer_id_bytes).decode(),
428
+ "main_key_type": "Ed25519",
429
+ "main_public_key": "0x" + main_pk.hex(),
430
+ "main_sig": "0x" + main_sig.hex(),
431
+ "ipfs_key_type": "Ed25519",
432
+ "ipfs_public_key": "0x" + ipfs_pk.hex(),
433
+ "ipfs_sig": "0x" + ipfs_sig.hex(),
434
+ "challenge_bytes": "0x" + challenge_bytes.hex(),
435
+ }
436
+
437
+ if args.dry_run:
438
+ print(
439
+ json.dumps(
440
+ {
441
+ "genesis_hash_hex": "0x" + genesis_hash.hex(),
442
+ "current_block_number": current_block_number,
443
+ "challenge_bytes_hex": "0x" + challenge_bytes.hex(),
444
+ "call_module": args.module,
445
+ "call_function": args.function,
446
+ "call_params": call_params,
447
+ },
448
+ indent=2,
449
+ )
450
+ )
451
+ return
452
+
453
+ try:
454
+ from substrateinterface import KeypairType
455
+
456
+ crypto_arg = (
457
+ KeypairType.SR25519
458
+ if args.owner_crypto == "sr25519"
459
+ else KeypairType.ED25519
460
+ )
461
+ kp = Keypair.create_from_uri(args.owner_uri, crypto_type=crypto_arg)
462
+ except Exception as e:
463
+ print(
464
+ f"Error creating keypair with specified type: {e}. Falling back to default sr25519."
465
+ )
466
+ kp = Keypair.create_from_uri(args.owner_uri)
467
+
468
+ call = substrate.compose_call(
469
+ call_module=args.module, call_function=args.function, call_params=call_params
470
+ )
471
+ extrinsic = substrate.create_signed_extrinsic(call=call, keypair=kp)
472
+ receipt = substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True)
473
+ print(
474
+ json.dumps(
475
+ {
476
+ "extrinsic_hash": receipt.extrinsic_hash,
477
+ "is_success": receipt.is_success,
478
+ "error_message": receipt.error_message,
479
+ "triggered_events": [str(event) for event in receipt.triggered_events],
480
+ },
481
+ indent=2,
482
+ )
483
+ )
484
+
485
+
486
+ if __name__ == "__main__":
487
+ main()
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "hippius"
3
- version = "0.2.48"
3
+ version = "0.2.49"
4
4
  description = "Python SDK and CLI for Hippius blockchain storage"
5
5
  authors = ["Dubs <dubs@dubs.rs>"]
6
6
  readme = "README.md"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes