Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for generic CIP30 wallets by name #1536

Merged
merged 2 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [HD wallet support](./doc/key-management.md) with mnemonic seed phrases ([#1498](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1498))
- Ogmios-specific functions for Local TX Monitor Ouroboros Mini-Protocol in `Contract.Backend.Ogmios` ([#1508](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1508/))
- New `mustSendChangeWithDatum` balancer constraint that adds datum to all change outputs ([#1510](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1510/))
- Support for generic CIP-30 wallets by name ([#1524](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1524))

### Changed

Expand Down
4 changes: 3 additions & 1 deletion src/Contract/Wallet.purs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import Ctl.Internal.Plutus.Conversion
)
import Ctl.Internal.Plutus.Conversion.Address (toPlutusAddressWithNetworkTag)
import Ctl.Internal.Wallet
( Wallet(Gero, Nami, Flint, Lode, Eternl, NuFi, Lace, KeyWallet)
( Wallet(Gero, Nami, Flint, Lode, Eternl, NuFi, Lace, KeyWallet, GenericCip30)
, WalletExtension
( NamiWallet
, GeroWallet
Expand All @@ -67,6 +67,7 @@ import Ctl.Internal.Wallet
, LodeWallet
, LaceWallet
, NuFiWallet
, GenericCip30Wallet
)
, apiVersion
, icon
Expand Down Expand Up @@ -107,6 +108,7 @@ import Ctl.Internal.Wallet.Spec
, ConnectToLace
, ConnectToEternl
, ConnectToNuFi
, ConnectToGenericCip30
)
) as X
import Data.Array (head)
Expand Down
5 changes: 3 additions & 2 deletions src/Internal/Contract/Sign.purs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Ctl.Internal.Cardano.Types.Transaction as Transaction
import Ctl.Internal.Contract.Monad (Contract)
import Ctl.Internal.Contract.Wallet (withWallet)
import Ctl.Internal.Wallet
( Wallet(KeyWallet, Lode, Eternl, Flint, Gero, Nami, NuFi, Lace)
( Wallet(Gero, Nami, Flint, Lode, Eternl, NuFi, Lace, GenericCip30, KeyWallet)
, callCip30Wallet
)
import Data.Array (fromFoldable)
Expand Down Expand Up @@ -45,7 +45,8 @@ signTransaction tx = do
liftAff $ callCip30Wallet eternl \nw -> flip nw.signTx tx
Lode lode -> liftAff $ callCip30Wallet lode \nw -> flip nw.signTx tx
NuFi nufi -> liftAff $ callCip30Wallet nufi \w -> flip w.signTx tx
Lace nufi -> liftAff $ callCip30Wallet nufi \w -> flip w.signTx tx
Lace lace -> liftAff $ callCip30Wallet lace \w -> flip w.signTx tx
GenericCip30 cip30 -> liftAff $ callCip30Wallet cip30 \w -> flip w.signTx tx
KeyWallet kw -> liftAff do
witnessSet <- (unwrap kw).signTx tx
pure $ Just (tx # _witnessSet <>~ witnessSet)
2 changes: 2 additions & 0 deletions src/Internal/Serialization/ToBytes.purs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import Ctl.Internal.Serialization.Types
, Vkeywitness
, Vkeywitnesses
)
import Ctl.Internal.Types.BigNum (BigNum)
import Ctl.Internal.Types.ByteArray (ByteArray)
import Ctl.Internal.Types.CborBytes (CborBytes(CborBytes))
import Untagged.Castable (class Castable)
Expand Down Expand Up @@ -63,6 +64,7 @@ type SerializableData = Address
|+| VRFKeyHash
|+| Vkeywitness
|+| Vkeywitnesses
|+| BigNum

-- Add more as needed

Expand Down
11 changes: 10 additions & 1 deletion src/Internal/Wallet.purs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Ctl.Internal.Wallet
( module KeyWallet
, module Cip30Wallet
, Wallet(Gero, Nami, Flint, Lode, Eternl, NuFi, Lace, KeyWallet)
, Wallet(Gero, Nami, Flint, Lode, Eternl, NuFi, Lace, GenericCip30, KeyWallet)
, WalletExtension
( NamiWallet
, LodeWallet
Expand All @@ -10,6 +10,7 @@ module Ctl.Internal.Wallet
, EternlWallet
, NuFiWallet
, LaceWallet
, GenericCip30Wallet
)
, isEternlAvailable
, isGeroAvailable
Expand Down Expand Up @@ -84,6 +85,7 @@ data Wallet
| Lode Cip30Wallet
| NuFi Cip30Wallet
| Lace Cip30Wallet
| GenericCip30 Cip30Wallet
| KeyWallet KeyWallet

data WalletExtension
Expand All @@ -94,6 +96,7 @@ data WalletExtension
| LodeWallet
| LaceWallet
| NuFiWallet
| GenericCip30Wallet String

mkKeyWallet :: PrivatePaymentKey -> Maybe PrivateStakeKey -> Wallet
mkKeyWallet payKey mbStakeKey = KeyWallet $ privateKeysToKeyWallet
Expand All @@ -119,6 +122,8 @@ mkWalletAff walletExtension =
LodeWallet -> _mkLodeWalletAff
NuFiWallet -> NuFi <$> mkCip30WalletAff (_enableWallet walletName)
LaceWallet -> Lace <$> mkCip30WalletAff (_enableWallet walletName)
GenericCip30Wallet name' ->
GenericCip30 <$> mkCip30WalletAff (_enableWallet name')
where
walletName = walletExtensionToName walletExtension

Expand Down Expand Up @@ -232,6 +237,7 @@ cip30Wallet = case _ of
Lode c30 -> Just c30
NuFi c30 -> Just c30
Lace c30 -> Just c30
GenericCip30 c30 -> Just c30
KeyWallet _ -> Nothing

walletExtensionToName :: WalletExtension -> String
Expand All @@ -243,6 +249,7 @@ walletExtensionToName = case _ of
LodeWallet -> "LodeWallet"
NuFiWallet -> "nufi"
LaceWallet -> "lace"
GenericCip30Wallet name' -> name'

walletToWalletExtension :: Wallet -> Maybe WalletExtension
walletToWalletExtension = case _ of
Expand All @@ -253,6 +260,7 @@ walletToWalletExtension = case _ of
Lode _ -> Just LodeWallet
NuFi _ -> Just NuFiWallet
Lace _ -> Just LaceWallet
GenericCip30 _ -> Nothing
KeyWallet _ -> Nothing

isEnabled :: WalletExtension -> Aff Boolean
Expand Down Expand Up @@ -311,6 +319,7 @@ actionBasedOnWallet walletAction keyWalletAction =
Lode wallet -> liftAff $ callCip30Wallet wallet walletAction
NuFi wallet -> liftAff $ callCip30Wallet wallet walletAction
Lace wallet -> liftAff $ callCip30Wallet wallet walletAction
GenericCip30 wallet -> liftAff $ callCip30Wallet wallet walletAction
KeyWallet kw -> keyWalletAction kw

callCip30Wallet
Expand Down
15 changes: 8 additions & 7 deletions src/Internal/Wallet/Cip30.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ exports._getNetworkId = conn => () => conn.getNetworkId();
exports._getUtxos = maybe => conn => () =>
conn.getUtxos().then(res => (res === null ? maybe.nothing : maybe.just(res)));

exports._getCollateral = maybe => conn => () =>
conn.experimental
.getCollateral()
.then(utxos =>
utxos !== null && utxos.length ? maybe.just(utxos) : maybe.nothing
);
exports._getCollateral = maybe => conn => requiredValue => () =>
(typeof conn.getCollateral === "function"
? conn.getCollateral(requiredValue)
: conn.experimental.getCollateral(requiredValue)
).then(utxos =>
utxos !== null && utxos.length ? maybe.just(utxos) : maybe.nothing
);

exports._getBalance = conn => () => conn.getBalance();

exports._getAddresses = conn => conn.getUsedAddresses;
exports._getAddresses = conn => () => conn.getUsedAddresses();

exports._getUnusedAddresses = conn => () => conn.getUnusedAddresses();

Expand Down
29 changes: 23 additions & 6 deletions src/Internal/Wallet/Cip30.purs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Ctl.Internal.Cardano.Types.Transaction
import Ctl.Internal.Cardano.Types.TransactionUnspentOutput
( TransactionUnspentOutput
)
import Ctl.Internal.Cardano.Types.Value (Value)
import Ctl.Internal.Cardano.Types.Value (Coin(Coin), Value)
import Ctl.Internal.Deserialization.FromBytes (fromBytes, fromBytesEffect)
import Ctl.Internal.Deserialization.UnspentOutput (convertValue)
import Ctl.Internal.Deserialization.UnspentOutput as Deserialization.UnspentOuput
Expand All @@ -36,6 +36,8 @@ import Ctl.Internal.Serialization.Address
, rewardAddressBytes
, rewardAddressFromAddress
)
import Ctl.Internal.Serialization.ToBytes (toBytes)
import Ctl.Internal.Types.BigNum as BigNum
import Ctl.Internal.Types.ByteArray (byteArrayToHex)
import Ctl.Internal.Types.CborBytes
( CborBytes
Expand All @@ -44,6 +46,7 @@ import Ctl.Internal.Types.CborBytes
, rawBytesAsCborBytes
)
import Ctl.Internal.Types.RawBytes (RawBytes, hexToRawBytes, rawBytesToHex)
import Data.BigInt (fromInt) as BigInt
import Data.Maybe (Maybe(Just, Nothing), maybe)
import Data.Newtype (unwrap)
import Data.Traversable (for, traverse)
Expand Down Expand Up @@ -140,12 +143,15 @@ getWalletAddresses conn = Promise.toAffE (_getAddresses conn) <#>
hexStringToAddress :: String -> Maybe Address
hexStringToAddress = fromBytes <<< rawBytesAsCborBytes <=< hexToRawBytes

defaultCollateralAmount :: Coin
defaultCollateralAmount = Coin $ BigInt.fromInt 5_000_000

-- | Get collateral using CIP-30 `getCollateral` method.
-- | Throws on `Promise` rejection by wallet, returns `Nothing` if no collateral
-- | is available.
getCollateral :: Cip30Connection -> Aff (Maybe (Array TransactionUnspentOutput))
getCollateral conn = do
mbUtxoStrs <- toAffE $ getCip30Collateral conn
mbUtxoStrs <- toAffE $ getCip30Collateral conn defaultCollateralAmount
let
(mbUtxoBytes :: Maybe (Array RawBytes)) =
join $ map (traverse hexToRawBytes) mbUtxoStrs
Expand Down Expand Up @@ -238,12 +244,23 @@ foreign import _getUtxos
foreign import _getCollateral
:: MaybeFfiHelper
-> Cip30Connection
-> String
-> Effect (Promise (Maybe (Array String)))

getCip30Collateral :: Cip30Connection -> Effect (Promise (Maybe (Array String)))
getCip30Collateral conn =
_getCollateral maybeFfiHelper conn `catchError`
\_ -> throwError $ error "Wallet doesn't implement `getCollateral`."
getCip30Collateral
:: Cip30Connection -> Coin -> Effect (Promise (Maybe (Array String)))
getCip30Collateral conn requiredValue = do
bigNumValue <- maybe (throw convertError) pure
$ BigNum.fromBigInt
$ unwrap requiredValue
let requiredValueStr = byteArrayToHex $ unwrap $ toBytes bigNumValue
_getCollateral maybeFfiHelper conn requiredValueStr `catchError`
\err -> throwError $ error $
"Failed to call `getCollateral`: " <> show err
where
convertError =
"Unable to convert CIP-30 getCollateral required value: " <>
show requiredValue

foreign import _getBalance :: Cip30Connection -> Effect (Promise String)

Expand Down
28 changes: 25 additions & 3 deletions src/Internal/Wallet/Cip30Mock.purs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
module Ctl.Internal.Wallet.Cip30Mock
( withCip30Mock
, WalletMock(MockFlint, MockGero, MockNami, MockLode, MockNuFi)
, WalletMock
( MockFlint
, MockGero
, MockNami
, MockLode
, MockNuFi
, MockGenericCip30
)
) where

import Prelude
Expand Down Expand Up @@ -40,7 +47,14 @@ import Ctl.Internal.Types.RewardAddress
)
import Ctl.Internal.Wallet
( Wallet
, WalletExtension(LodeWallet, NamiWallet, GeroWallet, FlintWallet, NuFiWallet)
, WalletExtension
( LodeWallet
, NamiWallet
, GeroWallet
, FlintWallet
, NuFiWallet
, GenericCip30Wallet
)
, mkWalletAff
)
import Ctl.Internal.Wallet.Key
Expand All @@ -66,7 +80,13 @@ import Effect.Class (liftEffect)
import Effect.Exception (error)
import Effect.Unsafe (unsafePerformEffect)

data WalletMock = MockFlint | MockGero | MockNami | MockLode | MockNuFi
data WalletMock
= MockFlint
| MockGero
| MockNami
| MockLode
| MockNuFi
| MockGenericCip30 String

-- | Construct a CIP-30 wallet mock that exposes `KeyWallet` functionality
-- | behind a CIP-30 interface and uses Ogmios to submit Txs.
Expand Down Expand Up @@ -103,6 +123,7 @@ withCip30Mock (KeyWallet keyWallet) mock contract = do
MockNami -> mkWalletAff NamiWallet
MockLode -> mkWalletAff LodeWallet
MockNuFi -> mkWalletAff NuFiWallet
MockGenericCip30 name -> mkWalletAff (GenericCip30Wallet name)

mockString :: String
mockString = case mock of
Expand All @@ -111,6 +132,7 @@ withCip30Mock (KeyWallet keyWallet) mock contract = do
MockNami -> "nami"
MockLode -> "LodeWallet"
MockNuFi -> "nufi"
MockGenericCip30 name -> name

type Cip30Mock =
{ getNetworkId :: Effect (Promise Int)
Expand Down
4 changes: 4 additions & 0 deletions src/Internal/Wallet/Spec.purs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module Ctl.Internal.Wallet.Spec
, ConnectToLode
, ConnectToNuFi
, ConnectToLace
, ConnectToGenericCip30
)
, Cip1852DerivationPath
, StakeKeyPresence(WithStakeKey, WithoutStakeKey)
Expand All @@ -32,6 +33,7 @@ import Ctl.Internal.Wallet
, LodeWallet
, NuFiWallet
, LaceWallet
, GenericCip30Wallet
)
, mkKeyWallet
, mkWalletAff
Expand Down Expand Up @@ -110,6 +112,7 @@ data WalletSpec
| ConnectToLode
| ConnectToNuFi
| ConnectToLace
| ConnectToGenericCip30 String

derive instance Generic WalletSpec _

Expand Down Expand Up @@ -148,6 +151,7 @@ mkWalletBySpec = case _ of
ConnectToLode -> mkWalletAff LodeWallet
ConnectToNuFi -> mkWalletAff NuFiWallet
ConnectToLace -> mkWalletAff LaceWallet
ConnectToGenericCip30 name -> mkWalletAff (GenericCip30Wallet name)

-- | Create a wallet given a mnemonic phrase, account index, address index and
-- | stake key presence flag.
Expand Down
4 changes: 3 additions & 1 deletion test/Plutip/Contract.purs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ import Ctl.Internal.Wallet
( WalletExtension(NamiWallet, GeroWallet, FlintWallet, NuFiWallet)
)
import Ctl.Internal.Wallet.Cip30Mock
( WalletMock(MockNami, MockGero, MockFlint, MockNuFi)
( WalletMock(MockNami, MockGero, MockFlint, MockNuFi, MockGenericCip30)
, withCip30Mock
)
import Data.Array (head, (!!))
Expand Down Expand Up @@ -1928,6 +1928,8 @@ suite = do
withWallets distribution \alice -> do
withCip30Mock alice MockNami do
Cip30.contract
withCip30Mock alice (MockGenericCip30 "nami") do
Cip30.contract
test "ECDSA example" do
let
distribution = withStakeKey privateStakeKey
Expand Down