Files
sonr/x/dwn/client/cli/tx_wallet.go
Prad NukalaandGitHub 13e6c3e84d Master (#1262)
* clear

* feat: Add everything

* fix: Commenht
2025-10-03 14:45:52 -04:00

442 lines
12 KiB
Go

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
}