2
2
module Cardano.Wallet.Kernel.Transactions (
3
3
pay
4
4
, estimateFees
5
+ , redeemAda
5
6
-- * Errors
6
7
, NewTransactionError (.. )
7
8
, SignTransactionError (.. )
8
9
, PaymentError (.. )
9
10
, EstimateFeesError (.. )
11
+ , RedeemAdaError (.. )
10
12
, cardanoFee
11
13
-- * Internal & testing use only
12
14
, newTransaction
@@ -32,12 +34,14 @@ import Formatting (bprint, build, sformat, (%))
32
34
import qualified Formatting.Buildable
33
35
34
36
import Pos.Chain.Txp (Utxo )
37
+ import qualified Pos.Client.Txp.Util as CTxp
35
38
import Pos.Core (Address , Coin , unsafeIntegerToCoin , unsafeSubCoin )
36
39
import qualified Pos.Core as Core
37
40
import Pos.Core.Txp (Tx (.. ), TxAux (.. ), TxId , TxIn (.. ), TxOut (.. ),
38
41
TxOutAux (.. ))
39
- import Pos.Crypto (EncryptedSecretKey , PassPhrase , SafeSigner (.. ),
40
- ShouldCheckPassphrase (.. ), hash )
42
+ import Pos.Crypto (EncryptedSecretKey , PassPhrase , RedeemSecretKey ,
43
+ SafeSigner (.. ), ShouldCheckPassphrase (.. ), hash ,
44
+ redeemToPublic )
41
45
42
46
import qualified Cardano.Wallet.Kernel.Addresses as Kernel
43
47
import Cardano.Wallet.Kernel.CoinSelection.FromGeneric
@@ -47,7 +51,8 @@ import Cardano.Wallet.Kernel.CoinSelection.FromGeneric
47
51
import qualified Cardano.Wallet.Kernel.CoinSelection.FromGeneric as CoinSelection
48
52
import Cardano.Wallet.Kernel.CoinSelection.Generic
49
53
(CoinSelHardErr (.. ))
50
- import Cardano.Wallet.Kernel.DB.AcidState (DB , NewPendingError )
54
+ import Cardano.Wallet.Kernel.DB.AcidState (DB , NewForeignError ,
55
+ NewPendingError )
51
56
import Cardano.Wallet.Kernel.DB.HdWallet
52
57
import qualified Cardano.Wallet.Kernel.DB.HdWallet as HD
53
58
import Cardano.Wallet.Kernel.DB.InDb
@@ -56,8 +61,8 @@ import Cardano.Wallet.Kernel.DB.TxMeta.Types
56
61
import Cardano.Wallet.Kernel.Internal (ActiveWallet (.. ),
57
62
walletKeystore , walletNode )
58
63
import qualified Cardano.Wallet.Kernel.Keystore as Keystore
59
- import Cardano.Wallet.Kernel.NodeStateAdaptor ( getMaxTxSize )
60
- import Cardano.Wallet.Kernel.Pending (newPending )
64
+ import qualified Cardano.Wallet.Kernel.NodeStateAdaptor as Node
65
+ import Cardano.Wallet.Kernel.Pending (newForeign , newPending )
61
66
import Cardano.Wallet.Kernel.Read (getWalletSnapshot )
62
67
import Cardano.Wallet.Kernel.Types (AccountId (.. ),
63
68
RawResolvedTx (.. ), WalletId (.. ))
@@ -173,7 +178,7 @@ newTransaction :: ActiveWallet
173
178
-> IO (Either NewTransactionError (TxAux , TxMeta , Utxo ))
174
179
newTransaction ActiveWallet {.. } spendingPassword options accountId payees = runExceptT $ do
175
180
initialEnv <- liftIO $ newEnvironment
176
- maxTxSize <- liftIO $ getMaxTxSize (walletPassive ^. walletNode)
181
+ maxTxSize <- liftIO $ Node. getMaxTxSize (walletPassive ^. walletNode)
177
182
let maxInputs = estimateMaxTxInputs dummyAddrAttrSize dummyTxAttrSize maxTxSize
178
183
179
184
-- STEP 0: Get available UTxO
@@ -413,3 +418,113 @@ cardanoFee inputs outputs = Core.mkCoin $
413
418
estimateCardanoFee linearFeePolicy inputs (toList $ fmap Core. getCoin outputs)
414
419
where
415
420
linearFeePolicy = Core. TxSizeLinear (Core. Coeff 155381 ) (Core. Coeff 43.946 )
421
+
422
+ {- ------------------------------------------------------------------------------
423
+ Ada redemption
424
+
425
+ Old wallet layer implemention lives in "Pos.Wallet.Web.Methods.Redeem".
426
+ -------------------------------------------------------------------------------}
427
+
428
+ data RedeemAdaError =
429
+ -- | Unknown account
430
+ --
431
+ -- NOTE: The Daedalus frontend requires users to create an account before
432
+ -- they can redeem Ada.
433
+ RedeemAdaUnknownAccountId UnknownHdAccount
434
+
435
+ -- | We failed to translate the redeem key to a valid Cardano address
436
+ | RedeemAdaErrorCreateAddressFailed Kernel. CreateAddressError
437
+
438
+ -- | There are no UTxO available at the redeem address
439
+ --
440
+ -- This probably means that the voucher has already been redeemed, but it
441
+ -- /could/ also mean that there was never any voucher at this address.
442
+ | RedeemAdaNotAvailable Address
443
+
444
+ -- | There are multiple outputs available at this address
445
+ --
446
+ -- This really should not happen at all. If this happens, we are running
447
+ -- in a test setup with an invalid genesis block.
448
+ | RedeemAdaMultipleOutputs Address
449
+
450
+ -- | We were unable to submit the transaction
451
+ --
452
+ -- If this error happens, it almost certainly indicates a bug.
453
+ | RedeemAdaNewForeignFailed NewForeignError
454
+
455
+ instance Buildable RedeemAdaError where
456
+ build (RedeemAdaUnknownAccountId err) =
457
+ bprint (" RedeemAdaUnknownAccountId " % build) err
458
+ build (RedeemAdaErrorCreateAddressFailed err) =
459
+ bprint (" RedeemAdaErrorCreateAddressFailed " % build) err
460
+ build (RedeemAdaNotAvailable addr) =
461
+ bprint (" RedeemAdaNotAvailable " % build) addr
462
+ build (RedeemAdaMultipleOutputs addr) =
463
+ bprint (" RedeemAdaMultipleOutputs " % build) addr
464
+ build (RedeemAdaNewForeignFailed err) =
465
+ bprint (" RedeemAdaNewForeignFailed " % build) err
466
+
467
+ -- | Redeem Ada voucher
468
+ --
469
+ -- NOTE: The account must already exist, it is /not/ created implicitly if it
470
+ -- does not yet exist.
471
+ redeemAda :: ActiveWallet
472
+ -> HdAccountId -- ^ Account ID
473
+ -> PassPhrase -- ^ Spending password
474
+ -> RedeemSecretKey -- ^ Redemption key
475
+ -> IO (Either RedeemAdaError (Tx , TxMeta ))
476
+ redeemAda w@ ActiveWallet {.. } accId pw rsk = runExceptT $ do
477
+ snapshot <- liftIO $ getWalletSnapshot walletPassive
478
+ _accExists <- withExceptT RedeemAdaUnknownAccountId $ exceptT $
479
+ lookupHdAccountId snapshot accId
480
+ changeAddr <- withExceptT RedeemAdaErrorCreateAddressFailed $ ExceptT $ liftIO $
481
+ Kernel. createAddress
482
+ pw
483
+ (AccountIdHdRnd accId)
484
+ walletPassive
485
+ (tx, meta) <- mkTx changeAddr
486
+ withExceptT RedeemAdaNewForeignFailed $ ExceptT $ liftIO $
487
+ newForeign
488
+ w
489
+ accId
490
+ tx
491
+ meta
492
+ return (taTx tx, meta)
493
+ where
494
+ redeemAddr :: Address
495
+ redeemAddr = Core. makeRedeemAddress $ redeemToPublic rsk
496
+
497
+ mkTx :: Address -> ExceptT RedeemAdaError IO (TxAux , TxMeta )
498
+ mkTx output = do
499
+ now <- liftIO $ Core. getCurrentTimestamp
500
+ utxo <- liftIO $
501
+ Node. withNodeState (walletPassive ^. walletNode) $ \ _lock ->
502
+ Node. filterUtxo isOutput
503
+ (inp@ (TxInUtxo inHash inIx), coin) <-
504
+ case utxo of
505
+ [i] -> return i
506
+ [] -> throwError $ RedeemAdaNotAvailable redeemAddr
507
+ _: _: _ -> throwError $ RedeemAdaMultipleOutputs redeemAddr
508
+ let out = TxOutAux $ TxOut output coin
509
+ txAux = CTxp. makeRedemptionTx
510
+ walletProtocolMagic
511
+ rsk
512
+ (inp :| [] )
513
+ (out :| [] )
514
+ txMeta = TxMeta {
515
+ _txMetaId = hash (taTx txAux)
516
+ , _txMetaAmount = coin
517
+ , _txMetaInputs = (inHash, inIx, redeemAddr, coin) :| []
518
+ , _txMetaOutputs = (output, coin) :| []
519
+ , _txMetaCreationAt = now
520
+ , _txMetaIsLocal = False -- input does not belong to wallet
521
+ , _txMetaIsOutgoing = False -- increases wallet's balance
522
+ , _txMetaWalletId = _fromDb $ getHdRootId (accId ^. hdAccountIdParent)
523
+ , _txMetaAccountIx = getHdAccountIx (accId ^. hdAccountIdIx)
524
+ }
525
+ return (txAux, txMeta)
526
+ where
527
+ isOutput :: (TxIn , TxOutAux ) -> Maybe (TxIn , Coin )
528
+ isOutput (inp, TxOutAux (TxOut addr coin)) = do
529
+ guard $ addr == redeemAddr
530
+ return (inp, coin)
0 commit comments