Skip to content

Commit 527b170

Browse files
authored
Merge pull request #1008 from IntersectMBO/stake-certificates
Add implementation for stake certificate creation in `cardano-wasm`
2 parents 5592300 + 3a34eda commit 527b170

16 files changed

Lines changed: 674 additions & 108 deletions

File tree

.github/workflows/haskell-wasm.yml

Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ on:
1010
- master
1111
# GH caches are removed when not accessed within 7 days - this schedule runs the job every 6 days making
1212
# sure that we always have some caches on master
13-
# schedule:
14-
# - cron: '0 0 */6 * *'
13+
schedule:
14+
- cron: '0 0 */6 * *'
1515

1616
jobs:
1717
build:
@@ -30,7 +30,7 @@ jobs:
3030

3131
env:
3232
# Modify this value to "invalidate" the cabal cache.
33-
CABAL_CACHE_VERSION: "2025-05-29"
33+
CABAL_CACHE_VERSION: "2026-05-29"
3434

3535
concurrency:
3636
group: >
@@ -90,59 +90,44 @@ jobs:
9090
STORE=$(wasm32-wasi-cabal path --store | tail -n 1)
9191
EOF
9292
93-
# Cache is disabled because GHA default builders are not able to build all dependencies
94-
# because they lack RAM, so having the cache expire would break the CI check.
95-
# For this reason, we are providing a build of the dependencies instead in
96-
# the "Restore cached deps" step, and we make the check not required.
97-
# When we are able to make this CI check self-sufficient, we should reenable the
98-
# caching and remove the manual restoring of cached deps.
99-
10093
# From the dependency list we restore the cached dependencies.
10194
# We use the hash of `dependencies.txt` as part of the cache key because that will be stable
10295
# until the `index-state` values in the `cabal.project` file changes.
103-
# - name: Restore cached dependencies
104-
# uses: actions/cache/restore@v4
105-
# id: cache
106-
# with:
107-
# path: |
108-
# ${{ env.STORE }}
109-
# dist-newstyle
110-
# key:
111-
# wasm-cache-${{ env.CABAL_CACHE_VERSION }}-${{ runner.os }}-${{ env.GHC }}-${{ hashFiles('cardano-wasm/dependencies.txt') }}
112-
# restore-keys: |
113-
# wasm-cache-${{ env.CABAL_CACHE_VERSION }}-${{ runner.os }}-${{ env.GHC }}-
114-
115-
- name: Restore cached deps
116-
run: |
117-
wget "https://agrius.feralhosting.com/palas/wasm-cache/d1a007cb07f43d2418e4d177c7e7dffc25b30cda49c50e669a1672ffadf42fe1.tar.xz"
118-
tar -xf d1a007cb07f43d2418e4d177c7e7dffc25b30cda49c50e669a1672ffadf42fe1.tar.xz
119-
rm d1a007cb07f43d2418e4d177c7e7dffc25b30cda49c50e669a1672ffadf42fe1.tar.xz
120-
rm -fr ~/.ghc-wasm/.cabal/store/
121-
mv store ~/.ghc-wasm/.cabal/
96+
- name: Restore cached dependencies
97+
uses: actions/cache/restore@v4
98+
id: cache
99+
with:
100+
path: |
101+
${{ env.STORE }}
102+
dist-newstyle
103+
key:
104+
wasm-cache-${{ env.CABAL_CACHE_VERSION }}-${{ runner.os }}-${{ env.GHC }}-${{ hashFiles('cardano-wasm/dependencies.txt') }}
105+
restore-keys: |
106+
wasm-cache-${{ env.CABAL_CACHE_VERSION }}-${{ runner.os }}-${{ env.GHC }}-
122107
123108
# Now we install the dependencies. If the cache was found and restored in the previous step,
124109
# this should be a no-op, but if the cache key was not found we need to build stuff so we can
125110
# cache it for the next step.
126111
- name: Install dependencies
127112
run: |
128-
wasm32-wasi-cabal build cardano-wasm --only-dependencies --no-semaphore -j1 --ghc-options="-j1"
113+
wasm32-wasi-cabal build cardano-wasm --only-dependencies
129114
130115
# Always store the cabal cache.
131-
# - name: Cache Cabal store
132-
# uses: actions/cache/save@v4
133-
# if: always()
134-
# with:
135-
# path: |
136-
# ${{ env.STORE }}
137-
# dist-newstyle
138-
# key:
139-
# ${{ steps.cache.outputs.cache-primary-key }}
116+
- name: Cache Cabal store
117+
uses: actions/cache/save@v4
118+
if: always()
119+
with:
120+
path: |
121+
${{ env.STORE }}
122+
dist-newstyle
123+
key:
124+
${{ steps.cache.outputs.cache-primary-key }}
140125

