From 967a8f84f45ffd64d3f17e4aceaabf4ada16249e Mon Sep 17 00:00:00 2001 From: Alfreedom <00tango.bromine@icloud.com> Date: Mon, 7 Jul 2025 16:14:53 +0200 Subject: [PATCH] scoped properties and tests --- .../lib/base/appkit_base_impl.dart | 1 + packages/reown_appkit/pubspec.yaml | 6 +- packages/reown_sign/lib/i_sign_client.dart | 2 + packages/reown_sign/lib/i_sign_dapp.dart | 1 + packages/reown_sign/lib/i_sign_wallet.dart | 1 + .../lib/models/json_rpc_models.dart | 2 + .../lib/models/json_rpc_models.freezed.dart | 72 +++++++++++++++++-- .../lib/models/json_rpc_models.g.dart | 6 ++ .../lib/models/proposal_models.dart | 1 + .../lib/models/proposal_models.freezed.dart | 34 ++++++++- .../lib/models/proposal_models.g.dart | 3 + .../reown_sign/lib/models/session_models.dart | 5 +- .../lib/models/session_models.freezed.dart | 34 ++++++++- .../lib/models/session_models.g.dart | 3 + packages/reown_sign/lib/sign_client.dart | 4 ++ packages/reown_sign/lib/sign_engine.dart | 47 ++++++++++++ .../lib/utils/sign_api_validator_utils.dart | 50 +++++++++++++ packages/reown_sign/pubspec.yaml | 3 +- .../test/utils/sign_client_test_wrapper.dart | 2 + packages/reown_sign/test/validation_test.dart | 66 +++++++++++++++++ .../reown_walletkit/lib/walletkit_impl.dart | 1 + packages/reown_walletkit/pubspec.yaml | 6 +- 22 files changed, 338 insertions(+), 12 deletions(-) diff --git a/packages/reown_appkit/lib/base/appkit_base_impl.dart b/packages/reown_appkit/lib/base/appkit_base_impl.dart index 4e2b9f2a..f2133df5 100644 --- a/packages/reown_appkit/lib/base/appkit_base_impl.dart +++ b/packages/reown_appkit/lib/base/appkit_base_impl.dart @@ -173,6 +173,7 @@ class ReownAppKit implements IReownAppKit { Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, String? pairingTopic, List? relays, List>? methods = DEFAULT_METHODS, diff --git a/packages/reown_appkit/pubspec.yaml b/packages/reown_appkit/pubspec.yaml index 7faabde5..2450b4e4 100644 --- a/packages/reown_appkit/pubspec.yaml +++ b/packages/reown_appkit/pubspec.yaml @@ -29,8 +29,10 @@ dependencies: pinenacl: ^0.6.0 plugin_platform_interface: ^2.1.8 qr_flutter_wc: ^0.0.3 - reown_core: ^1.1.6+1 - reown_sign: ^1.1.7+1 + reown_core: + path: ../reown_core/ + reown_sign: + path: ../reown_sign/ shimmer: ^3.0.0 synchronized: ^3.3.0+3 web_socket_channel: ^3.0.1 diff --git a/packages/reown_sign/lib/i_sign_client.dart b/packages/reown_sign/lib/i_sign_client.dart index 1017b766..d721df11 100644 --- a/packages/reown_sign/lib/i_sign_client.dart +++ b/packages/reown_sign/lib/i_sign_client.dart @@ -43,6 +43,7 @@ abstract class IReownSignClient { Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, String? pairingTopic, List? relays, List>? methods, @@ -54,6 +55,7 @@ abstract class IReownSignClient { required int id, required Map namespaces, Map? sessionProperties, + Map? scopedProperties, String? relayProtocol, }); Future reject({ diff --git a/packages/reown_sign/lib/i_sign_dapp.dart b/packages/reown_sign/lib/i_sign_dapp.dart index 7fe4f205..28d420a8 100644 --- a/packages/reown_sign/lib/i_sign_dapp.dart +++ b/packages/reown_sign/lib/i_sign_dapp.dart @@ -13,6 +13,7 @@ abstract class IReownSignDapp extends IReownSignCommon { Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, String? pairingTopic, List? relays, List>? methods, diff --git a/packages/reown_sign/lib/i_sign_wallet.dart b/packages/reown_sign/lib/i_sign_wallet.dart index f53c61e5..2cda4cbd 100644 --- a/packages/reown_sign/lib/i_sign_wallet.dart +++ b/packages/reown_sign/lib/i_sign_wallet.dart @@ -28,6 +28,7 @@ abstract class IReownSignWallet extends IReownSignCommon { required int id, required Map namespaces, Map? sessionProperties, + Map? scopedProperties, String? relayProtocol, }); Future rejectSession({ diff --git a/packages/reown_sign/lib/models/json_rpc_models.dart b/packages/reown_sign/lib/models/json_rpc_models.dart index 7ae5efa6..ef6414cb 100644 --- a/packages/reown_sign/lib/models/json_rpc_models.dart +++ b/packages/reown_sign/lib/models/json_rpc_models.dart @@ -40,6 +40,7 @@ class WcSessionProposeRequest with _$WcSessionProposeRequest { required Map requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, required ConnectionMetadata proposer, }) = _WcSessionProposeRequest; @@ -68,6 +69,7 @@ class WcSessionSettleRequest with _$WcSessionSettleRequest { Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, required int expiry, required ConnectionMetadata controller, }) = _WcSessionSettleRequest; diff --git a/packages/reown_sign/lib/models/json_rpc_models.freezed.dart b/packages/reown_sign/lib/models/json_rpc_models.freezed.dart index f8daef0d..53f514df 100644 --- a/packages/reown_sign/lib/models/json_rpc_models.freezed.dart +++ b/packages/reown_sign/lib/models/json_rpc_models.freezed.dart @@ -364,6 +364,8 @@ mixin _$WcSessionProposeRequest { throw _privateConstructorUsedError; Map? get sessionProperties => throw _privateConstructorUsedError; + Map? get scopedProperties => + throw _privateConstructorUsedError; ConnectionMetadata get proposer => throw _privateConstructorUsedError; /// Serializes this WcSessionProposeRequest to a JSON map. @@ -387,6 +389,7 @@ abstract class $WcSessionProposeRequestCopyWith<$Res> { Map requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, ConnectionMetadata proposer}); $ConnectionMetadataCopyWith<$Res> get proposer; @@ -412,6 +415,7 @@ class _$WcSessionProposeRequestCopyWithImpl<$Res, Object? requiredNamespaces = null, Object? optionalNamespaces = freezed, Object? sessionProperties = freezed, + Object? scopedProperties = freezed, Object? proposer = null, }) { return _then(_value.copyWith( @@ -431,6 +435,10 @@ class _$WcSessionProposeRequestCopyWithImpl<$Res, ? _value.sessionProperties : sessionProperties // ignore: cast_nullable_to_non_nullable as Map?, + scopedProperties: freezed == scopedProperties + ? _value.scopedProperties + : scopedProperties // ignore: cast_nullable_to_non_nullable + as Map?, proposer: null == proposer ? _value.proposer : proposer // ignore: cast_nullable_to_non_nullable @@ -463,6 +471,7 @@ abstract class _$$WcSessionProposeRequestImplCopyWith<$Res> Map requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, ConnectionMetadata proposer}); @override @@ -488,6 +497,7 @@ class __$$WcSessionProposeRequestImplCopyWithImpl<$Res> Object? requiredNamespaces = null, Object? optionalNamespaces = freezed, Object? sessionProperties = freezed, + Object? scopedProperties = freezed, Object? proposer = null, }) { return _then(_$WcSessionProposeRequestImpl( @@ -507,6 +517,10 @@ class __$$WcSessionProposeRequestImplCopyWithImpl<$Res> ? _value._sessionProperties : sessionProperties // ignore: cast_nullable_to_non_nullable as Map?, + scopedProperties: freezed == scopedProperties + ? _value._scopedProperties + : scopedProperties // ignore: cast_nullable_to_non_nullable + as Map?, proposer: null == proposer ? _value.proposer : proposer // ignore: cast_nullable_to_non_nullable @@ -524,11 +538,13 @@ class _$WcSessionProposeRequestImpl implements _WcSessionProposeRequest { required final Map requiredNamespaces, final Map? optionalNamespaces, final Map? sessionProperties, + final Map? scopedProperties, required this.proposer}) : _relays = relays, _requiredNamespaces = requiredNamespaces, _optionalNamespaces = optionalNamespaces, - _sessionProperties = sessionProperties; + _sessionProperties = sessionProperties, + _scopedProperties = scopedProperties; factory _$WcSessionProposeRequestImpl.fromJson(Map json) => _$$WcSessionProposeRequestImplFromJson(json); @@ -572,12 +588,22 @@ class _$WcSessionProposeRequestImpl implements _WcSessionProposeRequest { return EqualUnmodifiableMapView(value); } + final Map? _scopedProperties; + @override + Map? get scopedProperties { + final value = _scopedProperties; + if (value == null) return null; + if (_scopedProperties is EqualUnmodifiableMapView) return _scopedProperties; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + @override final ConnectionMetadata proposer; @override String toString() { - return 'WcSessionProposeRequest(relays: $relays, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, sessionProperties: $sessionProperties, proposer: $proposer)'; + return 'WcSessionProposeRequest(relays: $relays, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, sessionProperties: $sessionProperties, scopedProperties: $scopedProperties, proposer: $proposer)'; } @override @@ -592,6 +618,8 @@ class _$WcSessionProposeRequestImpl implements _WcSessionProposeRequest { .equals(other._optionalNamespaces, _optionalNamespaces) && const DeepCollectionEquality() .equals(other._sessionProperties, _sessionProperties) && + const DeepCollectionEquality() + .equals(other._scopedProperties, _scopedProperties) && (identical(other.proposer, proposer) || other.proposer == proposer)); } @@ -604,6 +632,7 @@ class _$WcSessionProposeRequestImpl implements _WcSessionProposeRequest { const DeepCollectionEquality().hash(_requiredNamespaces), const DeepCollectionEquality().hash(_optionalNamespaces), const DeepCollectionEquality().hash(_sessionProperties), + const DeepCollectionEquality().hash(_scopedProperties), proposer); /// Create a copy of WcSessionProposeRequest @@ -629,6 +658,7 @@ abstract class _WcSessionProposeRequest implements WcSessionProposeRequest { required final Map requiredNamespaces, final Map? optionalNamespaces, final Map? sessionProperties, + final Map? scopedProperties, required final ConnectionMetadata proposer}) = _$WcSessionProposeRequestImpl; @@ -644,6 +674,8 @@ abstract class _WcSessionProposeRequest implements WcSessionProposeRequest { @override Map? get sessionProperties; @override + Map? get scopedProperties; + @override ConnectionMetadata get proposer; /// Create a copy of WcSessionProposeRequest @@ -846,6 +878,8 @@ mixin _$WcSessionSettleRequest { throw _privateConstructorUsedError; Map? get sessionProperties => throw _privateConstructorUsedError; + Map? get scopedProperties => + throw _privateConstructorUsedError; int get expiry => throw _privateConstructorUsedError; ConnectionMetadata get controller => throw _privateConstructorUsedError; @@ -871,6 +905,7 @@ abstract class $WcSessionSettleRequestCopyWith<$Res> { Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, int expiry, ConnectionMetadata controller}); @@ -898,6 +933,7 @@ class _$WcSessionSettleRequestCopyWithImpl<$Res, Object? requiredNamespaces = freezed, Object? optionalNamespaces = freezed, Object? sessionProperties = freezed, + Object? scopedProperties = freezed, Object? expiry = null, Object? controller = null, }) { @@ -922,6 +958,10 @@ class _$WcSessionSettleRequestCopyWithImpl<$Res, ? _value.sessionProperties : sessionProperties // ignore: cast_nullable_to_non_nullable as Map?, + scopedProperties: freezed == scopedProperties + ? _value.scopedProperties + : scopedProperties // ignore: cast_nullable_to_non_nullable + as Map?, expiry: null == expiry ? _value.expiry : expiry // ignore: cast_nullable_to_non_nullable @@ -959,6 +999,7 @@ abstract class _$$WcSessionSettleRequestImplCopyWith<$Res> Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, int expiry, ConnectionMetadata controller}); @@ -986,6 +1027,7 @@ class __$$WcSessionSettleRequestImplCopyWithImpl<$Res> Object? requiredNamespaces = freezed, Object? optionalNamespaces = freezed, Object? sessionProperties = freezed, + Object? scopedProperties = freezed, Object? expiry = null, Object? controller = null, }) { @@ -1010,6 +1052,10 @@ class __$$WcSessionSettleRequestImplCopyWithImpl<$Res> ? _value._sessionProperties : sessionProperties // ignore: cast_nullable_to_non_nullable as Map?, + scopedProperties: freezed == scopedProperties + ? _value._scopedProperties + : scopedProperties // ignore: cast_nullable_to_non_nullable + as Map?, expiry: null == expiry ? _value.expiry : expiry // ignore: cast_nullable_to_non_nullable @@ -1032,12 +1078,14 @@ class _$WcSessionSettleRequestImpl implements _WcSessionSettleRequest { final Map? requiredNamespaces, final Map? optionalNamespaces, final Map? sessionProperties, + final Map? scopedProperties, required this.expiry, required this.controller}) : _namespaces = namespaces, _requiredNamespaces = requiredNamespaces, _optionalNamespaces = optionalNamespaces, - _sessionProperties = sessionProperties; + _sessionProperties = sessionProperties, + _scopedProperties = scopedProperties; factory _$WcSessionSettleRequestImpl.fromJson(Map json) => _$$WcSessionSettleRequestImplFromJson(json); @@ -1085,6 +1133,16 @@ class _$WcSessionSettleRequestImpl implements _WcSessionSettleRequest { return EqualUnmodifiableMapView(value); } + final Map? _scopedProperties; + @override + Map? get scopedProperties { + final value = _scopedProperties; + if (value == null) return null; + if (_scopedProperties is EqualUnmodifiableMapView) return _scopedProperties; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + @override final int expiry; @override @@ -1092,7 +1150,7 @@ class _$WcSessionSettleRequestImpl implements _WcSessionSettleRequest { @override String toString() { - return 'WcSessionSettleRequest(relay: $relay, namespaces: $namespaces, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, sessionProperties: $sessionProperties, expiry: $expiry, controller: $controller)'; + return 'WcSessionSettleRequest(relay: $relay, namespaces: $namespaces, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, sessionProperties: $sessionProperties, scopedProperties: $scopedProperties, expiry: $expiry, controller: $controller)'; } @override @@ -1109,6 +1167,8 @@ class _$WcSessionSettleRequestImpl implements _WcSessionSettleRequest { .equals(other._optionalNamespaces, _optionalNamespaces) && const DeepCollectionEquality() .equals(other._sessionProperties, _sessionProperties) && + const DeepCollectionEquality() + .equals(other._scopedProperties, _scopedProperties) && (identical(other.expiry, expiry) || other.expiry == expiry) && (identical(other.controller, controller) || other.controller == controller)); @@ -1123,6 +1183,7 @@ class _$WcSessionSettleRequestImpl implements _WcSessionSettleRequest { const DeepCollectionEquality().hash(_requiredNamespaces), const DeepCollectionEquality().hash(_optionalNamespaces), const DeepCollectionEquality().hash(_sessionProperties), + const DeepCollectionEquality().hash(_scopedProperties), expiry, controller); @@ -1150,6 +1211,7 @@ abstract class _WcSessionSettleRequest implements WcSessionSettleRequest { final Map? requiredNamespaces, final Map? optionalNamespaces, final Map? sessionProperties, + final Map? scopedProperties, required final int expiry, required final ConnectionMetadata controller}) = _$WcSessionSettleRequestImpl; @@ -1168,6 +1230,8 @@ abstract class _WcSessionSettleRequest implements WcSessionSettleRequest { @override Map? get sessionProperties; @override + Map? get scopedProperties; + @override int get expiry; @override ConnectionMetadata get controller; diff --git a/packages/reown_sign/lib/models/json_rpc_models.g.dart b/packages/reown_sign/lib/models/json_rpc_models.g.dart index d60a29cf..7d190b87 100644 --- a/packages/reown_sign/lib/models/json_rpc_models.g.dart +++ b/packages/reown_sign/lib/models/json_rpc_models.g.dart @@ -52,6 +52,7 @@ _$WcSessionProposeRequestImpl _$$WcSessionProposeRequestImplFromJson( (json['sessionProperties'] as Map?)?.map( (k, e) => MapEntry(k, e as String), ), + scopedProperties: json['scopedProperties'] as Map?, proposer: ConnectionMetadata.fromJson(json['proposer'] as Map), ); @@ -67,6 +68,8 @@ Map _$$WcSessionProposeRequestImplToJson( 'optionalNamespaces': value, if (instance.sessionProperties case final value?) 'sessionProperties': value, + if (instance.scopedProperties case final value?) + 'scopedProperties': value, 'proposer': instance.proposer.toJson(), }; @@ -105,6 +108,7 @@ _$WcSessionSettleRequestImpl _$$WcSessionSettleRequestImplFromJson( (json['sessionProperties'] as Map?)?.map( (k, e) => MapEntry(k, e as String), ), + scopedProperties: json['scopedProperties'] as Map?, expiry: (json['expiry'] as num).toInt(), controller: ConnectionMetadata.fromJson( json['controller'] as Map), @@ -123,6 +127,8 @@ Map _$$WcSessionSettleRequestImplToJson( 'optionalNamespaces': value, if (instance.sessionProperties case final value?) 'sessionProperties': value, + if (instance.scopedProperties case final value?) + 'scopedProperties': value, 'expiry': instance.expiry, 'controller': instance.controller.toJson(), }; diff --git a/packages/reown_sign/lib/models/proposal_models.dart b/packages/reown_sign/lib/models/proposal_models.dart index cb2cc115..e694cdbc 100644 --- a/packages/reown_sign/lib/models/proposal_models.dart +++ b/packages/reown_sign/lib/models/proposal_models.dart @@ -43,6 +43,7 @@ class ProposalData with _$ProposalData { required Map optionalNamespaces, required String pairingTopic, Map? sessionProperties, + Map? scopedProperties, Map? generatedNamespaces, }) = _ProposalData; diff --git a/packages/reown_sign/lib/models/proposal_models.freezed.dart b/packages/reown_sign/lib/models/proposal_models.freezed.dart index c8ede003..e5d9447d 100644 --- a/packages/reown_sign/lib/models/proposal_models.freezed.dart +++ b/packages/reown_sign/lib/models/proposal_models.freezed.dart @@ -430,6 +430,8 @@ mixin _$ProposalData { String get pairingTopic => throw _privateConstructorUsedError; Map? get sessionProperties => throw _privateConstructorUsedError; + Map? get scopedProperties => + throw _privateConstructorUsedError; Map? get generatedNamespaces => throw _privateConstructorUsedError; @@ -458,6 +460,7 @@ abstract class $ProposalDataCopyWith<$Res> { Map optionalNamespaces, String pairingTopic, Map? sessionProperties, + Map? scopedProperties, Map? generatedNamespaces}); $ConnectionMetadataCopyWith<$Res> get proposer; @@ -486,6 +489,7 @@ class _$ProposalDataCopyWithImpl<$Res, $Val extends ProposalData> Object? optionalNamespaces = null, Object? pairingTopic = null, Object? sessionProperties = freezed, + Object? scopedProperties = freezed, Object? generatedNamespaces = freezed, }) { return _then(_value.copyWith( @@ -521,6 +525,10 @@ class _$ProposalDataCopyWithImpl<$Res, $Val extends ProposalData> ? _value.sessionProperties : sessionProperties // ignore: cast_nullable_to_non_nullable as Map?, + scopedProperties: freezed == scopedProperties + ? _value.scopedProperties + : scopedProperties // ignore: cast_nullable_to_non_nullable + as Map?, generatedNamespaces: freezed == generatedNamespaces ? _value.generatedNamespaces : generatedNamespaces // ignore: cast_nullable_to_non_nullable @@ -556,6 +564,7 @@ abstract class _$$ProposalDataImplCopyWith<$Res> Map optionalNamespaces, String pairingTopic, Map? sessionProperties, + Map? scopedProperties, Map? generatedNamespaces}); @override @@ -583,6 +592,7 @@ class __$$ProposalDataImplCopyWithImpl<$Res> Object? optionalNamespaces = null, Object? pairingTopic = null, Object? sessionProperties = freezed, + Object? scopedProperties = freezed, Object? generatedNamespaces = freezed, }) { return _then(_$ProposalDataImpl( @@ -618,6 +628,10 @@ class __$$ProposalDataImplCopyWithImpl<$Res> ? _value._sessionProperties : sessionProperties // ignore: cast_nullable_to_non_nullable as Map?, + scopedProperties: freezed == scopedProperties + ? _value._scopedProperties + : scopedProperties // ignore: cast_nullable_to_non_nullable + as Map?, generatedNamespaces: freezed == generatedNamespaces ? _value._generatedNamespaces : generatedNamespaces // ignore: cast_nullable_to_non_nullable @@ -639,11 +653,13 @@ class _$ProposalDataImpl implements _ProposalData { required final Map optionalNamespaces, required this.pairingTopic, final Map? sessionProperties, + final Map? scopedProperties, final Map? generatedNamespaces}) : _relays = relays, _requiredNamespaces = requiredNamespaces, _optionalNamespaces = optionalNamespaces, _sessionProperties = sessionProperties, + _scopedProperties = scopedProperties, _generatedNamespaces = generatedNamespaces; factory _$ProposalDataImpl.fromJson(Map json) => @@ -694,6 +710,16 @@ class _$ProposalDataImpl implements _ProposalData { return EqualUnmodifiableMapView(value); } + final Map? _scopedProperties; + @override + Map? get scopedProperties { + final value = _scopedProperties; + if (value == null) return null; + if (_scopedProperties is EqualUnmodifiableMapView) return _scopedProperties; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + final Map? _generatedNamespaces; @override Map? get generatedNamespaces { @@ -707,7 +733,7 @@ class _$ProposalDataImpl implements _ProposalData { @override String toString() { - return 'ProposalData(id: $id, expiry: $expiry, relays: $relays, proposer: $proposer, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, pairingTopic: $pairingTopic, sessionProperties: $sessionProperties, generatedNamespaces: $generatedNamespaces)'; + return 'ProposalData(id: $id, expiry: $expiry, relays: $relays, proposer: $proposer, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, pairingTopic: $pairingTopic, sessionProperties: $sessionProperties, scopedProperties: $scopedProperties, generatedNamespaces: $generatedNamespaces)'; } @override @@ -728,6 +754,8 @@ class _$ProposalDataImpl implements _ProposalData { other.pairingTopic == pairingTopic) && const DeepCollectionEquality() .equals(other._sessionProperties, _sessionProperties) && + const DeepCollectionEquality() + .equals(other._scopedProperties, _scopedProperties) && const DeepCollectionEquality() .equals(other._generatedNamespaces, _generatedNamespaces)); } @@ -744,6 +772,7 @@ class _$ProposalDataImpl implements _ProposalData { const DeepCollectionEquality().hash(_optionalNamespaces), pairingTopic, const DeepCollectionEquality().hash(_sessionProperties), + const DeepCollectionEquality().hash(_scopedProperties), const DeepCollectionEquality().hash(_generatedNamespaces)); /// Create a copy of ProposalData @@ -772,6 +801,7 @@ abstract class _ProposalData implements ProposalData { required final Map optionalNamespaces, required final String pairingTopic, final Map? sessionProperties, + final Map? scopedProperties, final Map? generatedNamespaces}) = _$ProposalDataImpl; factory _ProposalData.fromJson(Map json) = @@ -794,6 +824,8 @@ abstract class _ProposalData implements ProposalData { @override Map? get sessionProperties; @override + Map? get scopedProperties; + @override Map? get generatedNamespaces; /// Create a copy of ProposalData diff --git a/packages/reown_sign/lib/models/proposal_models.g.dart b/packages/reown_sign/lib/models/proposal_models.g.dart index 0132cca3..44ddf527 100644 --- a/packages/reown_sign/lib/models/proposal_models.g.dart +++ b/packages/reown_sign/lib/models/proposal_models.g.dart @@ -63,6 +63,7 @@ _$ProposalDataImpl _$$ProposalDataImplFromJson(Map json) => (json['sessionProperties'] as Map?)?.map( (k, e) => MapEntry(k, e as String), ), + scopedProperties: json['scopedProperties'] as Map?, generatedNamespaces: (json['generatedNamespaces'] as Map?)?.map( (k, e) => MapEntry(k, Namespace.fromJson(e as Map)), @@ -82,6 +83,8 @@ Map _$$ProposalDataImplToJson(_$ProposalDataImpl instance) => 'pairingTopic': instance.pairingTopic, if (instance.sessionProperties case final value?) 'sessionProperties': value, + if (instance.scopedProperties case final value?) + 'scopedProperties': value, if (instance.generatedNamespaces?.map((k, e) => MapEntry(k, e.toJson())) case final value?) 'generatedNamespaces': value, diff --git a/packages/reown_sign/lib/models/session_models.dart b/packages/reown_sign/lib/models/session_models.dart index 028872a9..6e6b04db 100644 --- a/packages/reown_sign/lib/models/session_models.dart +++ b/packages/reown_sign/lib/models/session_models.dart @@ -17,6 +17,7 @@ class SessionProposalCompleter { final Map requiredNamespaces; final Map optionalNamespaces; final Map? sessionProperties; + final Map? scopedProperties; final Completer completer; const SessionProposalCompleter({ @@ -27,11 +28,12 @@ class SessionProposalCompleter { required this.optionalNamespaces, required this.completer, this.sessionProperties, + this.scopedProperties, }); @override String toString() { - return 'SessionProposalCompleter(id: $id, selfPublicKey: $selfPublicKey, pairingTopic: $pairingTopic, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, sessionProperties: $sessionProperties, completer: $completer)'; + return 'SessionProposalCompleter(id: $id, selfPublicKey: $selfPublicKey, pairingTopic: $pairingTopic, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, sessionProperties: $sessionProperties, scopedProperties: $scopedProperties, completer: $completer)'; } } @@ -65,6 +67,7 @@ class SessionData with _$SessionData { Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, List? authentication, @Default(TransportType.relay) TransportType transportType, }) = _SessionData; diff --git a/packages/reown_sign/lib/models/session_models.freezed.dart b/packages/reown_sign/lib/models/session_models.freezed.dart index ef5f6117..6499a959 100644 --- a/packages/reown_sign/lib/models/session_models.freezed.dart +++ b/packages/reown_sign/lib/models/session_models.freezed.dart @@ -282,6 +282,8 @@ mixin _$SessionData { throw _privateConstructorUsedError; Map? get sessionProperties => throw _privateConstructorUsedError; + Map? get scopedProperties => + throw _privateConstructorUsedError; List? get authentication => throw _privateConstructorUsedError; TransportType get transportType => throw _privateConstructorUsedError; @@ -314,6 +316,7 @@ abstract class $SessionDataCopyWith<$Res> { Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, List? authentication, TransportType transportType}); @@ -348,6 +351,7 @@ class _$SessionDataCopyWithImpl<$Res, $Val extends SessionData> Object? requiredNamespaces = freezed, Object? optionalNamespaces = freezed, Object? sessionProperties = freezed, + Object? scopedProperties = freezed, Object? authentication = freezed, Object? transportType = null, }) { @@ -400,6 +404,10 @@ class _$SessionDataCopyWithImpl<$Res, $Val extends SessionData> ? _value.sessionProperties : sessionProperties // ignore: cast_nullable_to_non_nullable as Map?, + scopedProperties: freezed == scopedProperties + ? _value.scopedProperties + : scopedProperties // ignore: cast_nullable_to_non_nullable + as Map?, authentication: freezed == authentication ? _value.authentication : authentication // ignore: cast_nullable_to_non_nullable @@ -453,6 +461,7 @@ abstract class _$$SessionDataImplCopyWith<$Res> Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, List? authentication, TransportType transportType}); @@ -487,6 +496,7 @@ class __$$SessionDataImplCopyWithImpl<$Res> Object? requiredNamespaces = freezed, Object? optionalNamespaces = freezed, Object? sessionProperties = freezed, + Object? scopedProperties = freezed, Object? authentication = freezed, Object? transportType = null, }) { @@ -539,6 +549,10 @@ class __$$SessionDataImplCopyWithImpl<$Res> ? _value._sessionProperties : sessionProperties // ignore: cast_nullable_to_non_nullable as Map?, + scopedProperties: freezed == scopedProperties + ? _value._scopedProperties + : scopedProperties // ignore: cast_nullable_to_non_nullable + as Map?, authentication: freezed == authentication ? _value._authentication : authentication // ignore: cast_nullable_to_non_nullable @@ -568,12 +582,14 @@ class _$SessionDataImpl implements _SessionData { final Map? requiredNamespaces, final Map? optionalNamespaces, final Map? sessionProperties, + final Map? scopedProperties, final List? authentication, this.transportType = TransportType.relay}) : _namespaces = namespaces, _requiredNamespaces = requiredNamespaces, _optionalNamespaces = optionalNamespaces, _sessionProperties = sessionProperties, + _scopedProperties = scopedProperties, _authentication = authentication; factory _$SessionDataImpl.fromJson(Map json) => @@ -636,6 +652,16 @@ class _$SessionDataImpl implements _SessionData { return EqualUnmodifiableMapView(value); } + final Map? _scopedProperties; + @override + Map? get scopedProperties { + final value = _scopedProperties; + if (value == null) return null; + if (_scopedProperties is EqualUnmodifiableMapView) return _scopedProperties; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + final List? _authentication; @override List? get authentication { @@ -652,7 +678,7 @@ class _$SessionDataImpl implements _SessionData { @override String toString() { - return 'SessionData(topic: $topic, pairingTopic: $pairingTopic, relay: $relay, expiry: $expiry, acknowledged: $acknowledged, controller: $controller, namespaces: $namespaces, self: $self, peer: $peer, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, sessionProperties: $sessionProperties, authentication: $authentication, transportType: $transportType)'; + return 'SessionData(topic: $topic, pairingTopic: $pairingTopic, relay: $relay, expiry: $expiry, acknowledged: $acknowledged, controller: $controller, namespaces: $namespaces, self: $self, peer: $peer, requiredNamespaces: $requiredNamespaces, optionalNamespaces: $optionalNamespaces, sessionProperties: $sessionProperties, scopedProperties: $scopedProperties, authentication: $authentication, transportType: $transportType)'; } @override @@ -679,6 +705,8 @@ class _$SessionDataImpl implements _SessionData { .equals(other._optionalNamespaces, _optionalNamespaces) && const DeepCollectionEquality() .equals(other._sessionProperties, _sessionProperties) && + const DeepCollectionEquality() + .equals(other._scopedProperties, _scopedProperties) && const DeepCollectionEquality() .equals(other._authentication, _authentication) && (identical(other.transportType, transportType) || @@ -701,6 +729,7 @@ class _$SessionDataImpl implements _SessionData { const DeepCollectionEquality().hash(_requiredNamespaces), const DeepCollectionEquality().hash(_optionalNamespaces), const DeepCollectionEquality().hash(_sessionProperties), + const DeepCollectionEquality().hash(_scopedProperties), const DeepCollectionEquality().hash(_authentication), transportType); @@ -734,6 +763,7 @@ abstract class _SessionData implements SessionData { final Map? requiredNamespaces, final Map? optionalNamespaces, final Map? sessionProperties, + final Map? scopedProperties, final List? authentication, final TransportType transportType}) = _$SessionDataImpl; @@ -765,6 +795,8 @@ abstract class _SessionData implements SessionData { @override Map? get sessionProperties; @override + Map? get scopedProperties; + @override List? get authentication; @override TransportType get transportType; diff --git a/packages/reown_sign/lib/models/session_models.g.dart b/packages/reown_sign/lib/models/session_models.g.dart index 5adeced3..bf7a1c7c 100644 --- a/packages/reown_sign/lib/models/session_models.g.dart +++ b/packages/reown_sign/lib/models/session_models.g.dart @@ -53,6 +53,7 @@ _$SessionDataImpl _$$SessionDataImplFromJson(Map json) => (json['sessionProperties'] as Map?)?.map( (k, e) => MapEntry(k, e as String), ), + scopedProperties: json['scopedProperties'] as Map?, authentication: (json['authentication'] as List?) ?.map((e) => Cacao.fromJson(e as Map)) .toList(), @@ -80,6 +81,8 @@ Map _$$SessionDataImplToJson(_$SessionDataImpl instance) => 'optionalNamespaces': value, if (instance.sessionProperties case final value?) 'sessionProperties': value, + if (instance.scopedProperties case final value?) + 'scopedProperties': value, if (instance.authentication?.map((e) => e.toJson()).toList() case final value?) 'authentication': value, diff --git a/packages/reown_sign/lib/sign_client.dart b/packages/reown_sign/lib/sign_client.dart index 64139c51..544e6a27 100644 --- a/packages/reown_sign/lib/sign_client.dart +++ b/packages/reown_sign/lib/sign_client.dart @@ -146,6 +146,7 @@ class ReownSignClient implements IReownSignClient { Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, String? pairingTopic, List? relays, List>? methods = ReownSign.DEFAULT_METHODS, @@ -155,6 +156,7 @@ class ReownSignClient implements IReownSignClient { requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces, sessionProperties: sessionProperties, + scopedProperties: scopedProperties, pairingTopic: pairingTopic, relays: relays, methods: methods, @@ -181,6 +183,7 @@ class ReownSignClient implements IReownSignClient { required int id, required Map namespaces, Map? sessionProperties, + Map? scopedProperties, String? relayProtocol, }) async { try { @@ -188,6 +191,7 @@ class ReownSignClient implements IReownSignClient { id: id, namespaces: namespaces, sessionProperties: sessionProperties, + scopedProperties: scopedProperties, relayProtocol: relayProtocol, ); } catch (e) { diff --git a/packages/reown_sign/lib/sign_engine.dart b/packages/reown_sign/lib/sign_engine.dart index cf0680be..41785f71 100644 --- a/packages/reown_sign/lib/sign_engine.dart +++ b/packages/reown_sign/lib/sign_engine.dart @@ -136,6 +136,7 @@ class ReownSign implements IReownSign { Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, String? pairingTopic, List? relays, List>? methods = DEFAULT_METHODS, @@ -147,6 +148,7 @@ class ReownSign implements IReownSign { requiredNamespaces: requiredNamespaces ?? {}, optionalNamespaces: optionalNamespaces ?? {}, sessionProperties: sessionProperties, + scopedProperties: scopedProperties, pairingTopic: pairingTopic, relays: relays, ); @@ -177,6 +179,7 @@ class ReownSign implements IReownSign { metadata: metadata, ), sessionProperties: sessionProperties, + scopedProperties: scopedProperties, ); final expiry = ReownCoreUtils.calculateExpiry( @@ -190,6 +193,7 @@ class ReownSign implements IReownSign { requiredNamespaces: request.requiredNamespaces, optionalNamespaces: request.optionalNamespaces ?? {}, sessionProperties: request.sessionProperties, + scopedProperties: request.scopedProperties, pairingTopic: pTopic, ); await _setProposal( @@ -207,6 +211,7 @@ class ReownSign implements IReownSign { requiredNamespaces: request.requiredNamespaces, optionalNamespaces: request.optionalNamespaces ?? {}, sessionProperties: request.sessionProperties, + scopedProperties: request.scopedProperties, completer: completer, ), ); @@ -287,6 +292,7 @@ class ReownSign implements IReownSign { required int id, required Map namespaces, Map? sessionProperties, + Map? scopedProperties, String? relayProtocol, }) async { // print('sign approveSession'); @@ -297,6 +303,7 @@ class ReownSign implements IReownSign { id: id, namespaces: namespaces, sessionProperties: sessionProperties, + scopedProperties: scopedProperties, relayProtocol: relayProtocol, ); @@ -355,6 +362,7 @@ class ReownSign implements IReownSign { ), peer: proposal.proposer, sessionProperties: proposal.sessionProperties, + scopedProperties: proposal.scopedProperties, transportType: TransportType.relay, ); @@ -368,6 +376,7 @@ class ReownSign implements IReownSign { relay: relay, namespaces: namespaces, sessionProperties: sessionProperties, + scopedProperties: scopedProperties, expiry: expiry, controller: ConnectionMetadata( publicKey: selfPubKey, @@ -1018,6 +1027,7 @@ class ReownSign implements IReownSign { requiredNamespaces: proposeRequest.requiredNamespaces, optionalNamespaces: proposeRequest.optionalNamespaces, sessionProperties: proposeRequest.sessionProperties, + scopedProperties: proposeRequest.scopedProperties, pairingTopic: topic, relays: proposeRequest.relays, ); @@ -1078,6 +1088,7 @@ class ReownSign implements IReownSign { requiredNamespaces: proposeRequest.requiredNamespaces, optionalNamespaces: proposeRequest.optionalNamespaces ?? {}, sessionProperties: proposeRequest.sessionProperties, + scopedProperties: proposeRequest.scopedProperties, pairingTopic: topic, generatedNamespaces: namespaces, ); @@ -1136,6 +1147,7 @@ class ReownSign implements IReownSign { controller: request.controller.publicKey, namespaces: request.namespaces, sessionProperties: request.sessionProperties, + scopedProperties: request.scopedProperties, self: ConnectionMetadata( publicKey: sProposalCompleter.selfPublicKey, metadata: metadata, @@ -1667,6 +1679,7 @@ class ReownSign implements IReownSign { Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, String? pairingTopic, List? relays, }) async { @@ -1696,6 +1709,24 @@ class ReownSign implements IReownSign { ); } + // validate session properties only if they are defined + if (sessionProperties != null) { + SignApiValidatorUtils.isValidSessionProperties( + properties: sessionProperties, + ); + } + + // validate scoped properties only if they are defined + if (scopedProperties != null) { + SignApiValidatorUtils.isValidScopedProperties( + properties: scopedProperties, + namespaces: [ + ...?requiredNamespaces?.keys, + ...?optionalNamespaces?.keys, + ], + ); + } + return true; } @@ -1703,6 +1734,7 @@ class ReownSign implements IReownSign { required int id, required Map namespaces, Map? sessionProperties, + Map? scopedProperties, String? relayProtocol, }) async { // No need to validate sessionProperties. Strict typing enforces Strings are valid @@ -1732,6 +1764,21 @@ class ReownSign implements IReownSign { context: 'approve()', ); + // validate session properties only if they are defined + if (sessionProperties != null) { + SignApiValidatorUtils.isValidSessionProperties( + properties: sessionProperties, + ); + } + + // validate scoped properties only if they are defined + if (scopedProperties != null) { + SignApiValidatorUtils.isValidScopedProperties( + properties: scopedProperties, + namespaces: namespaces.keys.toList(), + ); + } + return true; } diff --git a/packages/reown_sign/lib/utils/sign_api_validator_utils.dart b/packages/reown_sign/lib/utils/sign_api_validator_utils.dart index 99354bf9..0a2f2fbd 100644 --- a/packages/reown_sign/lib/utils/sign_api_validator_utils.dart +++ b/packages/reown_sign/lib/utils/sign_api_validator_utils.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:reown_core/reown_core.dart'; import 'package:reown_sign/models/proposal_models.dart'; import 'package:reown_sign/models/session_models.dart'; @@ -274,6 +276,54 @@ class SignApiValidatorUtils { return true; } + static bool isValidSessionProperties({ + required Map properties, + String type = 'sessionProperties', + }) { + final keys = properties.keys.toList(); + final values = properties.values.toList(); + + for (var i = 0; i < values.length; i++) { + final property = values[i]; + if (property == null) { + throw Errors.getSdkError( + Errors.MALFORMED_REQUEST_PARAMS, + context: + '$type must contain an existing value for each key. Received: $property for key ${keys[i]}', + ).toSignError(); + } + } + + return true; + } + + static bool isValidScopedProperties({ + required Map properties, + required List namespaces, + }) { + isValidSessionProperties( + properties: properties, + type: 'scopedProperties', + ); + + final scopedNamespaces = properties.keys.toList(); + final valid = scopedNamespaces.every((ns) { + final baseNs = ns.split(':')[0]; + return namespaces.contains(baseNs); + }); + + if (!valid) { + throw Errors.getSdkError( + Errors.MALFORMED_REQUEST_PARAMS, + context: 'scopedProperties must be a subset of namespaces, ' + 'received: ${jsonEncode(properties)}, ' + 'namespaces: ${jsonEncode(namespaces)}', + ).toSignError(); + } + + return true; + } + static bool isSessionCompatible({ required SessionData session, required Map requiredNamespaces, diff --git a/packages/reown_sign/pubspec.yaml b/packages/reown_sign/pubspec.yaml index ce48b412..18c0302b 100644 --- a/packages/reown_sign/pubspec.yaml +++ b/packages/reown_sign/pubspec.yaml @@ -15,7 +15,8 @@ dependencies: freezed_annotation: ^2.4.4 http: ^1.2.2 pointycastle: ^3.9.1 - reown_core: ^1.1.6+1 + reown_core: + path: ../reown_core/ web3dart: ^2.7.3 dev_dependencies: diff --git a/packages/reown_sign/test/utils/sign_client_test_wrapper.dart b/packages/reown_sign/test/utils/sign_client_test_wrapper.dart index 5dff95bb..42aeabeb 100644 --- a/packages/reown_sign/test/utils/sign_client_test_wrapper.dart +++ b/packages/reown_sign/test/utils/sign_client_test_wrapper.dart @@ -94,6 +94,7 @@ class SignClientTestWrapper implements IReownSign { Map? requiredNamespaces, Map? optionalNamespaces, Map? sessionProperties, + Map? scopedProperties, String? pairingTopic, List? relays, List>? methods = ReownSign.DEFAULT_METHODS, @@ -129,6 +130,7 @@ class SignClientTestWrapper implements IReownSign { required int id, required Map namespaces, Map? sessionProperties, + Map? scopedProperties, String? relayProtocol, }) async { try { diff --git a/packages/reown_sign/test/validation_test.dart b/packages/reown_sign/test/validation_test.dart index 3ee5f875..1368b19b 100644 --- a/packages/reown_sign/test/validation_test.dart +++ b/packages/reown_sign/test/validation_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter_test/flutter_test.dart'; import 'package:reown_sign/reown_sign.dart'; import 'package:reown_sign/utils/sign_api_validator_utils.dart'; @@ -137,6 +139,70 @@ void main() { ); }); + test('isValidSessionProperties', () { + final String type = 'test'; + expect( + SignApiValidatorUtils.isValidSessionProperties( + properties: {'key': 'value'}, + type: type, + ), + true, + ); + expect( + () => SignApiValidatorUtils.isValidSessionProperties( + properties: {'key': null}, + type: type, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Malformed request parameters. $type must contain an existing value for each key. Received: null for key key', + ), + ), + ); + }); + + test('isValidScopedProperties', () { + expect( + SignApiValidatorUtils.isValidScopedProperties( + properties: {'eip155:1': {}}, + namespaces: [ + ...TEST_REQUIRED_NAMESPACES.keys, + ], + ), + true, + ); + // should reject connect with scopedProperties when not defined in requiredNamespaces or optionalNamespaces + expect( + () => SignApiValidatorUtils.isValidScopedProperties( + properties: {'eip155:1': {}}, + namespaces: [], + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Malformed request parameters. scopedProperties must be a subset of namespaces, received: {"eip155:1":{}}, namespaces: []', + ), + ), + ); + // should reject connect with scopedProperties when not defined in requiredNamespaces or optionalNamespaces + expect( + () => SignApiValidatorUtils.isValidScopedProperties( + properties: {'test:1': {}}, + namespaces: TEST_REQUIRED_NAMESPACES.keys.toList(), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Malformed request parameters. scopedProperties must be a subset of namespaces, received: {"test:1":{}}, namespaces: ${jsonEncode(TEST_REQUIRED_NAMESPACES.keys.toList())}', + ), + ), + ); + }); + test('isValidAccounts', () { expect( SignApiValidatorUtils.isValidAccounts( diff --git a/packages/reown_walletkit/lib/walletkit_impl.dart b/packages/reown_walletkit/lib/walletkit_impl.dart index 31586de2..f9d5b45a 100644 --- a/packages/reown_walletkit/lib/walletkit_impl.dart +++ b/packages/reown_walletkit/lib/walletkit_impl.dart @@ -190,6 +190,7 @@ class ReownWalletKit with WidgetsBindingObserver implements IReownWalletKit { required int id, required Map namespaces, Map? sessionProperties, + Map? scopedProperties, String? relayProtocol, }) async { try { diff --git a/packages/reown_walletkit/pubspec.yaml b/packages/reown_walletkit/pubspec.yaml index ffb75c38..1c98a67a 100644 --- a/packages/reown_walletkit/pubspec.yaml +++ b/packages/reown_walletkit/pubspec.yaml @@ -12,8 +12,10 @@ dependencies: event: ^3.1.0 flutter: sdk: flutter - reown_core: ^1.1.6+1 - reown_sign: ^1.1.7+1 + reown_core: + path: ../reown_core/ + reown_sign: + path: ../reown_sign/ reown_yttrium: ^0.0.1