Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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": [
Expand Down
40 changes: 0 additions & 40 deletions sdks/flutter.mdx

This file was deleted.

112 changes: 112 additions & 0 deletions sdks/flutter/advanced-api-requests.mdx
Original file line number Diff line number Diff line change
@@ -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<TurnkeyProvider>(context, listen: false);

Future<void> 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.

172 changes: 172 additions & 0 deletions sdks/flutter/authentication/email-sms.mdx
Original file line number Diff line number Diff line change
@@ -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<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
final _emailController = TextEditingController();

@override
Widget build(BuildContext context) {
final tk = Provider.of<TurnkeyProvider>(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: '[email protected]'),
),
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<OTPScreen> createState() => _OTPScreenState();
}

class _OTPScreenState extends State<OTPScreen> {
final _otpController = TextEditingController();

@override
Widget build(BuildContext context) {
final tk = Provider.of<TurnkeyProvider>(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`.
Loading