diff --git a/libwallet/assets/btc/txauthor.go b/libwallet/assets/btc/txauthor.go index a8ae0f8ba..b7c14c892 100644 --- a/libwallet/assets/btc/txauthor.go +++ b/libwallet/assets/btc/txauthor.go @@ -276,9 +276,9 @@ func (asset *Asset) EstimateMaxSendAmount() (*sharedW.Amount, error) { } // Broadcast broadcasts the transaction to the network. -func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byte, error) { +func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) (string, error) { if !asset.WalletOpened() { - return nil, utils.ErrBTCNotInitialized + return "", utils.ErrBTCNotInitialized } asset.TxAuthoredInfo.mu.Lock() @@ -286,7 +286,7 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt unsignedTx, err := asset.unsignedTransaction() if err != nil { - return nil, utils.TranslateError(err) + return "", utils.TranslateError(err) } // If the change output is the only one, no need to change position. @@ -305,7 +305,7 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt err = asset.Internal().BTC.Unlock([]byte(privatePassphrase), lock) if err != nil { log.Errorf("unlocking the wallet failed: %v", err) - return nil, errors.New(utils.ErrInvalidPassphrase) + return "", errors.New(utils.ErrInvalidPassphrase) } // To discourage fee sniping, LockTime is explicitly set in the raw tx. @@ -317,7 +317,7 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt _, previousTXout, _, _, err := asset.Internal().BTC.FetchInputInfo(&txIn.PreviousOutPoint) if err != nil { log.Errorf("fetch previous outpoint txout failed: %v", err) - return nil, err + return "", err } prevOutScript := unsignedTx.PrevScripts[index] @@ -330,7 +330,7 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt ) if err != nil { log.Errorf("generating input signatures failed: %v", err) - return nil, err + return "", err } msgTx.TxIn[index].Witness = witness @@ -344,11 +344,11 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt prevOutAmount, prevOutFetcher) if err != nil { log.Errorf("creating validation engine failed: %v", err) - return nil, err + return "", err } if err := vm.Execute(); err != nil { log.Errorf("executing the validation engine failed: %v", err) - return nil, err + return "", err } } @@ -357,18 +357,19 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt err = msgTx.Serialize(&serializedTransaction) if err != nil { log.Errorf("encoding the tx to test its validity failed: %v", err) - return nil, err + return "", err } err = msgTx.Deserialize(bytes.NewReader(serializedTransaction.Bytes())) if err != nil { // Invalid tx log.Errorf("decoding the tx to test its validity failed: %v", err) - return nil, err + return "", err } err = asset.Internal().BTC.PublishTransaction(msgTx, transactionLabel) - return nil, utils.TranslateError(err) + txHash := msgTx.TxHash() + return txHash.String(), utils.TranslateError(err) } func (asset *Asset) unsignedTransaction() (*txauthor.AuthoredTx, error) { diff --git a/libwallet/assets/dcr/txauthor.go b/libwallet/assets/dcr/txauthor.go index bd82749ff..29fe23902 100644 --- a/libwallet/assets/dcr/txauthor.go +++ b/libwallet/assets/dcr/txauthor.go @@ -208,20 +208,20 @@ func (asset *Asset) EstimateMaxSendAmount() (*sharedW.Amount, error) { }, nil } -func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byte, error) { +func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) (string, error) { if !asset.WalletOpened() { - return nil, utils.ErrDCRNotInitialized + return "", utils.ErrDCRNotInitialized } n, err := asset.Internal().DCR.NetworkBackend() if err != nil { log.Error(err) - return nil, err + return "", err } unsignedTx, err := asset.unsignedTransaction() if err != nil { - return nil, utils.TranslateError(err) + return "", utils.TranslateError(err) } if unsignedTx.ChangeIndex >= 0 { @@ -233,7 +233,7 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt err = unsignedTx.Tx.Serialize(&txBuf) if err != nil { log.Error(err) - return nil, err + return "", err } var msgTx wire.MsgTx @@ -241,7 +241,7 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt if err != nil { log.Error(err) // Bytes do not represent a valid raw transaction - return nil, err + return "", err } lock := make(chan time.Time, 1) @@ -253,7 +253,7 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt err = asset.Internal().DCR.Unlock(ctx, []byte(privatePassphrase), lock) if err != nil { log.Error(err) - return nil, errors.New(utils.ErrInvalidPassphrase) + return "", errors.New(utils.ErrInvalidPassphrase) } var additionalPkScripts map[wire.OutPoint][]byte @@ -261,7 +261,7 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt invalidSigs, err := asset.Internal().DCR.SignTransaction(ctx, &msgTx, txscript.SigHashAll, additionalPkScripts, nil, nil) if err != nil { log.Error(err) - return nil, err + return "", err } invalidInputIndexes := make([]uint32, len(invalidSigs)) @@ -274,22 +274,21 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt err = msgTx.Serialize(&serializedTransaction) if err != nil { log.Error(err) - return nil, err + return "", err } err = msgTx.Deserialize(bytes.NewReader(serializedTransaction.Bytes())) if err != nil { // Invalid tx log.Error(err) - return nil, err + return "", err } txHash, err := asset.Internal().DCR.PublishTransaction(ctx, &msgTx, n) if err != nil { - return nil, utils.TranslateError(err) + return "", utils.TranslateError(err) } - - return txHash[:], asset.updateTxLabel(txHash, transactionLabel) + return txHash.String(), asset.updateTxLabel(txHash, transactionLabel) } // updateTxLabel saves the tx label in the local instance. diff --git a/libwallet/assets/ltc/txauthor.go b/libwallet/assets/ltc/txauthor.go index 5b94d0cea..c5cc9ebbc 100644 --- a/libwallet/assets/ltc/txauthor.go +++ b/libwallet/assets/ltc/txauthor.go @@ -252,9 +252,9 @@ func (asset *Asset) EstimateMaxSendAmount() (*sharedW.Amount, error) { } // Broadcast broadcasts the transaction to the network. -func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byte, error) { +func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) (string, error) { if !asset.WalletOpened() { - return nil, utils.ErrLTCNotInitialized + return "", utils.ErrLTCNotInitialized } asset.TxAuthoredInfo.mu.Lock() @@ -262,7 +262,7 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt unsignedTx, err := asset.unsignedTransaction() if err != nil { - return nil, utils.TranslateError(err) + return "", utils.TranslateError(err) } // If the change output is the only one, no need to change position. @@ -281,7 +281,7 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt err = asset.Internal().LTC.Unlock([]byte(privatePassphrase), lock) if err != nil { log.Errorf("unlocking the wallet failed: %v", err) - return nil, errors.New(utils.ErrInvalidPassphrase) + return "", errors.New(utils.ErrInvalidPassphrase) } // To discourage fee sniping, LockTime is explicitly set in the raw tx. @@ -293,7 +293,7 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt _, previousTXout, _, _, err := asset.Internal().LTC.FetchInputInfo(&txIn.PreviousOutPoint) if err != nil { log.Errorf("fetch previous outpoint txout failed: %v", err) - return nil, err + return "", err } prevOutScript := unsignedTx.PrevScripts[index] @@ -306,7 +306,7 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt ) if err != nil { log.Errorf("generating input signatures failed: %v", err) - return nil, err + return "", err } msgTx.TxIn[index].Witness = witness @@ -320,11 +320,11 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt prevOutAmount, prevOutFetcher) if err != nil { log.Errorf("creating validation engine failed: %v", err) - return nil, err + return "", err } if err := vm.Execute(); err != nil { log.Errorf("executing the validation engine failed: %v", err) - return nil, err + return "", err } } @@ -333,18 +333,19 @@ func (asset *Asset) Broadcast(privatePassphrase, transactionLabel string) ([]byt err = msgTx.Serialize(&serializedTransaction) if err != nil { log.Errorf("encoding the tx to test its validity failed: %v", err) - return nil, err + return "", err } err = msgTx.Deserialize(bytes.NewReader(serializedTransaction.Bytes())) if err != nil { // Invalid tx log.Errorf("decoding the tx to test its validity failed: %v", err) - return nil, err + return "", err } err = asset.Internal().LTC.PublishTransaction(msgTx, transactionLabel) - return nil, utils.TranslateError(err) + txHash := msgTx.TxHash() + return txHash.String(), utils.TranslateError(err) } func (asset *Asset) unsignedTransaction() (*txauthor.AuthoredTx, error) { diff --git a/libwallet/assets/wallet/asset_interface.go b/libwallet/assets/wallet/asset_interface.go index 10d98e3f9..a2193ae1b 100644 --- a/libwallet/assets/wallet/asset_interface.go +++ b/libwallet/assets/wallet/asset_interface.go @@ -111,7 +111,7 @@ type Asset interface { NewUnsignedTx(accountNumber int32, utxos []*UnspentOutput) error AddSendDestination(id int, address string, unitAmount int64, sendMax bool) error ComputeTxSizeEstimation(dstAddress string, utxos []*UnspentOutput) (int, error) - Broadcast(passphrase, label string) ([]byte, error) + Broadcast(passphrase, label string) (string, error) EstimateFeeAndSize() (*TxFeeAndSize, error) IsUnsignedTxExist() bool RemoveSendDestination(id int) diff --git a/ui/page/receive/receive_page.go b/ui/page/receive/receive_page.go index ec91c7126..0fc004106 100644 --- a/ui/page/receive/receive_page.go +++ b/ui/page/receive/receive_page.go @@ -60,7 +60,9 @@ type Page struct { selectedWallet sharedW.Asset navigateToSyncBtn cryptomaterial.Button - closeButton cryptomaterial.Button + closeButton cryptomaterial.Button + qrCopyButton *widget.Clickable + addressCopyButton *widget.Clickable } func NewReceivePage(l *load.Load, wallet sharedW.Asset) *Page { @@ -78,6 +80,8 @@ func NewReceivePage(l *load.Load, wallet sharedW.Asset) *Page { newAddr: l.Theme.NewClickable(false), card: l.Theme.Card(), backdrop: new(widget.Clickable), + qrCopyButton: new(widget.Clickable), + addressCopyButton: new(widget.Clickable), navigateToSyncBtn: l.Theme.Button(values.String(values.StrStartSync)), selectedWallet: wallet, } @@ -315,7 +319,9 @@ func (pg *Page) contentLayout(gtx C) D { if pg.qrImage == nil { return D{} } - return pg.Theme.ImageIcon(gtx, *pg.qrImage, 150) + return pg.qrCopyButton.Layout(gtx, func(gtx C) D { + return pg.Theme.ImageIcon(gtx, *pg.qrImage, 150) + }) }), layout.Rigid(layout.Spacer{Height: values.MarginPadding24}.Layout), layout.Rigid(pg.addressLayout), @@ -426,12 +432,14 @@ func (pg *Page) addressLayout(gtx C) D { } gtx.Constraints.Min.X = gtx.Constraints.Max.X return border.Layout(gtx, func(gtx C) D { - return components.VerticalInset(values.MarginPadding12).Layout(gtx, func(gtx C) D { - lbl := pg.Theme.Label(values.TextSizeTransform(pg.IsMobileView(), values.TextSize16), "") - if pg.currentAddress != "" && pg.selectedWallet.IsSynced() { - lbl.Text = pg.currentAddress - } - return layout.Center.Layout(gtx, lbl.Layout) + return pg.addressCopyButton.Layout(gtx, func(gtx C) D { + return components.VerticalInset(values.MarginPadding12).Layout(gtx, func(gtx C) D { + lbl := pg.Theme.Label(values.TextSizeTransform(pg.IsMobileView(), values.TextSize16), "") + if pg.currentAddress != "" && pg.selectedWallet.IsSynced() { + lbl.Text = pg.currentAddress + } + return layout.Center.Layout(gtx, lbl.Layout) + }) }) }) } @@ -495,7 +503,7 @@ generateAddress: func (pg *Page) handleCopyEvent(gtx C) { // Prevent copying again if the timer hasn't expired - if pg.copy.Clicked(gtx) && !pg.isCopying { + if (pg.copy.Clicked(gtx) || pg.qrCopyButton.Clicked(gtx) || pg.addressCopyButton.Clicked(gtx)) && !pg.isCopying { gtx.Execute(clipboard.WriteCmd{Data: io.NopCloser(strings.NewReader(pg.currentAddress))}) pg.Toast.Notify(values.String(values.StrCopied)) } diff --git a/ui/page/send/page.go b/ui/page/send/page.go index 9a5d39e56..61079aa4a 100644 --- a/ui/page/send/page.go +++ b/ui/page/send/page.go @@ -15,6 +15,7 @@ import ( "github.com/crypto-power/cryptopower/ui/load" "github.com/crypto-power/cryptopower/ui/modal" "github.com/crypto-power/cryptopower/ui/page/components" + txpage "github.com/crypto-power/cryptopower/ui/page/transaction" "github.com/crypto-power/cryptopower/ui/utils" "github.com/crypto-power/cryptopower/ui/values" ) @@ -607,7 +608,15 @@ func (pg *Page) HandleUserInteractions(gtx C) { if pg.nextButton.Clicked(gtx) { if pg.selectedWallet.IsUnsignedTxExist() { - pg.confirmTxModal = newSendConfirmModal(pg.Load, pg.authoredTxData, pg.selectedWallet) + pg.confirmTxModal = newSendConfirmModal(pg.Load, pg.authoredTxData, pg.selectedWallet, func(txHash string) { + if pg.modalLayout == nil { + transaction, err := pg.selectedWallet.GetTransactionRaw(txHash) + if err != nil { + log.Error("get transaction error: ", err) + } + pg.ParentNavigator().Display(txpage.NewTransactionDetailsPage(pg.Load, pg.selectedWallet, transaction)) + } + }) pg.confirmTxModal.exchangeRateSet = pg.exchangeRate != -1 && pg.usdExchangeSet // TODO handle if there are many description texts // this workaround shows the description text when there is only one recipient and does not show when have more than one recipient diff --git a/ui/page/send/send_confirm_modal.go b/ui/page/send/send_confirm_modal.go index 7bd5bfb96..c0faa2726 100644 --- a/ui/page/send/send_confirm_modal.go +++ b/ui/page/send/send_confirm_modal.go @@ -37,13 +37,15 @@ type sendConfirmModal struct { asset sharedW.Asset exchangeRateSet bool txLabel string + sentHandle func(string) } -func newSendConfirmModal(l *load.Load, data *authoredTxData, asset sharedW.Asset) *sendConfirmModal { +func newSendConfirmModal(l *load.Load, data *authoredTxData, asset sharedW.Asset, sentHandle func(string)) *sendConfirmModal { scm := &sendConfirmModal{ Load: l, authoredTxData: data, asset: asset, + sentHandle: sentHandle, } scm.Modal = l.Theme.ModalFloatTitle("send_confirm_modal", l.IsMobileView(), scm.firstLoad) @@ -88,12 +90,15 @@ func (scm *sendConfirmModal) broadcastTransaction() { scm.setLoading(true) go func() { defer scm.setLoading(false) - _, err := scm.asset.Broadcast(password, scm.txLabel) + txHash, err := scm.asset.Broadcast(password, scm.txLabel) if err != nil { scm.SetError(err.Error()) return } - successModal := modal.NewSuccessModal(scm.Load, values.String(values.StrTxSent), modal.DefaultClickFunc()) + successModal := modal.NewSuccessModal(scm.Load, values.String(values.StrTxSent), func(_ bool, _ *modal.InfoModal) bool { + scm.sentHandle(txHash) + return true + }) scm.ParentWindow().ShowModal(successModal) scm.txSent()