141126
# Now we build.
142127
- name: Build all
143128
run: |
144-
wasm32-wasi-cabal build cardano-wasm --no-semaphore -j1 --ghc-options="-j1"
145-
wasm32-wasi-cabal build cardano-wasi --no-semaphore -j1 --ghc-options="-j1"
129+
wasm32-wasi-cabal build cardano-wasm
130+
wasm32-wasi-cabal build cardano-wasi
146131
wasm-opt -O4 $(env -u CABAL_CONFIG wasm32-wasi-cabal list-bin exe:cardano-wasi | tail -n1) -o cardano-wasm/cardano-wasi.wasm
147132
148133
- name: Prepare example

cardano-wasm/cardano-wasm.cabal

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,18 @@ library cardano-wasi-lib
3030
src-lib
3131

3232
exposed-modules:
33+
Cardano.Wasm.Api.Certificate.StakeCertificate
3334
Cardano.Wasm.Api.GRPC
3435
Cardano.Wasm.Api.Info
3536
Cardano.Wasm.Api.InfoToTypeScript
3637
Cardano.Wasm.Api.Tx
3738
Cardano.Wasm.Api.TypeScriptDefs
3839
Cardano.Wasm.Api.Wallet
3940
Cardano.Wasm.ExceptionHandling
41+
Cardano.Wasm.NumberConversion
4042

4143
other-modules:
44+
Cardano.Wasm.Internal.Api.Era
4245
Cardano.Wasm.Internal.Api.Random
4346

4447
build-depends:
@@ -77,8 +80,9 @@ executable cardano-wasi
7780
if arch(wasm32)
7881
ghc-options:
7982
-no-hs-main
80-
"-optl-Wl,--strip-all,--export=hs_init,--export=newTx,--export=newExperimentalEraTx,--export=newConwayTx,--export=addTxInput,--export=addSimpleTxOut,--export=appendCertificateToTx,--export=setFee,--export=estimateMinFee,--export=signWithPaymentKey,--export=alsoSignWithPaymentKey,--export=toCbor,--export=generatePaymentWallet,--export=generateStakeWallet,--export=restorePaymentWalletFromSigningKeyBech32,--export=restoreStakeWalletFromSigningKeyBech32,--export=generateTestnetPaymentWallet,--export=generateTestnetStakeWallet,--export=restoreTestnetPaymentWalletFromSigningKeyBech32,--export=restoreTestnetStakeWalletFromSigningKeyBech32,--export=getAddressBech32,--export=getBech32ForPaymentVerificationKey,--export=getBech32ForPaymentSigningKey,--export=getBech32ForStakeVerificationKey,--export=getBech32ForStakeSigningKey,--export=getBase16ForPaymentVerificationKeyHash,--export=getBase16ForStakeVerificationKeyHash,--export=mallocNBytes,--export=getStrLen,--export=freeMemory"
83+
"-optl-Wl,--strip-all,--export=hs_init,--export=newTx,--export=newUpcomingEraTx,--export=addTxInput,--export=addSimpleTxOut,--export=appendCertificateToTx,--export=setFee,--export=estimateMinFee,--export=signWithPaymentKey,--export=alsoSignWithPaymentKey,--export=toCbor,--export=makeStakeAddressStakeDelegationCertificate,--export=makeStakeAddressStakeDelegationCertificateUpcomingEra,--export=makeStakeAddressRegistrationCertificate,--export=makeStakeAddressRegistrationCertificateUpcomingEra,--export=makeStakeAddressUnregistrationCertificate,--export=makeStakeAddressUnregistrationCertificateUpcomingEra,--export=generatePaymentWallet,--export=generateStakeWallet,--export=restorePaymentWalletFromSigningKeyBech32,--export=restoreStakeWalletFromSigningKeyBech32,--export=generateTestnetPaymentWallet,--export=generateTestnetStakeWallet,--export=restoreTestnetPaymentWalletFromSigningKeyBech32,--export=restoreTestnetStakeWalletFromSigningKeyBech32,--export=getAddressBech32,--export=getBech32ForPaymentVerificationKey,--export=getBech32ForPaymentSigningKey,--export=getBech32ForStakeVerificationKey,--export=getBech32ForStakeSigningKey,--export=getBase16ForPaymentVerificationKeyHash,--export=getBase16ForStakeVerificationKeyHash,--export=mallocNBytes,--export=getStrLen,--export=freeMemory"
8184
other-modules:
85+
Cardano.Wasi.Internal.Api.Certificate.StakeCertificate
8286
Cardano.Wasi.Internal.Api.GRPC
8387
Cardano.Wasi.Internal.Api.Memory
8488
Cardano.Wasi.Internal.Api.Tx

