Skip to content

Commit

Permalink
fix: catalyst cardano integration with Typhoon wallet (#636)
Browse files Browse the repository at this point in the history
* fix!: make api version nullable as some extensions return null

* fix: filter out unsupported extensions

* fix: getting supported extensions from typhoon extension

* fix: amount in getUtxos should accept a balance, not a Coin

* fix: api usage

* fix: send undefined values to JS instead of nulls

* fix: uncomment code

* docs: improve wording
  • Loading branch information
dtscalac authored Jul 23, 2024
1 parent 4924099 commit 2c6e270
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ Future<void> main() async {
print(await api.getUsedAddresses());
final unsignedTx = _buildUnsignedTx(
utxos: await api.getUtxos(amount: const Coin(1000000)),
utxos: await api.getUtxos(
amount: const Balance(
coin: Coin(1000000),
),
),
changeAddress: await api.getChangeAddress(),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@ class _MyHomePageState extends State<MyHomePage> {
try {
setState(() => _isLoading = true);
final api = await wallet.enable(
extensions: const [CipExtension(cip: 95)],
extensions: [
const CipExtension(cip: 30),
if (wallet.supportedExtensions.contains(const CipExtension(cip: 95)))
const CipExtension(cip: 95),
],
);
setState(() => _api = api);
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ Future<void> _signAndSubmitRbacTx({
final changeAddress = await api.getChangeAddress();

final utxos = await api.getUtxos(
amount: const Coin(1000000),
amount: const Balance(
coin: Coin(1000000),
),
);

final x509Envelope = await _buildMetadataEnvelope(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ Future<void> _signAndSubmitTx({
final changeAddress = await api.getChangeAddress();

final utxos = await api.getUtxos(
amount: const Coin(1000000),
amount: const Balance(
coin: Coin(1000000),
),
);

final unsignedTx = _buildUnsignedTx(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ abstract interface class CardanoWalletApi {
/// null shall be returned. The results can be further paginated by
/// [paginate] if it is not null.
Future<List<TransactionUnspentOutput>> getUtxos({
Coin? amount,
Balance? amount,
Paginate? paginate,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@ function _getWallets() {
if (cardano) {
const keys = Object.keys(window.cardano);
const possibleWallets = keys.map((k) => cardano[k]);
return possibleWallets.filter((w) => typeof w === "object" && "enable" in w);
return possibleWallets.filter((w) => typeof w === "object" && "enable" in w && "apiVersion" in w);
}

return [];
}

// Returns an instance of `undefined` to dart layer as `undefined`
// cannot be constructed directly in dart layer. Dart nulls are translated
// to JS nulls and JS distinguishes between undefined and null.
function _makeUndefined() {
return undefined;
}

// A namespace containing the JS functions that
// can be executed from dart side
const catalyst_cardano = {
getWallets: _getWallets,
makeUndefined: _makeUndefined,
}

// Expose cardano multiplatform as globally accessible
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import 'dart:js_interop';
import 'package:catalyst_cardano_platform_interface/catalyst_cardano_platform_interface.dart';
import 'package:catalyst_cardano_web/src/interop/catalyst_cardano_wallet_proxy.dart';

/// Returns a JS undefined object.
///
/// Use this function to obtain an instance of `undefined`
/// when you need to distinguish between `null` and `undefined`.
@JS()
external JSAny? makeUndefined();

/// Lists all injected Cardano wallet extensions that are reachable
/// via window.cardano.{walletName} in javascript.
@JS()
Expand All @@ -23,7 +30,7 @@ extension type JSCardanoWallet(JSObject _) implements JSObject {
external JSString get apiVersion;

/// See [CardanoWallet.supportedExtensions].
external JSArray<JSCipExtension> get supportedExtensions;
external JSArray<JSCipExtension>? get supportedExtensions;

/// See [CardanoWallet.isEnabled].
external JSPromise<JSBoolean> isEnabled();
Expand Down Expand Up @@ -62,13 +69,13 @@ extension type JSCardanoWalletApi(JSObject _) implements JSObject {

/// See [CardanoWalletApi.getUsedAddresses].
external JSPromise<JSArray<JSString>> getUsedAddresses([
JSPaginate? paginate,
JSAny? paginate,
]);

/// See [CardanoWalletApi.getUtxos].
external JSPromise<JSArray<JSString>> getUtxos([
JSNumber? amount,
JSPaginate? paginate,
JSAny? amount,
JSAny? paginate,
]);

/// See [CardanoWalletApi.signData].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ import 'package:catalyst_cardano_web/src/interop/catalyst_cardano_interop.dart';
import 'package:cbor/cbor.dart';
import 'package:convert/convert.dart';

/// Error message in exception thrown when trying
/// to execute a method which doesn't exist in JS layer.
///
/// Notably some wallet extensions decided not to implement
/// some method even if they are required by the CIP-30 standard.
///
/// Checking for this error messages allows to detect unimplemented method.
const _noSuchMethodError = 'NoSuchMethodError';

/// The minimal set of wallet extensions that
/// must be supported by every wallet extension.
const _fallbackExtensions = [CipExtension(cip: 30)];

/// A wrapper around [JSCardanoWallet] that translates between JS/dart layers.
class JSCardanoWalletProxy implements CardanoWallet {
final JSCardanoWallet _delegate;
Expand All @@ -24,7 +37,8 @@ class JSCardanoWalletProxy implements CardanoWallet {

@override
List<CipExtension> get supportedExtensions =>
_delegate.supportedExtensions.toDart.map((e) => e.toDart).toList();
_delegate.supportedExtensions?.toDart.map((e) => e.toDart).toList() ??
_fallbackExtensions;

@override
Future<bool> isEnabled() async {
Expand Down Expand Up @@ -77,11 +91,14 @@ class JSCardanoWalletApiProxy implements CardanoWalletApi {
@override
Future<List<CipExtension>> getExtensions() async {
try {
return await _delegate
.getExtensions()
.toDart
.then((array) => array.toDart.map((item) => item.toDart).toList());
return await _delegate.getExtensions().toDart.then(
(array) => array.toDart.map((item) => item.toDart).toList(),
);
} catch (ex) {
if (ex.toString().contains(_noSuchMethodError)) {
return _fallbackExtensions;
}

throw _mapApiException(ex) ?? _fallbackApiException(ex);
}
}
Expand Down Expand Up @@ -139,7 +156,7 @@ class JSCardanoWalletApiProxy implements CardanoWalletApi {
Future<List<ShelleyAddress>> getUsedAddresses({Paginate? paginate}) async {
try {
final jsPaginate =
paginate != null ? JSPaginate.fromDart(paginate) : null;
paginate != null ? JSPaginate.fromDart(paginate) : makeUndefined();

return await _delegate.getUsedAddresses(jsPaginate).toDart.then(
(array) => array.toDart
Expand All @@ -155,14 +172,16 @@ class JSCardanoWalletApiProxy implements CardanoWalletApi {

@override
Future<List<TransactionUnspentOutput>> getUtxos({
Coin? amount,
Balance? amount,
Paginate? paginate,
}) async {
try {
return await _delegate
.getUtxos(
amount?.value.toJS,
paginate != null ? JSPaginate.fromDart(paginate) : null,
amount != null
? hex.encode(cbor.encode(amount.toCbor())).toJS
: makeUndefined(),
paginate != null ? JSPaginate.fromDart(paginate) : makeUndefined(),
)
.toDart
.then(
Expand Down

0 comments on commit 2c6e270

Please sign in to comment.