Files

1094 lines
26 KiB
Go

// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package keycmd
import (
"context"
"fmt"
"math/big"
"time"
"github.com/luxfi/cli/v2/pkg/cobrautils"
"github.com/luxfi/cli/v2/pkg/contract"
"github.com/luxfi/cli/v2/pkg/warp"
"github.com/luxfi/cli/v2/pkg/key"
"github.com/luxfi/cli/v2/pkg/models"
"github.com/luxfi/cli/v2/pkg/networkoptions"
"github.com/luxfi/cli/v2/pkg/prompts"
"github.com/luxfi/cli/v2/pkg/utils"
"github.com/luxfi/cli/v2/pkg/ux"
"github.com/luxfi/cli/v2/pkg/vm"
"github.com/luxfi/cli/v2/sdk/evm"
"github.com/luxfi/ids"
luxdconstants "github.com/luxfi/node/v2/v2/utils/constants"
"github.com/luxfi/node/v2/v2/utils/crypto/keychain"
ledger "github.com/luxfi/node/v2/v2/utils/crypto/ledger"
"github.com/luxfi/node/v2/v2/utils/formatting/address"
"github.com/luxfi/node/v2/v2/utils/units"
avmtxs "github.com/luxfi/node/v2/v2/vms/xvm/txs"
"github.com/luxfi/node/v2/v2/vms/components/lux"
"github.com/luxfi/node/v2/v2/vms/platformvm/txs"
luxdfee "github.com/luxfi/node/v2/v2/vms/platformvm/txs/fee"
"github.com/luxfi/node/v2/v2/vms/secp256k1fx"
"github.com/luxfi/node/v2/v2/wallet/chain/p/builder"
"github.com/luxfi/node/v2/v2/wallet/subnet/primary"
walletoptions "github.com/luxfi/node/v2/v2/wallet"
"github.com/luxfi/node/v2/v2/wallet/chain/c"
goethereumcommon "github.com/ethereum/go-ethereum/common"
"github.com/spf13/cobra"
)
const (
keyNameFlag = "key"
ledgerIndexFlag = "ledger"
amountFlag = "amount"
destinationAddrFlag = "destination-addr"
wrongLedgerIndexVal = 32768
)
var (
keyName string
ledgerIndex uint32
destinationAddrStr string
amountFlt float64
// token transferrer experimental
originSubnet string
destinationSubnet string
originTransferrerAddress string
destinationTransferrerAddress string
destinationKeyName string
//
senderChainFlags contract.ChainSpec
receiverChainFlags contract.ChainSpec
)
func newTransferCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "transfer [options]",
Short: "Fund a ledger address or stored key from another one",
Long: `The key transfer command allows to transfer funds between stored keys or ledger addresses.`,
RunE: transferF,
Args: cobrautils.ExactArgs(0),
}
networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, false, networkoptions.DefaultSupportedNetworkOptions)
cmd.Flags().StringVarP(
&keyName,
keyNameFlag,
"k",
"",
"key associated to the sender or receiver address",
)
cmd.Flags().Uint32VarP(
&ledgerIndex,
ledgerIndexFlag,
"i",
wrongLedgerIndexVal,
"ledger index associated to the sender or receiver address",
)
cmd.Flags().StringVarP(
&destinationAddrStr,
destinationAddrFlag,
"a",
"",
"destination address",
)
cmd.Flags().StringVar(
&destinationKeyName,
"destination-key",
"",
"key associated to a destination address",
)
cmd.Flags().Float64VarP(
&amountFlt,
amountFlag,
"o",
0,
"amount to send or receive (LUX or TOKEN units)",
)
cmd.Flags().StringVar(
&originSubnet,
"origin-subnet",
"",
"subnet where the funds belong (token transferrer experimental)",
)
cmd.Flags().StringVar(
&destinationSubnet,
"destination-subnet",
"",
"subnet where the funds will be sent (token transferrer experimental)",
)
cmd.Flags().StringVar(
&originTransferrerAddress,
"origin-transferrer-address",
"",
"token transferrer address at the origin subnet (token transferrer experimental)",
)
cmd.Flags().StringVar(
&destinationTransferrerAddress,
"destination-transferrer-address",
"",
"token transferrer address at the destination subnet (token transferrer experimental)",
)
senderChainFlags.SetFlagNames(
"sender-blockchain",
"c-chain-sender",
"p-chain-sender",
"x-chain-sender",
"sender-blockchain-id",
)
senderChainFlags.AddToCmd(cmd, "send from %s")
receiverChainFlags.SetFlagNames(
"receiver-blockchain",
"c-chain-receiver",
"p-chain-receiver",
"x-chain-receiver",
"receiver-blockchain-id",
)
receiverChainFlags.AddToCmd(cmd, "receive at %s")
return cmd
}
func transferF(*cobra.Command, []string) error {
if keyName != "" && ledgerIndex != wrongLedgerIndexVal {
return fmt.Errorf("only one between a keyname or a ledger index must be given")
}
network, err := networkoptions.GetNetworkFromCmdLineFlags(
app,
"On what Network do you want to execute the transfer?",
globalNetworkFlags,
true,
false,
networkoptions.DefaultSupportedNetworkOptions,
"",
)
if err != nil {
return err
}
if !senderChainFlags.Defined() {
prompt := "Where are the funds to transfer?"
if cancel, err := contract.PromptChain(
app,
network,
prompt,
"",
&senderChainFlags,
); err != nil {
return err
} else if cancel {
return nil
}
}
if !receiverChainFlags.Defined() {
prompt := "Where are the funds going to?"
if cancel, err := contract.PromptChain(
app,
network,
prompt,
"",
&receiverChainFlags,
); err != nil {
return err
} else if cancel {
return nil
}
}
if (senderChainFlags.CChain && receiverChainFlags.CChain) ||
(senderChainFlags.BlockchainName != "" && senderChainFlags.BlockchainName == receiverChainFlags.BlockchainName) {
return intraEvmSend(network, senderChainFlags)
}
if !senderChainFlags.PChain && !senderChainFlags.XChain && !receiverChainFlags.PChain && !receiverChainFlags.XChain {
return interEvmSend(network, senderChainFlags, receiverChainFlags)
}
senderDesc, err := contract.GetBlockchainDesc(senderChainFlags)
if err != nil {
return err
}
receiverDesc, err := contract.GetBlockchainDesc(receiverChainFlags)
if err != nil {
return err
}
if senderChainFlags.BlockchainName != "" || receiverChainFlags.BlockchainName != "" || senderChainFlags.XChain {
return fmt.Errorf("transfer from %s to %s is not supported", senderDesc, receiverDesc)
}
if keyName == "" && ledgerIndex == wrongLedgerIndexVal {
var useLedger bool
goalStr := "as the sender address"
if receiverChainFlags.XChain {
ux.Logger.PrintToUser("P->X transfer is an intra-account operation.")
ux.Logger.PrintToUser("Tokens will be transferred to the same account address on the other chain")
goalStr = "specify the sender/receiver address"
}
if senderChainFlags.CChain && receiverChainFlags.PChain {
ux.Logger.PrintToUser("C->P transfer is an intra-account operation.")
ux.Logger.PrintToUser("Tokens will be transferred to the same account address on the other chain")
goalStr = "as the sender/receiver address"
}
useLedger, keyName, err = prompts.GetKeyOrLedger(app.Prompt, goalStr, app.GetKeyDir(), true)
if err != nil {
return err
}
if useLedger {
ledgerIndex, err = app.Prompt.CaptureUint32("Ledger index to use")
if err != nil {
return err
}
}
}
var kc keychain.Keychain
var sk *key.SoftKey
if keyName != "" {
sk, err = app.GetKey(keyName, network, false)
if err != nil {
return err
}
kc = sk.KeyChain()
} else {
ledgerDevice, err := ledger.New()
if err != nil {
return err
}
ledgerIndices := []uint32{ledgerIndex}
kc, err = keychain.NewLedgerKeychainFromIndices(ledgerDevice, ledgerIndices)
if err != nil {
return err
}
}
usingLedger := ledgerIndex != wrongLedgerIndexVal
if amountFlt == 0 {
amountFlt, err = captureAmount("LUX units")
if err != nil {
return err
}
}
amount := uint64(amountFlt * float64(units.Lux))
if destinationAddrStr == "" && senderChainFlags.PChain && (receiverChainFlags.PChain || receiverChainFlags.CChain) {
if destinationKeyName != "" {
k, err := app.GetKey(destinationKeyName, network, false)
if err != nil {
return err
}
if receiverChainFlags.CChain {
destinationAddrStr = k.C()
}
if receiverChainFlags.PChain {
addrs := k.P()
if len(addrs) == 0 {
return fmt.Errorf("unexpected null number of P-Chain addresses for key")
}
destinationAddrStr = addrs[0]
}
} else {
format := prompts.EVMFormat
if receiverChainFlags.PChain {
format = prompts.PChainFormat
}
destinationAddrStr, err = prompts.PromptAddress(
app.Prompt,
"destination address",
app.GetKeyDir(),
app.GetKey,
"",
network,
format,
"destination address",
)
if err != nil {
return err
}
}
}
if senderChainFlags.PChain && receiverChainFlags.PChain {
return pToPSend(
network,
kc,
usingLedger,
destinationAddrStr,
amount,
)
}
if senderChainFlags.PChain && receiverChainFlags.CChain {
return pToCSend(
network,
kc,
usingLedger,
destinationAddrStr,
amount,
)
}
if senderChainFlags.CChain && receiverChainFlags.PChain {
return cToPSend(
network,
kc,
sk,
usingLedger,
amount,
)
}
if senderChainFlags.PChain && receiverChainFlags.XChain {
return pToXSend(
network,
kc,
usingLedger,
amount,
)
}
return nil
}
func captureAmount(tokenDesc string) (float64, error) {
promptStr := fmt.Sprintf("Amount to send (%s)", tokenDesc)
amountFlt, err := app.Prompt.CaptureFloat(promptStr, func(v float64) error {
if v <= 0 {
return fmt.Errorf("value %f must be greater than zero", v)
}
return nil
})
if err != nil {
return 0, err
}
return amountFlt, nil
}
func intraEvmSend(
network models.Network,
senderChain contract.ChainSpec,
) error {
var (
err error
privateKey string
)
if keyName != "" {
k, err := app.GetKey(keyName, network, false)
if err != nil {
return err
}
privateKey = k.PrivKeyHex()
} else {
privateKey, err = prompts.PromptPrivateKey(
app.Prompt,
"sender private key",
app.GetKeyDir(),
app.GetKey,
"",
"",
)
if err != nil {
return err
}
}
if destinationKeyName != "" {
k, err := app.GetKey(destinationKeyName, network, false)
if err != nil {
return err
}
destinationAddrStr = k.C()
}
if destinationAddrStr == "" {
destinationAddrStr, err = prompts.PromptAddress(
app.Prompt,
"destination address",
app.GetKeyDir(),
app.GetKey,
"",
network,
prompts.EVMFormat,
"destination address",
)
if err != nil {
return err
}
}
if amountFlt == 0 {
amountFlt, err = app.Prompt.CaptureFloat(
"Amount to transfer",
func(f float64) error {
if f <= 0 {
return fmt.Errorf("not positive")
}
return nil
},
)
if err != nil {
return err
}
} else if amountFlt < 0 {
return fmt.Errorf("amount must be positive")
}
amountBigFlt := new(big.Float).SetFloat64(amountFlt)
amountBigFlt = amountBigFlt.Mul(amountBigFlt, new(big.Float).SetInt(vm.OneLux))
amount, _ := amountBigFlt.Int(nil)
senderURL, _, err := contract.GetBlockchainEndpoints(
app,
network,
senderChain,
true,
false,
)
if err != nil {
return err
}
client, err := evm.GetClient(senderURL)
if err != nil {
return err
}
receipt, err := client.FundAddress(privateKey, destinationAddrStr, amount)
if err != nil {
return err
}
chainName, err := contract.GetBlockchainDesc(senderChain)
if err != nil {
return err
}
ux.Logger.PrintToUser("%s Paid fee: %.9f LUX",
chainName,
evm.CalculateEvmFeeInLux(receipt.GasUsed, receipt.EffectiveGasPrice))
return err
}
func interEvmSend(
network models.Network,
senderChain contract.ChainSpec,
receiverChain contract.ChainSpec,
) error {
senderURL, _, err := contract.GetBlockchainEndpoints(
app,
network,
senderChain,
true,
false,
)
if err != nil {
return err
}
receiverBlockchainID, err := contract.GetBlockchainID(
app,
network,
receiverChain,
)
if err != nil {
return err
}
senderDesc, err := contract.GetBlockchainDesc(senderChainFlags)
if err != nil {
return err
}
receiverDesc, err := contract.GetBlockchainDesc(receiverChainFlags)
if err != nil {
return err
}
if originTransferrerAddress == "" {
addr, err := app.Prompt.CaptureAddress(
fmt.Sprintf("Enter the address of the Token Transferrer on %s", senderDesc),
)
if err != nil {
return err
}
originTransferrerAddress = addr.Hex()
} else {
if err := prompts.ValidateAddress(originTransferrerAddress); err != nil {
return err
}
}
if destinationTransferrerAddress == "" {
addr, err := app.Prompt.CaptureAddress(
fmt.Sprintf("Enter the address of the Token Transferrer on %s", receiverDesc),
)
if err != nil {
return err
}
destinationTransferrerAddress = addr.Hex()
} else {
if err := prompts.ValidateAddress(destinationTransferrerAddress); err != nil {
return err
}
}
if keyName == "" {
keyName, err = prompts.CaptureKeyName(app.Prompt, "fund the transfer", app.GetKeyDir(), true)
if err != nil {
return err
}
}
originK, err := app.GetKey(keyName, network, false)
if err != nil {
return err
}
privateKey := originK.PrivKeyHex()
var destinationAddr goethereumcommon.Address
if destinationAddrStr == "" && destinationKeyName == "" {
option, err := app.Prompt.CaptureList(
"Do you want to choose a stored key for the destination, or input a destination address?",
[]string{"Key", "Address"},
)
if err != nil {
return err
}
switch option {
case "Key":
destinationKeyName, err = prompts.CaptureKeyName(app.Prompt, "receive the transfer", app.GetKeyDir(), true)
if err != nil {
return err
}
case "Address":
addr, err := app.Prompt.CaptureAddress(
"Enter the destination address",
)
if err != nil {
return err
}
destinationAddrStr = addr.Hex()
}
}
switch {
case destinationAddrStr != "":
if err := prompts.ValidateAddress(destinationAddrStr); err != nil {
return err
}
destinationAddr = goethereumcommon.HexToAddress(destinationAddrStr)
case destinationKeyName != "":
destinationK, err := app.GetKey(destinationKeyName, network, false)
if err != nil {
return err
}
destinationAddrStr = destinationK.C()
destinationAddr = goethereumcommon.HexToAddress(destinationAddrStr)
default:
return fmt.Errorf("you should set the destination address or destination key")
}
if amountFlt == 0 {
amountFlt, err = captureAmount("TOKEN units")
if err != nil {
return err
}
}
amount := new(big.Float).SetFloat64(amountFlt)
amount = amount.Mul(amount, new(big.Float).SetFloat64(float64(units.Lux)))
amount = amount.Mul(amount, new(big.Float).SetFloat64(float64(units.Lux)))
amountInt, _ := amount.Int(nil)
receipt, receipt2, err := warp.Send(
senderURL,
goethereumcommon.HexToAddress(originTransferrerAddress),
privateKey,
receiverBlockchainID,
goethereumcommon.HexToAddress(destinationTransferrerAddress),
destinationAddr,
amountInt,
)
if err != nil {
return err
}
chainName, err := contract.GetBlockchainDesc(senderChain)
if err != nil {
return err
}
ux.Logger.PrintToUser("%s Paid fee: %.9f LUX",
chainName,
evm.CalculateEvmFeeInLux(receipt.GasUsed, receipt.EffectiveGasPrice))
if receipt2 != nil {
chainName, err := contract.GetBlockchainDesc(receiverChain)
if err != nil {
return err
}
ux.Logger.PrintToUser("%s Paid fee: %.9f LUX",
chainName,
evm.CalculateEvmFeeInLux(receipt2.GasUsed, receipt2.EffectiveGasPrice))
}
return nil
}
func pToPSend(
network models.Network,
kc keychain.Keychain,
usingLedger bool,
destinationAddrStr string,
amount uint64,
) error {
ethKeychain := secp256k1fx.NewKeychain()
wallet, err := primary.MakeWallet(
context.Background(),
network.Endpoint,
kc,
ethKeychain,
primary.WalletConfig{},
)
if err != nil {
return err
}
destinationAddr, err := address.ParseToID(destinationAddrStr)
if err != nil {
return err
}
to := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{destinationAddr},
}
output := &lux.TransferableOutput{
Asset: lux.Asset{ID: getBuilderContext(wallet).LUXAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: amount,
OutputOwners: to,
},
}
outputs := []*lux.TransferableOutput{output}
ux.Logger.PrintToUser("Issuing BaseTx P -> P")
if usingLedger {
ux.Logger.PrintToUser("*** Please sign 'Export Tx / P to X Chain' transaction on the ledger device *** ")
}
unsignedTx, err := wallet.P().Builder().NewBaseTx(
outputs,
)
if err != nil {
return fmt.Errorf("error building tx: %w", err)
}
tx := txs.Tx{Unsigned: unsignedTx}
if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil {
return fmt.Errorf("error signing tx: %w", err)
}
ctx, cancel := utils.GetAPIContext()
defer cancel()
err = wallet.P().IssueTx(
&tx,
walletoptions.WithContext(ctx),
)
if err != nil {
if ctx.Err() != nil {
err = fmt.Errorf("timeout issuing/verifying tx with ID %s: %w", tx.ID(), err)
} else {
err = fmt.Errorf("error issuing tx with ID %s: %w", tx.ID(), err)
}
return err
}
pContext := getBuilderContext(wallet)
pFeeCalculator := luxdfee.NewDynamicCalculator(pContext.ComplexityWeights, pContext.GasPrice)
txFee, err := pFeeCalculator.CalculateFee(unsignedTx)
if err != nil {
return err
}
ux.Logger.PrintToUser("P-Chain Paid fee: %.9f LUX", float64(txFee)/float64(units.Lux))
return nil
}
func pToXSend(
network models.Network,
kc keychain.Keychain,
usingLedger bool,
amount uint64,
) error {
ethKeychain := secp256k1fx.NewKeychain()
wallet, err := primary.MakeWallet(
context.Background(),
network.Endpoint,
kc,
ethKeychain,
primary.WalletConfig{},
)
if err != nil {
return err
}
to := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: kc.Addresses().List(),
}
if err := exportFromP(
amount,
wallet,
wallet.X().Builder().Context().BlockchainID,
"X",
to,
usingLedger,
); err != nil {
return err
}
time.Sleep(5 * time.Second)
return importIntoX(
wallet,
luxdconstants.PlatformChainID,
"P",
to,
usingLedger,
)
}
func exportFromP(
amount uint64,
wallet *primary.Wallet,
blockchainID ids.ID,
blockchainAlias string,
to secp256k1fx.OutputOwners,
usingLedger bool,
) error {
output := &lux.TransferableOutput{
Asset: lux.Asset{ID: getBuilderContext(wallet).LUXAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: amount,
OutputOwners: to,
},
}
outputs := []*lux.TransferableOutput{output}
ux.Logger.PrintToUser("Issuing ExportTx P -> %s", blockchainAlias)
if usingLedger {
ux.Logger.PrintToUser("*** Please sign 'Export Tx / P to %s Chain' transaction on the ledger device *** ", blockchainAlias)
}
unsignedTx, err := wallet.P().Builder().NewExportTx(
blockchainID,
outputs,
)
if err != nil {
return fmt.Errorf("error building tx: %w", err)
}
tx := txs.Tx{Unsigned: unsignedTx}
if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil {
return fmt.Errorf("error signing tx: %w", err)
}
ctx, cancel := utils.GetAPIContext()
defer cancel()
err = wallet.P().IssueTx(
&tx,
walletoptions.WithContext(ctx),
)
if err != nil {
if ctx.Err() != nil {
err = fmt.Errorf("timeout issuing/verifying tx with ID %s: %w", tx.ID(), err)
} else {
err = fmt.Errorf("error issuing tx with ID %s: %w", tx.ID(), err)
}
return err
}
pContext := getBuilderContext(wallet)
pFeeCalculator := luxdfee.NewDynamicCalculator(pContext.ComplexityWeights, pContext.GasPrice)
txFee, err := pFeeCalculator.CalculateFee(unsignedTx)
if err != nil {
return err
}
ux.Logger.PrintToUser("P-Chain Paid fee: %.9f LUX", float64(txFee)/float64(units.Lux))
return nil
}
func importIntoX(
wallet *primary.Wallet,
blockchainID ids.ID,
blockchainAlias string,
to secp256k1fx.OutputOwners,
usingLedger bool,
) error {
ux.Logger.PrintToUser("Issuing ImportTx %s -> X", blockchainAlias)
if usingLedger {
ux.Logger.PrintToUser("*** Please sign ImportTx transaction on the ledger device *** ")
}
unsignedTx, err := wallet.X().Builder().NewImportTx(
blockchainID,
&to,
)
if err != nil {
return fmt.Errorf("error building tx: %w", err)
}
tx := avmtxs.Tx{Unsigned: unsignedTx}
if err := wallet.X().Signer().Sign(context.Background(), &tx); err != nil {
return fmt.Errorf("error signing tx: %w", err)
}
ctx, cancel := utils.GetAPIContext()
defer cancel()
err = wallet.X().IssueTx(
&tx,
walletoptions.WithContext(ctx),
)
if err != nil {
if ctx.Err() != nil {
err = fmt.Errorf("timeout issuing/verifying tx with ID %s: %w", tx.ID(), err)
} else {
err = fmt.Errorf("error issuing tx with ID %s: %w", tx.ID(), err)
}
return err
}
ux.Logger.PrintToUser("X-Chain Paid fee: %.9f LUX", float64(wallet.X().Builder().Context().BaseTxFee)/float64(units.Lux))
return nil
}
func pToCSend(
network models.Network,
kc keychain.Keychain,
usingLedger bool,
destinationAddrStr string,
amount uint64,
) error {
ethKeychain := secp256k1fx.NewKeychain()
wallet, err := primary.MakeWallet(
context.Background(),
network.Endpoint,
kc,
ethKeychain,
primary.WalletConfig{},
)
if err != nil {
return err
}
to := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: kc.Addresses().List(),
}
if err := exportFromP(
amount,
wallet,
wallet.C().Builder().Context().BlockchainID,
"C",
to,
usingLedger,
); err != nil {
return err
}
time.Sleep(5 * time.Second)
if err != nil {
return err
}
return importIntoC(
network,
wallet,
luxdconstants.PlatformChainID,
"P",
destinationAddrStr,
usingLedger,
)
}
func importIntoC(
network models.Network,
wallet *primary.Wallet,
blockchainID ids.ID,
blockchainAlias string,
destinationAddrStr string,
usingLedger bool,
) error {
ux.Logger.PrintToUser("Issuing ImportTx %s -> C", blockchainAlias)
if usingLedger {
ux.Logger.PrintToUser("*** Please sign ImportTx transaction on the ledger device *** ")
}
amt, err := wallet.C().Builder().GetImportableBalance(blockchainID)
if err != nil {
return fmt.Errorf("error getting importable balance: %w", err)
}
client, err := evm.GetClient(network.BlockchainEndpoint("C"))
if err != nil {
return err
}
baseFee, err := client.EstimateBaseFee()
if err != nil {
return err
}
unsignedTx, err := wallet.C().Builder().NewImportTx(
blockchainID,
goethereumcommon.HexToAddress(destinationAddrStr),
baseFee,
)
if err != nil {
return fmt.Errorf("error building tx: %w", err)
}
tx := c.Tx{UnsignedAtomicTx: unsignedTx}
if err := wallet.C().Signer().SignAtomic(context.Background(), &tx); err != nil {
return fmt.Errorf("error signing tx: %w", err)
}
ctx, cancel := utils.GetAPIContext()
defer cancel()
err = wallet.C().IssueAtomicTx(
&tx,
walletoptions.WithContext(ctx),
)
if err != nil {
if ctx.Err() != nil {
err = fmt.Errorf("timeout issuing/verifying tx with ID %s: %w", tx.ID(), err)
} else {
err = fmt.Errorf("error issuing tx with ID %s: %w", tx.ID(), err)
}
return err
}
if len(unsignedTx.Outs) == 0 {
return fmt.Errorf("no outputs for C-Chain transaction")
}
ux.Logger.PrintToUser("C-Chain Paid fee: %.9f LUX", float64(amt-unsignedTx.Outs[0].Amount)/float64(units.Lux))
return nil
}
func cToPSend(
network models.Network,
kc keychain.Keychain,
sk *key.SoftKey,
usingLedger bool,
amount uint64,
) error {
ethKeychain := sk.KeyChain()
wallet, err := primary.MakeWallet(
context.Background(),
network.Endpoint,
kc,
ethKeychain,
primary.WalletConfig{},
)
if err != nil {
return err
}
to := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: kc.Addresses().List(),
}
if err := exportFromC(
network,
amount,
wallet,
luxdconstants.PlatformChainID,
"P",
to,
usingLedger,
); err != nil {
return err
}
time.Sleep(5 * time.Second)
wallet, err = primary.MakeWallet(
context.Background(),
network.Endpoint,
kc,
ethKeychain,
primary.WalletConfig{},
)
if err != nil {
return err
}
return importIntoP(
wallet,
wallet.C().Builder().Context().BlockchainID,
"C",
to,
usingLedger,
)
}
func exportFromC(
network models.Network,
amount uint64,
wallet *primary.Wallet,
blockchainID ids.ID,
blockchainAlias string,
to secp256k1fx.OutputOwners,
usingLedger bool,
) error {
ux.Logger.PrintToUser("Issuing ExportTx C -> %s", blockchainAlias)
if usingLedger {
ux.Logger.PrintToUser("*** Please sign ExportTx transaction on the ledger device *** ")
}
client, err := evm.GetClient(network.BlockchainEndpoint("C"))
if err != nil {
return err
}
baseFee, err := client.EstimateBaseFee()
if err != nil {
return err
}
outputs := []*secp256k1fx.TransferOutput{
{
Amt: amount,
OutputOwners: to,
},
}
unsignedTx, err := wallet.C().Builder().NewExportTx(
blockchainID,
outputs,
baseFee,
)
if err != nil {
return fmt.Errorf("error building tx: %w", err)
}
tx := c.Tx{UnsignedAtomicTx: unsignedTx}
if err := wallet.C().Signer().SignAtomic(context.Background(), &tx); err != nil {
return fmt.Errorf("error signing tx: %w", err)
}
ctx, cancel := utils.GetAPIContext()
defer cancel()
err = wallet.C().IssueAtomicTx(
&tx,
walletoptions.WithContext(ctx),
)
if err != nil {
if ctx.Err() != nil {
err = fmt.Errorf("timeout issuing/verifying tx with ID %s: %w", tx.ID(), err)
} else {
err = fmt.Errorf("error issuing tx with ID %s: %w", tx.ID(), err)
}
return err
}
if len(unsignedTx.Ins) == 0 {
return fmt.Errorf("no inputs for C-Chain transaction")
}
ux.Logger.PrintToUser("C-Chain Paid fee: %.9f LUX", float64(unsignedTx.Ins[0].Amount-amount)/float64(units.Lux))
return nil
}
func importIntoP(
wallet *primary.Wallet,
blockchainID ids.ID,
blockchainAlias string,
to secp256k1fx.OutputOwners,
usingLedger bool,
) error {
ux.Logger.PrintToUser("Issuing ImportTx %s -> P", blockchainAlias)
if usingLedger {
ux.Logger.PrintToUser("*** Please sign ImportTx transaction on the ledger device *** ")
}
unsignedTx, err := wallet.P().Builder().NewImportTx(
blockchainID,
&to,
)
if err != nil {
return fmt.Errorf("error building tx: %w", err)
}
tx := txs.Tx{Unsigned: unsignedTx}
if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil {
return fmt.Errorf("error signing tx: %w", err)
}
ctx, cancel := utils.GetAPIContext()
defer cancel()
err = wallet.P().IssueTx(
&tx,
walletoptions.WithContext(ctx),
)
if err != nil {
if ctx.Err() != nil {
err = fmt.Errorf("timeout issuing/verifying tx with ID %s: %w", tx.ID(), err)
} else {
err = fmt.Errorf("error issuing tx with ID %s: %w", tx.ID(), err)
}
return err
}
pContext := getBuilderContext(wallet)
pFeeCalculator := luxdfee.NewDynamicCalculator(pContext.ComplexityWeights, pContext.GasPrice)
txFee, err := pFeeCalculator.CalculateFee(unsignedTx)
if err != nil {
return err
}
ux.Logger.PrintToUser("P-Chain Paid fee: %.9f LUX", float64(txFee)/float64(units.Lux))
return nil
}
func getBuilderContext(wallet *primary.Wallet) *builder.Context {
if wallet == nil {
return nil
}
return wallet.P().Builder().Context()
}