Skip to content

Commit 82ef853

Browse files
authored
Merge pull request #917 from IntersectMBO/address-virtual-object
Create `Wallet` virtual object and add support for handling payment addresses
2 parents dfc2f63 + a281b69 commit 82ef853

10 files changed

Lines changed: 417 additions & 25 deletions

File tree

cardano-wasm/cardano-wasm.cabal

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,26 @@ executable cardano-wasm
3737
ghc-options:
3838
-no-hs-main
3939
-optl-mexec-model=reactor
40-
"-optl-Wl,--strip-all,--export=getApiInfo,--export=newConwayTx,--export=addTxInput,--export=addSimpleTxOut,--export=setFee,--export=estimateMinFee,--export=signWithPaymentKey,--export=alsoSignWithPaymentKey,--export=txToCbor,--export=newGrpcConnection,--export=getEra"
40+
"-optl-Wl,--strip-all"
4141
other-modules:
4242
Cardano.Wasm.Internal.Api.GRPC
4343
Cardano.Wasm.Internal.Api.Info
4444
Cardano.Wasm.Internal.Api.InfoToTypeScript
4545
Cardano.Wasm.Internal.Api.Tx
4646
Cardano.Wasm.Internal.Api.TypeScriptDefs
47+
Cardano.Wasm.Internal.Api.Wallet
4748
Cardano.Wasm.Internal.ExceptionHandling
4849
Cardano.Wasm.Internal.JavaScript.Bridge
4950
Cardano.Wasm.Internal.JavaScript.GRPC
5051
Cardano.Wasm.Internal.JavaScript.GRPCTypes
52+
Cardano.Wasm.Internal.JavaScript.Random
5153

5254
build-depends:
5355
aeson,
5456
base,
57+
bytestring,
5558
cardano-api,
59+
cardano-crypto-class,
5660
cardano-ledger-api,
5761
cardano-strict-containers,
5862
containers,
@@ -62,6 +66,7 @@ executable cardano-wasm
6266

6367
if arch(wasm32)
6468
build-depends:
69+
base16-bytestring,
6570
ghc-experimental,
6671
utf8-string,
6772

cardano-wasm/example/example.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ document.body.appendChild(output);
1616

