authenticate method

  1. @override
Future<SessionAuthRequestResponse> authenticate({
  1. required SessionAuthRequestParams params,
  2. String? walletUniversalLink,
  3. String? pairingTopic,
  4. List<List<String>>? methods = const [[MethodConstants.WC_SESSION_AUTHENTICATE]],
})
override

Implementation

@override
Future<SessionAuthRequestResponse> authenticate({
  required SessionAuthRequestParams params,
  String? walletUniversalLink,
  String? pairingTopic,
  List<List<String>>? methods = const [
    [MethodConstants.WC_SESSION_AUTHENTICATE]
  ],
}) async {
  _checkInitialized();
  AuthApiValidators.isValidAuthenticate(params);

  final isLinkMode = _isLinkModeAuthenticate(walletUniversalLink);
  final transportType =
      isLinkMode ? TransportType.linkMode : TransportType.relay;
  if (!transportType.isLinkMode) {
    _confirmOnlineStateOrThrow();
  }

  final chains = params.chains;
  final resources = params.resources ?? [];
  final requestMethods = params.methods ?? [];

  String? pTopic = pairingTopic;
  Uri? connectionUri;

  if (pTopic == null) {
    final CreateResponse pairing = await core.pairing.create(
      methods: methods,
    );
    pTopic = pairing.topic;
    connectionUri = pairing.uri;
  } else {
    core.pairing.isValidPairingTopic(topic: pTopic);
  }

  final publicKey = await core.crypto.generateKeyPair();
  final responseTopic = core.crypto.getUtils().hashKey(publicKey);

  await Future.wait([
    authKeys.set(
      StringConstants.OCAUTH_CLIENT_PUBLIC_KEY_NAME,
      AuthPublicKey(publicKey: publicKey),
    ),
    pairingTopics.set(responseTopic, pTopic),
  ]);

  if (requestMethods.isNotEmpty) {
    final namespace = NamespaceUtils.getNamespaceFromChain(chains.first);
    String recap = ReCapsUtils.createEncodedRecap(
      namespace,
      'request',
      requestMethods,
    );
    final existingRecap = ReCapsUtils.getRecapFromResources(
      resources: resources,
    );
    if (existingRecap != null) {
      // per Recaps spec, recap must occupy the last position in the resources array
      // using .removeLast() to remove the element given we already checked it's a recap and will replace it
      recap = ReCapsUtils.mergeEncodedRecaps(recap, resources.removeLast());
    }
    resources.add(recap);
  }

  // Subscribe to the responseTopic because we expect the response to use this topic
  await core.relayClient.subscribe(topic: responseTopic);

  final id = JsonRpcUtils.payloadId();
  final fallbackId = JsonRpcUtils.payloadId();

  // Ensure the expiry is greater than the minimum required for the request - currently 1h
  final method = MethodConstants.WC_SESSION_AUTHENTICATE;
  final opts = MethodConstants.RPC_OPTS[method]!['req']!;
  final authRequestExpiry = max((params.expiry ?? 0), opts.ttl);
  final expiryTimestamp = DateTime.now().add(
    Duration(seconds: authRequestExpiry),
  );

  final request = WcSessionAuthRequestParams(
    authPayload: SessionAuthPayload.fromRequestParams(params).copyWith(
      resources: resources,
    ),
    requester: ConnectionMetadata(
      publicKey: publicKey,
      metadata: metadata,
    ),
    expiryTimestamp: expiryTimestamp.millisecondsSinceEpoch,
    transportType: transportType, // TODO LinkMode remove? Ask Nacho or Gancho
  );

  // Set the one time use receiver public key for decoding the Type 1 envelope
  await core.pairing.setReceiverPublicKey(
    topic: responseTopic,
    publicKey: publicKey,
    expiry: authRequestExpiry,
  );

  Completer<SessionAuthResponse> completer = Completer();

  // ----- build fallback session proposal request ----- //

  final fallbackMethod = MethodConstants.WC_SESSION_PROPOSE;
  final fallbackOpts = MethodConstants.RPC_OPTS[fallbackMethod]!['req']!;
  final fallbackExpiryTimestamp = DateTime.now().add(
    Duration(seconds: fallbackOpts.ttl),
  );
  final proposalData = ProposalData(
    id: fallbackId,
    requiredNamespaces: {},
    optionalNamespaces: {
      'eip155': RequiredNamespace(
        chains: chains,
        methods: {'personal_sign', ...requestMethods}.toList(),
        events: EventsConstants.requiredEvents,
      ),
    },
    relays: [Relay(WalletConnectConstants.RELAYER_DEFAULT_PROTOCOL)],
    expiry: fallbackExpiryTimestamp.millisecondsSinceEpoch,
    proposer: ConnectionMetadata(
      publicKey: publicKey,
      metadata: metadata,
    ),
    pairingTopic: pTopic,
  );
  final proposeRequest = WcSessionProposeRequest(
    relays: proposalData.relays,
    requiredNamespaces: proposalData.requiredNamespaces,
    optionalNamespaces: proposalData.optionalNamespaces,
    proposer: proposalData.proposer,
  );
  await _setProposal(proposalData.id, proposalData);

  Completer<SessionData> completerFallback = Completer();

  pendingProposals.add(
    SessionProposalCompleter(
      id: fallbackId,
      selfPublicKey: proposalData.proposer.publicKey,
      pairingTopic: proposalData.pairingTopic,
      requiredNamespaces: proposalData.requiredNamespaces,
      optionalNamespaces: proposalData.optionalNamespaces,
      completer: completerFallback,
    ),
  );

  // ------------------------------------------------------- //
  late final Uri linkModeUri;
  if (isLinkMode) {
    final payload = JsonRpcUtils.formatJsonRpcRequest(
      method,
      request.toJson(),
      id: id,
    );
    final message = await core.crypto.encode(
      pTopic,
      payload,
      options: EncodeOptions(type: EncodeOptions.TYPE_2),
    );
    linkModeUri = WalletConnectUtils.getLinkModeURL(
      walletUniversalLink!,
      pTopic,
      message!,
    ).toLinkMode;
  } else {
    // Send Session Proposal request (Only when on Relay mode)
    _connectResponseHandler(
      pTopic,
      proposeRequest,
      fallbackId,
    );
  }

  // Send One-Click Auth request (When on Relay and LinkMode)
  // TODO LinkMode: try to reduce the amount of params, maybe using a pendingSessionAuthRequest
  _sessionAuthResponseHandler(
    id: id,
    publicKey: publicKey,
    pairingTopic: pTopic,
    responseTopic: responseTopic,
    walletUniversalLink: walletUniversalLink,
    isLinkMode: isLinkMode,
    request: request,
    expiry: authRequestExpiry, // TODO remove, use request.expiryTimestamp
    completer: completer,
  );

  return SessionAuthRequestResponse(
    id: id,
    pairingTopic: pTopic,
    // linkModeUri is sent in the response so the host app can trigger it
    uri: isLinkMode ? linkModeUri : connectionUri,
    completer: completer,
  );
}