mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
442 lines
12 KiB
Go
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
|
|
}
|