1717
function log(out) {
1818
console.log(out);
19-
if (typeof(out) == "object") {
20-
output.innerText += "> [object] {\n";
21-
for (let [key, val] of Object.entries(out)) {
22-
let text = val.toString();
23-
if (typeof(val) == "function") {
24-
text = text.split("{")[0];
25-
}
26-
output.innerText += "    " + key + ": " + text + "\n";
27-
}
28-
output.innerText += "  }\n";
19+
if (typeof (out) == "object") {
20+
output.innerText += "> [object] {\n";
21+
for (let [key, val] of Object.entries(out)) {
22+
let text = val.toString();
23+
if (typeof (val) == "function") {
24+
text = text.split("{")[0];
25+
}
26+
output.innerText += "    " + key + ": " + text + "\n";
27+
}
28+
output.innerText += "  }\n";
2929
} else {
30-
output.innerText += "> " + JSON.stringify(out) + "\n";
30+
output.innerText += "> " + JSON.stringify(out) + "\n";
3131
}
3232
}
3333

@@ -43,19 +43,27 @@ async function do_async_work() {
4343
log("Api object:");
4444
log(api);
4545

46+
let PREVIEW_MAGIC_NUMBER = 2;
47+
let secretKey = "addr_sk1648253w4tf6fv5fk28dc7crsjsaw7d9ymhztd4favg3cwkhz7x8sl5u3ms";
48+
let wallet = await api.restoreTestnetPaymentWalletFromSigningKeyBech32(PREVIEW_MAGIC_NUMBER, secretKey);
49+
let bech32Address = await wallet.getAddressBech32();
50+
51+
log("Bech32 of address:");
52+
log(bech32Address);
53+
4654
let emptyTx = await api.newConwayTx();
4755
log("UnsignedTx object:");
4856
log(emptyTx);
4957

5058
let tx = await emptyTx.addTxInput("be6efd42a3d7b9a00d09d77a5d41e55ceaf0bd093a8aa8a893ce70d9caafd978", 0)
51-
.addSimpleTxOut("addr_test1vzpfxhjyjdlgk5c0xt8xw26avqxs52rtf69993j4tajehpcue4v2v", 10_000_000n)
59+
.addSimpleTxOut("addr_test1vzpfxhjyjdlgk5c0xt8xw26avqxs52rtf69993j4tajehpcue4v2v", 10_000_000n)
5260

5361
let feeEstimate = await tx.estimateMinFee(protocolParams, 1, 0, 0);
5462
log("Estimated fee:");
5563
log(feeEstimate);
5664

5765
let signedTx = await tx.setFee(feeEstimate)
58-
.signWithPaymentKey("addr_sk1648253w4tf6fv5fk28dc7crsjsaw7d9ymhztd4favg3cwkhz7x8sl5u3ms");
66+
.signWithPaymentKey(secretKey);
5967
log("SignedTx object:");
6068
log(signedTx);
6169

cardano-wasm/js-test/basic-test.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ test('test output matches', async ({ page }) => {
88
// Wait for the test to finish running (we signal this by creating a tag with id "finish-tag" and text "Finished test!")
99
await expect(page.locator('#finish-tag')).toHaveText("Finished test!");
1010
// Check the output of the test (from the example folder), which is displayed in the code element with id "test-output". The output contains information about the various objects and results of trying some of the functions.
11-
await expect(page.locator('#test-output')).toHaveText('> "Api object:"> [object] { objectType: cardano-api newConwayTx: async function (...args) newGrpcConnection: async function (...args) }> "UnsignedTx object:"> [object] { objectType: UnsignedTx addTxInput: function(txId,txIx) addSimpleTxOut: function(destAddr,lovelaceAmount) setFee: function(lovelaceAmount) estimateMinFee: function(protocolParams,numKeyWitnesses,numByronKeyWitnesses,totalRefScriptSize) signWithPaymentKey: function(signingKey) }> "Estimated fee:"> 164005> "SignedTx object:"> [object] { objectType: SignedTx alsoSignWithPaymentKey: function(signingKey) txToCbor: function() }> "Tx CBOR:"> "84a300d9010281825820be6efd42a3d7b9a00d09d77a5d41e55ceaf0bd093a8aa8a893ce70d9caafd97800018182581d6082935e44937e8b530f32ce672b5d600d0a286b4e8a52c6555f659b871a00989680021a000280a5a100d9010281825820adfc1c30385916da87db1ba3328f0690a57ebb2a6ac9f6f86b2d97f943adae005840a49259b5977aea523b46f01261fbff93e0899e8700319e11f5ab96b67eb628fca1a233ce2d50ee3227b591b84f27237d920d63974d65728362382f751c4d9400f5f6"');
11+
await expect(page.locator('#test-output')).toHaveText('> \"Api object:\"> [object] { objectType: cardano-api newConwayTx: async function (...args) newGrpcConnection: async function (...args) generatePaymentWallet: async function (...args) restorePaymentWalletFromSigningKeyBech32: async function (...args) generateTestnetPaymentWallet: async function (...args) restoreTestnetPaymentWalletFromSigningKeyBech32: async function (...args) }> \"Bech32 of address:\"> \"addr_test1vp93p9em3regvgylxuvet6fgr3e9sn259pcejgrk4ykystcs7v8j6\"> \"UnsignedTx object:\"> [object] { objectType: UnsignedTx addTxInput: function(txId,txIx) addSimpleTxOut: function(destAddr,lovelaceAmount) setFee: function(lovelaceAmount) estimateMinFee: function(protocolParams,numKeyWitnesses,numByronKeyWitnesses,totalRefScriptSize) signWithPaymentKey: function(signingKey) }> \"Estimated fee:\"> 164005> \"SignedTx object:\"> [object] { objectType: SignedTx alsoSignWithPaymentKey: function(signingKey) txToCbor: function() }> \"Tx CBOR:\"> \"84a300d9010281825820be6efd42a3d7b9a00d09d77a5d41e55ceaf0bd093a8aa8a893ce70d9caafd97800018182581d6082935e44937e8b530f32ce672b5d600d0a286b4e8a52c6555f659b871a00989680021a000280a5a100d9010281825820adfc1c30385916da87db1ba3328f0690a57ebb2a6ac9f6f86b2d97f943adae005840a49259b5977aea523b46f01261fbff93e0899e8700319e11f5ab96b67eb628fca1a233ce2d50ee3227b591b84f27237d920d63974d65728362382f751c4d9400f5f6\"');
1212
});

cardano-wasm/lib-wrapper/cardano-api.d.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,40 @@ declare interface GrpcConnection {
9797
getEra(): Promise<number>;
9898
}
9999

100+
/**
101+
* Represents a wallet.
102+
*/
103+
declare interface Wallet {
104+
/**
105+
* The type of the object, used for identification (the "Wallet" string).
106+
*/
107+
objectType: string;
108+
109+
/**
110+
* Get the Bech32 representation of the address. (Can be shared for receiving funds.)
111+
* @returns The Bech32 representation of the address.
112+
*/
113+
getAddressBech32(): Promise<string>;
114+
115+
/**
116+
* Get the Bech32 representation of the verification key of the wallet. (Can be shared for verification.)
117+
* @returns The Bech32 representation of the verification key.
118+
*/
119+
getBech32ForVerificationKey(): Promise<string>;
120+
121+
/**
122+
* Get the Bech32 representation of the signing key of the wallet. (Must be kept secret.)
123+
* @returns The Bech32 representation of the signing key.
124+
*/
125+
getBech32ForSigningKey(): Promise<string>;
126+
127+
/**
128+
* Get the base16 representation of the hash of the verification key of the wallet.
129+
* @returns The base16 representation of the verification key hash.
130+
*/
131+
getBase16ForVerificationKeyHash(): Promise<string>;
132+
}
133+
100134
/**
101135
* The main Cardano API object with static methods.
102136
*/
@@ -118,5 +152,33 @@ declare interface CardanoAPI {
118152
* @returns A promise that resolves to a new `GrpcConnection`.
119153
*/
120154
newGrpcConnection(webGrpcUrl: string): Promise<GrpcConnection>;
155+
156+
/**
157+
* Generate a simple payment wallet for mainnet.
158+
* @returns A promise that resolves to a new `Wallet` object.
159+
*/
160+
generatePaymentWallet(): Promise<Wallet>;
161+
162+
/**
163+
* Restore a mainnet payment wallet from a Bech32 encoded signing key.
164+
* @param signingKeyBech32 The Bech32 encoded signing key.
165+
* @returns A promise that resolves to a new `Wallet` object.
166+
*/
167+
restorePaymentWalletFromSigningKeyBech32(signingKeyBech32: string): Promise<Wallet>;
168+
169+
/**
170+
* Generate a simple payment wallet for testnet, given the testnet's network magic.
171+
* @param networkMagic The network magic for the testnet.
172+
* @returns A promise that resolves to a new `Wallet` object.
173+
*/
174+
generateTestnetPaymentWallet(networkMagic: number): Promise<Wallet>;
175+
176+
/**
177+
* Restore a testnet payment wallet from a Bech32 encoded signing key.
178+
* @param networkMagic The network magic for the testnet.
179+
* @param signingKeyBech32 The Bech32 encoded signing key.
180+
* @returns A promise that resolves to a new `Wallet` object.
181+
*/
182+
restoreTestnetPaymentWalletFromSigningKeyBech32(networkMagic: number, signingKeyBech32: string): Promise<Wallet>;
121183
}
122184

cardano-wasm/src/Cardano/Wasm/Internal/Api/Info.hs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,45 @@ apiInfo =
127127
let unsignedTxObjectName = "UnsignedTx"
128128
signedTxObjectName = "SignedTx"
129129
grpcConnectionName = "GrpcConnection"
130+
walletObjectName = "Wallet"
131+
132+
walletObj =
133+
VirtualObjectInfo
134+
{ virtualObjectName = walletObjectName
135+
, virtualObjectDoc = "Represents a wallet."
136+
, virtualObjectMethods =
137+
[ MethodInfo
138+
{ methodName = "getAddressBech32"
139+
, methodDoc = "Get the Bech32 representation of the address. (Can be shared for receiving funds.)"
140+
, methodParams = []
141+
, methodReturnType = OtherType "string"
142+
, methodReturnDoc = "The Bech32 representation of the address."
143+
}
144+
, MethodInfo
145+
{ methodName = "getBech32ForVerificationKey"
146+
, methodDoc =
147+
"Get the Bech32 representation of the verification key of the wallet. (Can be shared for verification.)"
148+
, methodParams = []
149+
, methodReturnType = OtherType "string"
150+
, methodReturnDoc = "The Bech32 representation of the verification key."
151+
}
152+
, MethodInfo
153+
{ methodName = "getBech32ForSigningKey"
154+
, methodDoc =
155+
"Get the Bech32 representation of the signing key of the wallet. (Must be kept secret.)"
156+
, methodParams = []
157+
, methodReturnType = OtherType "string"
158+
, methodReturnDoc = "The Bech32 representation of the signing key."
159+
}
160+
, MethodInfo
161+
{ methodName = "getBase16ForVerificationKeyHash"
162+
, methodDoc = "Get the base16 representation of the hash of the verification key of the wallet."
163+
, methodParams = []
164+
, methodReturnType = OtherType "string"
165+
, methodReturnDoc = "The base16 representation of the verification key hash."
166+
}
167+
]
168+
}
130169

131170
unsignedTxObj =
132171
VirtualObjectInfo
@@ -242,9 +281,40 @@ apiInfo =
242281
, methodReturnType = NewObject grpcConnectionName
243282
, methodReturnDoc = "A promise that resolves to a new `GrpcConnection`."
244283
}
284+
, MethodInfo
285+
{ methodName = "generatePaymentWallet"
286+
, methodDoc = "Generate a simple payment wallet for mainnet."
287+
, methodParams = []
288+
, methodReturnType = NewObject walletObjectName
289+
, methodReturnDoc = "A promise that resolves to a new `Wallet` object."
290+
}
291+
, MethodInfo
292+
{ methodName = "restorePaymentWalletFromSigningKeyBech32"
293+
, methodDoc = "Restore a mainnet payment wallet from a Bech32 encoded signing key."
294+
, methodParams = [ParamInfo "signingKeyBech32" "string" "The Bech32 encoded signing key."]
295+
, methodReturnType = NewObject walletObjectName
296+
, methodReturnDoc = "A promise that resolves to a new `Wallet` object."
297+
}
298+
, MethodInfo
299+
{ methodName = "generateTestnetPaymentWallet"
300+
, methodDoc = "Generate a simple payment wallet for testnet, given the testnet's network magic."
301+
, methodParams = [ParamInfo "networkMagic" "number" "The network magic for the testnet."]
302+
, methodReturnType = NewObject walletObjectName
303+
, methodReturnDoc = "A promise that resolves to a new `Wallet` object."
304+
}
305+
, MethodInfo
306+
{ methodName = "restoreTestnetPaymentWalletFromSigningKeyBech32"
307+
, methodDoc = "Restore a testnet payment wallet from a Bech32 encoded signing key."
308+
, methodParams =
309+
[ ParamInfo "networkMagic" "number" "The network magic for the testnet."
310+
, ParamInfo "signingKeyBech32" "string" "The Bech32 encoded signing key."
311+
]
312+
, methodReturnType = NewObject walletObjectName
313+
, methodReturnDoc = "A promise that resolves to a new `Wallet` object."
314+
}
245315
]
246316
}
247-
, virtualObjects = [unsignedTxObj, signedTxObj, grpcConnection]
317+
, virtualObjects = [unsignedTxObj, signedTxObj, grpcConnection, walletObj]
248318
, initialiseFunctionDoc = "Initialises the Cardano API."
249319
, initialiseFunctionReturnDoc = "A promise that resolves to the main `CardanoAPI` object."
250320
}

