diff --git a/CHANGELOG-npm.md b/CHANGELOG-npm.md
index 37e0067..5f7e921 100644
--- a/CHANGELOG-npm.md
+++ b/CHANGELOG-npm.md
@@ -2,6 +2,9 @@
## Unreleased
+## 0.8.0
+- cardano: allow vote delegation
+
## 0.7.0
- btc: handle error when an input's previous transaction is required but missing
- btc: add support for regtest
diff --git a/NPM_VERSION b/NPM_VERSION
index bcaffe1..8adc70f 100644
--- a/NPM_VERSION
+++ b/NPM_VERSION
@@ -1 +1 @@
-0.7.0
\ No newline at end of file
+0.8.0
\ No newline at end of file
diff --git a/sandbox/src/Cardano.tsx b/sandbox/src/Cardano.tsx
index 322af09..c15e082 100644
--- a/sandbox/src/Cardano.tsx
+++ b/sandbox/src/Cardano.tsx
@@ -133,12 +133,11 @@ function CardanoAddress({ bb02 }: Props) {
-
);
}
function CardanoSignTransaction({ bb02 }: Props) {
- type TxType = 'normal' | 'zero-ttl' | 'tokens' | 'delegate' | 'withdraw-staking-rewards';
+ type TxType = 'normal' | 'zero-ttl' | 'tokens' | 'delegate' | 'vote-delegation' | 'vote-delegation-keyhash' | 'withdraw-staking-rewards';
const [txType, setTxType] = useState('normal');
const [running, setRunning] = useState(false);
const [result, setResult] = useState();
@@ -151,6 +150,8 @@ function CardanoSignTransaction({ bb02 }: Props) {
['zero-ttl', 'Transaction with TTL=0'],
['tokens', 'Transaction sending tokens'],
['delegate', 'Delegate staking to a pool'],
+ ['vote-delegation', 'Delegate vote to a dRep'],
+ ['vote-delegation-keyhash', 'Delegate vote to a dRep with a keyhash'],
['withdraw-staking-rewards', 'Withdraw staking rewards'],
];
@@ -176,6 +177,8 @@ function CardanoSignTransaction({ bb02 }: Props) {
};
const changeAddress = await bb02.cardanoAddress(network, changeConfig, false);
+ const drepType: bitbox.CardanoDrepType = 'alwaysAbstain';
+ const drepKeyHashType: bitbox.CardanoDrepType = 'keyHash';
const transaction = () => {
switch (txType) {
case 'normal':
@@ -290,6 +293,57 @@ function CardanoSignTransaction({ bb02 }: Props) {
validityIntervalStart: BigInt(41110811),
allowZeroTTL: false,
};
+ case 'vote-delegation':
+ return {
+ network,
+ inputs,
+ outputs: [
+ {
+ encodedAddress: changeAddress,
+ value: BigInt(2741512),
+ scriptConfig: changeConfig,
+ },
+ ],
+ fee: BigInt(191681),
+ ttl: BigInt(41539125),
+ certificates: [
+ {
+ voteDelegation: {
+ keypath: "m/1852'/1815'/0'/2/0",
+ type: drepType,
+ },
+ },
+ ],
+ withdrawals: [],
+ validityIntervalStart: BigInt(41110811),
+ allowZeroTTL: false,
+ };
+ case 'vote-delegation-keyhash':
+ return {
+ network,
+ inputs,
+ outputs: [
+ {
+ encodedAddress: changeAddress,
+ value: BigInt(2741512),
+ scriptConfig: changeConfig,
+ },
+ ],
+ fee: BigInt(191681),
+ ttl: BigInt(41539125),
+ certificates: [
+ {
+ voteDelegation: {
+ keypath: "m/1852'/1815'/0'/2/0",
+ type: drepKeyHashType,
+ drepCredhash: new Uint8Array(hexToArrayBuffer("abababababababababababababababababababababababababababab")),
+ },
+ },
+ ],
+ withdrawals: [],
+ validityIntervalStart: BigInt(41110811),
+ allowZeroTTL: false,
+ };
case 'withdraw-staking-rewards':
return {
network,
diff --git a/scripts/build-protos.rs b/scripts/build-protos.rs
index 8547470..d5ef0e0 100644
--- a/scripts/build-protos.rs
+++ b/scripts/build-protos.rs
@@ -67,11 +67,11 @@ fn add_serde_attrs(c: &mut prost_build::Config) {
"shiftcrypto.bitbox02.BTCPubRequest.XPubType.CAPITAL_YPUB",
"serde(rename = \"Ypub\")",
),
+ // Cardano
(
"shiftcrypto.bitbox02.CardanoNetwork.CardanoMainnet",
"serde(rename = \"mainnet\")",
),
- // Cardano
(
"shiftcrypto.bitbox02.CardanoNetwork.CardanoTestnet",
"serde(rename = \"testnet\")",
@@ -92,6 +92,10 @@ fn add_serde_attrs(c: &mut prost_build::Config) {
"keypath_stake",
"serde(deserialize_with = \"crate::keypath::serde_deserialize\")",
),
+ (
+ "shiftcrypto.bitbox02.CardanoSignTransactionRequest.Certificate.VoteDelegation.type",
+ "serde(deserialize_with = \"crate::cardano::serde_deserialize_drep_type\")",
+ ),
];
for (path, attr) in type_attrs {
diff --git a/src/cardano.rs b/src/cardano.rs
index 83412b7..ddb10e6 100644
--- a/src/cardano.rs
+++ b/src/cardano.rs
@@ -15,6 +15,16 @@ where
Ok(network as i32)
}
+#[cfg(feature = "wasm")]
+pub(crate) fn serde_deserialize_drep_type<'de, D>(deserializer: D) -> Result
+where
+ D: serde::Deserializer<'de>,
+{
+ use serde::Deserialize;
+ let drep_type = pb::cardano_sign_transaction_request::certificate::vote_delegation::CardanoDRepType::deserialize(deserializer)?;
+ Ok(drep_type as i32)
+}
+
#[cfg(feature = "wasm")]
#[derive(serde::Deserialize)]
pub(crate) struct SerdeScriptConfig(pb::cardano_script_config::Config);
diff --git a/src/shiftcrypto.bitbox02.rs b/src/shiftcrypto.bitbox02.rs
index 0e6de11..fb00593 100644
--- a/src/shiftcrypto.bitbox02.rs
+++ b/src/shiftcrypto.bitbox02.rs
@@ -1248,6 +1248,10 @@ pub mod cardano_sign_transaction_request {
)]
pub keypath: ::prost::alloc::vec::Vec,
#[prost(enumeration = "vote_delegation::CardanoDRepType", tag = "2")]
+ #[cfg_attr(
+ feature = "wasm",
+ serde(deserialize_with = "crate::cardano::serde_deserialize_drep_type")
+ )]
pub r#type: i32,
#[prost(bytes = "vec", optional, tag = "3")]
pub drep_credhash: ::core::option::Option<::prost::alloc::vec::Vec>,
diff --git a/src/wasm/types.rs b/src/wasm/types.rs
index d8c5b5e..63352e9 100644
--- a/src/wasm/types.rs
+++ b/src/wasm/types.rs
@@ -104,6 +104,7 @@ type CardanoOutput = {
scriptConfig?: CardanoScriptConfig;
assetGroups?: CardanoAssetGroup[];
}
+type CardanoDrepType = 'keyHash' | 'scriptHash' | 'alwaysAbstain' | 'alwaysNoConfidence'
type CardanoCertificate =
| {
stakeRegistration: {
@@ -120,6 +121,13 @@ type CardanoCertificate =
keypath: Keypath
poolKeyhash: Uint8Array
}
+ }
+ | {
+ voteDelegation: {
+ keypath: Keypath
+ type: CardanoDrepType
+ drepCredhash?: Uint8Array
+ }
};
type CardanoWithdrawal = {
keypath: Keypath;
diff --git a/tests/subtests/test_cardano.rs b/tests/subtests/test_cardano.rs
index 93834d0..a317428 100644
--- a/tests/subtests/test_cardano.rs
+++ b/tests/subtests/test_cardano.rs
@@ -223,6 +223,63 @@ pub async fn test(bitbox: &PairedBitBox) {
);
}
}
+ // Delegating vote to a drep with a keyhash
+ {
+ let transaction = pb::CardanoSignTransactionRequest {
+ network: pb::CardanoNetwork::CardanoMainnet as i32,
+ inputs: vec![pb::cardano_sign_transaction_request::Input {
+ keypath: keypath_input.to_vec(),
+ prev_out_hash: hex::decode(
+ "59864ee73ca5d91098a32b3ce9811bac1996dcbaefa6b6247dcaafb5779c2538",
+ )
+ .unwrap(),
+ prev_out_index: 0,
+ }],
+ outputs: vec![pb::cardano_sign_transaction_request::Output {
+ encoded_address: change_address.clone(),
+ value: 2741512,
+ script_config: Some(change_config.clone()),
+ ..Default::default()
+ }],
+ fee: 191681,
+ ttl: 41539125,
+ certificates: vec![
+ pb::cardano_sign_transaction_request::Certificate {
+ cert: Some(
+ pb::cardano_sign_transaction_request::certificate::Cert::VoteDelegation(
+ pb::cardano_sign_transaction_request::certificate::VoteDelegation {
+ keypath: vec![2147485500, 2147485463, 2147483648, 2, 0],
+ r#type: pb::cardano_sign_transaction_request::certificate::vote_delegation::CardanoDRepType::KeyHash.into(),
+ drep_credhash: Some(hex::decode(
+ "abababababababababababababababababababababababababababab",
+ )
+ .unwrap()),
+ },
+ ),
+ ),
+ },
+ ],
+ withdrawals: vec![],
+ validity_interval_start: 41110811,
+ allow_zero_ttl: false,
+ };
+
+ if semver::VersionReq::parse(">=9.21.0")
+ .unwrap()
+ .matches(bitbox.version())
+ {
+ let witness = bitbox.cardano_sign_transaction(transaction).await.unwrap();
+ assert_eq!(witness.shelley_witnesses.len(), 2);
+ assert_eq!(
+ hex::encode(&witness.shelley_witnesses[0].public_key),
+ "6b5d4134cfc66281827d51cb0196f1a951ce168c19ba1314233f43d39d91e2bc",
+ );
+ assert_eq!(
+ hex::encode(&witness.shelley_witnesses[1].public_key),
+ "ed0d6426efcae3b02b963db0997845ba43ed53c131aa2f0faa01976ddcdb3751",
+ );
+ }
+ }
// Withdrawing staking rewards...
{
let transaction = pb::CardanoSignTransactionRequest {