* clear

* feat: Add everything

* fix: Commenht
This commit is contained in:
Prad Nukala
2025-10-03 14:45:52 -04:00
committed by GitHub
parent 43b4a11c06
commit 13e6c3e84d
1935 changed files with 655061 additions and 40058 deletions
+98
View File
@@ -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
}
+24
View File
@@ -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
View File
@@ -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
}
+305
View File
@@ -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
}
+157
View File
@@ -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
}
+190
View File
@@ -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
View File
@@ -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
}
+441
View File
@@ -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
}
+154
View File
@@ -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
}