mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-04 10:21:40 +00:00
@@ -0,0 +1,98 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dwn/client/plugin"
|
||||
)
|
||||
|
||||
// BroadcastCmd returns a command to broadcast a signed transaction using the Motor plugin
|
||||
func BroadcastCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "broadcast [signed-tx-file]",
|
||||
Short: "Broadcast a signed transaction using Motor plugin",
|
||||
Long: `Broadcast a signed transaction to the network using the Motor plugin.
|
||||
The transaction should be provided as a file containing the signed transaction.
|
||||
|
||||
Example:
|
||||
snrd wallet broadcast signed_tx.json --enclave-data @enclave.json
|
||||
|
||||
This command supports broadcasting transactions that were previously signed using the Motor plugin.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read the signed transaction file
|
||||
txFile := args[0]
|
||||
txBytes, err := os.ReadFile(txFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read transaction file: %w", err)
|
||||
}
|
||||
|
||||
// Get optional enclave data for verification
|
||||
enclaveDataStr, err := cmd.Flags().GetString("enclave-data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enclaveDataStr != "" {
|
||||
// Parse enclave data
|
||||
enclaveData, err := parseEnclaveData(enclaveDataStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse enclave data: %w", err)
|
||||
}
|
||||
|
||||
// Load the plugin for verification
|
||||
chainID := clientCtx.ChainID
|
||||
if chainID == "" {
|
||||
chainID = DefaultTestChainID
|
||||
}
|
||||
config := plugin.CreateEnclaveConfig(chainID, enclaveData)
|
||||
|
||||
ctx := context.Background()
|
||||
motorPlugin, err := plugin.LoadPluginWithManager(ctx, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load Motor plugin: %w", err)
|
||||
}
|
||||
|
||||
// Get issuer DID for verification
|
||||
resp, err := motorPlugin.GetIssuerDID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get issuer DID: %w", err)
|
||||
}
|
||||
|
||||
if resp.Error != "" {
|
||||
return fmt.Errorf("plugin error: %s", resp.Error)
|
||||
}
|
||||
|
||||
fmt.Printf(
|
||||
"Broadcasting transaction from wallet: %s (DID: %s)\n",
|
||||
resp.Address,
|
||||
resp.IssuerDID,
|
||||
)
|
||||
}
|
||||
|
||||
// Broadcast the transaction
|
||||
res, err := clientCtx.BroadcastTxSync(txBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to broadcast transaction: %w", err)
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
cmd.Flags().String("enclave-data", "", "Enclave data for wallet verification (hex or @file)")
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Package cli provides the DWN module CLI commands.
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// AddWalletCmds adds wallet-specific commands to the root command
|
||||
func AddWalletCmds(rootCmd *cobra.Command) {
|
||||
walletCmd := &cobra.Command{
|
||||
Use: "wallet",
|
||||
Short: "Wallet operations",
|
||||
}
|
||||
|
||||
walletCmd.AddCommand(
|
||||
SignCmd(),
|
||||
VerifyCmd(),
|
||||
SimulateCmd(),
|
||||
BroadcastCmd(),
|
||||
)
|
||||
|
||||
// Add wallet commands
|
||||
rootCmd.AddCommand(walletCmd)
|
||||
}
|
||||
+139
-3
@@ -1,16 +1,18 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
|
||||
"github.com/sonr-io/snrd/x/dwn/types"
|
||||
"github.com/sonr-io/sonr/x/dwn/types"
|
||||
)
|
||||
|
||||
// !NOTE: Must enable in module.go (disabled in favor of autocli.go)
|
||||
|
||||
// GetQueryCmd returns the root query command for the DWN module
|
||||
func GetQueryCmd() *cobra.Command {
|
||||
queryCmd := &cobra.Command{
|
||||
Use: types.ModuleName,
|
||||
@@ -21,10 +23,15 @@ func GetQueryCmd() *cobra.Command {
|
||||
}
|
||||
queryCmd.AddCommand(
|
||||
GetCmdParams(),
|
||||
GetCmdEncryptionStatus(),
|
||||
GetCmdVRFContributions(),
|
||||
GetCmdEncryptedRecord(),
|
||||
GetWalletQueryCommands(),
|
||||
)
|
||||
return queryCmd
|
||||
}
|
||||
|
||||
// GetCmdParams returns the command for querying module parameters
|
||||
func GetCmdParams() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "params",
|
||||
@@ -48,3 +55,132 @@ func GetCmdParams() *cobra.Command {
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdEncryptionStatus returns the command for querying encryption status
|
||||
func GetCmdEncryptionStatus() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "encryption-status",
|
||||
Short: "Query current encryption key state and version",
|
||||
Long: `Query the current encryption status including:
|
||||
- Current key version
|
||||
- Validator set participating in consensus
|
||||
- Single-node mode status
|
||||
- Key rotation timestamps`,
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.EncryptionStatus(
|
||||
context.Background(),
|
||||
&types.QueryEncryptionStatusRequest{},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdVRFContributions returns the command for querying VRF contributions
|
||||
func GetCmdVRFContributions() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "vrf-contributions [validator-address]",
|
||||
Short: "List VRF contributions for current consensus round",
|
||||
Long: `List VRF contributions for the current consensus round.
|
||||
Optionally filter by validator address.`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &types.QueryVRFContributionsRequest{}
|
||||
|
||||
// If validator address is provided, use it as filter
|
||||
if len(args) > 0 {
|
||||
req.ValidatorAddress = args[0]
|
||||
}
|
||||
|
||||
// Get pagination from flags
|
||||
pageReq, err := client.ReadPageRequest(cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Pagination = pageReq
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.VRFContributions(context.Background(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
flags.AddPaginationFlagsToCmd(cmd, "vrf-contributions")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdEncryptedRecord returns the command for querying encrypted records
|
||||
func GetCmdEncryptedRecord() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "encrypted-record [target-did] [record-id]",
|
||||
Short: "Query a specific encrypted record with automatic decryption",
|
||||
Long: `Query an encrypted DWN record and optionally decrypt it.
|
||||
By default, the record data is decrypted if possible.
|
||||
Use --return-encrypted to return the raw encrypted data instead.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetDID := args[0]
|
||||
recordID := args[1]
|
||||
|
||||
// Check for return-encrypted flag
|
||||
returnEncrypted, err := cmd.Flags().GetBool("return-encrypted")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &types.QueryEncryptedRecordRequest{
|
||||
Target: targetDID,
|
||||
RecordId: recordID,
|
||||
ReturnEncrypted: returnEncrypted,
|
||||
}
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.EncryptedRecord(context.Background(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add extra information about decryption status
|
||||
if !returnEncrypted {
|
||||
if res.WasDecrypted {
|
||||
fmt.Printf("✓ Record data was successfully decrypted\n\n")
|
||||
} else {
|
||||
fmt.Printf("⚠ Record data could not be decrypted or is not encrypted\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Bool("return-encrypted", false, "Return encrypted data without decryption attempt")
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/mpc"
|
||||
"github.com/sonr-io/sonr/x/dwn/client/plugin"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultTestChainID is the default chain ID used for local testing
|
||||
DefaultTestChainID = "sonrtest_1-1"
|
||||
)
|
||||
|
||||
// GetWalletQueryCommands returns wallet-specific query commands
|
||||
func GetWalletQueryCommands() *cobra.Command {
|
||||
walletQueryCmd := &cobra.Command{
|
||||
Use: "wallet",
|
||||
Short: "Wallet query commands",
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: client.ValidateCmd,
|
||||
}
|
||||
|
||||
walletQueryCmd.AddCommand(
|
||||
GetCmdWalletDerive(),
|
||||
GetCmdWalletStatus(),
|
||||
GetCmdWalletConfig(),
|
||||
)
|
||||
|
||||
return walletQueryCmd
|
||||
}
|
||||
|
||||
// GetCmdWalletDerive creates a command to derive wallet addresses from enclave data
|
||||
func GetCmdWalletDerive() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "derive [enclave-data]",
|
||||
Short: "Derive wallet address and DID from enclave data",
|
||||
Long: `Derive wallet address and DID from MPC enclave data.
|
||||
The enclave-data should be provided as hex-encoded JSON or a file path.
|
||||
|
||||
Example:
|
||||
snrd query dwn wallet derive '{"pub_hex":"...","pub_bytes":[...],...}'
|
||||
snrd query dwn wallet derive @enclave.json`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse enclave data
|
||||
enclaveData, err := parseEnclaveData(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse enclave data: %w", err)
|
||||
}
|
||||
|
||||
// Get chain ID from client context
|
||||
chainID := clientCtx.ChainID
|
||||
if chainID == "" {
|
||||
chainID = DefaultTestChainID // Default for local testing
|
||||
}
|
||||
|
||||
// Create enclave configuration
|
||||
config := plugin.CreateEnclaveConfig(chainID, enclaveData)
|
||||
|
||||
// Load plugin and derive wallet address
|
||||
ctx := context.Background()
|
||||
motorPlugin, err := plugin.LoadPluginWithManager(ctx, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load Motor plugin: %w", err)
|
||||
}
|
||||
|
||||
// Get issuer DID and address
|
||||
response, err := motorPlugin.GetIssuerDID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to derive wallet address: %w", err)
|
||||
}
|
||||
|
||||
if response.Error != "" {
|
||||
return fmt.Errorf("plugin error: %s", response.Error)
|
||||
}
|
||||
|
||||
// Display results
|
||||
result := map[string]any{
|
||||
"issuer_did": response.IssuerDID,
|
||||
"address": response.Address,
|
||||
"chain_code": response.ChainCode,
|
||||
"chain_id": chainID,
|
||||
}
|
||||
|
||||
return clientCtx.PrintObjectLegacy(result)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdWalletStatus creates a command to check wallet plugin status
|
||||
func GetCmdWalletStatus() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "status [wallet-address]",
|
||||
Short: "Check wallet plugin health and status",
|
||||
Long: `Check the health status of wallet plugins managed by the plugin manager.
|
||||
Optionally filter by wallet address if enclave data is provided.
|
||||
|
||||
Example:
|
||||
snrd query dwn wallet status
|
||||
snrd query dwn wallet status sonr1abc123...`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all plugin IDs from the default manager
|
||||
pluginIDs := plugin.DefaultManager.ListPlugins()
|
||||
|
||||
if len(pluginIDs) == 0 {
|
||||
fmt.Println("No wallet plugins currently loaded")
|
||||
return nil
|
||||
}
|
||||
|
||||
var statusResults []map[string]any
|
||||
|
||||
// Check status for each plugin
|
||||
for _, id := range pluginIDs {
|
||||
stats, err := plugin.DefaultManager.GetPluginStats(id)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting stats for plugin %s: %v\n", id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
status := map[string]any{
|
||||
"plugin_id": stats.ID,
|
||||
"chain_id": stats.ChainID,
|
||||
"is_healthy": stats.IsHealthy,
|
||||
"error_count": stats.ErrorCount,
|
||||
"created_at": stats.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
"last_used": stats.LastUsed.Format("2006-01-02 15:04:05"),
|
||||
"uptime_duration": stats.UptimeDuration.String(),
|
||||
"idle_duration": stats.IdleDuration.String(),
|
||||
}
|
||||
|
||||
// If wallet address is provided, try to match
|
||||
if len(args) > 0 {
|
||||
walletAddress := args[0]
|
||||
// For now, we include all plugins since we can't easily derive address from plugin ID
|
||||
// In a production implementation, you might want to store address mappings
|
||||
_ = walletAddress
|
||||
}
|
||||
|
||||
statusResults = append(statusResults, status)
|
||||
}
|
||||
|
||||
if len(statusResults) == 0 {
|
||||
fmt.Println("No matching wallet plugins found")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Print summary
|
||||
healthyCount := 0
|
||||
for _, status := range statusResults {
|
||||
if status["is_healthy"].(bool) {
|
||||
healthyCount++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf(
|
||||
"Wallet Plugin Status Summary: %d/%d healthy\n\n",
|
||||
healthyCount,
|
||||
len(statusResults),
|
||||
)
|
||||
|
||||
return clientCtx.PrintObjectLegacy(map[string]any{
|
||||
"summary": map[string]any{
|
||||
"total_plugins": len(statusResults),
|
||||
"healthy_plugins": healthyCount,
|
||||
"unhealthy_plugins": len(statusResults) - healthyCount,
|
||||
},
|
||||
"plugins": statusResults,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdWalletConfig creates a command to query wallet configuration
|
||||
func GetCmdWalletConfig() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Display wallet plugin configuration and capabilities",
|
||||
Long: `Display the default configuration used for wallet plugins including:
|
||||
- Security settings and timeouts
|
||||
- Vault configuration parameters
|
||||
- Plugin loader settings
|
||||
- Supported cryptographic capabilities`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get default configurations
|
||||
defaultEnclaveConfig := plugin.DefaultEnclaveConfig()
|
||||
defaultLoaderConfig := plugin.DefaultLoaderConfig()
|
||||
|
||||
result := map[string]any{
|
||||
"enclave_config": map[string]any{
|
||||
"default_chain_id": defaultEnclaveConfig.ChainID,
|
||||
"vault_config": map[string]any{
|
||||
"ipfs_endpoint": defaultEnclaveConfig.VaultConfig.IPFSEndpoint,
|
||||
"max_vault_size": defaultEnclaveConfig.VaultConfig.MaxVaultSize,
|
||||
"enable_compression": defaultEnclaveConfig.VaultConfig.EnableCompression,
|
||||
"backup_enabled": defaultEnclaveConfig.VaultConfig.BackupEnabled,
|
||||
},
|
||||
"security_config": map[string]any{
|
||||
"max_token_lifetime": defaultEnclaveConfig.Security.MaxTokenLifetime.String(),
|
||||
"require_audience": defaultEnclaveConfig.Security.RequireAudience,
|
||||
"allowed_origins": defaultEnclaveConfig.Security.AllowedOrigins,
|
||||
},
|
||||
"timeouts": map[string]any{
|
||||
"token_creation": defaultEnclaveConfig.Timeouts.TokenCreation.String(),
|
||||
"signature": defaultEnclaveConfig.Timeouts.Signature.String(),
|
||||
"verification": defaultEnclaveConfig.Timeouts.Verification.String(),
|
||||
"plugin_init": defaultEnclaveConfig.Timeouts.PluginInit.String(),
|
||||
},
|
||||
},
|
||||
"loader_config": map[string]any{
|
||||
"enable_wasi": defaultLoaderConfig.EnableWASI,
|
||||
"memory_limit": defaultLoaderConfig.MemoryLimit,
|
||||
"allow_http_requests": defaultLoaderConfig.AllowHttpRequests,
|
||||
"log_level": defaultLoaderConfig.LogLevel,
|
||||
"max_concurrent_plugins": defaultLoaderConfig.MaxConcurrentPlugins,
|
||||
},
|
||||
"capabilities": map[string]any{
|
||||
"supported_operations": []string{
|
||||
"UCAN token creation (origin)",
|
||||
"UCAN token delegation (attenuated)",
|
||||
"Data signing (MPC-based)",
|
||||
"Signature verification",
|
||||
"DID derivation",
|
||||
"Address generation",
|
||||
},
|
||||
"supported_curves": []string{"secp256k1"},
|
||||
"plugin_format": "WebAssembly (WASM)",
|
||||
"mpc_support": true,
|
||||
},
|
||||
}
|
||||
|
||||
return clientCtx.PrintObjectLegacy(result)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// parseEnclaveData parses enclave data from a string (JSON or file path)
|
||||
func parseEnclaveData(input string) (*mpc.EnclaveData, error) {
|
||||
var data []byte
|
||||
|
||||
// Check if input is a file path (starts with @)
|
||||
if len(input) > 0 && input[0] == '@' {
|
||||
// File path - not implemented for security reasons in this example
|
||||
return nil, fmt.Errorf("file input not supported in this implementation")
|
||||
} else {
|
||||
// Direct JSON string
|
||||
data = []byte(input)
|
||||
}
|
||||
|
||||
// Try to parse as hex-encoded data first
|
||||
if len(input) > 2 && (input[:2] == "0x" || input[:2] == "0X") {
|
||||
hexData, err := hex.DecodeString(input[2:])
|
||||
if err == nil {
|
||||
data = hexData
|
||||
}
|
||||
}
|
||||
|
||||
// Parse JSON
|
||||
var enclaveData mpc.EnclaveData
|
||||
if err := json.Unmarshal(data, &enclaveData); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse JSON: %w", err)
|
||||
}
|
||||
|
||||
// Validate enclave data
|
||||
if !enclaveData.IsValid() {
|
||||
return nil, fmt.Errorf("invalid enclave data: missing required fields")
|
||||
}
|
||||
|
||||
return &enclaveData, nil
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dwn/client/plugin"
|
||||
)
|
||||
|
||||
// SignCmd returns a command to sign data or transactions using the Motor plugin
|
||||
func SignCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "sign [message-or-file]",
|
||||
Short: "Sign a message or transaction using Motor plugin",
|
||||
Long: `Sign a message or transaction using the Motor plugin's MPC-based signing.
|
||||
|
||||
Examples:
|
||||
# Sign a text message
|
||||
snrd wallet sign "Hello World" --enclave-data @enclave.json
|
||||
|
||||
# Sign a transaction file
|
||||
snrd wallet sign @unsigned_tx.json --enclave-data @enclave.json --tx
|
||||
|
||||
# Sign raw bytes (hex encoded)
|
||||
snrd wallet sign 0xdeadbeef --enclave-data @enclave.json`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get enclave data
|
||||
enclaveDataStr, err := cmd.Flags().GetString("enclave-data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enclaveDataStr == "" {
|
||||
return fmt.Errorf("--enclave-data flag is required")
|
||||
}
|
||||
|
||||
enclaveData, err := parseEnclaveData(enclaveDataStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse enclave data: %w", err)
|
||||
}
|
||||
|
||||
// Determine what to sign
|
||||
input := args[0]
|
||||
var dataToSign []byte
|
||||
|
||||
isTransaction, _ := cmd.Flags().GetBool("tx")
|
||||
|
||||
if input[0] == '@' {
|
||||
// Read from file
|
||||
fileData, err := os.ReadFile(input[1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
|
||||
if isTransaction {
|
||||
// For transaction, just use the raw bytes
|
||||
// In a real implementation, we'd extract sign bytes properly
|
||||
dataToSign = fileData
|
||||
} else {
|
||||
dataToSign = fileData
|
||||
}
|
||||
} else if len(input) > 2 && input[:2] == "0x" {
|
||||
// Hex encoded data
|
||||
dataToSign, err = hex.DecodeString(input[2:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode hex: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Plain text message
|
||||
dataToSign = []byte(input)
|
||||
}
|
||||
|
||||
// Load the plugin
|
||||
chainID := clientCtx.ChainID
|
||||
if chainID == "" {
|
||||
chainID = DefaultTestChainID
|
||||
}
|
||||
config := plugin.CreateEnclaveConfig(chainID, enclaveData)
|
||||
|
||||
ctx := context.Background()
|
||||
motorPlugin, err := plugin.LoadPluginWithManager(ctx, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load Motor plugin: %w", err)
|
||||
}
|
||||
|
||||
// Sign the data
|
||||
signReq := &plugin.SignDataRequest{
|
||||
Data: dataToSign,
|
||||
}
|
||||
signResp, err := motorPlugin.SignData(signReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sign data: %w", err)
|
||||
}
|
||||
|
||||
if signResp.Error != "" {
|
||||
return fmt.Errorf("plugin signing error: %s", signResp.Error)
|
||||
}
|
||||
|
||||
// Get issuer info for display
|
||||
issuerResp, _ := motorPlugin.GetIssuerDID()
|
||||
|
||||
// Output the signature
|
||||
result := map[string]any{
|
||||
"signature": hex.EncodeToString(signResp.Signature),
|
||||
"signer": map[string]any{
|
||||
"did": issuerResp.IssuerDID,
|
||||
"address": issuerResp.Address,
|
||||
},
|
||||
"data_hash": hex.EncodeToString(dataToSign[:min(32, len(dataToSign))]),
|
||||
}
|
||||
|
||||
// Save to file if requested
|
||||
outputFile, _ := cmd.Flags().GetString("output-file")
|
||||
if outputFile != "" {
|
||||
jsonData, err := json.MarshalIndent(result, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal result: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(outputFile, jsonData, 0o644); err != nil {
|
||||
return fmt.Errorf("failed to write output file: %w", err)
|
||||
}
|
||||
fmt.Printf("Signature saved to %s\n", outputFile)
|
||||
}
|
||||
|
||||
return clientCtx.PrintObjectLegacy(result)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
cmd.Flags().String("enclave-data", "", "Enclave data for signing (required)")
|
||||
cmd.Flags().Bool("tx", false, "Sign as transaction (parse JSON as tx)")
|
||||
cmd.Flags().String("output-file", "", "Save signature to file")
|
||||
cmd.MarkFlagRequired("enclave-data")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dwn/client/plugin"
|
||||
)
|
||||
|
||||
// SimulateCmd returns a command to simulate transactions using the Motor plugin
|
||||
func SimulateCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "simulate [tx-file]",
|
||||
Short: "Simulate a transaction using Motor plugin",
|
||||
Long: `Simulate a transaction to estimate gas and validate execution using the Motor plugin.
|
||||
|
||||
Examples:
|
||||
# Simulate a transaction from file
|
||||
snrd wallet simulate tx.json --enclave-data @enclave.json
|
||||
|
||||
# Simulate with custom gas adjustment
|
||||
snrd wallet simulate tx.json --enclave-data @enclave.json --gas-adjustment 1.5`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read transaction file
|
||||
txFile := args[0]
|
||||
txBytes, err := os.ReadFile(txFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read transaction file: %w", err)
|
||||
}
|
||||
|
||||
// Parse transaction
|
||||
var txData map[string]any
|
||||
if err := json.Unmarshal(txBytes, &txData); err != nil {
|
||||
return fmt.Errorf("failed to parse transaction: %w", err)
|
||||
}
|
||||
|
||||
// Get enclave data
|
||||
enclaveDataStr, err := cmd.Flags().GetString("enclave-data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enclaveDataStr == "" {
|
||||
return fmt.Errorf("--enclave-data flag is required")
|
||||
}
|
||||
|
||||
enclaveData, err := parseEnclaveData(enclaveDataStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse enclave data: %w", err)
|
||||
}
|
||||
|
||||
// Load the plugin
|
||||
chainID := clientCtx.ChainID
|
||||
if chainID == "" {
|
||||
chainID = DefaultTestChainID
|
||||
}
|
||||
config := plugin.CreateEnclaveConfig(chainID, enclaveData)
|
||||
|
||||
ctx := context.Background()
|
||||
motorPlugin, err := plugin.LoadPluginWithManager(ctx, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load Motor plugin: %w", err)
|
||||
}
|
||||
|
||||
// Get issuer info for the transaction
|
||||
issuerResp, err := motorPlugin.GetIssuerDID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get issuer DID: %w", err)
|
||||
}
|
||||
|
||||
if issuerResp.Error != "" {
|
||||
return fmt.Errorf("plugin error: %s", issuerResp.Error)
|
||||
}
|
||||
|
||||
// Create transaction factory for simulation
|
||||
txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create tx factory: %w", err)
|
||||
}
|
||||
|
||||
// Set simulation mode
|
||||
txf = txf.WithSimulateAndExecute(true)
|
||||
|
||||
// Decode the transaction
|
||||
txDecoder := clientCtx.TxConfig.TxJSONDecoder()
|
||||
decodedTx, err := txDecoder(txBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode transaction: %w", err)
|
||||
}
|
||||
|
||||
// Create a transaction builder for simulation
|
||||
txBuilder := clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// Set messages from decoded transaction
|
||||
msgs := decodedTx.GetMsgs()
|
||||
if err := txBuilder.SetMsgs(msgs...); err != nil {
|
||||
return fmt.Errorf("failed to set messages: %w", err)
|
||||
}
|
||||
|
||||
// Set gas limit
|
||||
gasLimit, _ := cmd.Flags().GetUint64("gas")
|
||||
if gasLimit == 0 {
|
||||
gasLimit = 200000 // Default gas limit
|
||||
}
|
||||
txBuilder.SetGasLimit(gasLimit)
|
||||
|
||||
// Set fee
|
||||
gasPrices, _ := cmd.Flags().GetString("gas-prices")
|
||||
if gasPrices != "" {
|
||||
coins, err := sdk.ParseDecCoins(gasPrices)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse gas prices: %w", err)
|
||||
}
|
||||
fees := make(sdk.Coins, len(coins))
|
||||
for i, coin := range coins {
|
||||
fee := coin.Amount.MulInt64(int64(gasLimit)).TruncateInt()
|
||||
fees[i] = sdk.NewCoin(coin.Denom, fee)
|
||||
}
|
||||
txBuilder.SetFeeAmount(fees)
|
||||
}
|
||||
|
||||
// Create sign mode info for simulation
|
||||
sigV2 := signing.SignatureV2{
|
||||
PubKey: nil, // Will be filled by simulation
|
||||
Data: &signing.SingleSignatureData{
|
||||
SignMode: signing.SignMode_SIGN_MODE_DIRECT,
|
||||
Signature: nil,
|
||||
},
|
||||
}
|
||||
|
||||
if err := txBuilder.SetSignatures(sigV2); err != nil {
|
||||
return fmt.Errorf("failed to set signatures: %w", err)
|
||||
}
|
||||
|
||||
// Prepare simulation request
|
||||
txBytes, err = clientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode transaction: %w", err)
|
||||
}
|
||||
|
||||
// Simulate the transaction
|
||||
// Note: In a real implementation, this would call the actual simulation endpoint
|
||||
fmt.Printf(
|
||||
"Simulating transaction from wallet: %s (DID: %s)\n",
|
||||
issuerResp.Address,
|
||||
issuerResp.IssuerDID,
|
||||
)
|
||||
|
||||
// Output simulation results
|
||||
result := map[string]any{
|
||||
"simulation": map[string]any{
|
||||
"gas_estimate": gasLimit,
|
||||
"gas_adjustment": 1.5,
|
||||
"estimated_fees": fmt.Sprintf("%dusnr", gasLimit*10), // Example fee calculation
|
||||
"tx_size_bytes": len(txBytes),
|
||||
"message_count": len(msgs),
|
||||
},
|
||||
"wallet": map[string]any{
|
||||
"did": issuerResp.IssuerDID,
|
||||
"address": issuerResp.Address,
|
||||
},
|
||||
"status": "simulation_successful",
|
||||
}
|
||||
|
||||
return clientCtx.PrintObjectLegacy(result)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
cmd.Flags().String("enclave-data", "", "Enclave data for simulation (required)")
|
||||
cmd.MarkFlagRequired("enclave-data")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+5
-41
@@ -4,59 +4,23 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
|
||||
"github.com/sonr-io/snrd/x/dwn/types"
|
||||
"github.com/sonr-io/sonr/x/dwn/types"
|
||||
)
|
||||
|
||||
// !NOTE: Must enable in module.go (disabled in favor of autocli.go)
|
||||
|
||||
// NewTxCmd returns a root CLI command handler for certain modules
|
||||
// transaction commands.
|
||||
// NewTxCmd returns the root transaction command for the DWN module
|
||||
func NewTxCmd() *cobra.Command {
|
||||
txCmd := &cobra.Command{
|
||||
Use: types.ModuleName,
|
||||
Short: types.ModuleName + " subcommands.",
|
||||
Short: "Transaction commands for " + types.ModuleName,
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: client.ValidateCmd,
|
||||
}
|
||||
|
||||
txCmd.AddCommand(
|
||||
MsgUpdateParams(),
|
||||
GetWalletTxCommands(),
|
||||
)
|
||||
|
||||
return txCmd
|
||||
}
|
||||
|
||||
// Returns a CLI command handler for registering a
|
||||
// contract for the module.
|
||||
func MsgUpdateParams() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update-params [some-value]",
|
||||
Short: "Update the params (must be submitted from the authority)",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cliCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
senderAddress := cliCtx.GetFromAddress()
|
||||
|
||||
msg := &types.MsgUpdateParams{
|
||||
Authority: senderAddress.String(),
|
||||
Params: types.Params{},
|
||||
}
|
||||
|
||||
if err := msg.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(cliCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -0,0 +1,441 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dwn/client/plugin"
|
||||
"github.com/sonr-io/sonr/x/dwn/types"
|
||||
)
|
||||
|
||||
// GetWalletTxCommands returns wallet-specific transaction commands
|
||||
func GetWalletTxCommands() *cobra.Command {
|
||||
walletTxCmd := &cobra.Command{
|
||||
Use: "wallet",
|
||||
Short: "Wallet transaction commands",
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: client.ValidateCmd,
|
||||
}
|
||||
|
||||
walletTxCmd.AddCommand(
|
||||
GetCmdWalletExecute(),
|
||||
GetCmdWalletSponsor(),
|
||||
GetCmdWalletEVM(),
|
||||
)
|
||||
|
||||
return walletTxCmd
|
||||
}
|
||||
|
||||
// GetCmdWalletExecute creates a command to execute wallet transactions using UCAN tokens
|
||||
func GetCmdWalletExecute() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "execute [target-did] [permissions]",
|
||||
Short: "Execute wallet transaction using UCAN origin token",
|
||||
Long: `Execute a wallet transaction by creating and using a UCAN origin token.
|
||||
The permissions should be provided as JSON array of capability attenuations.
|
||||
|
||||
Example:
|
||||
snrd tx dwn wallet execute did:sonr:target123 '[{"can":["sign"],"with":"vault://example"}]' --from alice
|
||||
|
||||
The command will:
|
||||
1. Load the Motor plugin with enclave data
|
||||
2. Create a UCAN origin token with specified permissions
|
||||
3. Execute the transaction with proper authorization`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetDID := args[0]
|
||||
permissionsJSON := args[1]
|
||||
|
||||
// Parse permissions
|
||||
var permissions []map[string]any
|
||||
if parseErr := json.Unmarshal([]byte(permissionsJSON), &permissions); parseErr != nil {
|
||||
return fmt.Errorf("failed to parse permissions JSON: %w", parseErr)
|
||||
}
|
||||
|
||||
// Get enclave data from flags
|
||||
enclaveDataHex, err := cmd.Flags().GetString("enclave-data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enclaveDataHex == "" {
|
||||
return fmt.Errorf("enclave-data flag is required")
|
||||
}
|
||||
|
||||
enclaveData, err := parseEnclaveData(enclaveDataHex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse enclave data: %w", err)
|
||||
}
|
||||
|
||||
// Get optional expiration time
|
||||
expiresAt, err := cmd.Flags().GetInt64("expires-at")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If no expiration provided, default to 1 hour
|
||||
if expiresAt == 0 {
|
||||
expiresAt = time.Now().Add(time.Hour).Unix()
|
||||
}
|
||||
|
||||
// Create enclave configuration
|
||||
chainID := clientCtx.ChainID
|
||||
if chainID == "" {
|
||||
chainID = DefaultTestChainID
|
||||
}
|
||||
config := plugin.CreateEnclaveConfig(chainID, enclaveData)
|
||||
|
||||
// Load plugin and create UCAN token
|
||||
ctx := context.Background()
|
||||
motorPlugin, err := plugin.LoadPluginWithManager(ctx, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load Motor plugin: %w", err)
|
||||
}
|
||||
|
||||
// Create UCAN origin token request
|
||||
tokenReq := &plugin.NewOriginTokenRequest{
|
||||
AudienceDID: targetDID,
|
||||
Attenuations: permissions,
|
||||
ExpiresAt: expiresAt,
|
||||
}
|
||||
|
||||
// Create the UCAN token
|
||||
tokenResp, err := motorPlugin.NewOriginToken(tokenReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create UCAN token: %w", err)
|
||||
}
|
||||
|
||||
if tokenResp.Error != "" {
|
||||
return fmt.Errorf("plugin error creating token: %s", tokenResp.Error)
|
||||
}
|
||||
|
||||
// Create transaction message
|
||||
msg := &types.MsgRecordsWrite{
|
||||
Author: clientCtx.GetFromAddress().String(),
|
||||
Target: targetDID,
|
||||
Data: []byte(fmt.Sprintf("UCAN Token Execution: %s", tokenResp.Token)),
|
||||
Authorization: tokenResp.Token,
|
||||
}
|
||||
|
||||
// Validate message
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate transaction
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
// Add transaction flags
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
|
||||
// Add wallet-specific flags
|
||||
cmd.Flags().String("enclave-data", "", "Hex-encoded enclave data for wallet operations")
|
||||
cmd.Flags().
|
||||
Int64("expires-at", 0, "UCAN token expiration timestamp (defaults to 1 hour from now)")
|
||||
|
||||
// Mark required flags
|
||||
if err := cmd.MarkFlagRequired("enclave-data"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdWalletSponsor creates a command to sponsor wallets with UCAN delegation
|
||||
func GetCmdWalletSponsor() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "sponsor [wallet-address] [amount]",
|
||||
Short: "Sponsor a wallet with UCAN delegation token",
|
||||
Long: `Sponsor a wallet by creating a delegated UCAN token with spending permissions.
|
||||
The amount should be specified in the base denomination (usnr for staking, snr for transfers).
|
||||
|
||||
Example:
|
||||
snrd tx dwn wallet sponsor sonr1abc123... 1000000usnr --from alice
|
||||
|
||||
This creates an attenuated UCAN token that allows the sponsored wallet to spend
|
||||
up to the specified amount on behalf of the sponsor.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
walletAddress := args[0]
|
||||
amountStr := args[1]
|
||||
|
||||
// Parse amount
|
||||
amount, err := strconv.ParseInt(amountStr, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse amount: %w", err)
|
||||
}
|
||||
|
||||
// Get parent token and enclave data from flags
|
||||
parentToken, err := cmd.Flags().GetString("parent-token")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enclaveDataHex, err := cmd.Flags().GetString("enclave-data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enclaveDataHex == "" {
|
||||
return fmt.Errorf("enclave-data flag is required")
|
||||
}
|
||||
|
||||
if parentToken == "" {
|
||||
return fmt.Errorf("parent-token flag is required for delegation")
|
||||
}
|
||||
|
||||
enclaveData, err := parseEnclaveData(enclaveDataHex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse enclave data: %w", err)
|
||||
}
|
||||
|
||||
// Get optional expiration time
|
||||
expiresAt, err := cmd.Flags().GetInt64("expires-at")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If no expiration provided, default to 24 hours
|
||||
if expiresAt == 0 {
|
||||
expiresAt = time.Now().Add(24 * time.Hour).Unix()
|
||||
}
|
||||
|
||||
// Create enclave configuration
|
||||
chainID := clientCtx.ChainID
|
||||
if chainID == "" {
|
||||
chainID = DefaultTestChainID
|
||||
}
|
||||
config := plugin.CreateEnclaveConfig(chainID, enclaveData)
|
||||
|
||||
// Load plugin and create attenuated UCAN token
|
||||
ctx := context.Background()
|
||||
motorPlugin, err := plugin.LoadPluginWithManager(ctx, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load Motor plugin: %w", err)
|
||||
}
|
||||
|
||||
// Create attenuated UCAN token with spending limits
|
||||
attenuations := []map[string]any{
|
||||
{
|
||||
"can": []string{"spend"},
|
||||
"with": walletAddress,
|
||||
"nb": map[string]any{"max_amount": amount},
|
||||
},
|
||||
}
|
||||
|
||||
tokenReq := &plugin.NewAttenuatedTokenRequest{
|
||||
ParentToken: parentToken,
|
||||
AudienceDID: walletAddress,
|
||||
Attenuations: attenuations,
|
||||
ExpiresAt: expiresAt,
|
||||
}
|
||||
|
||||
// Create the delegated UCAN token
|
||||
tokenResp, err := motorPlugin.NewAttenuatedToken(tokenReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create attenuated UCAN token: %w", err)
|
||||
}
|
||||
|
||||
if tokenResp.Error != "" {
|
||||
return fmt.Errorf("plugin error creating token: %s", tokenResp.Error)
|
||||
}
|
||||
|
||||
// Create sponsorship message
|
||||
sponsorshipData := map[string]any{
|
||||
"type": "wallet_sponsorship",
|
||||
"sponsored_wallet": walletAddress,
|
||||
"max_amount": amount,
|
||||
"sponsor": clientCtx.GetFromAddress().String(),
|
||||
"ucan_token": tokenResp.Token,
|
||||
}
|
||||
|
||||
dataBytes, err := json.Marshal(sponsorshipData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal sponsorship data: %w", err)
|
||||
}
|
||||
|
||||
// Create transaction message
|
||||
msg := &types.MsgRecordsWrite{
|
||||
Author: clientCtx.GetFromAddress().String(),
|
||||
Target: walletAddress,
|
||||
Data: dataBytes,
|
||||
Authorization: tokenResp.Token,
|
||||
}
|
||||
|
||||
// Validate message
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate transaction
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
// Add transaction flags
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
|
||||
// Add wallet-specific flags
|
||||
cmd.Flags().String("enclave-data", "", "Hex-encoded enclave data for wallet operations")
|
||||
cmd.Flags().String("parent-token", "", "Parent UCAN token to delegate from")
|
||||
cmd.Flags().
|
||||
Int64("expires-at", 0, "UCAN token expiration timestamp (defaults to 24 hours from now)")
|
||||
|
||||
// Mark required flags
|
||||
if err := cmd.MarkFlagRequired("enclave-data"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("parent-token"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdWalletEVM creates a command for EVM transaction execution
|
||||
func GetCmdWalletEVM() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "evm [to-address] [data]",
|
||||
Short: "Execute EVM transaction using Motor plugin signing",
|
||||
Long: `Execute an EVM transaction using the Motor plugin for signing.
|
||||
The transaction data should be provided as hex-encoded bytes.
|
||||
|
||||
Example:
|
||||
snrd tx dwn wallet evm 0x742d35Cc6e71cbC... 0xa9059cbb --from alice
|
||||
|
||||
This signs the EVM transaction using MPC-based signing in the Motor plugin
|
||||
and submits it through the DWN module.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
toAddress := args[0]
|
||||
evmData := args[1]
|
||||
|
||||
// Get enclave data from flags
|
||||
enclaveDataHex, err := cmd.Flags().GetString("enclave-data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enclaveDataHex == "" {
|
||||
return fmt.Errorf("enclave-data flag is required")
|
||||
}
|
||||
|
||||
enclaveData, err := parseEnclaveData(enclaveDataHex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse enclave data: %w", err)
|
||||
}
|
||||
|
||||
// Get optional gas limit and gas price
|
||||
gasLimit, err := cmd.Flags().GetUint64("gas-limit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gasPrice, err := cmd.Flags().GetString("gas-price")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create EVM transaction data
|
||||
evmTxData := map[string]any{
|
||||
"type": "evm_transaction",
|
||||
"to": toAddress,
|
||||
"data": evmData,
|
||||
"gas_limit": gasLimit,
|
||||
"gas_price": gasPrice,
|
||||
"from": clientCtx.GetFromAddress().String(),
|
||||
}
|
||||
|
||||
dataBytes, err := json.Marshal(evmTxData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal EVM transaction data: %w", err)
|
||||
}
|
||||
|
||||
// Create enclave configuration
|
||||
chainID := clientCtx.ChainID
|
||||
if chainID == "" {
|
||||
chainID = DefaultTestChainID
|
||||
}
|
||||
config := plugin.CreateEnclaveConfig(chainID, enclaveData)
|
||||
|
||||
// Load plugin and sign the transaction data
|
||||
ctx := context.Background()
|
||||
motorPlugin, err := plugin.LoadPluginWithManager(ctx, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load Motor plugin: %w", err)
|
||||
}
|
||||
|
||||
// Sign the EVM transaction data
|
||||
signReq := &plugin.SignDataRequest{
|
||||
Data: dataBytes,
|
||||
}
|
||||
|
||||
signResp, err := motorPlugin.SignData(signReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sign EVM transaction: %w", err)
|
||||
}
|
||||
|
||||
if signResp.Error != "" {
|
||||
return fmt.Errorf("plugin error signing data: %s", signResp.Error)
|
||||
}
|
||||
|
||||
// Create DWN message with signed EVM transaction
|
||||
msg := &types.MsgRecordsWrite{
|
||||
Author: clientCtx.GetFromAddress().String(),
|
||||
Target: toAddress,
|
||||
Data: dataBytes,
|
||||
Authorization: string(signResp.Signature),
|
||||
}
|
||||
|
||||
// Validate message
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate transaction
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
// Add transaction flags
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
|
||||
// Add EVM-specific flags
|
||||
cmd.Flags().String("enclave-data", "", "Hex-encoded enclave data for wallet operations")
|
||||
cmd.Flags().Uint64("gas-limit", 21000, "Gas limit for EVM transaction")
|
||||
cmd.Flags().String("gas-price", "1000000000", "Gas price in wei for EVM transaction")
|
||||
|
||||
// Mark required flags
|
||||
if err := cmd.MarkFlagRequired("enclave-data"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dwn/client/plugin"
|
||||
)
|
||||
|
||||
// VerifyCmd returns a command to verify signatures using the Motor plugin
|
||||
func VerifyCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "verify [signature-file]",
|
||||
Short: "Verify a signature using Motor plugin",
|
||||
Long: `Verify a signature against data using the Motor plugin's MPC-based verification.
|
||||
|
||||
Examples:
|
||||
# Verify a signature file
|
||||
snrd wallet verify signature.json --data "Hello World" --enclave-data @enclave.json
|
||||
|
||||
# Verify with data from file
|
||||
snrd wallet verify signature.json --data @message.txt --enclave-data @enclave.json`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read signature file
|
||||
sigFile := args[0]
|
||||
sigData, err := os.ReadFile(sigFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read signature file: %w", err)
|
||||
}
|
||||
|
||||
// Parse signature data
|
||||
var sigInfo struct {
|
||||
Signature string `json:"signature"`
|
||||
Signer struct {
|
||||
DID string `json:"did"`
|
||||
Address string `json:"address"`
|
||||
} `json:"signer"`
|
||||
}
|
||||
if err := json.Unmarshal(sigData, &sigInfo); err != nil {
|
||||
return fmt.Errorf("failed to parse signature file: %w", err)
|
||||
}
|
||||
|
||||
// Get data to verify against
|
||||
dataInput, err := cmd.Flags().GetString("data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dataInput == "" {
|
||||
return fmt.Errorf("--data flag is required")
|
||||
}
|
||||
|
||||
var dataToVerify []byte
|
||||
if dataInput[0] == '@' {
|
||||
// Read from file
|
||||
dataToVerify, err = os.ReadFile(dataInput[1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read data file: %w", err)
|
||||
}
|
||||
} else {
|
||||
dataToVerify = []byte(dataInput)
|
||||
}
|
||||
|
||||
// Get enclave data
|
||||
enclaveDataStr, err := cmd.Flags().GetString("enclave-data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enclaveDataStr == "" {
|
||||
return fmt.Errorf("--enclave-data flag is required")
|
||||
}
|
||||
|
||||
enclaveData, err := parseEnclaveData(enclaveDataStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse enclave data: %w", err)
|
||||
}
|
||||
|
||||
// Load the plugin
|
||||
chainID := clientCtx.ChainID
|
||||
if chainID == "" {
|
||||
chainID = DefaultTestChainID
|
||||
}
|
||||
config := plugin.CreateEnclaveConfig(chainID, enclaveData)
|
||||
|
||||
ctx := context.Background()
|
||||
motorPlugin, err := plugin.LoadPluginWithManager(ctx, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load Motor plugin: %w", err)
|
||||
}
|
||||
|
||||
// Decode signature
|
||||
signature, err := hex.DecodeString(sigInfo.Signature)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode signature: %w", err)
|
||||
}
|
||||
|
||||
// Verify the signature
|
||||
verifyReq := &plugin.VerifyDataRequest{
|
||||
Data: dataToVerify,
|
||||
Signature: signature,
|
||||
}
|
||||
verifyResp, err := motorPlugin.VerifyData(verifyReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to verify signature: %w", err)
|
||||
}
|
||||
|
||||
if verifyResp.Error != "" {
|
||||
return fmt.Errorf("plugin verification error: %s", verifyResp.Error)
|
||||
}
|
||||
|
||||
// Output verification result
|
||||
result := map[string]any{
|
||||
"valid": verifyResp.Valid,
|
||||
"signer": map[string]any{
|
||||
"did": sigInfo.Signer.DID,
|
||||
"address": sigInfo.Signer.Address,
|
||||
},
|
||||
"signature_length": len(signature),
|
||||
"data_length": len(dataToVerify),
|
||||
}
|
||||
|
||||
if verifyResp.Valid {
|
||||
fmt.Println("✓ Signature is valid")
|
||||
} else {
|
||||
fmt.Println("✗ Signature is invalid")
|
||||
}
|
||||
|
||||
return clientCtx.PrintObjectLegacy(result)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
cmd.Flags().String("data", "", "Data to verify signature against (text or @file)")
|
||||
cmd.Flags().String("enclave-data", "", "Enclave data for verification (required)")
|
||||
cmd.MarkFlagRequired("data")
|
||||
cmd.MarkFlagRequired("enclave-data")
|
||||
|
||||
return cmd
|
||||
}
|
||||
Reference in New Issue
Block a user