From 6d985bbce2e2e2f5d827f3c19a752c6c19ce93af Mon Sep 17 00:00:00 2001 From: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Date: Tue, 14 May 2024 13:24:17 +0200 Subject: [PATCH 1/3] feat: add shelley address & cardano serialization lib (#478) * feat: add shelley address & cardano serialization lib * fix: address to string * style: disable cspell for cardano addresses --------- Co-authored-by: Dominik Toton --- .config/dictionaries/project.dic | 9 +- catalyst_voices/pubspec.yaml | 2 + .../CHANGELOG.md | 3 + .../catalyst_cardano_serialization/README.md | 1 + .../analysis_options.yaml | 4 + .../lib/catalyst_cardano_serialization.dart | 6 + .../lib/src/address.dart | 128 +++++++++++++ .../lib/src/exceptions.dart | 51 ++++++ .../lib/src/fees.dart | 30 +++ .../lib/src/hashes.dart | 101 ++++++++++ .../lib/src/transaction.dart | 173 ++++++++++++++++++ .../lib/src/types.dart | 60 ++++++ .../pubspec.yaml | 21 +++ .../test/address_test.dart | 74 ++++++++ .../test/fees_test.dart | 39 ++++ .../test/hashes_test.dart | 73 ++++++++ .../test/transaction_test.dart | 38 ++++ .../test/types_test.dart | 33 ++++ .../test/utils/test_data.dart | 81 ++++++++ melos.yaml | 5 + 20 files changed, 931 insertions(+), 1 deletion(-) create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/CHANGELOG.md create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/README.md create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/analysis_options.yaml create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/lib/catalyst_cardano_serialization.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/lib/src/address.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/lib/src/exceptions.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/lib/src/fees.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/lib/src/hashes.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/lib/src/transaction.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/lib/src/types.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/pubspec.yaml create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/test/address_test.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/test/fees_test.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/test/hashes_test.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/test/transaction_test.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/test/types_test.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/test/utils/test_data.dart diff --git a/.config/dictionaries/project.dic b/.config/dictionaries/project.dic index ca89d88213..fd5be5e448 100644 --- a/.config/dictionaries/project.dic +++ b/.config/dictionaries/project.dic @@ -189,4 +189,11 @@ xcodeproj xctest xctestrun xcworkspace -yoroi \ No newline at end of file +yoroi +multiplatform +Multiplatform +Easterling +lovelace +lovelaces +pinenacl +dtscalac diff --git a/catalyst_voices/pubspec.yaml b/catalyst_voices/pubspec.yaml index 15c94c316a..810aaed179 100644 --- a/catalyst_voices/pubspec.yaml +++ b/catalyst_voices/pubspec.yaml @@ -10,6 +10,8 @@ environment: dependencies: animated_text_kit: ^4.2.2 animations: ^2.0.11 + catalyst_cardano_serialization: + path: ../catalyst_voices_packages/catalyst_cardano_serialization catalyst_voices_assets: path: ./packages/catalyst_voices_assets catalyst_voices_blocs: diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/CHANGELOG.md b/catalyst_voices_packages/catalyst_cardano_serialization/CHANGELOG.md new file mode 100644 index 0000000000..a533b45bb9 --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/CHANGELOG.md @@ -0,0 +1,3 @@ +# 0.1.0 + +* Initial release. diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/README.md b/catalyst_voices_packages/catalyst_cardano_serialization/README.md new file mode 100644 index 0000000000..191e021c7c --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/README.md @@ -0,0 +1 @@ +# catalyst_cardano_serialization diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/analysis_options.yaml b/catalyst_voices_packages/catalyst_cardano_serialization/analysis_options.yaml new file mode 100644 index 0000000000..886855b51a --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:catalyst_analysis/analysis_options.1.0.0.yaml + +analyzer: + exclude: [build/**, lib/*.g.dart, lib/generated/**] diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/catalyst_cardano_serialization.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/catalyst_cardano_serialization.dart new file mode 100644 index 0000000000..35d28db13b --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/catalyst_cardano_serialization.dart @@ -0,0 +1,6 @@ +export 'src/address.dart'; +export 'src/exceptions.dart'; +export 'src/fees.dart'; +export 'src/hashes.dart'; +export 'src/transaction.dart'; +export 'src/types.dart'; diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/address.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/address.dart new file mode 100644 index 0000000000..a0aefa1477 --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/address.dart @@ -0,0 +1,128 @@ +// Copyright 2021 Richard Easterling +// SPDX-License-Identifier: Apache-2.0 + +// ignore_for_file: avoid_equals_and_hash_code_on_mutable_classes + +import 'package:bip32_ed25519/bip32_ed25519.dart'; +import 'package:catalyst_cardano_serialization/src/exceptions.dart'; +import 'package:catalyst_cardano_serialization/src/types.dart'; +import 'package:cbor/cbor.dart'; + +/// [ShelleyAddress] supports bech32 encoded addresses as defined in CIP19. +class ShelleyAddress { + /// The prefix of a base address. + static const String defaultAddrHrp = 'addr'; + + /// The prefix of a stake/reward address. + static const String defaultRewardHrp = 'stake'; + + /// The hrp suffix of an address on testnet network. + static const String testnetHrpSuffix = '_test'; + + static const Bech32Encoder _mainNetEncoder = + Bech32Encoder(hrp: defaultAddrHrp); + static const Bech32Encoder _testNetEncoder = + Bech32Encoder(hrp: defaultAddrHrp + testnetHrpSuffix); + static const Bech32Encoder _mainNetRewardEncoder = + Bech32Encoder(hrp: defaultRewardHrp); + static const Bech32Encoder _testNetRewardEncoder = + Bech32Encoder(hrp: defaultRewardHrp + testnetHrpSuffix); + + /// Raw bytes of address. + /// Format [ 8 bit header | payload ] + final Uint8List bytes; + + /// The prefix specifying the address type and networkId. + final String hrp; + + /// The constructor for [ShelleyAddress] from raw [bytes] and [hrp]. + ShelleyAddress(List bytes, {this.hrp = defaultAddrHrp}) + : bytes = Uint8List.fromList(bytes); + + /// The constructor which parses the address from bech32 format. + factory ShelleyAddress.fromBech32(String address) { + final hrp = _hrpPrefix(address); + if (hrp.isEmpty) { + throw InvalidAddressException( + 'not a valid Bech32 address - no prefix: $address', + ); + } + + switch (hrp) { + case defaultAddrHrp: + return ShelleyAddress(_mainNetEncoder.decode(address), hrp: hrp); + case const (defaultAddrHrp + testnetHrpSuffix): + return ShelleyAddress(_testNetEncoder.decode(address), hrp: hrp); + case defaultRewardHrp: + return ShelleyAddress(_mainNetRewardEncoder.decode(address), hrp: hrp); + case const (defaultRewardHrp + testnetHrpSuffix): + return ShelleyAddress(_testNetRewardEncoder.decode(address), hrp: hrp); + default: + return ShelleyAddress( + Bech32Encoder(hrp: hrp).decode(address), + hrp: hrp, + ); + } + } + + /// Returns the [NetworkId] related to this address. + NetworkId get network => NetworkId.testnet.id == (bytes[0] & 0x0f) + ? NetworkId.testnet + : NetworkId.mainnet; + + /// Encodes the address in bech32 format. + String toBech32() { + final prefix = _computeHrp(network, hrp); + switch (prefix) { + case defaultAddrHrp: + return _mainNetEncoder.encode(bytes); + case const (defaultAddrHrp + testnetHrpSuffix): + return _testNetEncoder.encode(bytes); + case defaultRewardHrp: + return _mainNetRewardEncoder.encode(bytes); + case const (defaultRewardHrp + testnetHrpSuffix): + return _testNetRewardEncoder.encode(bytes); + default: + return Bech32Encoder(hrp: prefix).encode(bytes); + } + } + + /// Serializes the type as cbor. + CborValue toCbor() => CborBytes(bytes); + + @override + int get hashCode => Object.hash(bytes, hrp); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! ShelleyAddress) return false; + if (bytes.length != other.bytes.length) return false; + if (hrp != other.hrp) return false; + + for (var i = 0; i < bytes.length; i++) { + if (bytes[i] != other.bytes[i]) return false; + } + return true; + } + + @override + String toString() => toBech32(); + + /// If were using the testnet, make sure the hrp ends with '_test' + static String _computeHrp(NetworkId id, String prefix) { + if (id == NetworkId.mainnet) { + return prefix; + } else if (prefix.endsWith(testnetHrpSuffix)) { + return prefix; + } else { + return prefix + testnetHrpSuffix; + } + } + + static String _hrpPrefix(String addr) { + final s = addr.trim(); + final i = s.indexOf('1'); + return s.substring(0, i > 0 ? i : 0); + } +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/exceptions.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/exceptions.dart new file mode 100644 index 0000000000..96bf101459 --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/exceptions.dart @@ -0,0 +1,51 @@ +/// Exception thrown when the transaction exceeds the allowed maximum size. +final class MaxTxSizeExceededException implements Exception { + /// The maximum amount of bytes per transaction. + final int maxTxSize; + + /// The amount of bytes of transaction that exceeded it's maximum size. + final int actualTxSize; + + /// The default constructor for [MaxTxSizeExceededException]. + const MaxTxSizeExceededException({ + required this.maxTxSize, + required this.actualTxSize, + }); + + @override + String toString() => 'MaxTxSizeExceededException(' + 'maxTxSize=$maxTxSize' + ', actualTxSize:$actualTxSize' + ')'; +} + +/// Exception thrown when building a transaction that doesn't specify the fee. +final class TxFeeNotSpecifiedException implements Exception { + /// The default constructor for [TxFeeNotSpecifiedException]. + const TxFeeNotSpecifiedException(); + + @override + String toString() => 'TxFeeNotSpecifiedException'; +} + +/// Exception thrown when parsing a hash that has incorrect length. +final class HashFormatException implements Exception { + /// The default constructor for [HashFormatException]. + const HashFormatException(); + + @override + String toString() => 'HashFormatException'; +} + +/// Exception thrown if the address doesn't match the bech32 specification +/// for Shelley addresses. +final class InvalidAddressException implements Exception { + /// Exception details. + final String message; + + /// Default constructor [InvalidAddressException]. + const InvalidAddressException(this.message); + + @override + String toString() => 'InvalidAddressException: $message'; +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/fees.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/fees.dart new file mode 100644 index 0000000000..a47471798c --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/fees.dart @@ -0,0 +1,30 @@ +import 'package:catalyst_cardano_serialization/src/transaction.dart'; +import 'package:catalyst_cardano_serialization/src/types.dart'; +import 'package:cbor/cbor.dart'; + +/// Calculates fees for the transaction on Cardano blockchain. +/// +/// The fee is calculated using the following formula: +/// - `fee = constant + tx.bytes.len * coefficient` +final class LinearFee { + /// The constant amount of [Coin] that is charged per transaction. + final Coin constant; + + /// The amount of [Coin] per transaction byte that is charged per transaction. + final Coin coefficient; + + /// The default constructor for the [LinearFee]. + /// The parameters are Cardano protocol parameters. + const LinearFee({ + required this.constant, + required this.coefficient, + }); + + /// Calculates the fee for the transaction denominated in lovelaces. + /// + /// The formula doesn't take into account smart contract scripts. + Coin minNoScriptFee(Transaction tx) { + final bytesCount = cbor.encode(tx.toCbor()).length; + return Coin(bytesCount) * coefficient + constant; + } +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/hashes.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/hashes.dart new file mode 100644 index 0000000000..4c9cbbc86e --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/hashes.dart @@ -0,0 +1,101 @@ +// ignore_for_file: avoid_equals_and_hash_code_on_mutable_classes + +import 'dart:typed_data'; + +import 'package:catalyst_cardano_serialization/src/exceptions.dart'; +import 'package:catalyst_cardano_serialization/src/transaction.dart'; +import 'package:cbor/cbor.dart'; +import 'package:convert/convert.dart'; +import 'package:pinenacl/digests.dart'; + +/// Implements a common base of hash types that holds +/// binary [bytes] of exact [length]. +abstract base class BaseHash { + /// The raw [bytes] of a hash. + final List bytes; + + /// Constructs the [BaseHash] from raw [bytes]. + BaseHash.fromBytes({required this.bytes}) { + if (bytes.length != length) { + throw const HashFormatException(); + } + } + + /// Constructs the [BaseHash] from a hex string representation + /// of [bytes]. + BaseHash.fromHex(String string) : this.fromBytes(bytes: hex.decode(string)); + + /// The expected length of the transaction hash bytes. + int get length; + + /// Serializes the type as cbor. + CborValue toCbor() => CborBytes(bytes); + + /// Returns the hex string representation of [bytes]. + String toHex() => hex.encode(bytes); + + @override + String toString() => toHex(); + + @override + int get hashCode => Object.hash(bytes, length); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! BaseHash) return false; + + // prevent subclasses of different types to be equal to each other, + // even if they hold the same bytes they represent different kinds + if (other.runtimeType != runtimeType) return false; + + if (length != other.length) return false; + + for (var i = 0; i < bytes.length; i++) { + if (bytes[i] != other.bytes[i]) return false; + } + + return true; + } +} + +/// Describes the hash of the transaction which serves as proof +/// of transaction validation. +final class TransactionHash extends BaseHash { + static const int _length = 32; + + /// Constructs the [TransactionHash] from raw [bytes]. + TransactionHash.fromBytes({required super.bytes}) : super.fromBytes(); + + /// Constructs the [TransactionHash] from a hex string representation + /// of [bytes]. + TransactionHash.fromHex(super.string) : super.fromHex(); + + @override + int get length => _length; +} + +/// Describes the hash of auxiliary data which is included +/// in the transaction body. +final class AuxiliaryDataHash extends BaseHash { + static const int _length = 32; + + /// Constructs the [AuxiliaryDataHash] from raw [bytes]. + AuxiliaryDataHash.fromBytes({required super.bytes}) : super.fromBytes(); + + /// Constructs the [AuxiliaryDataHash] from a hex string representation + /// of [bytes]. + AuxiliaryDataHash.fromHex(super.string) : super.fromHex(); + + /// Constructs the [AuxiliaryDataHash] from a [AuxiliaryData]. + AuxiliaryDataHash.fromAuxiliaryData(AuxiliaryData data) + : super.fromBytes( + bytes: Hash.blake2b( + Uint8List.fromList(cbor.encode(data.toCbor())), + digestSize: _length, + ), + ); + + @override + int get length => _length; +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/transaction.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/transaction.dart new file mode 100644 index 0000000000..a087917110 --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/transaction.dart @@ -0,0 +1,173 @@ +import 'package:catalyst_cardano_serialization/src/address.dart'; +import 'package:catalyst_cardano_serialization/src/hashes.dart'; +import 'package:catalyst_cardano_serialization/src/types.dart'; +import 'package:cbor/cbor.dart'; + +/// Represents the signed transaction with a list of witnesses +/// which are used to verify the validity of a transaction. +final class Transaction { + /// The transaction body containing the inputs, outputs, fees, etc. + final TransactionBody body; + + /// True if the transaction is valid, false otherwise. + final bool isValid; + + /// The optional transaction metadata. + final AuxiliaryData? auxiliaryData; + + /// The default constructor for the [Transaction]. + const Transaction({ + required this.body, + required this.isValid, + this.auxiliaryData, + }); + + /// Serializes the type as cbor. + CborValue toCbor() { + return CborList([ + body.toCbor(), + // TODO(dtscalac): implement witnesses + CborMap({}), + CborBool(isValid), + (auxiliaryData ?? const AuxiliaryData()).toCbor(), + ]); + } +} + +/// Represents the details of a transaction including inputs, outputs, fee, etc. +/// +/// Does not contain the witnesses which are used to verify the transaction. +final class TransactionBody { + /// The transaction inputs. + final Set inputs; + + /// The transaction outputs. + final List outputs; + + /// The fee for the transaction. + final Coin fee; + + /// The absolute slot value before the tx becomes invalid. + final SlotBigNum? ttl; + + /// The hash of the optional [AuxiliaryData] + /// which is the metadata of the transaction. + final AuxiliaryDataHash? auxiliaryDataHash; + + /// Specifies on which network the code will run. + final NetworkId? networkId; + + /// The default constructor for [TransactionBody]. + const TransactionBody({ + required this.inputs, + required this.outputs, + required this.fee, + this.ttl, + this.auxiliaryDataHash, + this.networkId, + }); + + /// Serializes the type as cbor. + CborValue toCbor() { + return CborMap({ + const CborSmallInt(0): CborList([ + for (final input in inputs) input.toCbor(), + ]), + const CborSmallInt(1): CborList([ + for (final output in outputs) output.toCbor(), + ]), + const CborSmallInt(2): fee.toCbor(), + if (ttl != null) const CborSmallInt(3): ttl!.toCbor(), + }); + } +} + +/// The transaction output of a previous transaction, +/// acts as input for the next transaction. +final class TransactionInput { + /// The hash of the given transaction. + final TransactionHash transactionId; + + /// The index of the utxo in the given transaction. + final int index; + + /// The default constructor for [TransactionInput]. + const TransactionInput({ + required this.transactionId, + required this.index, + }); + + /// Serializes the type as cbor. + CborValue toCbor() { + return CborList([ + transactionId.toCbor(), + CborSmallInt(index), + ]); + } +} + +/// The transaction output which assigns the owner of given address +/// with leftover change from previous transaction. +final class TransactionOutput { + /// The address associated with the transaction. + final ShelleyAddress address; + + /// The leftover change from the previous transaction that can be spent. + final Coin amount; + + /// The default constructor for the [TransactionOutput]. + const TransactionOutput({ + required this.address, + required this.amount, + }); + + /// Serializes the type as cbor. + CborValue toCbor() { + return CborList([ + address.toCbor(), + amount.toCbor(), + ]); + } +} + +/// The UTXO that can be used as an input in a new transaction. +final class TransactionUnspentOutput { + /// The transaction output of a previous transaction, + /// acts as input for the next transaction. + final TransactionInput input; + + /// The transaction output which assigns the owner of given address + /// with leftover change from previous transaction. + final TransactionOutput output; + + /// The default constructor for [TransactionUnspentOutput]. + const TransactionUnspentOutput({ + required this.input, + required this.output, + }); + + /// Serializes the type as cbor. + CborValue toCbor() { + return CborList([ + input.toCbor(), + output.toCbor(), + ]); + } +} + +/// The transaction metadata as a list of key-value pairs (a map). +final class AuxiliaryData { + /// The transaction metadata map. + final Map map; + + /// The default constructor for the [AuxiliaryData]. + const AuxiliaryData({this.map = const {}}); + + /// Serializes the type as cbor. + CborValue toCbor() { + return CborMap( + map, + tags: map.isNotEmpty ? const [] : [CborCustomTags.map], + ); + } +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/types.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/types.dart new file mode 100644 index 0000000000..fbb9571756 --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/types.dart @@ -0,0 +1,60 @@ +import 'package:cbor/cbor.dart'; + +/// Specifies on which network the code will run. +enum NetworkId { + /// The production network + mainnet(id: 1), + + /// The test network. + testnet(id: 0); + + /// The magic protocol number acting as the identifier of the network. + final int id; + + const NetworkId({required this.id}); +} + +/// Specifies an amount of ADA in terms of lovelace. +extension type const Coin(int value) { + /// Adds [other] value to this value and returns a new [Coin]. + Coin operator +(Coin other) => Coin(value + other.value); + + /// Subtracts [other] values from this value and returns a new [Coin]. + Coin operator -(Coin other) => Coin(value - other.value); + + /// Multiplies this value by [other] values and returns a new [Coin]. + Coin operator *(Coin other) => Coin(value * other.value); + + /// Divides this value by [other] value without remainder + /// and returns a new [Coin]. + Coin operator ~/(Coin other) => Coin(value ~/ other.value); + + /// Returns true if [value] is greater than [other] value. + bool operator >(Coin other) => value > other.value; + + /// Returns true if [value] is greater than or equal [other] value. + bool operator >=(Coin other) => value > other.value || value == other.value; + + /// Returns true if [value] is smaller than [other] value. + bool operator <(Coin other) => value < other.value; + + /// Returns true if [value] is smaller than or equal [other] value. + bool operator <=(Coin other) => value < other.value || value == other.value; + + /// Serializes the type as cbor. + CborValue toCbor() => CborSmallInt(value); +} + +/// A blockchain slot number. +extension type const SlotBigNum(int value) { + /// Serializes the type as cbor. + CborValue toCbor() => CborSmallInt(value); +} + +/// Holds cbor tags not specified by the official cbor package. +final class CborCustomTags { + const CborCustomTags._(); + + /// A cbor tag describing a key-value pairs data. + static const int map = 259; +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/pubspec.yaml b/catalyst_voices_packages/catalyst_cardano_serialization/pubspec.yaml new file mode 100644 index 0000000000..c6c0c143b1 --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/pubspec.yaml @@ -0,0 +1,21 @@ +name: catalyst_cardano_serialization +description: Dart package providing serialization/deserialization for common structures for Cardano blockchain. +repository: https://github.com/input-output-hk/catalyst-voices/tree/main/catalyst_voices_packages/catalyst_cardano_serialization +issue_tracker: https://github.com/input-output-hk/catalyst-voices/issues +topics: [blockchain, cardano, cryptocurrency, wallet] +version: 0.1.0 + +environment: + sdk: ">=3.3.0 <4.0.0" + +dependencies: + bech32: ^0.2.2 + bip32_ed25519: ^0.5.0 + cbor: ^6.2.0 + convert: ^3.1.1 + pinenacl: ^0.5.1 + +dev_dependencies: + catalyst_analysis: + path: ../catalyst_analysis + test: ^1.24.9 diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/address_test.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/address_test.dart new file mode 100644 index 0000000000..26474d56cf --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/address_test.dart @@ -0,0 +1,74 @@ +import 'package:catalyst_cardano_serialization/src/address.dart'; +import 'package:catalyst_cardano_serialization/src/types.dart'; +import 'package:test/test.dart'; + +import 'utils/test_data.dart'; + +void main() { + group(ShelleyAddress, () { + test('round-trip conversion from and to bytes', () { + expect( + ShelleyAddress(mainnetAddr.bytes, hrp: mainnetAddr.hrp), + equals(mainnetAddr), + ); + + expect( + ShelleyAddress(testnetAddr.bytes, hrp: testnetAddr.hrp), + equals(testnetAddr), + ); + + expect( + ShelleyAddress(mainnetStakeAddr.bytes, hrp: mainnetStakeAddr.hrp), + equals(mainnetStakeAddr), + ); + + expect( + ShelleyAddress(testnetStakeAddr.bytes, hrp: testnetStakeAddr.hrp), + equals(testnetStakeAddr), + ); + }); + + test('round-trip conversion from and to bech32', () { + expect( + ShelleyAddress.fromBech32(mainnetAddr.toBech32()), + equals(mainnetAddr), + ); + + expect( + ShelleyAddress.fromBech32(testnetAddr.toBech32()), + equals(testnetAddr), + ); + + expect( + ShelleyAddress.fromBech32(mainnetStakeAddr.toBech32()), + equals(mainnetStakeAddr), + ); + + expect( + ShelleyAddress.fromBech32(testnetStakeAddr.toBech32()), + equals(testnetStakeAddr), + ); + }); + + test('hrp from address', () { + expect(mainnetAddr.hrp, equals('addr')); + expect(testnetAddr.hrp, equals('addr_test')); + expect(mainnetStakeAddr.hrp, equals('stake')); + expect(testnetStakeAddr.hrp, equals('stake_test')); + }); + + test('network ID from address', () { + expect(mainnetAddr.network, equals(NetworkId.mainnet)); + expect(testnetAddr.network, equals(NetworkId.testnet)); + expect(mainnetStakeAddr.network, equals(NetworkId.mainnet)); + expect(testnetStakeAddr.network, equals(NetworkId.testnet)); + }); + + test('toString returns bech32', () { + expect(mainnetAddr.toString(), equals(mainnetAddr.toBech32())); + expect(testnetAddr.toString(), equals(testnetAddr.toBech32())); + expect(mainnetStakeAddr.toString(), equals(mainnetStakeAddr.toBech32())); + expect(testnetStakeAddr.toString(), equals(testnetStakeAddr.toBech32())); + }); + }); +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/fees_test.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/fees_test.dart new file mode 100644 index 0000000000..3d5dbb360b --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/fees_test.dart @@ -0,0 +1,39 @@ +import 'package:catalyst_cardano_serialization/src/fees.dart'; +import 'package:catalyst_cardano_serialization/src/types.dart'; +import 'package:test/test.dart'; + +import 'utils/test_data.dart'; + +void main() { + group(LinearFee, () { + test('minFeeNoScript with current protocol params', () { + const linearFee = LinearFee( + constant: Coin(155381), + coefficient: Coin(44), + ); + + final tx = fullTestTransaction(); + expect(linearFee.minNoScriptFee(tx), equals(159693)); + }); + + test('minFeeNoScript with constant fee only', () { + const linearFee = LinearFee( + constant: Coin(155381), + coefficient: Coin(0), + ); + + final tx = fullTestTransaction(); + expect(linearFee.minNoScriptFee(tx), equals(linearFee.constant)); + }); + + test('minFeeNoScript with coefficient fee only', () { + const linearFee = LinearFee( + constant: Coin(0), + coefficient: Coin(44), + ); + + final tx = fullTestTransaction(); + expect(linearFee.minNoScriptFee(tx), equals(4312)); + }); + }); +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/hashes_test.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/hashes_test.dart new file mode 100644 index 0000000000..f55bedc45f --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/hashes_test.dart @@ -0,0 +1,73 @@ +import 'package:catalyst_cardano_serialization/src/hashes.dart'; +import 'package:catalyst_cardano_serialization/src/transaction.dart'; +import 'package:cbor/cbor.dart'; +import 'package:convert/convert.dart'; +import 'package:test/test.dart'; + +void main() { + group(TransactionHash, () { + const hexString = + '4d3f576f26db29139981a69443c2325daa812cc353a31b5a4db794a5bcbb06c2'; + final bytes = hex.decode(hexString); + + test('from and to hex', () { + final hash = TransactionHash.fromHex(hexString); + expect(hash.toHex(), equals(hexString)); + }); + + test('from and to bytes', () { + final hash = TransactionHash.fromBytes(bytes: bytes); + expect(hash.bytes, equals(bytes)); + }); + + test('toCbor returns bytes', () { + final hash = TransactionHash.fromBytes(bytes: bytes); + final encodedCbor = cbor.encode(hash.toCbor()); + final decodedCbor = cbor.decode(encodedCbor); + expect(decodedCbor, isA()); + expect((decodedCbor as CborBytes).bytes, equals(bytes)); + }); + }); + + group(AuxiliaryDataHash, () { + const hexString = + '4d3f576f26db29139981a69443c2325daa812cc353a31b5a4db794a5bcbb06c2'; + final bytes = hex.decode(hexString); + + test('from and to hex', () { + final hash = AuxiliaryDataHash.fromHex(hexString); + expect(hash.toHex(), equals(hexString)); + }); + + test('from and to bytes', () { + final hash = AuxiliaryDataHash.fromBytes(bytes: bytes); + expect(hash.bytes, equals(bytes)); + }); + + test('from auxiliary data', () { + final data = AuxiliaryData( + map: { + const CborSmallInt(1): CborString('Test'), + }, + ); + + final hash = AuxiliaryDataHash.fromAuxiliaryData(data); + expect( + hash, + equals( + AuxiliaryDataHash.fromHex( + '568d2b7d4052f6ce9f2c60b942b10da9d19e60c8bf24b17aa7bcfb3caffcc1ea', + ), + ), + ); + }); + + test('toCbor returns bytes', () { + final hash = AuxiliaryDataHash.fromBytes(bytes: bytes); + final encodedCbor = cbor.encode(hash.toCbor()); + final decodedCbor = cbor.decode(encodedCbor); + expect(decodedCbor, isA()); + expect((decodedCbor as CborBytes).bytes, equals(bytes)); + }); + }); +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/transaction_test.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/transaction_test.dart new file mode 100644 index 0000000000..b67c4a9183 --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/transaction_test.dart @@ -0,0 +1,38 @@ +import 'package:catalyst_cardano_serialization/src/transaction.dart'; +import 'package:cbor/cbor.dart'; +import 'package:convert/convert.dart'; +import 'package:test/test.dart'; + +import 'utils/test_data.dart'; + +void main() { + group(Transaction, () { + test('transaction with all supported fields serialized to bytes', () { + final bytes = cbor.encode(fullTestTransaction().toCbor()); + final hexString = hex.encode(bytes); + + expect( + hexString, + equals( + '84a40081825820583a3a5150bc7990656020ffb4e5a1be1589ce6f1a430aacb8e7e0' + '89b894d3d101018182581d609493315cd92eb5d8c4304e67b7e16ae36d61d3450269' + '4657811a2c8e1a004c4b40021a009896800319a029a0f5a1016454657374', + ), + ); + }); + + test('transaction with required fields serialized to bytes', () { + final bytes = cbor.encode(minimalTestTransaction().toCbor()); + final hexString = hex.encode(bytes); + + expect( + hexString, + equals( + '84a30081825820583a3a5150bc7990656020ffb4e5a1be1589ce6f1a430aacb8e7e0' + '89b894d3d101018182581d609493315cd92eb5d8c4304e67b7e16ae36d61d3450269' + '4657811a2c8e1a004c4b40021a00989680a0f5d90103a0', + ), + ); + }); + }); +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/types_test.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/types_test.dart new file mode 100644 index 0000000000..7aeeaf13a4 --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/types_test.dart @@ -0,0 +1,33 @@ +import 'package:catalyst_cardano_serialization/src/types.dart'; +import 'package:test/test.dart'; + +void main() { + group(Coin, () { + test('addition', () { + expect(const Coin(3) + const Coin(100), equals(const Coin(103))); + expect(const Coin(-100) + const Coin(100), equals(const Coin(0))); + }); + + test('subtraction', () { + expect(const Coin(5) - const Coin(2), equals(const Coin(3))); + expect(const Coin(10) - const Coin(27), equals(const Coin(-17))); + }); + + test('multiplication', () { + expect(const Coin(3) * const Coin(6), equals(const Coin(18))); + expect(const Coin(-5) * const Coin(7), equals(const Coin(-35))); + }); + + test('division', () { + expect(const Coin(3) ~/ const Coin(2), equals(const Coin(1))); + expect(const Coin(100) ~/ const Coin(50), equals(const Coin(2))); + }); + + test('comparison', () { + expect(const Coin(3) > const Coin(2), isTrue); + expect(const Coin(3) >= const Coin(3), isTrue); + expect(const Coin(100) < const Coin(100), isFalse); + expect(const Coin(100) <= const Coin(100), isTrue); + }); + }); +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/utils/test_data.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/utils/test_data.dart new file mode 100644 index 0000000000..ee58d63609 --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/utils/test_data.dart @@ -0,0 +1,81 @@ +import 'package:catalyst_cardano_serialization/src/address.dart'; +import 'package:catalyst_cardano_serialization/src/hashes.dart'; +import 'package:catalyst_cardano_serialization/src/transaction.dart'; +import 'package:catalyst_cardano_serialization/src/types.dart'; +import 'package:cbor/cbor.dart'; + +/* cSpell:disable */ +final mainnetAddr = ShelleyAddress.fromBech32( + 'addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqws' + 'x5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x', +); +final testnetAddr = ShelleyAddress.fromBech32( + 'addr_test1vz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerspjrlsz', +); +final mainnetStakeAddr = ShelleyAddress.fromBech32( + 'stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw', +); +final testnetStakeAddr = ShelleyAddress.fromBech32( + 'stake_test1uqehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gssrtvn', +); + +final testTransactionHash = TransactionHash.fromHex( + '583a3a5150bc7990656020ffb4e5a1be' + '1589ce6f1a430aacb8e7e089b894d3d1', +); + +/// Returns a minimal transaction with optional fields skipped. +Transaction minimalTestTransaction() { + return Transaction( + body: TransactionBody( + inputs: { + TransactionInput( + transactionId: testTransactionHash, + index: 1, + ), + }, + outputs: [ + TransactionOutput( + address: testnetAddr, + amount: const Coin(5000000), + ), + ], + fee: const Coin(10000000), + ), + isValid: true, + ); +} + +/// Returns a full transaction with all possible optional fields. +Transaction fullTestTransaction() { + final auxiliaryData = AuxiliaryData( + map: { + const CborSmallInt(1): CborString('Test'), + }, + ); + + return Transaction( + body: TransactionBody( + inputs: { + TransactionInput( + transactionId: testTransactionHash, + index: 1, + ), + }, + outputs: [ + TransactionOutput( + address: testnetAddr, + amount: const Coin(5000000), + ), + ], + fee: const Coin(10000000), + ttl: const SlotBigNum(41001), + auxiliaryDataHash: AuxiliaryDataHash.fromAuxiliaryData(auxiliaryData), + networkId: NetworkId.testnet, + ), + isValid: true, + auxiliaryData: auxiliaryData, + ); +} + +/* cSpell:enable */ diff --git a/melos.yaml b/melos.yaml index 21730e065a..f930ba47bd 100644 --- a/melos.yaml +++ b/melos.yaml @@ -24,6 +24,11 @@ command: meta: ^1.10.0 result_type: ^0.2.0 plugin_platform_interface: ^2.1.7 + bech32: ^0.2.2 + bip32_ed25519: ^0.5.0 + cbor: ^6.2.0 + convert: ^3.1.1 + pinenacl: ^0.5.1 dev_dependencies: test: ^1.24.9 build_runner: ^2.3.3 From f066a261d678d3eba0e47ff8bca56e74fcdbc01e Mon Sep 17 00:00:00 2001 From: Lucio Baglione Date: Tue, 14 May 2024 18:29:05 +0200 Subject: [PATCH 2/3] feat: Add Catalyst Brand M3 Theme definition (#479) * feat: ThemeBuilder support for dark themes. * feat: Add light colors for Catalyst brand. * feat: Add light Theme definition for Catalyst brand. * feat: Add Catalyst dark theme. * feat: Add icons colors to scheme. * chore: Format. * chore: Change name for VoicesColorScheme. * chore: Adjust Catalyst Theme line heights. --- .config/dictionaries/project.dic | 1 + .../lib/pages/coming_soon/description.dart | 2 +- .../lib/pages/coming_soon/logo.dart | 2 +- .../lib/pages/coming_soon/title.dart | 4 +- catalyst_voices/lib/pages/home/home_page.dart | 2 +- .../assets/colors/colors.xml | 105 ++++++- .../example/lib/src/main.dart | 2 +- .../lib/generated/colors.gen.dart | 291 ++++++++++++++++-- .../lib/src/theme_builder/theme_builder.dart | 35 ++- .../lib/src/themes/catalyst.dart | 232 +++++++++++++- .../lib/src/themes/fallback.dart | 5 +- .../lib/src/themes/voices_color_scheme.dart | 219 +++++++++++++ .../catalyst_voices_brands/pubspec.yaml | 1 + .../test/src/catalyst_voices_brands_test.dart | 84 +++-- 14 files changed, 886 insertions(+), 99 deletions(-) create mode 100644 catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/voices_color_scheme.dart diff --git a/.config/dictionaries/project.dic b/.config/dictionaries/project.dic index fd5be5e448..7ad7189209 100644 --- a/.config/dictionaries/project.dic +++ b/.config/dictionaries/project.dic @@ -80,6 +80,7 @@ junitreport Keyhash keyserver lcov +lerp Leshiy libapp libavcodec diff --git a/catalyst_voices/lib/pages/coming_soon/description.dart b/catalyst_voices/lib/pages/coming_soon/description.dart index 90214c098b..82fe7ae460 100644 --- a/catalyst_voices/lib/pages/coming_soon/description.dart +++ b/catalyst_voices/lib/pages/coming_soon/description.dart @@ -13,7 +13,7 @@ class ComingSoonDescription extends StatelessWidget { height: 66, child: DefaultTextStyle( style: GoogleFonts.notoSans( - textStyle: const TextStyle(color: VoicesColors.blueText), + textStyle: const TextStyle(color: VoicesColors.lightTextOnPrimary), fontSize: 16, fontWeight: FontWeight.w400, ), diff --git a/catalyst_voices/lib/pages/coming_soon/logo.dart b/catalyst_voices/lib/pages/coming_soon/logo.dart index 0547e27a5a..d3854da508 100644 --- a/catalyst_voices/lib/pages/coming_soon/logo.dart +++ b/catalyst_voices/lib/pages/coming_soon/logo.dart @@ -20,7 +20,7 @@ class ComingSoonLogo extends StatelessWidget { child: Text( l10n.comingSoonSubtitle, style: GoogleFonts.notoSans( - textStyle: const TextStyle(color: VoicesColors.blue), + textStyle: const TextStyle(color: VoicesColors.lightPrimary), fontSize: 19, fontWeight: FontWeight.w500, ), diff --git a/catalyst_voices/lib/pages/coming_soon/title.dart b/catalyst_voices/lib/pages/coming_soon/title.dart index e2230b295c..4f3ad1a614 100644 --- a/catalyst_voices/lib/pages/coming_soon/title.dart +++ b/catalyst_voices/lib/pages/coming_soon/title.dart @@ -18,7 +18,7 @@ class ComingSoonTitle extends StatelessWidget { children: [ DefaultTextStyle( style: GoogleFonts.poppins( - textStyle: const TextStyle(color: VoicesColors.blue), + textStyle: const TextStyle(color: VoicesColors.lightPrimary), fontSize: 53, height: 1.15, fontWeight: FontWeight.w700, @@ -35,7 +35,7 @@ class ComingSoonTitle extends StatelessWidget { ), DefaultTextStyle( style: GoogleFonts.poppins( - textStyle: const TextStyle(color: VoicesColors.blue), + textStyle: const TextStyle(color: VoicesColors.lightPrimary), fontSize: 53, height: 1.15, fontWeight: FontWeight.w700, diff --git a/catalyst_voices/lib/pages/home/home_page.dart b/catalyst_voices/lib/pages/home/home_page.dart index ca1ca53729..ebaec830fc 100644 --- a/catalyst_voices/lib/pages/home/home_page.dart +++ b/catalyst_voices/lib/pages/home/home_page.dart @@ -18,7 +18,7 @@ final class HomePage extends StatelessWidget { Text( context.l10n.homeScreenText, style: const TextStyle( - color: VoicesColors.purpleGradientStart, + color: VoicesColors.lightPrimary, fontFamily: VoicesFonts.sFPro, fontSize: 32, ), diff --git a/catalyst_voices/packages/catalyst_voices_assets/assets/colors/colors.xml b/catalyst_voices/packages/catalyst_voices_assets/assets/colors/colors.xml index 0a9f7a30dd..b5a26c57ce 100644 --- a/catalyst_voices/packages/catalyst_voices_assets/assets/colors/colors.xml +++ b/catalyst_voices/packages/catalyst_voices_assets/assets/colors/colors.xml @@ -1,14 +1,97 @@ - #FFFFFF - #000000 - #512DA8 - #673AB7 - #512DA8 - #222126 - #7CAE7A - #B02E0C - #565656 - #1235C7 - #506288 + #212A3D + #506288 + #FFFFFF + #61212A3D + #123CD3 + #FFFFFF + #A1B4F7 + #081B5E + #C014EB + #FFFFFF + #E6A1F7 + #4D085E + #D9DEE8 + #61BFC8D9 + #CC0000 + #FFFFFF + #FFD1D1 + #700000 + #218230 + #FFFFFF + #CEF3D4 + #13491B + #E76309 + #FFFFFF + #FDE1CE + #582603 + #14212A3D + #1F212A3D + #29212A3D + #A1B4F7 + #14123CD3 + #1F123CD3 + #29123CD3 + #14C014EB + #1FC014EB + #29C014EB + #14CC0000 + #1FCC0000 + #29CC0000 + #212A3D + #FFFFFF + #61212A3D + #123CD3 + #C014EB + #218230 + #E76309 + #CC0000 + #FFFFFF + #E6E9F0 + #0C288D + #61D9DEE8 + #728EF3 + #0C288D + #1035BC + #E8ECFD + #DF8AF5 + #26042F + #9910BC + #9910BC + #7F90B3 + #364463 + #FF9999 + #380000 + #AD0000 + #FFD1D1 + #BAEDC2 + #08210C + #1D722A + #CEF3D4 + #FBC9A7 + #2C1302 + #B64E07 + #FDE1CE + #1FBFC8D9 + #1FBFC8D9 + #29212A3D + #0C288D + #1F123CD3 + #1F123CD3 + #29123CD3 + #1FD972F3 + #1FC014EB + #29C014EB + #1FFFC2C2 + #1FCC0000 + #29CC0000 + #F2F4F8 + #212A3D + #61BFC8D9 + #728EF3 + #DF8AF5 + #85E093 + #FAB484 + #FF9999 diff --git a/catalyst_voices/packages/catalyst_voices_assets/example/lib/src/main.dart b/catalyst_voices/packages/catalyst_voices_assets/example/lib/src/main.dart index d2f8dccbbf..537effa13d 100644 --- a/catalyst_voices/packages/catalyst_voices_assets/example/lib/src/main.dart +++ b/catalyst_voices/packages/catalyst_voices_assets/example/lib/src/main.dart @@ -17,7 +17,7 @@ final class Example extends StatelessWidget { const Text( 'Catalyst Assets', style: TextStyle( - color: VoicesColors.purpleGradientStart, + color: VoicesColors.lightPrimary, fontFamily: VoicesFonts.sFPro, fontSize: 32, ), diff --git a/catalyst_voices/packages/catalyst_voices_assets/lib/generated/colors.gen.dart b/catalyst_voices/packages/catalyst_voices_assets/lib/generated/colors.gen.dart index 6259051f28..8e121dd46c 100644 --- a/catalyst_voices/packages/catalyst_voices_assets/lib/generated/colors.gen.dart +++ b/catalyst_voices/packages/catalyst_voices_assets/lib/generated/colors.gen.dart @@ -13,36 +13,285 @@ import 'package:flutter/material.dart'; class VoicesColors { VoicesColors._(); - /// Color: #1235C7 - static const Color blue = Color(0xFF1235C7); + /// Color: #FF9999 + static const Color darkError = Color(0xFFFF9999); - /// Color: #506288 - static const Color blueText = Color(0xFF506288); + /// Color: #AD0000 + static const Color darkErrorContainer = Color(0xFFAD0000); + + /// Color: #212A3D + static const Color darkIconsBackground = Color(0xFF212A3D); + + /// Color: #61BFC8D9 + static const Color darkIconsDisabled = Color(0x61BFC8D9); + + /// Color: #FF9999 + static const Color darkIconsError = Color(0xFFFF9999); + + /// Color: #F2F4F8 + static const Color darkIconsForeground = Color(0xFFF2F4F8); + + /// Color: #728EF3 + static const Color darkIconsPrimary = Color(0xFF728EF3); + + /// Color: #DF8AF5 + static const Color darkIconsSecondary = Color(0xFFDF8AF5); + + /// Color: #85E093 + static const Color darkIconsSuccess = Color(0xFF85E093); + + /// Color: #FAB484 + static const Color darkIconsWarning = Color(0xFFFAB484); + + /// Color: #380000 + static const Color darkOnError = Color(0xFF380000); + + /// Color: #FFD1D1 + static const Color darkOnErrorContainer = Color(0xFFFFD1D1); + + /// Color: #0C288D + static const Color darkOnPrimary = Color(0xFF0C288D); + + /// Color: #E8ECFD + static const Color darkOnPrimaryContainer = Color(0xFFE8ECFD); + + /// Color: #26042F + static const Color darkOnSecondary = Color(0xFF26042F); + + /// Color: #9910BC + static const Color darkOnSecondaryContainer = Color(0xFF9910BC); + + /// Color: #08210C + static const Color darkOnSuccess = Color(0xFF08210C); + + /// Color: #CEF3D4 + static const Color darkOnSuccessContainer = Color(0xFFCEF3D4); + + /// Color: #1FCC0000 + static const Color darkOnSurfaceError012 = Color(0x1FCC0000); + + /// Color: #29CC0000 + static const Color darkOnSurfaceError016 = Color(0x29CC0000); + + /// Color: #1FFFC2C2 + static const Color darkOnSurfaceError08 = Color(0x1FFFC2C2); + + /// Color: #1FBFC8D9 + static const Color darkOnSurfaceNeutral012 = Color(0x1FBFC8D9); + + /// Color: #29212A3D + static const Color darkOnSurfaceNeutral016 = Color(0x29212A3D); + + /// Color: #1FBFC8D9 + static const Color darkOnSurfaceNeutral08 = Color(0x1FBFC8D9); + + /// Color: #1F123CD3 + static const Color darkOnSurfacePrimary012 = Color(0x1F123CD3); + + /// Color: #29123CD3 + static const Color darkOnSurfacePrimary016 = Color(0x29123CD3); + + /// Color: #1F123CD3 + static const Color darkOnSurfacePrimary08 = Color(0x1F123CD3); + + /// Color: #0C288D + static const Color darkOnSurfacePrimaryContainer = Color(0xFF0C288D); + + /// Color: #1FC014EB + static const Color darkOnSurfaceSecondary012 = Color(0x1FC014EB); + + /// Color: #29C014EB + static const Color darkOnSurfaceSecondary016 = Color(0x29C014EB); + + /// Color: #1FD972F3 + static const Color darkOnSurfaceSecondary08 = Color(0x1FD972F3); + + /// Color: #2C1302 + static const Color darkOnWarning = Color(0xFF2C1302); + + /// Color: #FDE1CE + static const Color darkOnWarningContainer = Color(0xFFFDE1CE); + + /// Color: #7F90B3 + static const Color darkOutline = Color(0xFF7F90B3); + + /// Color: #364463 + static const Color darkOutlineVariant = Color(0xFF364463); + + /// Color: #728EF3 + static const Color darkPrimary = Color(0xFF728EF3); + + /// Color: #1035BC + static const Color darkPrimaryContainer = Color(0xFF1035BC); + + /// Color: #DF8AF5 + static const Color darkSecondary = Color(0xFFDF8AF5); + + /// Color: #9910BC + static const Color darkSecondaryContainer = Color(0xFF9910BC); + + /// Color: #BAEDC2 + static const Color darkSuccess = Color(0xFFBAEDC2); + + /// Color: #1D722A + static const Color darkSuccessContainer = Color(0xFF1D722A); + + /// Color: #61D9DEE8 + static const Color darkTextDisabled = Color(0x61D9DEE8); + + /// Color: #E6E9F0 + static const Color darkTextOnPrimary = Color(0xFFE6E9F0); + + /// Color: #0C288D + static const Color darkTextOnPrimaryContainer = Color(0xFF0C288D); + + /// Color: #FFFFFF + static const Color darkTextPrimary = Color(0xFFFFFFFF); + + /// Color: #FBC9A7 + static const Color darkWarning = Color(0xFFFBC9A7); - /// Color: #000000 - static const Color darkBackground = Color(0xFF000000); + /// Color: #B64E07 + static const Color darkWarningContainer = Color(0xFFB64E07); - /// Color: #222126 - static const Color darkCard = Color(0xFF222126); + /// Color: #CC0000 + static const Color lightError = Color(0xFFCC0000); - /// Color: #7CAE7A - static const Color green = Color(0xFF7CAE7A); + /// Color: #FFD1D1 + static const Color lightErrorContainer = Color(0xFFFFD1D1); - /// Color: #512DA8 - static const Color purple = Color(0xFF512DA8); + /// Color: #FFFFFF + static const Color lightIconsBackground = Color(0xFFFFFFFF); + + /// Color: #61212A3D + static const Color lightIconsDisabled = Color(0x61212A3D); + + /// Color: #CC0000 + static const Color lightIconsError = Color(0xFFCC0000); + + /// Color: #212A3D + static const Color lightIconsForeground = Color(0xFF212A3D); + + /// Color: #123CD3 + static const Color lightIconsPrimary = Color(0xFF123CD3); + + /// Color: #C014EB + static const Color lightIconsSecondary = Color(0xFFC014EB); + + /// Color: #218230 + static const Color lightIconsSuccess = Color(0xFF218230); + + /// Color: #E76309 + static const Color lightIconsWarning = Color(0xFFE76309); + + /// Color: #FFFFFF + static const Color lightOnError = Color(0xFFFFFFFF); + + /// Color: #700000 + static const Color lightOnErrorContainer = Color(0xFF700000); + + /// Color: #FFFFFF + static const Color lightOnPrimary = Color(0xFFFFFFFF); + + /// Color: #081B5E + static const Color lightOnPrimaryContainer = Color(0xFF081B5E); + + /// Color: #FFFFFF + static const Color lightOnSecondary = Color(0xFFFFFFFF); + + /// Color: #4D085E + static const Color lightOnSecondaryContainer = Color(0xFF4D085E); + + /// Color: #FFFFFF + static const Color lightOnSuccess = Color(0xFFFFFFFF); + + /// Color: #13491B + static const Color lightOnSuccessContainer = Color(0xFF13491B); + + /// Color: #1FCC0000 + static const Color lightOnSurfaceError012 = Color(0x1FCC0000); + + /// Color: #29CC0000 + static const Color lightOnSurfaceError016 = Color(0x29CC0000); + + /// Color: #14CC0000 + static const Color lightOnSurfaceError08 = Color(0x14CC0000); - /// Color: #673AB7 - static const Color purpleGradientStart = Color(0xFF673AB7); + /// Color: #1F212A3D + static const Color lightOnSurfaceNeutral012 = Color(0x1F212A3D); - /// Color: #512DA8 - static const Color purpleGradientStop = Color(0xFF512DA8); + /// Color: #29212A3D + static const Color lightOnSurfaceNeutral016 = Color(0x29212A3D); - /// Color: #B02E0C - static const Color red = Color(0xFFB02E0C); + /// Color: #14212A3D + static const Color lightOnSurfaceNeutral08 = Color(0x14212A3D); - /// Color: #565656 - static const Color today = Color(0xFF565656); + /// Color: #1F123CD3 + static const Color lightOnSurfacePrimary012 = Color(0x1F123CD3); + + /// Color: #29123CD3 + static const Color lightOnSurfacePrimary016 = Color(0x29123CD3); + + /// Color: #14123CD3 + static const Color lightOnSurfacePrimary08 = Color(0x14123CD3); + + /// Color: #A1B4F7 + static const Color lightOnSurfacePrimaryContainer = Color(0xFFA1B4F7); + + /// Color: #1FC014EB + static const Color lightOnSurfaceSecondary012 = Color(0x1FC014EB); + + /// Color: #29C014EB + static const Color lightOnSurfaceSecondary016 = Color(0x29C014EB); + + /// Color: #14C014EB + static const Color lightOnSurfaceSecondary08 = Color(0x14C014EB); /// Color: #FFFFFF - static const Color white = Color(0xFFFFFFFF); + static const Color lightOnWarning = Color(0xFFFFFFFF); + + /// Color: #582603 + static const Color lightOnWarningContainer = Color(0xFF582603); + + /// Color: #D9DEE8 + static const Color lightOutline = Color(0xFFD9DEE8); + + /// Color: #61BFC8D9 + static const Color lightOutlineVariant = Color(0x61BFC8D9); + + /// Color: #123CD3 + static const Color lightPrimary = Color(0xFF123CD3); + + /// Color: #A1B4F7 + static const Color lightPrimaryContainer = Color(0xFFA1B4F7); + + /// Color: #C014EB + static const Color lightSecondary = Color(0xFFC014EB); + + /// Color: #E6A1F7 + static const Color lightSecondaryContainer = Color(0xFFE6A1F7); + + /// Color: #218230 + static const Color lightSuccess = Color(0xFF218230); + + /// Color: #CEF3D4 + static const Color lightSuccessContainer = Color(0xFFCEF3D4); + + /// Color: #61212A3D + static const Color lightTextDisabled = Color(0x61212A3D); + + /// Color: #506288 + static const Color lightTextOnPrimary = Color(0xFF506288); + + /// Color: #FFFFFF + static const Color lightTextOnPrimaryContainer = Color(0xFFFFFFFF); + + /// Color: #212A3D + static const Color lightTextPrimary = Color(0xFF212A3D); + + /// Color: #E76309 + static const Color lightWarning = Color(0xFFE76309); + + /// Color: #FDE1CE + static const Color lightWarningContainer = Color(0xFFFDE1CE); } diff --git a/catalyst_voices/packages/catalyst_voices_brands/lib/src/theme_builder/theme_builder.dart b/catalyst_voices/packages/catalyst_voices_brands/lib/src/theme_builder/theme_builder.dart index 40c4ae71cb..440921a96a 100644 --- a/catalyst_voices/packages/catalyst_voices_brands/lib/src/theme_builder/theme_builder.dart +++ b/catalyst_voices/packages/catalyst_voices_brands/lib/src/theme_builder/theme_builder.dart @@ -4,23 +4,34 @@ import 'package:catalyst_voices_brands/src/themes/fallback.dart'; import 'package:flutter/material.dart'; /// A utility class to build themes dynamically based on brand keys. -/// +/// /// [buildTheme] can be used to obtain the corresponding theme data for the /// [BrandKey] passed to the method. -/// -/// For each brand there is a specific key defined in the [BrandKey] enum +/// +/// [buildDarkTheme] operates in the same way but picks the dark version of the +/// theme for a specific brand. +/// +/// For each brand there is a specific key defined in the [BrandKey] enum /// and a corresponding [ThemeData] in the `themes` folder. +/// For each brand a light and a dark [ThemeData] should be defined. /// -/// [buildTheme] defaults to the [catalyst] theme. +/// [buildTheme] and [buildDarkTheme] default to the [catalyst] theme. class ThemeBuilder { + static final Map lightThemes = { + BrandKey.catalyst: catalyst, + BrandKey.fallback: fallback, + }; + + static final Map darkThemes = { + BrandKey.catalyst: darkCatalyst, + BrandKey.fallback: darkFallback, + }; + static ThemeData buildTheme(BrandKey? brandKey) { - switch (brandKey) { - case BrandKey.catalyst: - return catalyst; - case BrandKey.fallback: - return fallback; - case null: - return catalyst; - } + return lightThemes[brandKey ?? BrandKey.catalyst]!; + } + + static ThemeData buildDarkTheme(BrandKey? brandKey) { + return darkThemes[brandKey ?? BrandKey.catalyst]!; } } diff --git a/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart b/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart index 8e3c5698d9..885c4efcf6 100644 --- a/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart +++ b/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart @@ -1,10 +1,232 @@ import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:catalyst_voices_assets/generated/colors.gen.dart'; +import 'package:catalyst_voices_brands/src/themes/voices_color_scheme.dart'; import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +ThemeData _buildThemeData( + ColorScheme colorScheme, + VoicesColorScheme voicesColorScheme, +) { + return ThemeData( + textTheme: TextTheme( + displayLarge: GoogleFonts.notoSans( + color: voicesColorScheme.textPrimary, + fontSize: 57, + letterSpacing: -1.14, + fontWeight: FontWeight.w700, + height: 1.12, + ), + displayMedium: GoogleFonts.poppins( + color: voicesColorScheme.textPrimary, + fontSize: 45, + fontWeight: FontWeight.w700, + height: 1.15, + ), + displaySmall: GoogleFonts.poppins( + color: voicesColorScheme.textPrimary, + fontSize: 36, + fontWeight: FontWeight.w700, + height: 1.22, + ), + headlineLarge: GoogleFonts.poppins( + color: voicesColorScheme.textPrimary, + fontSize: 32, + fontWeight: FontWeight.w700, + height: 1.25, + ), + headlineMedium: GoogleFonts.poppins( + color: voicesColorScheme.textPrimary, + fontSize: 28, + fontWeight: FontWeight.w700, + height: 1.28, + ), + headlineSmall: GoogleFonts.poppins( + color: voicesColorScheme.textPrimary, + fontSize: 24, + fontWeight: FontWeight.w700, + height: 1.33, + ), + titleLarge: GoogleFonts.poppins( + color: voicesColorScheme.textPrimary, + fontSize: 22, + fontWeight: FontWeight.w700, + height: 1.27, + letterSpacing: 0.66, + ), + titleMedium: GoogleFonts.poppins( + color: voicesColorScheme.textPrimary, + fontSize: 16, + fontWeight: FontWeight.w700, + height: 1.5, + letterSpacing: 0.48, + ), + titleSmall: GoogleFonts.poppins( + color: voicesColorScheme.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w700, + height: 1.42, + letterSpacing: 0.42, + ), + bodyLarge: GoogleFonts.notoSans( + color: voicesColorScheme.textPrimary, + fontSize: 16, + fontWeight: FontWeight.w500, + height: 1.5, + letterSpacing: 0.08, + ), + bodyMedium: GoogleFonts.notoSans( + color: voicesColorScheme.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w500, + height: 1.42, + letterSpacing: 0.04, + ), + bodySmall: GoogleFonts.notoSans( + color: voicesColorScheme.textPrimary, + fontSize: 12, + fontWeight: FontWeight.w500, + height: 1.33, + letterSpacing: 0.05, + ), + labelLarge: GoogleFonts.notoSans( + color: voicesColorScheme.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w500, + height: 1.42, + letterSpacing: 0.10, + ), + labelMedium: GoogleFonts.notoSans( + color: voicesColorScheme.textPrimary, + fontSize: 12, + fontWeight: FontWeight.w500, + height: 1, + ), + labelSmall: GoogleFonts.notoSans( + color: voicesColorScheme.textPrimary, + fontSize: 11, + fontWeight: FontWeight.w500, + height: 1.45, + letterSpacing: 0.50, + ), + ), + colorScheme: colorScheme, + extensions: >[ + voicesColorScheme, + ], + ); +} + +const ColorScheme lightColorScheme = ColorScheme.light( + primary: VoicesColors.lightPrimary, + primaryContainer: VoicesColors.lightPrimaryContainer, + onPrimaryContainer: VoicesColors.lightOnPrimaryContainer, + secondary: VoicesColors.lightSecondary, + onSecondary: VoicesColors.lightOnSecondary, + secondaryContainer: VoicesColors.lightSecondaryContainer, + onSecondaryContainer: VoicesColors.lightOnSecondaryContainer, + error: VoicesColors.lightError, + errorContainer: VoicesColors.lightErrorContainer, + onErrorContainer: VoicesColors.lightOnErrorContainer, + outline: VoicesColors.lightOutline, + outlineVariant: VoicesColors.lightOutlineVariant, +); + +const VoicesColorScheme lightVoicesColorScheme = VoicesColorScheme( + textPrimary: VoicesColors.lightTextPrimary, + textOnPrimary: VoicesColors.lightTextOnPrimary, + textOnPrimaryContainer: VoicesColors.lightTextOnPrimaryContainer, + textDisabled: VoicesColors.lightTextDisabled, + success: VoicesColors.lightSuccess, + onSuccess: VoicesColors.lightOnSuccess, + successContainer: VoicesColors.lightSuccessContainer, + onSuccessContainer: VoicesColors.lightOnSuccessContainer, + warning: VoicesColors.lightWarning, + onWarning: VoicesColors.lightOnWarning, + warningContainer: VoicesColors.lightWarningContainer, + onWarningContainer: VoicesColors.lightOnWarningContainer, + onSurfaceNeutral08: VoicesColors.lightOnSurfaceNeutral08, + onSurfaceNeutral012: VoicesColors.lightOnSurfaceNeutral012, + onSurfaceNeutral016: VoicesColors.lightOnSurfaceNeutral016, + onSurfacePrimaryContainer: VoicesColors.lightOnSurfacePrimaryContainer, + onSurfacePrimary08: VoicesColors.lightOnSurfacePrimary08, + onSurfacePrimary012: VoicesColors.lightOnSurfacePrimary012, + onSurfacePrimary016: VoicesColors.lightOnSurfacePrimary016, + onSurfaceSecondary08: VoicesColors.lightOnSurfaceSecondary08, + onSurfaceSecondary012: VoicesColors.lightOnSurfaceSecondary012, + onSurfaceSecondary016: VoicesColors.lightOnSurfaceSecondary016, + onSurfaceError08: VoicesColors.lightOnSurfaceError08, + onSurfaceError012: VoicesColors.lightOnSurfaceError012, + onSurfaceError016: VoicesColors.lightOnSurfaceError016, + iconsForeground: VoicesColors.lightIconsForeground, + iconsBackground: VoicesColors.lightIconsBackground, + iconsDisabled: VoicesColors.lightIconsDisabled, + iconsPrimary: VoicesColors.lightIconsPrimary, + iconsSecondary: VoicesColors.lightIconsSecondary, + iconsSuccess: VoicesColors.lightIconsSuccess, + iconsWarning: VoicesColors.lightIconsWarning, + iconsError: VoicesColors.lightIconsError, +); + +const ColorScheme darkColorScheme = ColorScheme.dark( + primary: VoicesColors.darkPrimary, + primaryContainer: VoicesColors.darkPrimaryContainer, + onPrimaryContainer: VoicesColors.darkOnPrimaryContainer, + secondary: VoicesColors.darkSecondary, + onSecondary: VoicesColors.darkOnSecondary, + secondaryContainer: VoicesColors.darkSecondaryContainer, + onSecondaryContainer: VoicesColors.darkOnSecondaryContainer, + error: VoicesColors.darkError, + errorContainer: VoicesColors.darkErrorContainer, + onErrorContainer: VoicesColors.darkOnErrorContainer, + outline: VoicesColors.darkOutline, + outlineVariant: VoicesColors.darkOutlineVariant, +); + +const VoicesColorScheme darkVoicesColorScheme = VoicesColorScheme( + textPrimary: VoicesColors.darkTextPrimary, + textOnPrimary: VoicesColors.darkTextOnPrimary, + textOnPrimaryContainer: VoicesColors.darkTextOnPrimaryContainer, + textDisabled: VoicesColors.darkTextDisabled, + success: VoicesColors.darkSuccess, + onSuccess: VoicesColors.darkOnSuccess, + successContainer: VoicesColors.darkSuccessContainer, + onSuccessContainer: VoicesColors.darkOnSuccessContainer, + warning: VoicesColors.darkWarning, + onWarning: VoicesColors.darkOnWarning, + warningContainer: VoicesColors.darkWarningContainer, + onWarningContainer: VoicesColors.darkOnWarningContainer, + onSurfaceNeutral08: VoicesColors.darkOnSurfaceNeutral08, + onSurfaceNeutral012: VoicesColors.darkOnSurfaceNeutral012, + onSurfaceNeutral016: VoicesColors.darkOnSurfaceNeutral016, + onSurfacePrimaryContainer: VoicesColors.darkOnSurfacePrimaryContainer, + onSurfacePrimary08: VoicesColors.darkOnSurfacePrimary08, + onSurfacePrimary012: VoicesColors.darkOnSurfacePrimary012, + onSurfacePrimary016: VoicesColors.darkOnSurfacePrimary016, + onSurfaceSecondary08: VoicesColors.darkOnSurfaceSecondary08, + onSurfaceSecondary012: VoicesColors.darkOnSurfaceSecondary012, + onSurfaceSecondary016: VoicesColors.darkOnSurfaceSecondary016, + onSurfaceError08: VoicesColors.darkOnSurfaceError08, + onSurfaceError012: VoicesColors.darkOnSurfaceError012, + onSurfaceError016: VoicesColors.darkOnSurfaceError016, + iconsForeground: VoicesColors.darkIconsForeground, + iconsBackground: VoicesColors.darkIconsBackground, + iconsDisabled: VoicesColors.darkIconsDisabled, + iconsPrimary: VoicesColors.darkIconsPrimary, + iconsSecondary: VoicesColors.darkIconsSecondary, + iconsSuccess: VoicesColors.darkIconsSuccess, + iconsWarning: VoicesColors.darkIconsWarning, + iconsError: VoicesColors.darkIconsError, +); /// [ThemeData] for the `catalyst` brand. -final ThemeData catalyst = ThemeData( - colorScheme: ColorScheme.fromSeed( - seedColor: VoicesColors.blue, - primary: VoicesColors.blue, - ), +final ThemeData catalyst = _buildThemeData( + lightColorScheme, + lightVoicesColorScheme, +); + +/// Dark [ThemeData] for the `catalyst` brand. +final ThemeData darkCatalyst = _buildThemeData( + darkColorScheme, + darkVoicesColorScheme, ); diff --git a/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/fallback.dart b/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/fallback.dart index fc8dca40e4..7fa296225d 100644 --- a/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/fallback.dart +++ b/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/fallback.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; /// [ThemeData] for the `fallback` brand. -final ThemeData fallback = ThemeData(useMaterial3: true); +final ThemeData fallback = ThemeData.light(); + +/// Dark [ThemeData] for the `fallback` brand. +final ThemeData darkFallback = ThemeData.dark(); diff --git a/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/voices_color_scheme.dart b/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/voices_color_scheme.dart new file mode 100644 index 0000000000..2708ae520f --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/voices_color_scheme.dart @@ -0,0 +1,219 @@ +import 'package:flutter/material.dart'; + +@immutable +class VoicesColorScheme extends ThemeExtension { + final Color? textPrimary; + final Color? textOnPrimary; + final Color? textOnPrimaryContainer; + final Color? textDisabled; + final Color? success; + final Color? onSuccess; + final Color? successContainer; + final Color? onSuccessContainer; + final Color? warning; + final Color? onWarning; + final Color? warningContainer; + final Color? onWarningContainer; + final Color? onSurfaceNeutral08; + final Color? onSurfaceNeutral012; + final Color? onSurfaceNeutral016; + final Color? onSurfacePrimaryContainer; + final Color? onSurfacePrimary08; + final Color? onSurfacePrimary012; + final Color? onSurfacePrimary016; + final Color? onSurfaceSecondary08; + final Color? onSurfaceSecondary012; + final Color? onSurfaceSecondary016; + final Color? onSurfaceError08; + final Color? onSurfaceError012; + final Color? onSurfaceError016; + final Color? iconsForeground; + final Color? iconsBackground; + final Color? iconsDisabled; + final Color? iconsPrimary; + final Color? iconsSecondary; + final Color? iconsSuccess; + final Color? iconsWarning; + final Color? iconsError; + + const VoicesColorScheme({ + required this.textPrimary, + required this.textOnPrimary, + required this.textOnPrimaryContainer, + required this.textDisabled, + required this.success, + required this.onSuccess, + required this.successContainer, + required this.onSuccessContainer, + required this.warning, + required this.onWarning, + required this.warningContainer, + required this.onWarningContainer, + required this.onSurfaceNeutral08, + required this.onSurfaceNeutral012, + required this.onSurfaceNeutral016, + required this.onSurfacePrimaryContainer, + required this.onSurfacePrimary08, + required this.onSurfacePrimary012, + required this.onSurfacePrimary016, + required this.onSurfaceSecondary08, + required this.onSurfaceSecondary012, + required this.onSurfaceSecondary016, + required this.onSurfaceError08, + required this.onSurfaceError012, + required this.onSurfaceError016, + required this.iconsForeground, + required this.iconsBackground, + required this.iconsDisabled, + required this.iconsPrimary, + required this.iconsSecondary, + required this.iconsSuccess, + required this.iconsWarning, + required this.iconsError, + }); + + @override + ThemeExtension copyWith({ + Color? textPrimary, + Color? textOnPrimary, + Color? textOnPrimaryContainer, + Color? textDisabled, + Color? success, + Color? onSuccess, + Color? successContainer, + Color? onSuccessContainer, + Color? warning, + Color? onWarning, + Color? warningContainer, + Color? onWarningContainer, + Color? onSurfaceNeutral08, + Color? onSurfaceNeutral012, + Color? onSurfaceNeutral016, + Color? onSurfacePrimaryContainer, + Color? onSurfacePrimary08, + Color? onSurfacePrimary012, + Color? onSurfacePrimary016, + Color? onSurfaceSecondary08, + Color? onSurfaceSecondary012, + Color? onSurfaceSecondary016, + Color? onSurfaceError08, + Color? onSurfaceError012, + Color? onSurfaceError016, + Color? iconsForeground, + Color? iconsBackground, + Color? iconsDisabled, + Color? iconsPrimary, + Color? iconsSecondary, + Color? iconsSuccess, + Color? iconsWarning, + Color? iconsError, + }) { + return VoicesColorScheme( + textPrimary: textPrimary ?? this.textPrimary, + textOnPrimary: textOnPrimary ?? this.textOnPrimary, + textOnPrimaryContainer: + textOnPrimaryContainer ?? this.textOnPrimaryContainer, + textDisabled: textDisabled ?? this.textDisabled, + success: success ?? this.success, + onSuccess: onSuccess ?? this.onSuccess, + successContainer: successContainer ?? this.successContainer, + onSuccessContainer: onSuccessContainer ?? this.onSuccessContainer, + warning: warning ?? this.warning, + onWarning: onWarning ?? this.onWarning, + warningContainer: warningContainer ?? this.warningContainer, + onWarningContainer: onWarningContainer ?? this.onWarningContainer, + onSurfaceNeutral08: onSurfaceNeutral08 ?? this.onSurfaceError08, + onSurfaceNeutral012: onSurfaceNeutral012 ?? this.onSurfaceError012, + onSurfaceNeutral016: onSurfaceNeutral016 ?? this.onSurfaceError016, + onSurfacePrimaryContainer: + onSurfacePrimaryContainer ?? this.onSurfacePrimaryContainer, + onSurfacePrimary08: onSurfacePrimary08 ?? this.onSurfacePrimary08, + onSurfacePrimary012: onSurfacePrimary012 ?? this.onSurfacePrimary012, + onSurfacePrimary016: onSurfacePrimary016 ?? this.onSurfacePrimary016, + onSurfaceSecondary08: onSurfaceSecondary08 ?? this.onSurfaceSecondary08, + onSurfaceSecondary012: + onSurfaceSecondary012 ?? this.onSurfaceSecondary012, + onSurfaceSecondary016: + onSurfaceSecondary016 ?? this.onSurfaceSecondary016, + onSurfaceError08: onSurfaceError08 ?? this.onSurfaceError08, + onSurfaceError012: onSurfaceError012 ?? this.onSurfaceError012, + onSurfaceError016: onSurfaceError016 ?? this.onSurfaceError016, + iconsForeground: iconsForeground ?? this.iconsForeground, + iconsBackground: iconsBackground ?? this.iconsBackground, + iconsDisabled: iconsDisabled ?? this.iconsDisabled, + iconsPrimary: iconsPrimary ?? this.iconsPrimary, + iconsSecondary: iconsSecondary ?? this.iconsSecondary, + iconsSuccess: iconsSuccess ?? this.iconsSuccess, + iconsWarning: iconsWarning ?? this.iconsWarning, + iconsError: iconsError ?? this.iconsError, + ); + } + + @override + VoicesColorScheme lerp( + ThemeExtension? other, + double t, + ) { + if (other is! VoicesColorScheme) { + return this; + } + return VoicesColorScheme( + textPrimary: Color.lerp(textPrimary, other.textPrimary, t), + textOnPrimary: Color.lerp(textOnPrimary, other.textOnPrimary, t), + textOnPrimaryContainer: + Color.lerp(textOnPrimaryContainer, other.textOnPrimaryContainer, t), + textDisabled: Color.lerp(textDisabled, other.textDisabled, t), + success: Color.lerp(success, other.success, t), + onSuccess: Color.lerp(onSuccess, other.onSuccess, t), + successContainer: Color.lerp(successContainer, other.successContainer, t), + onSuccessContainer: + Color.lerp(onSuccessContainer, other.onSuccessContainer, t), + warning: Color.lerp(warning, other.warning, t), + onWarning: Color.lerp(onWarning, other.onWarning, t), + warningContainer: Color.lerp(warningContainer, other.warningContainer, t), + onWarningContainer: + Color.lerp(onWarningContainer, other.onWarningContainer, t), + onSurfaceNeutral08: + Color.lerp(onSurfaceNeutral08, other.onSurfaceNeutral08, t), + onSurfaceNeutral012: + Color.lerp(onSurfaceNeutral012, other.onSurfaceNeutral012, t), + onSurfaceNeutral016: + Color.lerp(onSurfaceNeutral016, other.onSurfaceNeutral016, t), + onSurfacePrimaryContainer: Color.lerp( + onSurfacePrimaryContainer, + other.onSurfacePrimaryContainer, + t, + ), + onSurfacePrimary08: + Color.lerp(onSurfacePrimary08, other.onSurfacePrimary08, t), + onSurfacePrimary012: + Color.lerp(onSurfacePrimary012, other.onSurfacePrimary012, t), + onSurfacePrimary016: + Color.lerp(onSurfacePrimary016, other.onSurfacePrimary016, t), + onSurfaceSecondary08: + Color.lerp(onSurfaceSecondary08, other.onSurfaceSecondary08, t), + onSurfaceSecondary012: + Color.lerp(onSurfaceSecondary012, other.onSurfaceSecondary012, t), + onSurfaceSecondary016: + Color.lerp(onSurfaceSecondary016, other.onSurfaceSecondary016, t), + onSurfaceError08: Color.lerp(onSurfaceError08, other.onSurfaceError08, t), + onSurfaceError012: + Color.lerp(onSurfaceError012, other.onSurfaceError012, t), + onSurfaceError016: + Color.lerp(onSurfaceError016, other.onSurfaceError016, t), + iconsForeground: Color.lerp(iconsForeground, other.iconsForeground, t), + iconsBackground: Color.lerp(iconsBackground, other.iconsBackground, t), + iconsDisabled: Color.lerp(iconsDisabled, other.iconsDisabled, t), + iconsPrimary: Color.lerp(iconsPrimary, other.iconsPrimary, t), + iconsSecondary: Color.lerp(iconsSecondary, other.iconsSecondary, t), + iconsSuccess: Color.lerp(iconsSuccess, other.iconsSuccess, t), + iconsWarning: Color.lerp(iconsWarning, other.iconsWarning, t), + iconsError: Color.lerp(iconsError, other.iconsError, t), + ); + } +} + +extension VoicesColorSchemeExtension on ThemeData { + VoicesColorScheme get colors => extension()!; + Color get linksPrimary => primaryColor; +} diff --git a/catalyst_voices/packages/catalyst_voices_brands/pubspec.yaml b/catalyst_voices/packages/catalyst_voices_brands/pubspec.yaml index acd46ab52c..c77b7a4a4a 100644 --- a/catalyst_voices/packages/catalyst_voices_brands/pubspec.yaml +++ b/catalyst_voices/packages/catalyst_voices_brands/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: path: ../catalyst_voices_assets flutter: sdk: flutter + google_fonts: ^6.2.1 dev_dependencies: catalyst_analysis: diff --git a/catalyst_voices/packages/catalyst_voices_brands/test/src/catalyst_voices_brands_test.dart b/catalyst_voices/packages/catalyst_voices_brands/test/src/catalyst_voices_brands_test.dart index ea24aace4c..eea47f7db2 100644 --- a/catalyst_voices/packages/catalyst_voices_brands/test/src/catalyst_voices_brands_test.dart +++ b/catalyst_voices/packages/catalyst_voices_brands/test/src/catalyst_voices_brands_test.dart @@ -10,51 +10,51 @@ void main() { const fallbackKey = Key('F'); Widget buildApp() => BlocProvider( - create: (context) => BrandBloc(), - child: BlocBuilder( - builder: (context, state) { - return MaterialApp( - home: Builder( - builder: (context) => Scaffold( - body: Row( - children: [ - MaterialButton( - key: catalystKey, - color: Theme.of(context).primaryColor, - onPressed: () { - context.read().add( - const BrandChangedEvent(BrandKey.catalyst), - ); - }, - child: const Text('Catalyst'), + create: (context) => BrandBloc(), + child: BlocBuilder( + builder: (context, state) { + return MaterialApp( + home: Builder( + builder: (context) => Scaffold( + body: Row( + children: [ + MaterialButton( + key: catalystKey, + color: Theme.of(context).primaryColor, + onPressed: () { + context.read().add( + const BrandChangedEvent(BrandKey.catalyst), + ); + }, + child: const Text('Catalyst'), + ), + MaterialButton( + key: fallbackKey, + color: Theme.of(context).primaryColor, + child: const Text('Fallback'), + onPressed: () { + context.read().add( + const BrandChangedEvent(BrandKey.fallback), + ); + }, + ), + ], ), - MaterialButton( - key: fallbackKey, - color: Theme.of(context).primaryColor, - child: const Text('Fallback'), - onPressed: () { - context.read().add( - const BrandChangedEvent(BrandKey.fallback), - ); - }, - ), - ], + ), ), - ), - ), - theme: ThemeBuilder.buildTheme(state.brandKey), - ); - }, - ), - ); + theme: ThemeBuilder.buildTheme(state.brandKey), + darkTheme: ThemeBuilder.buildTheme(state.brandKey), + ); + }, + ), + ); group('Test brands', () { - // Colors used in the Brand themes as primary. They are used for // the color of the widgets we are testing and they are the colors // we will check against to ensure correct rendering. final fallbackColor = ThemeData(useMaterial3: true).primaryColor; - const catalystColor = VoicesColors.blue; + const catalystColor = VoicesColors.lightPrimary; testWidgets('Default Catalyst theme is applied', (tester) async { await tester.pumpWidget( @@ -85,10 +85,10 @@ void main() { expect(catalystButton, findsOneWidget); expect(fallbackButton, findsOneWidget); - + await tester.tap(fallbackButton); // We need to wait for the animation to complete - await tester.pumpAndSettle(); + await tester.pumpAndSettle(); expect( tester.widget(catalystButton).color, fallbackColor, @@ -109,14 +109,14 @@ void main() { expect(catalystButton, findsOneWidget); expect(fallbackButton, findsOneWidget); - + // We first switch do FallbackBrand, we wait for the animation completion // and then we switch back to the CatalystBrand to check the correct // color is applied. await tester.tap(fallbackButton); await tester.pumpAndSettle(); await tester.tap(catalystButton); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(); expect( tester.widget(catalystButton).color, catalystColor, @@ -126,7 +126,5 @@ void main() { catalystColor, ); }); - }); - } From dfbb9f837f88fbd0e3e02eff67a7515826380df1 Mon Sep 17 00:00:00 2001 From: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Date: Wed, 15 May 2024 14:18:49 +0200 Subject: [PATCH 3/3] feat: catalyst cardano witnesses management (#490) * feat: add shelley address & cardano serialization lib * fix: address to string * style: disable cspell for cardano addresses * fix: address hashcode * refactor: make coin an extension type, rename magicId to id * style: use expression syntax * style: use expressions * style: typo * feat: add comparison operators * feat: add greater or equal, less or equal operators * feat: add utils for transaction hash * feat: add transaction serialization * feat: add algorithm for fee calculation * docs: improve method * style: add class modifier to prevent unintented overrides * feat: catalyst cardano serialization - transactions (#484) * fix: address hashcode * feat: add utils for transaction hash * feat: add transaction serialization * feat: add algorithm for fee calculation * docs: improve method * style: add class modifier to prevent unintented overrides --------- Co-authored-by: Dominik Toton * feat: add witnesses management * feat: add witnesses --------- Co-authored-by: Dominik Toton --- .config/dictionaries/project.dic | 1 + .../lib/catalyst_cardano_serialization.dart | 1 + .../lib/src/exceptions.dart | 41 +++++++- .../lib/src/hashes.dart | 9 ++ .../lib/src/transaction.dart | 9 +- .../lib/src/types.dart | 8 -- .../lib/src/utils/cbor.dart | 48 ++++++++++ .../lib/src/witness.dart | 93 +++++++++++++++++++ .../test/address_test.dart | 2 +- .../test/fees_test.dart | 2 +- .../test/hashes_test.dart | 19 ++++ .../test/{utils => test_utils}/test_data.dart | 6 ++ .../test/transaction_test.dart | 2 +- .../test/utils/cbor_test.dart | 29 ++++++ .../test/witness_test.dart | 22 +++++ 15 files changed, 278 insertions(+), 14 deletions(-) create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/lib/src/utils/cbor.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/lib/src/witness.dart rename catalyst_voices_packages/catalyst_cardano_serialization/test/{utils => test_utils}/test_data.dart (90%) create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/test/utils/cbor_test.dart create mode 100644 catalyst_voices_packages/catalyst_cardano_serialization/test/witness_test.dart diff --git a/.config/dictionaries/project.dic b/.config/dictionaries/project.dic index 7ad7189209..7d0d4d2fd8 100644 --- a/.config/dictionaries/project.dic +++ b/.config/dictionaries/project.dic @@ -198,3 +198,4 @@ lovelace lovelaces pinenacl dtscalac +vkeys diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/catalyst_cardano_serialization.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/catalyst_cardano_serialization.dart index 35d28db13b..bf48afc09d 100644 --- a/catalyst_voices_packages/catalyst_cardano_serialization/lib/catalyst_cardano_serialization.dart +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/catalyst_cardano_serialization.dart @@ -4,3 +4,4 @@ export 'src/fees.dart'; export 'src/hashes.dart'; export 'src/transaction.dart'; export 'src/types.dart'; +export 'src/witness.dart'; diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/exceptions.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/exceptions.dart index 96bf101459..ba6eb0c25a 100644 --- a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/exceptions.dart +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/exceptions.dart @@ -1,3 +1,5 @@ +import 'package:catalyst_cardano_serialization/src/types.dart'; + /// Exception thrown when the transaction exceeds the allowed maximum size. final class MaxTxSizeExceededException implements Exception { /// The maximum amount of bytes per transaction. @@ -14,11 +16,32 @@ final class MaxTxSizeExceededException implements Exception { @override String toString() => 'MaxTxSizeExceededException(' - 'maxTxSize=$maxTxSize' + 'maxTxSize:$maxTxSize' ', actualTxSize:$actualTxSize' ')'; } +/// Exception thrown when the transaction outputs exceed the inputs. +final class InsufficientUtxoBalanceException implements Exception { + /// The amount of [Coin] that user has. + final Coin actualAmount; + + /// The amount of [Coin] that user wants to spend. + final Coin requiredAmount; + + /// The default constructor for [InsufficientUtxoBalanceException]. + const InsufficientUtxoBalanceException({ + required this.actualAmount, + required this.requiredAmount, + }); + + @override + String toString() => 'InsufficientUtxoBalanceException(' + 'actualAmount:$actualAmount' + ', requiredAmount:$requiredAmount' + ')'; +} + /// Exception thrown when building a transaction that doesn't specify the fee. final class TxFeeNotSpecifiedException implements Exception { /// The default constructor for [TxFeeNotSpecifiedException]. @@ -49,3 +72,19 @@ final class InvalidAddressException implements Exception { @override String toString() => 'InvalidAddressException: $message'; } + +/// Exception thrown when the number of witnesses doesn't match +/// the expected amount. +/// +/// When calculating the fee for the transaction the amount of witnesses +/// needs to be specified since they affect the transaction bytes length. +/// +/// Thus less or more witnesses than were included when calculating +/// the fee are not allowed. +final class InvalidTransactionWitnessesException implements Exception { + /// The default constructor for [InvalidTransactionWitnessesException]. + const InvalidTransactionWitnessesException(); + + @override + String toString() => 'InvalidTransactionWitnessesException'; +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/hashes.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/hashes.dart index 4c9cbbc86e..e499cbc87b 100644 --- a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/hashes.dart +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/hashes.dart @@ -71,6 +71,15 @@ final class TransactionHash extends BaseHash { /// of [bytes]. TransactionHash.fromHex(super.string) : super.fromHex(); + /// Constructs the [TransactionHash] from a [TransactionBody]. + TransactionHash.fromTransactionBody(TransactionBody body) + : super.fromBytes( + bytes: Hash.blake2b( + Uint8List.fromList(cbor.encode(body.toCbor())), + digestSize: _length, + ), + ); + @override int get length => _length; } diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/transaction.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/transaction.dart index a087917110..5b26b34d08 100644 --- a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/transaction.dart +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/transaction.dart @@ -1,6 +1,8 @@ import 'package:catalyst_cardano_serialization/src/address.dart'; import 'package:catalyst_cardano_serialization/src/hashes.dart'; import 'package:catalyst_cardano_serialization/src/types.dart'; +import 'package:catalyst_cardano_serialization/src/utils/cbor.dart'; +import 'package:catalyst_cardano_serialization/src/witness.dart'; import 'package:cbor/cbor.dart'; /// Represents the signed transaction with a list of witnesses @@ -9,6 +11,9 @@ final class Transaction { /// The transaction body containing the inputs, outputs, fees, etc. final TransactionBody body; + /// The set of witnesses that have signed given transaction. + final TransactionWitnessSet witnessSet; + /// True if the transaction is valid, false otherwise. final bool isValid; @@ -19,6 +24,7 @@ final class Transaction { const Transaction({ required this.body, required this.isValid, + required this.witnessSet, this.auxiliaryData, }); @@ -26,8 +32,7 @@ final class Transaction { CborValue toCbor() { return CborList([ body.toCbor(), - // TODO(dtscalac): implement witnesses - CborMap({}), + witnessSet.toCbor(), CborBool(isValid), (auxiliaryData ?? const AuxiliaryData()).toCbor(), ]); diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/types.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/types.dart index fbb9571756..addf9f731c 100644 --- a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/types.dart +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/types.dart @@ -50,11 +50,3 @@ extension type const SlotBigNum(int value) { /// Serializes the type as cbor. CborValue toCbor() => CborSmallInt(value); } - -/// Holds cbor tags not specified by the official cbor package. -final class CborCustomTags { - const CborCustomTags._(); - - /// A cbor tag describing a key-value pairs data. - static const int map = 259; -} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/utils/cbor.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/utils/cbor.dart new file mode 100644 index 0000000000..6ce64d2ee1 --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/utils/cbor.dart @@ -0,0 +1,48 @@ +/// Holds cbor tags not specified by the official cbor package. +final class CborCustomTags { + const CborCustomTags._(); + + /// A cbor tag describing a key-value pairs data. + static const int map = 259; +} + +/// How many bytes are used in cbor encoding for a major type/length. +enum CborSize { + /// Length/data is encoded inside of the type information. + inline(bytes: 0), + + /// Length/data is in 1 byte following the type information. + one(bytes: 1), + + /// Length/data is in 2 bytes following the type information. + two(bytes: 2), + + /// Length/data is in 4 bytes following the type information. + four(bytes: 4), + + /// Length/data is in 8 bytes following the type information. + eight(bytes: 8); + + /// The amount of bytes it takes to encode the type in cbor. + final int bytes; + + const CborSize({required this.bytes}); + + /// The max int value that can be inlined in cbor without extra bytes. + static const int maxInlineEncoding = 23; + + /// Calculates the [CborSize] for arbitrary [value]. + static CborSize ofInt(int value) { + if (value <= maxInlineEncoding) { + return CborSize.inline; + } else if (value < 0x100) { + return CborSize.one; + } else if (value < 0x10000) { + return CborSize.two; + } else if (value < 0x100000000) { + return CborSize.four; + } else { + return CborSize.eight; + } + } +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/witness.dart b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/witness.dart new file mode 100644 index 0000000000..7ad376805f --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/lib/src/witness.dart @@ -0,0 +1,93 @@ +import 'package:cbor/cbor.dart'; + +/// A set of witnesses that sign the transaction. +class TransactionWitnessSet { + /// The witnesses that sign the transaction. + final Set vkeyWitnesses; + + /// The default constructor for [TransactionWitnessSet]. + const TransactionWitnessSet({required this.vkeyWitnesses}); + + /// Serializes the type as cbor. + CborValue toCbor() { + return CborMap({ + for (final vkey in vkeyWitnesses.indexed) + CborSmallInt(vkey.$1): vkey.$2.toCbor(), + }); + } +} + +/// The transaction witness with a [signature] of the transaction. +class VkeyWitness { + /// The public key of the witness. + final Vkey vkey; + + /// The witness signature of the transaction. + final Ed25519Signature signature; + + /// The default constructor for [VkeyWitness]. + const VkeyWitness({ + required this.vkey, + required this.signature, + }); + + /// Builds a fake [VkeyWitness] that helps to measure target transaction + /// size when the transaction hasn't been signed yet. + factory VkeyWitness.seeded(int byte) { + return VkeyWitness( + vkey: Vkey.seeded(byte), + signature: Ed25519Signature.seeded(byte), + ); + } + + /// Serializes the type as cbor. + CborValue toCbor() { + return CborList([ + vkey.toCbor(), + signature.toCbor(), + ]); + } +} + +/// The public key of the witness. +extension type Vkey._(List bytes) { + /// The length of the [Vkey]. + static const int length = 32; + + /// The default constructor for [Vkey]. + Vkey.fromBytes(this.bytes) { + if (bytes.length != length) { + throw ArgumentError('Vkey length does not match: ${bytes.length}'); + } + } + + /// Returns the [Vkey] filled with [byte] that can be + /// used to reserve size to calculate the final transaction bytes size. + factory Vkey.seeded(int byte) => Vkey.fromBytes(List.filled(length, byte)); + + /// Serializes the type as cbor. + CborValue toCbor() => CborBytes(bytes); +} + +/// The witness signature of the transaction. +extension type Ed25519Signature._(List bytes) { + /// The length of the [Ed25519Signature]. + static const int length = 64; + + /// The default constructor for [Ed25519Signature]. + Ed25519Signature.fromBytes(this.bytes) { + if (bytes.length != length) { + throw ArgumentError( + 'Ed25519Signature length does not match: ${bytes.length}', + ); + } + } + + /// Returns the [Ed25519Signature] filled with [byte] that can be + /// used to reserve size to calculate the final transaction bytes size. + factory Ed25519Signature.seeded(int byte) => + Ed25519Signature.fromBytes(List.filled(length, byte)); + + /// Serializes the type as cbor. + CborValue toCbor() => CborBytes(bytes); +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/address_test.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/address_test.dart index 26474d56cf..4775dd82ce 100644 --- a/catalyst_voices_packages/catalyst_cardano_serialization/test/address_test.dart +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/address_test.dart @@ -2,7 +2,7 @@ import 'package:catalyst_cardano_serialization/src/address.dart'; import 'package:catalyst_cardano_serialization/src/types.dart'; import 'package:test/test.dart'; -import 'utils/test_data.dart'; +import 'test_utils/test_data.dart'; void main() { group(ShelleyAddress, () { diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/fees_test.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/fees_test.dart index 3d5dbb360b..82482ec4b5 100644 --- a/catalyst_voices_packages/catalyst_cardano_serialization/test/fees_test.dart +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/fees_test.dart @@ -2,7 +2,7 @@ import 'package:catalyst_cardano_serialization/src/fees.dart'; import 'package:catalyst_cardano_serialization/src/types.dart'; import 'package:test/test.dart'; -import 'utils/test_data.dart'; +import 'test_utils/test_data.dart'; void main() { group(LinearFee, () { diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/hashes_test.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/hashes_test.dart index f55bedc45f..d68b88516b 100644 --- a/catalyst_voices_packages/catalyst_cardano_serialization/test/hashes_test.dart +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/hashes_test.dart @@ -1,5 +1,6 @@ import 'package:catalyst_cardano_serialization/src/hashes.dart'; import 'package:catalyst_cardano_serialization/src/transaction.dart'; +import 'package:catalyst_cardano_serialization/src/types.dart'; import 'package:cbor/cbor.dart'; import 'package:convert/convert.dart'; import 'package:test/test.dart'; @@ -20,6 +21,24 @@ void main() { expect(hash.bytes, equals(bytes)); }); + test('from transaction body', () { + const body = TransactionBody( + inputs: {}, + outputs: [], + fee: Coin(0), + ); + + final hash = TransactionHash.fromTransactionBody(body); + expect( + hash, + equals( + TransactionHash.fromHex( + '36fdff68dfe3660f1ceea60f018a0fd7a83da13def229108794c397a879b0436', + ), + ), + ); + }); + test('toCbor returns bytes', () { final hash = TransactionHash.fromBytes(bytes: bytes); final encodedCbor = cbor.encode(hash.toCbor()); diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/utils/test_data.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/test_utils/test_data.dart similarity index 90% rename from catalyst_voices_packages/catalyst_cardano_serialization/test/utils/test_data.dart rename to catalyst_voices_packages/catalyst_cardano_serialization/test/test_utils/test_data.dart index ee58d63609..44e148c686 100644 --- a/catalyst_voices_packages/catalyst_cardano_serialization/test/utils/test_data.dart +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/test_utils/test_data.dart @@ -2,6 +2,7 @@ import 'package:catalyst_cardano_serialization/src/address.dart'; import 'package:catalyst_cardano_serialization/src/hashes.dart'; import 'package:catalyst_cardano_serialization/src/transaction.dart'; import 'package:catalyst_cardano_serialization/src/types.dart'; +import 'package:catalyst_cardano_serialization/src/witness.dart'; import 'package:cbor/cbor.dart'; /* cSpell:disable */ @@ -43,6 +44,7 @@ Transaction minimalTestTransaction() { fee: const Coin(10000000), ), isValid: true, + witnessSet: const TransactionWitnessSet(vkeyWitnesses: {}), ); } @@ -74,6 +76,10 @@ Transaction fullTestTransaction() { networkId: NetworkId.testnet, ), isValid: true, + // TODO(dtscalac): provide witness + witnessSet: const TransactionWitnessSet( + vkeyWitnesses: {}, + ), auxiliaryData: auxiliaryData, ); } diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/transaction_test.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/transaction_test.dart index b67c4a9183..52d8f88084 100644 --- a/catalyst_voices_packages/catalyst_cardano_serialization/test/transaction_test.dart +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/transaction_test.dart @@ -3,7 +3,7 @@ import 'package:cbor/cbor.dart'; import 'package:convert/convert.dart'; import 'package:test/test.dart'; -import 'utils/test_data.dart'; +import 'test_utils/test_data.dart'; void main() { group(Transaction, () { diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/utils/cbor_test.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/utils/cbor_test.dart new file mode 100644 index 0000000000..cadd24b03a --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/utils/cbor_test.dart @@ -0,0 +1,29 @@ +import 'package:catalyst_cardano_serialization/src/utils/cbor.dart'; +import 'package:test/test.dart'; + +void main() { + group(CborSize, () { + test('of int value', () { + expect(CborSize.ofInt(0), equals(CborSize.inline)); + expect( + CborSize.ofInt(CborSize.maxInlineEncoding), + equals(CborSize.inline), + ); + expect(CborSize.ofInt(0xff), equals(CborSize.one)); + expect(CborSize.ofInt(0x100), equals(CborSize.two)); + expect(CborSize.ofInt(0xffff), equals(CborSize.two)); + expect(CborSize.ofInt(0x10000), equals(CborSize.four)); + expect(CborSize.ofInt(0xffffffff), equals(CborSize.four)); + expect(CborSize.ofInt(0x100000000), equals(CborSize.eight)); + expect(CborSize.ofInt(0xfffffffffffff), equals(CborSize.eight)); + }); + + test('bytes length', () { + expect(CborSize.inline.bytes, equals(0)); + expect(CborSize.one.bytes, equals(1)); + expect(CborSize.two.bytes, equals(2)); + expect(CborSize.four.bytes, equals(4)); + expect(CborSize.eight.bytes, equals(8)); + }); + }); +} diff --git a/catalyst_voices_packages/catalyst_cardano_serialization/test/witness_test.dart b/catalyst_voices_packages/catalyst_cardano_serialization/test/witness_test.dart new file mode 100644 index 0000000000..ae9f5f0625 --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano_serialization/test/witness_test.dart @@ -0,0 +1,22 @@ +import 'package:catalyst_cardano_serialization/src/witness.dart'; +import 'package:test/test.dart'; + +void main() { + group(Vkey, () { + test('seeded has correct length', () { + expect( + Vkey.seeded(1).bytes.length, + equals(Vkey.length), + ); + }); + }); + + group(Ed25519Signature, () { + test('seeded has correct length', () { + expect( + Ed25519Signature.seeded(2).bytes.length, + equals(Ed25519Signature.length), + ); + }); + }); +}