diff --git a/.vscode/settings.json b/.vscode/settings.json
index 48cd83e..ef727d1 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -5,5 +5,5 @@
},
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
- "cSpell.words": ["preprod"]
+ "cSpell.words": ["DEREGISTRATION", "drep", "preprod"]
}
diff --git a/CHANGELOG.MD b/CHANGELOG.MD
index 980a339..c277757 100644
--- a/CHANGELOG.MD
+++ b/CHANGELOG.MD
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
+### Added
+
+- Support for voting delegation certificates
+
+### Changed
+
+- Upgraded cardano-serialization-lib to Chang-compatible release (v12)
+
## [2.2.1] - 2023-11-29
### Fixed
diff --git a/docs/badge-coverage.svg b/docs/badge-coverage.svg
index 6d18e4a..986ac1f 100644
--- a/docs/badge-coverage.svg
+++ b/docs/badge-coverage.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/constants/index.ts b/src/constants/index.ts
index 228e3a0..130e322 100644
--- a/src/constants/index.ts
+++ b/src/constants/index.ts
@@ -5,6 +5,9 @@ export const CertificateType = {
STAKE_DEREGISTRATION: 1,
STAKE_DELEGATION: 2,
STAKE_POOL_REGISTRATION: 3,
+ STAKE_REGISTRATION_CONWAY: 7,
+ STAKE_DEREGISTRATION_CONWAY: 8,
+ VOTE_DELEGATION: 9,
} as const;
export const ERROR = {
diff --git a/src/types/types.ts b/src/types/types.ts
index 7977da2..80b4184 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -67,6 +67,23 @@ export enum CardanoAddressType {
REWARD_SCRIPT = 15,
}
+export enum CardanoDRepType {
+ KEY_HASH = 0,
+ SCRIPT_HASH = 1,
+ ABSTAIN = 2,
+ NO_CONFIDENCE = 3,
+}
+
+export type DRep =
+ | {
+ type: CardanoDRepType.KEY_HASH | CardanoDRepType.SCRIPT_HASH;
+ keyHash: string;
+ }
+ | {
+ type: CardanoDRepType.ABSTAIN | CardanoDRepType.NO_CONFIDENCE;
+ keyHash?: never;
+ };
+
export interface CoinSelectionResult {
tx: { body: string; hash: string; size: number };
inputs: Utxo[];
@@ -116,10 +133,19 @@ export interface CertificateStakePoolRegistration {
pool_parameters: Record;
}
+export interface CertificateVoteDelegation {
+ type: CertificateTypeType['VOTE_DELEGATION'];
+ dRep: {
+ type: CardanoDRepType;
+ keyHash: string;
+ };
+}
+
export type Certificate =
| CertificateStakeRegistration
| CertificateStakeDelegation
- | CertificateStakePoolRegistration;
+ | CertificateStakePoolRegistration
+ | CertificateVoteDelegation;
export interface Options {
feeParams?: { a: string };
diff --git a/src/utils/common.ts b/src/utils/common.ts
index 8def620..7e19fe3 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -15,6 +15,7 @@ import {
UserOutput,
Asset,
ChangeOutput,
+ CardanoDRepType,
} from '../types/types';
import { CoinSelectionError } from './errors';
@@ -299,6 +300,34 @@ export const prepareCertificates = (
CardanoWasm.StakeDeregistration.new(stakeCred),
),
);
+ } else if (cert.type === CertificateType.VOTE_DELEGATION) {
+ let targetDRep: CardanoWasm.DRep;
+ switch (cert.dRep.type) {
+ case CardanoDRepType.ABSTAIN:
+ targetDRep = CardanoWasm.DRep.new_always_abstain();
+ break;
+ case CardanoDRepType.NO_CONFIDENCE:
+ targetDRep = CardanoWasm.DRep.new_always_no_confidence();
+ break;
+ case CardanoDRepType.KEY_HASH:
+ targetDRep = CardanoWasm.DRep.new_key_hash(
+ CardanoWasm.Ed25519KeyHash.from_hex(cert.dRep.keyHash),
+ );
+ break;
+ case CardanoDRepType.SCRIPT_HASH:
+ targetDRep = CardanoWasm.DRep.new_script_hash(
+ CardanoWasm.ScriptHash.from_hex(cert.dRep.keyHash),
+ );
+ break;
+ }
+
+ if (targetDRep) {
+ preparedCertificates.add(
+ CardanoWasm.Certificate.new_vote_delegation(
+ CardanoWasm.VoteDelegation.new(stakeCred, targetDRep),
+ ),
+ );
+ }
} else {
throw new CoinSelectionError(ERROR.UNSUPPORTED_CERTIFICATE_TYPE);
}
@@ -311,6 +340,7 @@ export const calculateRequiredDeposit = (
): number => {
const CertificateDeposit = {
[CertificateType.STAKE_DELEGATION]: 0,
+ [CertificateType.VOTE_DELEGATION]: 0,
[CertificateType.STAKE_POOL_REGISTRATION]: 500000000,
[CertificateType.STAKE_REGISTRATION]: 2000000,
[CertificateType.STAKE_DEREGISTRATION]: -2000000,
diff --git a/src/utils/trezor/index.ts b/src/utils/trezor/index.ts
index f0ac253..b95a729 100644
--- a/src/utils/trezor/index.ts
+++ b/src/utils/trezor/index.ts
@@ -2,6 +2,7 @@ import {
transformToTokenBundle,
transformToTrezorInputs,
transformToTrezorOutputs,
+ drepIdToHex,
} from './transformations';
import { signTransaction } from './sign';
@@ -10,4 +11,5 @@ export {
transformToTrezorInputs,
transformToTrezorOutputs,
signTransaction,
+ drepIdToHex,
};
diff --git a/src/utils/trezor/transformations.ts b/src/utils/trezor/transformations.ts
index 47c6096..d3838b9 100644
--- a/src/utils/trezor/transformations.ts
+++ b/src/utils/trezor/transformations.ts
@@ -1,9 +1,11 @@
+import * as CardanoWasm from '@emurgo/cardano-serialization-lib-nodejs';
+
import {
CardanoAddressParameters,
CardanoInput,
CardanoOutput,
} from '../../types/trezor';
-import { Asset, FinalOutput, Utxo } from '../../types/types';
+import { Asset, CardanoDRepType, FinalOutput, Utxo } from '../../types/types';
import { parseAsset } from '../common';
interface AssetInPolicy {
@@ -92,3 +94,36 @@ export const transformToTrezorOutputs = (
};
});
};
+
+export const drepIdToHex = (
+ drepId: string,
+): {
+ type: CardanoDRepType.KEY_HASH | CardanoDRepType.SCRIPT_HASH;
+ hex: string;
+} => {
+ const drep = CardanoWasm.DRep.from_bech32(drepId);
+ const kind = drep.kind() as unknown as
+ | CardanoDRepType.KEY_HASH
+ | CardanoDRepType.SCRIPT_HASH;
+
+ let drepHex: string | undefined;
+ switch (kind) {
+ case CardanoDRepType.KEY_HASH:
+ drepHex = drep.to_key_hash()?.to_hex();
+ break;
+ case CardanoDRepType.SCRIPT_HASH:
+ drepHex = drep.to_script_hash()?.to_hex();
+ break;
+ }
+
+ if (!drepHex) {
+ throw Error('Invalid drepId');
+ }
+
+ const drepData = {
+ type: kind,
+ hex: drepHex,
+ };
+ drep.free();
+ return drepData;
+};
diff --git a/tests/methods/fixtures/largestFirst.ts b/tests/methods/fixtures/largestFirst.ts
index 26fa547..89dcbda 100644
--- a/tests/methods/fixtures/largestFirst.ts
+++ b/tests/methods/fixtures/largestFirst.ts
@@ -1,4 +1,8 @@
-import { Certificate } from '../../../src/types/types';
+import {
+ CardanoDRepType,
+ Certificate,
+ CertificateVoteDelegation,
+} from '../../../src/types/types';
import {
changeAddress,
setMaxAdaInputs,
@@ -737,6 +741,87 @@ export const coinSelection = [
],
},
},
+ {
+ description:
+ 'withdrawing rewards: 1 ADA only utxo, 1 change output, vote delegation abstain',
+ utxos: [utxo1],
+ outputs: [],
+ changeAddress: changeAddress,
+ certificates: [
+ {
+ type: 9,
+ dRep: {
+ type: 2, // abstain
+ },
+ } as CertificateVoteDelegation,
+ ],
+ withdrawals: [
+ {
+ amount: '10000000',
+ stakingPath: "m/1852'/1815'/0'/2/0",
+ stakeAddress:
+ 'stake1u8yk3dcuj8yylwvnzz953yups6mmuvt0vtjmxl2gmgceqjqz2yfd2',
+ },
+ ],
+ accountPubKey:
+ 'ec8fdf616242f430855ad7477acda53395eb30c295f5a7ef038712578877375b5a2f00353c9c5cc88c7ff18e71dc08724d90fc238213b789c0b02438e336be07',
+ options: {},
+ result: {
+ totalSpent: '177425',
+ fee: '177425',
+ deposit: '0',
+ inputs: [utxo1],
+ outputs: [
+ {
+ isChange: true,
+ address: changeAddress,
+ amount: '14822575',
+ assets: [],
+ },
+ ],
+ },
+ },
+ {
+ description:
+ 'withdrawing rewards: 1 ADA only utxo, 1 change output, vote delegation drep id',
+ utxos: [utxo1],
+ outputs: [],
+ changeAddress: changeAddress,
+ certificates: [
+ {
+ type: 9,
+ dRep: {
+ type: CardanoDRepType.KEY_HASH,
+ keyHash: '3a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c490711',
+ },
+ } as CertificateVoteDelegation,
+ ],
+ withdrawals: [
+ {
+ amount: '10000000',
+ stakingPath: "m/1852'/1815'/0'/2/0",
+ stakeAddress:
+ 'stake1u8yk3dcuj8yylwvnzz953yups6mmuvt0vtjmxl2gmgceqjqz2yfd2',
+ },
+ ],
+ accountPubKey:
+ 'ec8fdf616242f430855ad7477acda53395eb30c295f5a7ef038712578877375b5a2f00353c9c5cc88c7ff18e71dc08724d90fc238213b789c0b02438e336be07',
+ options: {},
+ result: {
+ totalSpent: '178745',
+ fee: '178745',
+ deposit: '0',
+ inputs: [utxo1],
+ outputs: [
+ {
+ isChange: true,
+ address: changeAddress,
+ amount: '14821255',
+ assets: [],
+ },
+ ],
+ },
+ },
{
description:
'withdrawing rewards: multiple utxos, multiple withdrawals, 1 change output',
diff --git a/tests/setup.ts b/tests/setup.ts
index a5a05e3..dfad7d0 100644
--- a/tests/setup.ts
+++ b/tests/setup.ts
@@ -13,7 +13,7 @@ export const sanityCheck = (res: CoinSelectionResult): void => {
(acc, input) =>
acc.checked_add(
BigNum.from_str(
- input.amount.find(a => a.unit === 'lovelace').quantity || '0',
+ input.amount.find(a => a.unit === 'lovelace')?.quantity || '0',
),
),
BigNum.from_str('0'),
diff --git a/tests/utils/trezor/fixtures/transformations.ts b/tests/utils/trezor/fixtures/transformations.ts
index 6ae3a25..c8f3852 100644
--- a/tests/utils/trezor/fixtures/transformations.ts
+++ b/tests/utils/trezor/fixtures/transformations.ts
@@ -87,3 +87,22 @@ export const transformToTrezorOutputs = [
],
},
];
+
+export const drepIdToHex = [
+ {
+ description: 'keyHash drep',
+ drepId: 'drep16pxnn38ykshfahwmkaqmke3kdqaksg4w935d7uztvh8y5l48pxv',
+ result: {
+ type: 0,
+ hex: 'd04d39c4e4b42e9edddbb741bb6636683b6822ae2c68df704b65ce4a',
+ },
+ },
+ {
+ description: 'scriptHash drep',
+ drepId: 'drep_script16pxnn38ykshfahwmkaqmke3kdqaksg4w935d7uztvh8y5sh6f6d',
+ result: {
+ type: 1,
+ hex: 'd04d39c4e4b42e9edddbb741bb6636683b6822ae2c68df704b65ce4a',
+ },
+ },
+];
diff --git a/tests/utils/trezor/transformations.test.ts b/tests/utils/trezor/transformations.test.ts
index 88d7076..de63dfb 100644
--- a/tests/utils/trezor/transformations.test.ts
+++ b/tests/utils/trezor/transformations.test.ts
@@ -13,4 +13,10 @@ describe('trezor transformation utils', () => {
).toMatchObject(f.result);
});
});
+
+ fixtures.drepIdToHex.forEach(f => {
+ test(f.description, () => {
+ expect(utils.drepIdToHex(f.drepId)).toStrictEqual(f.result);
+ });
+ });
});