signInWithExternalWallet method

  1. @override
Future<SdkUser> signInWithExternalWallet(
  1. String address
)
override

Signs in through SIWE with an already-connected wallet and creates a new Dynamic session.

Implementation

@override
Future<SdkUser> signInWithExternalWallet(String address) async {
  Future<SdkUser> instrumentationOperation() async {
    final operationScope = _state.flowScope.capture(replace: true);
    await operationScope.ready();
    operationScope.commit(() {
        _state.elevatedAccessTokens = <ElevatedAccessToken>[];
        _state.elevatedAccessTokensChanged.emit(_state.elevatedAccessTokens);
    });
    operationScope.commit(() {
        _state.mfaToken = null;
        _state.mfaTokenChanged.emit(_state.mfaToken);
    });
    if (!_state.initialized) {
      throw DynamicStateException("call initialize() before using the client");
    }
    final universalLink = _config.universalLink;
    if (universalLink == null) {
      throw DynamicStateException("universalLink must be configured to sign in with an external wallet");
    }
    final account = (_externalWalletState.externalWalletAccounts == null ? null : firstWhereOrNull<ExternalWalletAccount>(_externalWalletState.externalWalletAccounts!, (item) => item.address == address));
    if (account == null) {
      throw DynamicStateException("address is not a connected external wallet — call awaitExternalWalletConnection first");
    }

    final appName = _config.appName ?? "";
    operationScope.check();
    final nonceResponse = await _api.get<NoncesResponse>(path: "/nonces", token: null, sessionPublicKey: _state.sessionPublicKey, parse: NoncesResponse.fromJson, query: <String, String>{"count": "1"});
    operationScope.check();
    final nonce = firstOrNull(nonceResponse.nonces);
    if (nonce == null) {
      throw DynamicParseException("no wallet sign-in nonce returned");
    }
    final blockchainName = switch (account.chain) { "EVM" => "Ethereum", "SOL" => "Solana", "BTC" => "Bitcoin", "SUI" => "Sui", "COSMOS" => "Cosmos", "STARK" => "Starknet", _ => account.chain };
    final chainIdLine = switch (account.chain) { "EVM" => "\nChain ID: 1", _ => "" };
    final messageToSign = "${ApiClient.urlHost(universalLink)} wants you to sign in with your ${blockchainName} account:\n${address}\n\nWelcome to ${appName}. Signing is the only way we can truly know that you are the owner of the wallet you are connecting. Signing is a safe, gas-less transaction that does not in any way give ${appName} permission to perform any transactions with your wallet.\n\nURI: ${universalLink}\nVersion: 1${chainIdLine}\nNonce: ${nonce}\nIssued At: ${DateTime.now().toUtc().toIso8601String()}\nRequest ID: ${_config.environmentId}";

    _state.externalWalletActionRequested.emit(ExternalWalletActionRequest(action: "signMessage", walletName: account.displayName, address: address, chain: account.chain));
    operationScope.check();
    final signature = await _externalWalletProvider.signMessage(account.chain, address, messageToSign);
    operationScope.check();

    operationScope.check();
    final verify = await _api.post<VerifyResponse>(path: "/wallets/signin", body: <String, dynamic>{"signedMessage": signature, "messageToSign": messageToSign, "publicWalletAddress": address, "chain": account.chain, "walletName": account.verifyWalletName(), "walletProvider": "walletConnect"}, token: null, sessionPublicKey: _state.sessionPublicKey, parse: VerifyResponse.fromJson);
    operationScope.check();

    final token = (verify.minifiedJwt ?? verify.jwt);
    if (token == null) {
      throw DynamicParseException("verify response contained no token");
    }
    final expiresAtMs = (verify.expiresAt ?? 0) * DynamicConstants.millisecondsPerSecond;

    operationScope.commit(() {
        _state.token = token;
        _state.tokenChanged.emit(_state.token);
    });
    operationScope.commit(() {
        _state.sessionExpiresAtMs = expiresAtMs;
        _state.sessionExpiresAtMsChanged.emit(_state.sessionExpiresAtMs);
    });
    operationScope.commit(() {
        _state.user = verify.user;
        _state.userChanged.emit(_state.user);
    });
    operationScope.check();
    await operationScope.persist(() => _secureStorage.setItem("dynamic_${_config.environmentId}_session", jsonEncode(StoredSession(token: token, sessionExpiration: expiresAtMs).toJson())));
    operationScope.check();
    _logger.log("info", "signed in with external wallet");

    final signedInUser = verify.user;
    if (signedInUser == null) {
      throw DynamicParseException("signin response contained no user");
    }
    return signedInUser;
  }
  return await _api.instrumentation.runAsync("externalWallet.signInWithExternalWallet", instrumentationOperation);
}