cardano-wasm/src/Cardano/Wasm/Internal/Api/Tx.hs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ import Cardano.Api.Plutus qualified as Shelley
3030
import Cardano.Api.Tx qualified as TxBody
3131

3232
import Cardano.Ledger.Api qualified as Ledger
33-
import Cardano.Wasm.Internal.ExceptionHandling (justOrError, rightOrError)
33+
import Cardano.Wasm.Internal.ExceptionHandling (justOrError, rightOrError, toMonadFail)
3434

35-
import Control.Monad.Catch (Exception (displayException), MonadThrow)
35+
import Control.Monad.Catch (MonadThrow)
3636
import Data.Aeson (ToJSON (toJSON), (.=))
3737
import Data.Aeson qualified as Aeson
3838
import Data.Aeson.Types qualified as Aeson
@@ -217,12 +217,6 @@ alsoSignWithPaymentKeyImpl (SignedTxObject era (Exp.SignedTx tx)) signingKey =
217217
era
218218
(Exp.SignedTx txWithWits)
219219

220-
-- | Convert an 'Either' value to a 'MonadFail' monad. This can be useful for converting
221-
-- MonadThrow monads into Aeson Parser monads, but it loses the stack trace information.
222-
toMonadFail :: (Exception e, MonadFail m) => Either e a -> m a
223-
toMonadFail (Left e) = fail $ displayException e
224-
toMonadFail (Right a) = return a
225-
226220
-- | Convert a signed transaction object to a base16 encoded string of its CBOR representation.
227221
toCborImpl :: SignedTxObject -> String
228222
toCborImpl (SignedTxObject era signedTx) =

0 commit comments

Comments
 (0)