diff --git a/docs.json b/docs.json index cf8cfc41..decf01b3 100644 --- a/docs.json +++ b/docs.json @@ -126,7 +126,7 @@ "pages": [ "sdks/react/landing", "sdks/react-native", - "sdks/flutter", + "sdks/flutter/landing", "sdks/swift", "sdks/typescript-frontend/landing", "sdks/javascript-server" @@ -408,7 +408,26 @@ "sdks/react-native/advanced-api-requests" ] }, - "sdks/flutter", + { + "group": "Flutter", + "pages": [ + "sdks/flutter/index", + "sdks/flutter/getting-started", + { + "group": "Authentication", + "pages": [ + "sdks/flutter/authentication/overview", + "sdks/flutter/authentication/email-sms", + "sdks/flutter/authentication/passkey", + "sdks/flutter/authentication/social-logins" + ] + }, + "sdks/flutter/sub-organization-customization", + "sdks/flutter/using-embedded-wallets", + "sdks/flutter/signing", + "sdks/flutter/advanced-api-requests" + ] + }, { "group": "Swift", "pages": [ diff --git a/sdks/flutter.mdx b/sdks/flutter.mdx deleted file mode 100644 index c52a6fb5..00000000 --- a/sdks/flutter.mdx +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: "Flutter" -description: "This documentation contains guides for using the Flutter SDK." ---- - -We have created a set of Dart packages that can be used to interact with the Turnkey API. These can be combined to create a fully-featured Flutter app, powered by Turnkey. - -## Packages - -| Package Name | Description | Link | -| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -| turnkey_sdk_flutter | A Flutter package that simplifies the integration of the Turnkey API into Flutter applications. It provides secure session management, authentication, and cryptographic operations | [turnkey_sdk_flutter](https://pub.dev/packages/turnkey_sdk_flutter) | -| turnkey_http | A lower-level, fully typed HTTP client for interacting with the Turnkey API. | [turnkey_http](https://pub.dev/packages/turnkey_http) | -| turnkey_api_key_stamper | A Dart package for API stamping functionalities. It is meant to be used with Turnkey's HTTP package. | [turnkey_api_key_stamper](https://pub.dev/packages/turnkey_api_key_stamper) | -| turnkey_flutter_passkey_stamper | A Flutter package for stamping payloads with passkeys. It is meant to be used with Turnkey's HTTP package. | [turnkey_flutter_passkey_stamper](https://pub.dev/packages/turnkey_flutter_passkey_stamper) | -| turnkey_crypto | This package consolidates common cryptographic utilities used across our applications, particularly primitives related to keys, encryption, and decryption in a pure Dart implementation. | [turnkey_crypto](https://pub.dev/packages/turnkey_crypto) | -| turnkey_encoding | This package contains decoding and encoding functions used by other Turnkey packages. | [turnkey_encoding](https://pub.dev/packages/turnkey_encoding) | - -You can visit [Turnkey's pub.dev publisher page](https://pub.dev/publishers/turnkey.com/packages) to see all all the packages we have published and install them in your Flutter project. - -## Getting Started - -The easiest way to build a Flutter app with Turnkey is to use our [Flutter demo app](https://github.com/tkhq/dart-sdk/tree/main/examples/flutter-demo-app) as a starter. This app is a fully-featured example that demonstrates how to use the Turnkey's Flutter SDK to authenticate users, create wallets, export wallets, sign messages, and more. - -The app includes a backend [JavaScript server](https://github.com/tkhq/dart-sdk/tree/main/examples/flutter-demo-app/api-server) which uses [@turnkey/sdk-server](https://www.npmjs.com/package/@turnkey/sdk-server) to make requests to Turnkey that must be signed by the parent organization. An example of a request that must be signed by the parent organization is creating a [sub-organization](/concepts/sub-organizations). [(code reference)](https://github.com/tkhq/dart-sdk/blob/51405520d721910961fa942ddc5f5a3030783b9b/examples/flutter-demo-app/api-server/src/controllers/turnkey-api.ts#L139-L178) - -Some requests made to Turnkey must be signed by the sub-organization. These are written in Dart and are ran by the app directly. You can find these requests in the app's [TurnkeyProvider](https://github.com/tkhq/dart-sdk/blob/main/examples/flutter-demo-app/lib/providers/turnkey.dart). An example of a request that must be signed by the sub-organization is creating a wallet. [(code reference)](https://github.com/tkhq/dart-sdk/blob/51405520d721910961fa942ddc5f5a3030783b9b/examples/flutter-demo-app/lib/providers/turnkey.dart#L531-L559) - -## Video - - - - - -#### Complete the installation and setup by following the instructions in the [README](https://github.com/tkhq/dart-sdk/blob/main/examples/flutter-demo-app/README.md) file. diff --git a/sdks/flutter/advanced-api-requests.mdx b/sdks/flutter/advanced-api-requests.mdx new file mode 100644 index 00000000..cef999b2 --- /dev/null +++ b/sdks/flutter/advanced-api-requests.mdx @@ -0,0 +1,112 @@ +--- + +title: "Advanced API requests" +description: "Learn how to make advanced API requests to Turnkey's infrastructure." +sidebarTitle: "Advanced API requests" +--- + +## Overview + +Turnkey's Flutter SDK provides a wide variety of helper functions to abstract interactions with Turnkey's infrastructure. +However, you can also make advanced API requests directly to Turnkey's endpoints if you need more control or want to implement custom features in your Flutter app. + +## The underlying client + +To make advanced API requests, use the `client` retrieved from the `TurnkeyProvider`. +This client is tied to the active session, so stamping, authentication, and organization context are automatically handled for you. + +You can see the [API Reference](/api-reference/overview) for a complete list of available API endpoints and their parameters. All of these can be accessed through the `client`. + +Here's how you can use the `client` to make a `signRawPayload` request to Turnkey: + +```dart title=lib/widgets/sign_raw_payload_button.dart +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class SignRawPayloadButton extends StatelessWidget { + const SignRawPayloadButton({super.key}); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + + Future doSignRawPayload() async { + try { + final wallet = tk.wallets?.first; + final account = wallet?.accounts.first; + if (account == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No account available to sign with')), + ); + return; + } + + final message = 'Hello, Turnkey!'; + final payload = utf8 + .encode(message) + .fold('', (s, byte) => s + byte.toRadixString(16).padLeft(2, '0')); + + final response = await tk.client.signRawPayload( + SignRawPayloadParams( + signWith: account.address, + payload: payload, + encoding: v1PayloadEncoding.payload_encoding_hexadecimal, + hashFunction: v1HashFunction.hash_function_not_applicable, + ), + ); + + debugPrint('Message signed: ${response.signature}'); + } catch (error) { + debugPrint('Error signing message: $error'); + } + } + + return ElevatedButton( + onPressed: doSignRawPayload, + child: const Text('Sign Message'), + ); + } +} +``` + +## Viewing the activity + +When creating, modifying, or using resources within Turnkey, an activity is created. You can learn more about activities in the [Activities](/api-reference/activities/overview) section. + +If you use the `client`, you can view all the metadata of the activity you are performing. +This includes the activity ID, status, and more. + +```dart +final response = await tk.client.updateWallet( + UpdateWalletParams( + walletId: 'a-wallet-id-123', + walletName: 'New wallet name', + ), +); + +final activity = response.activity; +if (activity != null) { + debugPrint('Activity ID: ${activity.id}'); + debugPrint('Status: ${activity.status}'); +} +``` + + +## Stamping with a passkey + +You can create a client that uses a passkey for stamping instead of using securely stored api keys. Use the `createPasskeyClient` method from the `TurnkeyProvider` to create a new client instance configured for passkey stamping. + +An `rpId` is required to use passkey stamping; you can either pass it directly when creating the client or set it in the `TurnkeyProvider` config. + +```dart +final passkeyClient = await tk.createPasskeyClient( + passkeyStamperConfig: PasskeyStamperConfig( // You can pass this into the TurnkeyProvider config instead + rpId: 'example.com', + ), +); +``` + +To learn more about using passkeys in your Flutter app, check out the [Passkey Authentication](/sdks/flutter/authentication/passkey) guide. + diff --git a/sdks/flutter/authentication/email-sms.mdx b/sdks/flutter/authentication/email-sms.mdx new file mode 100644 index 00000000..47779fd0 --- /dev/null +++ b/sdks/flutter/authentication/email-sms.mdx @@ -0,0 +1,172 @@ +--- + +title: "Email and SMS OTP authentication" +description: "Set up and implement email or SMS OTP authentication using Turnkey's Flutter SDK." +sidebarTitle: "Email & SMS" +--- + +## Overview + +This guide shows how to implement OTP authentication using `turnkey_sdk_flutter`. You'll trigger an OTP to a user's email address (or phone number), navigate to a verification screen, and verify the 6-digit code. + +Before you begin: + +* Complete the provider setup from **Getting Started** and enable **Auth Proxy** with **Email OTP** and/or **SMS OTP** in the Turnkey Dashboard. +* Ensure your `TurnkeyConfig` is pointing at the correct `authProxyConfigId` (OTP settings are controlled in the dashboard). + +## Request an OTP (email) + +Create or update your login screen to request an email OTP using `initOtp`. The snippet below navigates to an OTP screen with the returned `otpId` and the email address. + +```dart title=lib/screens/login.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; +import 'otp.dart'; + +class LoginScreen extends StatefulWidget { + const LoginScreen({super.key}); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + final _emailController = TextEditingController(); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + + return Scaffold( + appBar: AppBar(title: const Text('Login')), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + TextField( + controller: _emailController, + keyboardType: TextInputType.emailAddress, + decoration: const InputDecoration(hintText: 'you@example.com'), + ), + const SizedBox(height: 12), + ElevatedButton( + onPressed: () async { + final email = _emailController.text.trim(); + if (email.isEmpty || !email.contains('@')) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Enter a valid email')), + ); + return; + } + + // Request OTP from Turnkey + final otpId = await tk.initOtp( + otpType: OtpType.Email, + contact: email, + ); + + if (!context.mounted) return; + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => OTPScreen( + otpId: otpId, + contact: email, + otpType: OtpType.Email, + ), + ), + ); + }, + child: const Text('Continue with email'), + ), + ], + ), + ), + ); + } +} +``` + +## Verify the OTP code + +On the OTP screen, pass `contact` and `otpId` via the constructor and call `loginOrSignUpWithOtp` with the 6-digit code. + +```dart title=lib/screens/otp.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class OTPScreen extends StatefulWidget { + final String otpId; + final String contact; // email or phone number + final OtpType otpType; // OtpType.Email or OtpType.Sms + + const OTPScreen({ + super.key, + required this.otpId, + required this.contact, + required this.otpType, + }); + + @override + State createState() => _OTPScreenState(); +} + +class _OTPScreenState extends State { + final _otpController = TextEditingController(); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + + return Scaffold( + appBar: AppBar(title: const Text('Verify code')), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + TextField( + controller: _otpController, + keyboardType: TextInputType.number, + maxLength: 6, // default OTP length is 6 + decoration: const InputDecoration(hintText: 'Enter 6-digit code', counterText: ''), + ), + const SizedBox(height: 12), + ElevatedButton( + onPressed: () async { + final code = _otpController.text.trim(); + if (code.length != 6) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Enter the 6-digit code')), + ); + return; + } + + // Verify OTP and log in/sign up + await tk.loginOrSignUpWithOtp( + otpId: widget.otpId, + otpCode: code, + contact: widget.contact, + otpType: widget.otpType, + ); + + // Success will trigger onSessionSelected in your TurnkeyConfig + if (!mounted) return; + Navigator.pop(context); + }, + child: const Text('Verify'), + ), + ], + ), + ), + ); + } +} +``` + +### Notes + +* The default OTP length is **6**; if you customized OTP settings in the dashboard, validate accordingly. +* To resend a code, call `initOtp` again with the same contact. +* For SMS, replace `OtpType.Email` with `OtpType.Sms` and pass a phone number in `contact`. diff --git a/sdks/flutter/authentication/overview.mdx b/sdks/flutter/authentication/overview.mdx new file mode 100644 index 00000000..9ec275e2 --- /dev/null +++ b/sdks/flutter/authentication/overview.mdx @@ -0,0 +1,156 @@ +--- +title: "Overview" +description: "Learn how to set up, log in, or sign up easily in your Flutter app using Turnkey's Flutter SDK." +sidebarTitle: "Overview" +--- + +Turnkey's Flutter SDK makes authentication simple. You can call specific login and signup functions (email/SMS OTP, passkeys, or social logins) to build your own UI and auth flow. + +## Authentication state + +In Flutter, you access auth state via the `TurnkeyProvider` (a `ChangeNotifier`). The presence of a `session` indicates the user is authenticated. + +```dart title=lib/widgets/auth_status.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class AuthStatus extends StatelessWidget { + const AuthStatus({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, tk, _) { + final session = tk.session; // null when not authenticated + final user = tk.user; // may be null until fetched + + if (session != null) { + return Text('Welcome back, ${user?.userName ?? 'user'}!'); + } + + return ElevatedButton( + onPressed: () { + // Navigate to your login screen or trigger your chosen auth flow + // e.g., email OTP, passkey, or social login + Navigator.of(context).pushNamed('/login'); + }, + child: const Text('Log in'), + ); + }, + ); + } +} +``` + +You can also set up an `onSessionSelected` callback in `TurnkeyConfig` to handle post-authentication logic, such as redirecting. + +```dart title=snippet (in main.dart where you construct TurnkeyProvider) +final turnkeyProvider = TurnkeyProvider( + config: TurnkeyConfig( + // ... your existing config ... + onSessionSelected: (session) { + // User authenticated — perform navigation or data preloads + navigatorKey.currentState?.pushReplacementNamed('/dashboard'); + }, + ), +); +``` + +## Listening to provider updates + +`TurnkeyProvider` is a `ChangeNotifier`, so you can attach listeners to respond to internal state changes (e.g., when wallets/users are fetched). This pattern is useful when you want to mirror provider data into local widget state. + +Below is an example (adapted from the [demo app](https://github.com/tkhq/dart-sdk/tree/main/examples/flutter-demo-app)) that keeps a local `selectedWallet`/`selectedAccount` in sync with the provider: + +```dart title=lib/screens/dashboard.dart (excerpt) +class DashboardScreenState extends State { + Wallet? _selectedWallet; + v1WalletAccount? _selectedAccount; + + late final TurnkeyProvider _turnkeyProvider; + late final VoidCallback _providerListener; + + @override + void initState() { + super.initState(); + + // Capture provider once; no context in listener later. + _turnkeyProvider = Provider.of(context, listen: false); + + _providerListener = _handleProviderUpdate; + _turnkeyProvider.addListener(_providerListener); + + // Initialize local selections from provider. + _updateSelectedWalletFromProvider(_turnkeyProvider); + } + + @override + void dispose() { + // Always remove listeners to avoid calling setState on a disposed widget. + _turnkeyProvider.removeListener(_providerListener); + super.dispose(); + } + + void _handleProviderUpdate() { + if (!context.mounted) return; + _updateSelectedWalletFromProvider(_turnkeyProvider); + } + + void _updateSelectedWalletFromProvider(TurnkeyProvider provider) { + final wallets = provider.wallets; + final user = provider.user; + + if (user == null || wallets == null || wallets.isEmpty) return; + if (wallets.first.accounts.isEmpty) return; + + if (!context.mounted) return; + setState(() { + _selectedWallet = wallets.first; + _selectedAccount = wallets.first.accounts.first; + }); + } +} +``` + +> **Tips** +> +> * Use `addListener/removeListener` for one-off reactions to state changes. +> * Use `Consumer`, `Selector`, or `context.select` when you want automatic rebuilds based on specific slices of provider state. +> * Avoid calling `setState` inside your listener after the widget is disposed — always guard with `if (!mounted) return;` or `if (!context.mounted) return;`. + +## Customize sub-organization creation + +Need to configure default user names, passkey names, wallet creations or anything sub-org related? You can learn more about customizing the sub-orgs you create in the [Sub-Organization Customization](/sdks/flutter/sub-organization-customization). + +Follow the guides below to learn how to set up email and SMS authentication, passkey authentication, and social logins in your Flutter app. + + + + Learn how to set up email and SMS authentication in your Flutter app. + + + Learn how to set up passkey authentication in your Flutter app. + + + Discover how to add social logins (Google, Apple, X, Discord) and handle wallet creation and account derivation. + + \ No newline at end of file diff --git a/sdks/flutter/authentication/passkey.mdx b/sdks/flutter/authentication/passkey.mdx new file mode 100644 index 00000000..47f8e6cf --- /dev/null +++ b/sdks/flutter/authentication/passkey.mdx @@ -0,0 +1,154 @@ +--- + +title: "Passkey authentication" +description: "Set up and implement passkey authentication using Turnkey's Flutter SDK." +sidebarTitle: "Passkeys" +--- + +## Overview + +This guide shows how to implement passkey authentication in a Flutter app using `turnkey_sdk_flutter`. +You'll add the necessary platform configuration, set up the provider with a proper `rpId`, and call `signUpWithPasskey` and `loginWithPasskey` from your UI. + +## Passkey setup (platform) + +Passkeys require an **Relying Party ID (rpId)** that matches a domain you control and a verified association between your app and that domain. + +### iOS — Associated Domains + +1. **Enable the entitlement** in Xcode for your iOS target: + + * Select your app target → **Signing & Capabilities** → **+ Capability** → **Associated Domains**. + * Add an entry: `webcredentials:yourdomain.com` (replace with your domain). + +2. **Entitlements file** (if editing manually): `ios/Runner/Runner.entitlements` + +```xml title=ios/Runner/Runner.entitlements + + + + + com.apple.developer.associated-domains + + webcredentials:yourdomain.com + + + +``` + +> Ensure your provisioning profile includes **Associated Domains**. The domain must serve a valid **Apple App Site Association (AASA)** file. + +3. **AASA file** hosted by your domain at `https://yourdomain.com/.well-known/apple-app-site-association`. + The file should include your app's team/app identifiers. Refer to Apple's documentation for the exact structure. + +### Android — Digital Asset Links + +1. Host a **Digital Asset Links** statement at: + `https://yourdomain.com/.well-known/assetlinks.json` + +2. Include your app's package name and signing certificate SHA‑256 fingerprint. + +```json title=public/.well-known/assetlinks.json +[ + { + "relation": [ + "delegate_permission/common.get_login_creds" + ], + "target": { + "namespace": "android_app", + "package_name": "com.example.myapp", + "sha256_cert_fingerprints": [ + "AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90" + ] + } + } +] +``` + +> Make sure the file is served with `Content-Type: application/json` and no redirects. + +## Provider configuration + +Set `passkeyConfig.rpId` to the domain you associated above (e.g., `yourdomain.com`). This must match the iOS/Android domain setup. + +```dart title=lib/main.dart (excerpt) +final turnkeyProvider = TurnkeyProvider( + config: TurnkeyConfig( + // ... your existing config ... + passkeyConfig: PasskeyConfig( + rpId: 'yourdomain.com', // Note: You can also manually pass the rpId into the login/signUp methods + ), + ), +); +``` + +## Usage + +Below are minimal examples to sign up and log in with passkeys. + +### Sign up with passkey + +```dart title=lib/screens/login.dart (excerpt) +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class LoginScreen extends StatelessWidget { + const LoginScreen({super.key}); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ElevatedButton( + onPressed: () async { + await tk.signUpWithPasskey(); // rpId can be passed here optionally instead of in the TurnkeyProvider config + // onSessionSelected will handle navigation on success + }, + child: const Text('Sign up with passkey'), + ), + ], + ); + } +} +``` + +### Log in with passkey + +```dart title=lib/screens/login.dart (excerpt) +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class LoginScreen extends StatelessWidget { + const LoginScreen({super.key}); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ElevatedButton( + onPressed: () async { + await tk.loginWithPasskey(); // rpId can be passed here optionally instead of in the TurnkeyProvider config + // onSessionSelected will handle navigation on success + }, + child: const Text('Log in with passkey'), + ), + ], + ); + } +} +``` + +## Tips + +* `rpId` **must** match the domain configured in your platform setup, otherwise passkey operations will fail. +* iOS: confirm your build includes **Associated Domains** and the AASA file is reachable. +* Android: confirm `assetlinks.json` is valid and your app's package name + signing certificate fingerprint are correct. +* Consider setting a dynamic display name for passkeys on sign-up so users can identify authenticators later. diff --git a/sdks/flutter/authentication/social-logins.mdx b/sdks/flutter/authentication/social-logins.mdx new file mode 100644 index 00000000..77fa6ebd --- /dev/null +++ b/sdks/flutter/authentication/social-logins.mdx @@ -0,0 +1,242 @@ +--- + +title: "Social logins" +description: "Configure and implement social logins in your Flutter app using `turnkey_sdk_flutter`." +sidebarTitle: "Social logins" +--- + +## Configuring OAuth + +Using OAuth requires configuration in the Turnkey Dashboard and in your app. + +### Enabling OAuth + +Navigate to the **Wallet Kit** section in the [Turnkey Dashboard](https://app.turnkey.com/dashboard/walletKit) and enable **OAuth**. If you have not enabled the Auth Proxy, enable it first. See [Getting Started](/sdks/flutter/getting-started) for details. + +OAuth providers configuration + +### Configuring OAuth providers + +Enable the providers you want to use under **Social logins**. + +OAuth client IDs configuration + +#### Client IDs + +You can enter client IDs for each provider and the redirect URL directly in the dashboard: + +OAuth client IDs configuration + +OAuth redirect URL configuration + +Or provide client IDs and the redirect URI through your app configuration and pass them to the `TurnkeyProvider`. + + + For OAuth 2.0 providers, you will need to upload the client ID and secret in the dashboard. See the **OAuth 2.0 providers** section below for details. + + +#### Client configuration + +If you prefer configuring via code, provide your client IDs and optional redirect URI through `TurnkeyConfig.authConfig.oAuthConfig`, and set an `appScheme` to complete deep links. + +```dart title=lib/main.dart (excerpt) +final turnkeyProvider = TurnkeyProvider( + config: TurnkeyConfig( + // ... your existing config ... + appScheme: 'myapp', // Required for OAuth deep link completion + authConfig: AuthConfig( + oAuthConfig: OAuthConfig( + // Note: If no redirect URI is provided, the default redirect URI will be used: https://oauth-redirect.turnkey.com. This url must be configured to redirect back to your app! + oauthRedirectUri: '', // Optional: e.g., "https://your-app.com/oauth-callback" + + // Client IDs from your provider dashboards + googleClientId: '', + appleClientId: '', + facebookClientId: '', + xClientId: '', + discordClientId: '', + ), + ), + ), +); +``` + +### App scheme configuration (deep links) + +Register your app scheme on each platform to complete the OAuth flow. + +#### iOS (Info.plist) + +```xml title=ios/Runner/Info.plist (excerpt) +CFBundleURLTypes + + + CFBundleURLName + myapp + CFBundleURLSchemes + + myapp + + + +``` + +#### Android (AndroidManifest.xml) + +```xml title=android/app/src/main/AndroidManifest.xml (excerpt) + + + + + + +``` + +Replace `myapp` with your actual scheme and ensure it matches `appScheme` in `TurnkeyConfig`. + +## Usage + +Call the helper for each provider from your `TurnkeyProvider` instance: `handleGoogleOauth`, `handleAppleOauth`, `handleFacebookOauth`, `handleDiscordOauth`, and `handleXOauth`. + +```dart title=lib/screens/social_logins.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class SocialLoginButtons extends StatelessWidget { + const SocialLoginButtons({super.key}); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + + Future run(Future Function() fn) async { + try { + await fn(); + // onSessionSelected in TurnkeyConfig can handle navigation on success + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $e')), + ); + } + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ElevatedButton( + onPressed: () => run(tk.handleGoogleOauth), + child: const Text('Continue with Google'), + ), + ElevatedButton( + onPressed: () => run(tk.handleAppleOauth), + child: const Text('Continue with Apple'), + ), + ElevatedButton( + onPressed: () => run(tk.handleFacebookOauth), + child: const Text('Continue with Facebook'), + ), + ElevatedButton( + onPressed: () => run(tk.handleDiscordOauth), + child: const Text('Continue with Discord'), + ), + ElevatedButton( + onPressed: () => run(tk.handleXOauth), + child: const Text('Continue with X'), + ), + ], + ); + } +} +``` + +## Provider details + +### OAuth providers + +#### Google + +Requirements: + +* Dashboard: enable Google in Wallet Kit → Authentication. +* Client ID: Web client ID from the Google developer console, set in the Dashboard or via `OAuthConfig`. + +Usage: + +```dart +await context.read().handleGoogleOauth(); +``` + +#### Apple + +Requirements: + +* Dashboard: enable Apple. +* Client ID: Apple Services ID, set in the Dashboard or via `OAuthConfig`. +* Redirect URI must match your configured value (e.g., `myapp://`). + +Usage: + +```dart +await context.read().handleAppleOauth(); +``` + +#### Facebook + +Requirements: + +* Dashboard: enable Facebook. +* Client ID: set in the Dashboard or via `OAuthConfig`. +* Redirect URI must match your configured value (e.g., `myapp://`). + +Usage: + +```dart +await context.read().handleFacebookOauth(); +``` + +### OAuth 2.0 providers + +For providers that use OAuth 2.0 (e.g., X, Discord), configure additional settings in the Turnkey Dashboard. + +In **Wallet Kit → Socials**, click **Add provider**. + +OAuth2.0 providers configuration + +Select the provider and fill in the required fields from the provider console. Secrets are encrypted on upload. + +Adding an OAuth2.0 provider + +Then go to **Authentication** and enable the provider under **SDK Configuration**. + +Enabling an OAuth2.0 provider + +Click **Select** to choose your newly added client ID, then **Save Settings**. Alternatively, enter the client ID through `OAuthConfig`. + +#### Discord + +Requirements: + +* Dashboard: enable Discord (OAuth 2.0). +* Client ID: set in Dashboard or via `OAuthConfig`. +* Redirect URI must match your configured value (e.g., `myapp://`). + +Usage: + +```dart +await context.read().handleDiscordOauth(); +``` + +#### X (Twitter) + +Requirements: + +* Dashboard: enable X (OAuth 2.0). +* Client ID: set in Dashboard or via `OAuthConfig`. +* Redirect URI must match your configured value (e.g., `myapp://`). + +Usage: + +```dart +await context.read().handleXOauth(); +``` diff --git a/sdks/flutter/getting-started.mdx b/sdks/flutter/getting-started.mdx new file mode 100644 index 00000000..59f22fd8 --- /dev/null +++ b/sdks/flutter/getting-started.mdx @@ -0,0 +1,179 @@ +--- +title: "Getting started with Turnkey's Flutter SDK" +description: "Learn how to setup Turnkey's Flutter SDK into your application. This guide walks you through enabling Turnkey's Auth Proxy, installing the SDK, and configuring the provider." +sidebarTitle: "Getting started" +--- + +## Turnkey organization setup + +To start, you must create a Turnkey organization via the [Turnkey dashboard](https://app.turnkey.com). The steps to do so are described in the [Account Setup](/getting-started/quickstart) section. + +For this setup, we will be using Turnkey's Auth Proxy to handle authentication. We can enable and configure this through the Turnkey dashboard. + + + + Navigate to the **Wallet Kit** section in the Turnkey Dashboard and enable the + **Auth Proxy**. + + + Auth Proxy toggle + + + + + + + +You can choose which auth methods to enable and customize various options from this screen. For this quickstart, let's enable **email OTP** and **passkeys**. When you're done, click **Save**. + +Auth Proxy options + +Wallet kit options + + + + + + + +Once you're finished with the auth proxy setup, you can copy the **auth proxy config ID** + +Auth Proxy Config id + +and your **organization ID** from the dashboard. + +Organization id + +These will be used in the next steps to configure your app. + + + + + +## Installation + +You can use `turnkey_sdk_flutter` in any Flutter application. + +If you're starting fresh, create a new Flutter app: + +```bash +flutter create my_turnkey_app +cd my_turnkey_app +``` + +Add the Turnkey Flutter SDK and supporting packages: + + + +```bash dart +flutter pub add turnkey_sdk_flutter provider flutter_inappwebview +``` + + + + +## Provider + +Wrap your app with `ChangeNotifierProvider` and configure `TurnkeyProvider`. + + +```dart lib/main.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + + +final GlobalKey navigatorKey = GlobalKey(); + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Optional: define some callbacks + void onSessionSelected(Session session) { + // Do something when the user logs in, e.g. navigate to home screen + } + + void onSessionCleared(Session session) { + // Do something when the user logs out, e.g. navigate to login screen + } + + void onInitialized(Object? error) { + // Do something when the SDK is initialized + } + + final turnkeyProvider = TurnkeyProvider( + config: TurnkeyConfig( + authProxyConfigId: "", + organizationId: "", + + // Optional: attach some callbacks + onSessionSelected: onSessionSelected, + onSessionCleared: onSessionCleared, + onInitialized: onInitialized, + ), + ); + + runApp( + ChangeNotifierProvider( + create: (_) => turnkeyProvider, + child: const MyApp(), + ), + ); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + navigatorKey: navigatorKey, + title: 'My Turnkey App', + theme: ThemeData(useMaterial3: true), + home: const MyApp(), // Replace with your app's screen + ); + } +} +``` + +## Optional: Ready state & error surfacing + +You can await `turnkeyProvider.ready` during startup to surface initialization errors to the user: + +```dart +turnkeyProvider.ready.then((_) { + debugPrint('Turnkey is ready'); +}).catchError((error) { + debugPrint('Error during Turnkey initialization: $error'); + // Show a snackbar/toast after the current frame +}); +``` + + +## Demo app + +You can check out a complete demo app that uses Turnkey's Flutter SDK on [GitHub](https://github.com/tkhq/dart-sdk/tree/main/examples/flutter-demo-app). Feel free to clone and modify it to get started quickly! + + + + +## Next steps + +Ready to start building your app? Check out the **Authentication** guide to learn how to set up login/signup in your Flutter application. diff --git a/sdks/flutter/index.mdx b/sdks/flutter/index.mdx new file mode 100644 index 00000000..dbda184b --- /dev/null +++ b/sdks/flutter/index.mdx @@ -0,0 +1,62 @@ +--- +title: "Overview" +description: "Turnkey's Flutter SDK [`(turnkey_sdk_flutter)`](https://pub.dev/packages/turnkey_sdk_flutter) is the easiest way to integrate Turnkey's Embedded Wallets into your Flutter applications, no backend required. It provides a set of functions and utilities to interact with Turnkey's APIs, a powerful session management system, built-in stampers, and a raw HTTP client for advanced use cases." +sidebarTitle: "Overview" +--- + +Find `turnkey_sdk_flutter` on [pub.dev](https://pub.dev/packages/turnkey_sdk_flutter) or view the source code on [GitHub](https://github.com/tkhq/dart-sdk)! + + + + + The initial setup guide for integrating Turnkey's Flutter SDK into your app. + + + Learn how to set up authentication that leverages Turnkey's Auth Proxy, enabling fast and secure user authentication in your Flutter app. + + + Discover how to create and manage embedded wallets in your Flutter + application, including wallet creation, account derivation, and more. + + + + Learn how to sign transactions and messages in your Flutter app using + Turnkey's Embedded Wallets. + + + + Learn how to customize the sub-organization settings in your Flutter app, + including wallet creation options and default usernames for different + authentication methods. + + \ No newline at end of file diff --git a/sdks/flutter/landing.mdx b/sdks/flutter/landing.mdx new file mode 100644 index 00000000..1b316755 --- /dev/null +++ b/sdks/flutter/landing.mdx @@ -0,0 +1,7 @@ +--- +title: "Flutter" +description: "Turnkey's Flutter SDK [`(turnkey_sdk_flutter)`](https://pub.dev/packages/turnkey_sdk_flutter) is the easiest way to integrate Turnkey's Embedded Wallets into your Flutter applications, no backend required. It provides a set of functions and utilities to interact with Turnkey's APIs, a powerful session management system, built-in stampers, and a raw HTTP client for advanced use cases." +sidebarTitle: "Flutter" +--- + +Head over to the [Getting Started](/sdks/flutter/getting-started) guide to set up your Flutter app with Turnkey's Embedded Wallets. diff --git a/sdks/flutter/signing.mdx b/sdks/flutter/signing.mdx new file mode 100644 index 00000000..49f5f6e8 --- /dev/null +++ b/sdks/flutter/signing.mdx @@ -0,0 +1,95 @@ +--- + +title: "Signing" +description: "Learn how to sign messages and transactions in your Flutter app using Turnkey's Embedded Wallets." +sidebarTitle: "Signing" +--- + +## Overview + +Turnkey's Flutter SDK makes it simple to sign messages and transactions using embedded wallets managed by the `TurnkeyProvider`. + + +## Signing messages + +To sign a message, select a wallet account and call `signMessage` from the `TurnkeyProvider`. You can optionally pass in the payload's `encoding` and `hashFunction` if needed. + +```dart title=lib/widgets/sign_message_button.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class SignMessageButton extends StatelessWidget { + const SignMessageButton({super.key}); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + + Future doSignMessage() async { + try { + final wallet = tk.wallets?.first; + final account = wallet?.accounts.first; + + + final message = 'Hello, Turnkey!'; + final signature = await tk.signMessage( + walletAccount: account, + message: message, + ); + + debugPrint('Message signature: $signature'); + } catch (e) { + debugPrint('Sign message failed: $e'); + } + } + + return ElevatedButton( + onPressed: doSignMessage, + child: const Text('Sign Message'), + ); + } +} +``` + +## Signing transactions + +To sign transactions, use the `signTransaction` function. Select a wallet account's address, prepare an unsigned transaction, and specify a transaction type. + +```dart title=lib/widgets/sign_transaction_button.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class SignTransactionButton extends StatelessWidget { + const SignTransactionButton({super.key}); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + + Future doSignTransaction() async { + try { + final wallet = tk.wallets?.first; + final account = wallet?.accounts.first; + + final unsignedTx = '0x...'; // unsigned transaction hex + final signature = await tk.signTransaction( + signWith: account.address, // use the address of the wallet account + unsignedTransaction: unsignedTx, + transactionType: v1TransactionType.transaction_type_ethereum, + ); + + debugPrint('Tx signature: $signature'); + } catch (e) { + debugPrint('Sign transaction failed: $e'); + } + } + + return ElevatedButton( + onPressed: doSignTransaction, + child: const Text('Sign Transaction'), + ); + } +} +``` diff --git a/sdks/flutter/sub-organization-customization.mdx b/sdks/flutter/sub-organization-customization.mdx new file mode 100644 index 00000000..83ee7401 --- /dev/null +++ b/sdks/flutter/sub-organization-customization.mdx @@ -0,0 +1,99 @@ +--- + +title: "Sub-organization customization" +description: "Learn how to customize the sub-organization creation process in your Flutter app." +sidebarTitle: "Sub-organization customization" +--- + +## Overview + +Turnkey's Flutter SDK allows you to customize the sub-organization creation process in your Flutter application. This is useful if you want to tailor the onboarding experience — such as automatically creating wallets, configuring authentication method-specific settings, setting default usernames, or naming passkeys. + +These customization options can be applied differently for each authentication method. + +## Customization + +You can customize sub-org creation through `authConfig.createSubOrgParams` inside your `TurnkeyConfig`. + +Each authentication method can have its own configuration. For example, you can assign a default username for users who sign up via email OTP and a separate wallet setup for passkey users. + +```dart title=lib/main.dart (excerpt) +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +final turnkeyProvider = TurnkeyProvider( + config: TurnkeyConfig( + // ... your existing configuration + authConfig: AuthConfig( + createSubOrgParams: MethodCreateSubOrgParams( + emailOtpAuth: CreateSubOrgParams( + userName: 'An email user', + customWallet: CustomWallet( + walletName: 'Email Wallet', + walletAccounts: [ + v1WalletAccountParams( + curve: v1Curve.curve_secp256k1, + pathFormat: v1PathFormat.path_format_bip32, + path: "m/44'/60'/0'/0/0", + addressFormat: v1AddressFormat.address_format_ethereum, + ), + ], + ), + ), + passkeyAuth: CreateSubOrgParams( + userName: 'A passkey user', + customWallet: CustomWallet( + walletName: 'Passkey Wallet', + walletAccounts: [ + v1WalletAccountParams( + curve: v1Curve.curve_ed25519, + pathFormat: v1PathFormat.path_format_bip32, + path: "m/44'/501'/0'/0'", + addressFormat: v1AddressFormat.address_format_solana, + ), + ], + ), + ), + ), + ), + ), +); +``` + +You may also define a single set of parameters to apply across all authentication methods: + +```dart title=lib/main.dart (excerpt) +final subOrgParams = CreateSubOrgParams( + userName: 'User-${DateTime.now().millisecondsSinceEpoch}', + customWallet: CustomWallet( + walletName: 'Wallet-${DateTime.now().millisecondsSinceEpoch}', + walletAccounts: [ + v1WalletAccountParams( + curve: v1Curve.curve_secp256k1, + pathFormat: v1PathFormat.path_format_bip32, + path: "m/44'/60'/0'/0/0", + addressFormat: v1AddressFormat.address_format_ethereum, + ), + ], + ), +); + +final turnkeyProvider = TurnkeyProvider( + config: TurnkeyConfig( + // ... your existing configuration + authConfig: AuthConfig( + createSubOrgParams: MethodCreateSubOrgParams( + emailOtpAuth: subOrgParams, + smsOtpAuth: subOrgParams, + oAuth: subOrgParams, + passkeyAuth: subOrgParams, + ), + ), + ), +); +``` + +For a full list of parameters available, inspect the `CreateSubOrgParams` class in the Flutter SDK. + +## Next steps + +Now that you can customize sub-org creation in Flutter, check out the [Using Embedded Wallets](/sdks/flutter/using-embedded-wallets) guide next to learn how to create and manage wallets within your app! diff --git a/sdks/flutter/using-embedded-wallets.mdx b/sdks/flutter/using-embedded-wallets.mdx new file mode 100644 index 00000000..b9c31b76 --- /dev/null +++ b/sdks/flutter/using-embedded-wallets.mdx @@ -0,0 +1,256 @@ +--- +title: "Using embedded wallets" +description: "Learn how to create and manage embedded wallets in your Flutter application using Turnkey's Embedded Wallet Kit." +sidebarTitle: "Using embedded wallets" +--- + +## Overview + +Turnkey's Flutter SDK provides a straightforward way to create and manage embedded wallets in your Flutter application. You can create wallets, derive accounts, import wallets, and keep local UI state in sync with provider data. + +Before starting, review the concepts of **[Wallets](/concepts/wallets)** and **[Wallet Accounts](/concepts/wallets#accounts)**. + +## Creating an embedded wallet + +After the user authenticates (i.e., a `session` exists on `TurnkeyProvider`), you can create an embedded wallet and specify which accounts to create within it. + +```dart title=lib/widgets/create_wallet_button.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class CreateWalletButton extends StatelessWidget { + const CreateWalletButton({super.key}); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + + Future handleCreateWallet() async { + try { + final walletId = await tk.createWallet( + walletName: 'My New Wallet', + accounts: [ + // One Ethereum account + v1WalletAccountParams( + curve: v1Curve.curve_secp256k1, + pathFormat: v1PathFormat.path_format_bip32, + path: "m/44'/60'/0'/0/0", + addressFormat: v1AddressFormat.address_format_ethereum, + ), + // One Solana account + v1WalletAccountParams( + curve: v1Curve.curve_ed25519, + pathFormat: v1PathFormat.path_format_bip32, + path: "m/44'/501'/0'/0'", + addressFormat: v1AddressFormat.address_format_solana, + ), + ], + ); + debugPrint('Wallet created: $walletId'); + } catch (e) { + debugPrint('Error creating wallet: $e'); + } + } + + return ElevatedButton( + onPressed: handleCreateWallet, + child: const Text('Create Wallet'), + ); + } +} +``` + +## Listing and refreshing wallets + +`TurnkeyProvider.wallets` contains all embedded wallets for the current sub-organization. It updates automatically when wallets change. + +```dart title=lib/widgets/wallet_list.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class WalletList extends StatelessWidget { + const WalletList({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (_, tk, __) { + final wallets = tk.wallets ?? []; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final w in wallets) + Text('${w.name} — ${w.id}'), + ], + ); + }, + ); + } +} +``` + +To refresh explicitly: + +```dart title=lib/widgets/refresh_wallets_button.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class RefreshWalletsButton extends StatelessWidget { + const RefreshWalletsButton({super.key}); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + return ElevatedButton( + onPressed: () => tk.refreshWallets(), + child: const Text('Refresh Wallets'), + ); + } +} +``` + +## Creating wallet accounts (after wallet creation) + +To add accounts to an **existing** wallet, use the `createWalletAccounts` method from the underlying `client`. + +```dart title=lib/widgets/add_account_button.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class AddAccountButton extends StatelessWidget { + const AddAccountButton({super.key}); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + + Future handleAddAccount() async { + try { + final wallet = tk.wallets?.first; + if (wallet == null) return; + + await tk.client.createWalletAccounts( + CreateWalletAccountsParams( + walletId: wallet.walletId, + accounts: [ + v1WalletAccountParams( + curve: v1Curve.curve_secp256k1, + pathFormat: v1PathFormat.path_format_bip32, + path: "m/44'/60'/1'/0/0", // next index + addressFormat: v1AddressFormat.address_format_ethereum, + ), + ], + ), + ); + + debugPrint('Account created'); + } catch (e) { + debugPrint('Error creating account: $e'); + } + } + + return ElevatedButton( + onPressed: handleAddAccount, + child: const Text('Add Ethereum Account'), + ); + } +} +``` + +## Importing wallets + +You can import a wallet from a mnemonic using the `importWallet` method and optionally specify accounts to pre-create. + +```dart title=lib/widgets/import_wallet_button.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class ImportWalletButton extends StatelessWidget { + const ImportWalletButton({super.key}); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + + Future handleImport() async { + try { + final walletId = await tk.importWallet( + mnemonic: '', // The user will enter this + walletName: 'Imported Wallet', + accounts: [ + v1WalletAccountParams( + curve: v1Curve.curve_secp256k1, + pathFormat: v1PathFormat.path_format_bip32, + path: "m/44'/60'/0'/0/0", + addressFormat: v1AddressFormat.address_format_ethereum, + ), + ], + ); + debugPrint('Imported wallet: $walletId'); + } catch (e) { + debugPrint('Import failed: $e'); + } + } + + return ElevatedButton( + onPressed: handleImport, + child: const Text('Import Wallet (mnemonic)'), + ); + } +} +``` + +## Exporting wallets + +You can export a wallet's mnemonic using the `exportWallet` method. This method will automatically decrypt the mnemonic for you. + +```dart title=lib/widgets/export_wallet_button.dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:turnkey_sdk_flutter/turnkey_sdk_flutter.dart'; + +class ExportWalletButton extends StatelessWidget { + const ExportWalletButton({super.key}); + + @override + Widget build(BuildContext context) { + final tk = Provider.of(context, listen: false); + + Future handleExport() async { + try { + final wallet = tk.wallets?.first; + if (wallet == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No wallet available to export')), + ); + return; + } + + // Default: returns decrypted mnemonic for development workflows + final mnemonic = await tk.exportWallet(walletId: wallet.walletId); + debugPrint('Wallet mnemonic: $mnemonic'); + + } catch (e) { + debugPrint('Export failed: $e'); + } + } + + return ElevatedButton( + onPressed: handleExport, + child: const Text('Export Wallet'), + ); + } +} +``` + +## Next steps + +Continue to **[Signing](/sdks/flutter/signing)** to learn how to sign messages and transactions with your embedded wallets. + + +