Skip to content

Commit f037d0d

Browse files
authored
Merge pull request #901 from IntersectMBO/cardano-wasm-typescript
Add typescript declarations for `cardano-wasm` API and generate it automatically from `ApiInfo`
2 parents de27f9f + bb53acb commit f037d0d

File tree

16 files changed

+635
-78
lines changed

16 files changed

+635
-78
lines changed

cabal.project

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ if impl(ghc < 9.8)
2929

3030
constraints: process >= 1.6.26.1
3131

32+
-- It may slow down build plan preparation, but without it cabal has problems
33+
-- with solving constraints. Remove this when not needed anymore.
34+
max-backjumps: 50000
35+
3236
program-options
3337
ghc-options: -Werror
3438

cardano-wasm/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ That will create it with the name `cardano-wasm.js` in the current folder.
203203

204204
You can find more information in [this url](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/wasm.html).
205205

206-
And you can find an example of how to use it in the `example` subfolder. This example assumes that the generated `.wasm` and `.js` files reside in the same folder as the code in `example` subfolder.
206+
And you can find an example of how to use it in the `example` subfolder. This example assumes that the generated `.wasm` and `.js` files as well as the files from the `lib-wrapper` subfolder, all reside in the same folder as the code in `example` subfolder.
207207

208208
## Running the example
209209