cardano-wasm/examples/basic/example.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,26 @@ async function get_protocol_params() {
1111
let protocolParams = await get_protocol_params();
1212

1313
const output = document.createElement("code");
14-
output.innerText = "";
14+
output.textContent = "";
1515
output.id = "test-output";
1616
document.body.appendChild(output);
1717

1818
function log(out) {
1919
console.log(out);
2020
if (typeof (out) == "object") {
21-
output.innerText += "> [object] {\n";
21+
output.textContent += "> [object] {\n";
2222
for (let [key, val] of Object.entries(out)) {
2323
let text = val.toString();
2424
if (typeof (val) == "function") {
2525
text = text.split("{")[0];
2626
}
27-
output.innerText += "    " + key + ": " + text + "\n";
27+
output.textContent += "    " + key + ": " + text + "\n";
2828
}
29-
output.innerText += "  }\n";
29+
output.textContent += "  }\n";
3030
} else if (typeof (out) == "bigint") {
31-
output.innerText += "> " + out.toString() + "n\n";
31+
output.textContent += "> " + out.toString() + "n\n";
3232
} else {
33-
output.innerText += "> " + JSON.stringify(out) + "\n";
33+
output.textContent += "> " + JSON.stringify(out) + "\n";
3434
}
3535
}
3636

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
> "Api object:"
2+
> [object] {
3+
    objectType: cardano-api
4+
    tx: [object Object]
5+
    newGrpcConnection: async function (...args)
6+
    certificate: [object Object]
7+
    wallet: [object Object]
8+
  }
9+
> "Bech32 of address:"
10+
> "addr_test1vp93p9em3regvgylxuvet6fgr3e9sn259pcejgrk4ykystcs7v8j6"
11+
> "UnsignedTx object:"
12+
> [object] {
13+
    objectType: UnsignedTx
14+
    addTxInput: function (txId,txIx)
15+
    addSimpleTxOut: function (destAddr,lovelaceAmount)
16+
    appendCertificateToTx: function (certCbor)
17+
    setFee: function (lovelaceAmount)
18+
    estimateMinFee: function (protocolParams,numKeyWitnesses,numByronKeyWitnesses,totalRefScriptSize)
19+
    signWithPaymentKey: function (signingKey)
20+
  }
21+
> "Estimated fee:"
22+
> 164005n
23+
> "SignedTx object:"
24+
> [object] {
25+
    objectType: SignedTx
26+
    alsoSignWithPaymentKey: function (signingKey)
27+
    txToCbor: function ()
28+
  }
29+
> "Tx CBOR:"
30+
> "84a300d9010281825820be6efd42a3d7b9a00d09d77a5d41e55ceaf0bd093a8aa8a893ce70d9caafd97800018182581d6082935e44937e8b530f32ce672b5d600d0a286b4e8a52c6555f659b871a00989680021a000280a5a100d9010281825820adfc1c30385916da87db1ba3328f0690a57ebb2a6ac9f6f86b2d97f943adae005840a49259b5977aea523b46f01261fbff93e0899e8700319e11f5ab96b67eb628fca1a233ce2d50ee3227b591b84f27237d920d63974d65728362382f751c4d9400f5f6"
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import { test, expect } from '@playwright/test';
2+
import { readFileSync, writeFileSync } from 'fs';
3+
import { resolve } from 'path';
4+
5+
const goldenPath = resolve(__dirname, 'basic-test.golden');
6+
const recreateGoldenFiles = !!process.env.RECREATE_GOLDEN_FILES;
27

38
test('test output matches', async ({ page }) => {
49
// Navigate to the test page
@@ -7,6 +12,14 @@ test('test output matches', async ({ page }) => {
712
await expect(page).toHaveTitle(/cardano-wasm test/);
813
// Wait for the test to finish running (we signal this by creating a tag with id "finish-tag" and text "Finished test!")
914
await expect(page.locator('#finish-tag')).toHaveText("Finished test!");
10-
// 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 tx: [object Object] newGrpcConnection: async function (...args) wallet: [object Object] }> \"Bech32 of address:\"> \"addr_test1vp93p9em3regvgylxuvet6fgr3e9sn259pcejgrk4ykystcs7v8j6\"> \"UnsignedTx object:\"> [object] { objectType: UnsignedTx addTxInput: function (txId,txIx) addSimpleTxOut: function (destAddr,lovelaceAmount) appendCertificateToTx: function (certCbor) setFee: function (lovelaceAmount) estimateMinFee: function (protocolParams,numKeyWitnesses,numByronKeyWitnesses,totalRefScriptSize) signWithPaymentKey: function (signingKey) }> \"Estimated fee:\"> 164005n> \"SignedTx object:\"> [object] { objectType: SignedTx alsoSignWithPaymentKey: function (signingKey) txToCbor: function () }> \"Tx CBOR:\"> \"84a300d9010281825820be6efd42a3d7b9a00d09d77a5d41e55ceaf0bd093a8aa8a893ce70d9caafd97800018182581d6082935e44937e8b530f32ce672b5d600d0a286b4e8a52c6555f659b871a00989680021a000280a5a100d9010281825820adfc1c30385916da87db1ba3328f0690a57ebb2a6ac9f6f86b2d97f943adae005840a49259b5977aea523b46f01261fbff93e0899e8700319e11f5ab96b67eb628fca1a233ce2d50ee3227b591b84f27237d920d63974d65728362382f751c4d9400f5f6\"");
15+
// Check the output of the test against the golden file
16+
const actual = await page.locator('#test-output').textContent();
17+
18+
if (recreateGoldenFiles) {
19+
writeFileSync(goldenPath, actual ?? '', 'utf-8');
20+
console.log(`Golden file updated: ${goldenPath}`);
21+
} else {
22+
const goldenText = readFileSync(goldenPath, 'utf-8');
23+
expect(actual).toBe(goldenText);
24+
}
1225
});

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

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ declare interface CardanoApi {
2020
*/
2121
tx: {
2222
/**
23-
* Create a new unsigned transaction in the current era (currently Conway).
23+
* Create a new unsigned transaction in the current mainnet era (currently Conway).
2424
* @returns A promise that resolves to a new `UnsignedTx` object.
2525
*/
2626
newTx(): Promise<UnsignedTx>;
2727

2828
/**
29-
* Create a new unsigned transaction in the current experimental era (currently unavailable).
29+
* Create a new unsigned transaction in the upcoming mainnet era (currently Dijkstra).
3030
* @returns A promise that resolves to a new `UnsignedTx` object.
3131
*/
32-
newExperimentalEraTx(): Promise<UnsignedTx>;
32+
newUpcomingEraTx(): Promise<UnsignedTx>;
3333

3434
/**
3535
* Create a new unsigned transaction in the Conway era.
@@ -45,6 +45,69 @@ declare interface CardanoApi {
4545
*/
4646
newGrpcConnection(webGrpcUrl: string): Promise<GrpcConnection>;
4747

48+
/**
49+
* Methods for creating certificates.
50+
*/
51+
certificate: {
52+
/**
53+
* Methods for creating certificates in the current mainnet era (currently Conway).
54+
*/
55+
mainnetEra: {
56+
/**
57+
* Make a certificate that delegates a stake address to a stake pool in the current mainnet era (currently Conway).
58+
* @param stakeKeyHash The stake key hash in base16 format.
59+
* @param poolId The pool ID in base16 format.
60+
* @returns A promise that resolves to the CBOR-encoded certificate as a hex string.
61+
*/
62+
makeStakeAddressStakeDelegationCertificate(stakeKeyHash: string, poolId: string): Promise<string>;
63+
64+
/**
65+
* Make a stake address registration certificate in the current mainnet era (currently Conway).
66+
* @param stakeKeyHash The stake key hash in base16 format.
67+
* @param deposit The deposit amount in lovelaces.
68+
* @returns A promise that resolves to the CBOR-encoded certificate as a hex string.
69+
*/
70+
makeStakeAddressRegistrationCertificate(stakeKeyHash: string, deposit: bigint): Promise<string>;
71+
72+
/**
73+
* Make a stake address unregistration certificate in the current mainnet era (currently Conway).
74+
* @param stakeKeyHash The stake key hash in base16 format.
75+
* @param deposit The deposit amount in lovelaces.
76+
* @returns A promise that resolves to the CBOR-encoded certificate as a hex string.
77+
*/
78+
makeStakeAddressUnregistrationCertificate(stakeKeyHash: string, deposit: bigint): Promise<string>;
79+
}
80+
81+
/**
82+
* Methods for creating certificates in the current upcoming era (currently Dijkstra).
83+
*/
84+
upcomingEra: {
85+
/**
86+
* Make a certificate that delegates a stake address to a stake pool in the current upcoming era (currently Dijkstra).
87+
* @param stakeKeyHash The stake key hash in base16 format.
88+
* @param poolId The pool ID in base16 format.
89+
* @returns A promise that resolves to the CBOR-encoded certificate as a hex string.
90+
*/
91+
makeStakeAddressStakeDelegationCertificateUpcomingEra(stakeKeyHash: string, poolId: string): Promise<string>;
92+
93+
/**
94+
* Make a stake address registration certificate in the current upcoming era (currently Dijkstra).
95+
* @param stakeKeyHash The stake key hash in base16 format.
96+
* @param deposit The deposit amount in lovelaces.
97+
* @returns A promise that resolves to the CBOR-encoded certificate as a hex string.
98+
*/
99+
makeStakeAddressRegistrationCertificateUpcomingEra(stakeKeyHash: string, deposit: bigint): Promise<string>;
100+
101+
/**
102+
* Make a stake address unregistration certificate in the current upcoming era (currently Dijkstra).
103+
* @param stakeKeyHash The stake key hash in base16 format.
104+
* @param deposit The deposit amount in lovelaces.
105+
* @returns A promise that resolves to the CBOR-encoded certificate as a hex string.
106+
*/
107+
makeStakeAddressUnregistrationCertificateUpcomingEra(stakeKeyHash: string, deposit: bigint): Promise<string>;
108+
}
109+
}
110+
48111
/**
49112
* Methods for generating and restoring wallets.
50113
*/

cardano-wasm/lib-wrapper/grpc-connection.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ declare interface GrpcConnection {
1111

1212
/**
1313
* Get the era from the Cardano Node using a GRPC-web client.
14-
* @returns A promise that resolves to the current era number.
14+
* @returns A promise that resolves to the current mainnet era number.
1515
*/
1616
getEra(): Promise<number>;
1717

0 commit comments

Comments
 (0)