@@ -214,12 +214,13 @@ To run the example in the `example` subfolder:
214214
echo "$(env -u CABAL_CONFIG wasm32-wasi-cabal list-bin exe:cardano-wasm | tail -n1)"
215215
```
216216
2. Copy the generated `cardano-wasm.js` file (generated by the `post-link.mjs` command in the section above) to the `example` subfolder.
217-
3. Navigate to the `example` subfolder in your terminal.
218-
4. Due to browser security restrictions (CORS policy), you may need to serve the `index.html` file through a local HTTP server. A simple way to do this is using Python's built-in HTTP server:
217+
3. Copy the wrapper files from the `lib-wrapper` folder into the `example` subfolder.
218+
4. Navigate to the `example` subfolder in your terminal.
219+
5. Due to browser security restrictions (CORS policy), you may need to serve the `index.html` file through a local HTTP server. A simple way to do this is using Python's built-in HTTP server:
219220
```console
220221
python3 -m http.server 8001
221222
```
222-
5. Open your web browser and navigate to `http://localhost:8001/`. You should see a blank page, and if you open the developer console you should be able to see an output like the following:
223+
6. Open your web browser and navigate to `http://localhost:8001/`. You should see a blank page, and if you open the developer console you should be able to see an output like the following:
223224
```console
224225
[Log] wasi: – 0 – 0 (wasi.js, line 1)
225226
[Log] wasi: – 0 – 0 (wasi.js, line 1)

cardano-wasm/app/Main.hs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
module Main (main) where
1+
module Main where
2+
3+
import Cardano.Wasm.Internal.Api.Info (apiInfo)
4+
import Cardano.Wasm.Internal.Api.InfoToTypeScript (apiInfoToTypeScriptFile)
5+
import Cardano.Wasm.Internal.Api.TypeScriptDefs (printTypeScriptFile)
26

37
main :: IO ()
4-
main = pure ()
8+
main = printTypeScriptFile (apiInfoToTypeScriptFile apiInfo)

cardano-wasm/cardano-wasm.cabal

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ executable cardano-wasm
4040
"-optl-Wl,--strip-all,--export=getApiInfo,--export=newConwayTx,--export=addTxInput,--export=addSimpleTxOut,--export=setFee,--export=estimateMinFee,--export=signWithPaymentKey,--export=alsoSignWithPaymentKey,--export=txToCbor"
4141
other-modules:
4242
Cardano.Wasm.Internal.Api.Info
43+
Cardano.Wasm.Internal.Api.InfoToTypeScript
4344
Cardano.Wasm.Internal.Api.Tx
45+
Cardano.Wasm.Internal.Api.TypeScriptDefs
4446
Cardano.Wasm.Internal.ExceptionHandling
4547
Cardano.Wasm.Internal.JavaScript.Bridge
4648

@@ -59,3 +61,31 @@ executable cardano-wasm
5961
build-depends:
6062
ghc-experimental,
6163
utf8-string,
64+
65+
test-suite cardano-wasm-golden
66+
type: exitcode-stdio-1.0
67+
main-is: cardano-wasm-golden.hs
68+
hs-source-dirs: test/cardano-wasm-golden
69+
70+
if !arch(wasm32)
71+
import: project-config
72+
build-depends:
73+
hedgehog >=1.1,
74+
hedgehog-extras ^>=0.8,
75+
tasty,
76+
tasty-hedgehog,
77+
78+
ghc-options:
79+
-threaded
80+
-rtsopts
81+
"-with-rtsopts=-N -T"
82+
83+
build-tool-depends:
84+
cardano-wasm:cardano-wasm,
85+
tasty-discover:tasty-discover,
86+
87+
other-modules:
88+
Test.Golden.Cardano.Wasm.TypeScript
89+
else
90+
build-depends:
91+
base

cardano-wasm/example/example.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import cardano_api from "./cardano-api.js";
2+
3+
let promise = cardano_api();
4+
5+
async function get_protocol_params() {
6+
const response = await fetch("./preview_pparams.json");
7+
return (await response.json());
8+
}
9+
10+
let protocolParams = await get_protocol_params();
11+
12+
async function do_async_work() {
13+
let api = await promise;
14+
console.log("Api object:");
15+
console.log(api);
16+
17+
let emptyTx = await api.newConwayTx();
18+
console.log("UnsignedTx object:");
19+
console.log(emptyTx);
20+
21+
let tx = await emptyTx.addTxInput("be6efd42a3d7b9a00d09d77a5d41e55ceaf0bd093a8aa8a893ce70d9caafd978", 0)
22+
.addSimpleTxOut("addr_test1vzpfxhjyjdlgk5c0xt8xw26avqxs52rtf69993j4tajehpcue4v2v", 10_000_000n)
23+
24+
let feeEstimate = await tx.estimateMinFee(protocolParams, 1, 0, 0);
25+
console.log("Estimated fee:");
26+
console.log(feeEstimate);
27+
28+
let signedTx = await tx.setFee(feeEstimate)
29+
.signWithPaymentKey("addr_sk1648253w4tf6fv5fk28dc7crsjsaw7d9ymhztd4favg3cwkhz7x8sl5u3ms");
30+
console.log("SignedTx object:");
31+
console.log(signedTx);
32+
33+
let txCbor = await signedTx.txToCbor();
34+
console.log("Tx CBOR:");
35+
console.log(txCbor);
36+
}
37+
38+
do_async_work().then(() => { });

cardano-wasm/example/index.html

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,7 @@
11
<html>
22

33
<body>
4-
<script type="module">
5-
import cardano_api from "./cardano-api.js";
6-
let promise = cardano_api();
7-
async function get_protocol_params() {
8-
const response = await fetch("./preview_pparams.json");
9-
return (await response.json());
10-
}
11-
let protocolParams = await get_protocol_params();
12-
async function do_async_work() {
13-
let api = await promise;
14-
console.log("Api object:");
15-
console.log(api);
16-
17-
let emptyTx = await api.newConwayTx();
18-
console.log("UnsignedTx object:");
19-
console.log(emptyTx);
20-
21-
let tx = await emptyTx.addTxInput("be6efd42a3d7b9a00d09d77a5d41e55ceaf0bd093a8aa8a893ce70d9caafd978", 0)
22-
.addSimpleTxOut("addr_test1vzpfxhjyjdlgk5c0xt8xw26avqxs52rtf69993j4tajehpcue4v2v", 10_000_000n)
23-
24-
let feeEstimate = await tx.estimateMinFee(protocolParams, 1, 0, 0);
25-
console.log("Estimated fee:");
26-
console.log(feeEstimate);
27-
28-
let signedTx = await tx.setFee(feeEstimate)
29-
.signWithPaymentKey("addr_sk1648253w4tf6fv5fk28dc7crsjsaw7d9ymhztd4favg3cwkhz7x8sl5u3ms");
30-
console.log("SignedTx object:");
31-
console.log(signedTx);
32-
33-
let txCbor = await signedTx.txToCbor();
34-
console.log("Tx CBOR:");
35-
console.log(txCbor);
36-
}
37-
do_async_work().then(() => { });
38-
</script>
4+
<script type="module" src="./example.js"></script>
395
</body>
406

417
</html>
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// cardano-api.d.ts
2+
3+
export default initialise;
4+
5+
/**
6+
* Initialises the Cardano API.
7+
* @returns A promise that resolves to the main `CardanoAPI` object.
8+
*/
9+
declare function initialise(): Promise<CardanoAPI>;
10+
11+
/**
12+
* Represents an unsigned transaction.
13+
*/
14+
declare interface UnsignedTx {
15+
/**
16+
* The type of the object, used for identification (the "UnsignedTx" string).
17+
*/
18+
objectType: string;
19+
20+
/**
21+
* Adds a simple transaction input to the transaction.
22+
* @param txId The transaction ID of the input UTxO.
23+
* @param txIx The index of the input within the UTxO.
24+
* @returns The `UnsignedTx` object with the added input.
25+
*/
26+
addTxInput(txId: string, txIx: number): UnsignedTx;
27+
28+
/**
29+
* Adds a simple transaction output to the transaction.
30+
* @param destAddr The destination address.
31+
* @param lovelaceAmount The amount in lovelaces to output.
32+
* @returns The `UnsignedTx` object with the added output.
33+
*/
34+
addSimpleTxOut(destAddr: string, lovelaceAmount: bigint): UnsignedTx;
35+
36+
/**
37+
* Sets the fee for the transaction.
38+
* @param lovelaceAmount The fee amount in lovelaces.
39+
* @returns The `UnsignedTx` object with the set fee.
40+
*/
41+
setFee(lovelaceAmount: bigint): UnsignedTx;
42+
43+
/**
44+
* Estimates the minimum fee for the transaction.
45+
* @param protocolParams The protocol parameters.
46+
* @param numKeyWitnesses The number of key witnesses.
47+
* @param numByronKeyWitnesses The number of Byron key witnesses.
48+
* @param totalRefScriptSize The total size of reference scripts in bytes.
49+
* @returns A promise that resolves to the estimated minimum fee in lovelaces.
50+
*/
51+
estimateMinFee(protocolParams: any, numKeyWitnesses: number, numByronKeyWitnesses: number, totalRefScriptSize: number): Promise<BigInt>;
52+
53+
/**
54+
* Signs the transaction with a payment key.
55+
* @param signingKey The signing key to witness the transaction.
56+
* @returns A promise that resolves to a `SignedTx` object.
57+
*/
58+
signWithPaymentKey(signingKey: string): Promise<SignedTx>;
59+
}
60+
61+
/**
62+
* Represents a signed transaction.
63+
*/
64+
declare interface SignedTx {
65+
/**
66+
* The type of the object, used for identification (the "SignedTx" string).
67+
*/
68+
objectType: string;
69+
70+
/**
71+
* Adds an extra signature to the transaction with a payment key.
72+
* @param signingKey The signing key to witness the transaction.
73+
* @returns The `SignedTx` object with the additional signature.
74+
*/
75+
alsoSignWithPaymentKey(signingKey: string): SignedTx;
76+
77+
/**
78+
* Converts the signed transaction to its CBOR representation.
79+
* @returns A promise that resolves to the CBOR representation of the transaction as a hex string.
80+
*/
81+
txToCbor(): Promise<string>;
82+
}
83+
84+
/**
85+
* The main Cardano API object with static methods.
86+
*/
87+
declare interface CardanoAPI {
88+
/**
89+
* The type of the object, used for identification (the "CardanoAPI" string).
90+
*/
91+
objectType: string;
92+
93+
/**
94+
* Creates a new Conway-era transaction.
95+
* @returns A promise that resolves to a new `UnsignedTx` object.
96+
*/
97+
newConwayTx(): Promise<UnsignedTx>;
98+
}
99+

cardano-wasm/example/cardano-api.js renamed to cardano-wasm/lib-wrapper/cardano-api.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
/// <reference path="./cardano-api.d.ts" />
2+
13
import { WASI } from "https://unpkg.com/@bjorn3/[email protected]/dist/index.js";
24
import ghc_wasm_jsffi from "./cardano-wasm.js";
35
const __exports = {};
46
const wasi = new WASI([], [], []);
5-
async function initialize() {
7+
async function initialise() {
68
let { instance } = await WebAssembly.instantiateStreaming(fetch("./cardano-wasm.wasm"), {
79
ghc_wasm_jsffi: ghc_wasm_jsffi(__exports),
810
wasi_snapshot_preview1: wasi.wasiImport,
@@ -12,7 +14,7 @@ async function initialize() {
1214

1315
// Wrap a function with variable arguments to make the parameters inspectable
1416
function fixateArgs(params, func) {
15-
const paramString = params.join(',');
17+
const paramString = params.map(p => p.name).join(',');
1618
// Dynamically create a function that captures 'func' from the closure.
1719
// 'this' and 'arguments' are passed through from the wrapper to 'func'.
1820
// Using eval allows the returned function to have named parameters for inspectability.
@@ -26,7 +28,7 @@ async function initialize() {
2628

2729
// Same as fixateArgs but for async functions
2830
async function fixateArgsAsync(params, func) {
29-
const paramString = params.join(',');
31+
const paramString = params.map(p => p.name).join(',');
3032
// Dynamically create an async function.
3133
const wrapper = eval(`
3234
(async function(${paramString}) {
@@ -80,7 +82,7 @@ async function initialize() {
8082
});
8183

8284
// Populate the main API object with static methods
83-
apiInfo.staticMethods.forEach(method => {
85+
apiInfo.mainObject.methods.forEach(method => {
8486
cardanoAPI[method.name] = async function (...args) {
8587
const resultPromise = instance.exports[method.name](...args);
8688

@@ -93,4 +95,4 @@ async function initialize() {
9395
});
9496
return cardanoAPI;
9597
}
96-
export default initialize;
98+
export default initialise;

0 commit comments

Comments
 (0)