Skip to content

Commit ef8c6d1

Browse files
committed
RN export
1 parent 98dc191 commit ef8c6d1

File tree

5 files changed

+283
-127
lines changed

5 files changed

+283
-127
lines changed
Lines changed: 139 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import {
2-
RNAlchemySigner,
3-
type ExportWalletResult,
4-
} from "@account-kit/react-native-signer";
1+
import { RNAlchemySigner, type ExportWalletResult } from "@account-kit/react-native-signer";
52
import { Alert } from "react-native";
63

7-
// Example usage of wallet export in React Native
8-
4+
/**
5+
* Example: Export private key using the local storage approach
6+
*
7+
* This implementation uses Turnkey's recommended approach for mobile:
8+
* 1. Generate a P256 key pair locally
9+
* 2. Request export encrypted to the public key
10+
* 3. Decrypt the bundle using HPKE with the private key
11+
* 4. Clean up the key from storage
12+
*/
913
async function exportPrivateKey() {
10-
// Initialize the signer
1114
const signer = RNAlchemySigner({
1215
client: {
1316
connection: {
@@ -18,59 +21,124 @@ async function exportPrivateKey() {
1821
});
1922

2023
try {
21-
// Method 1: Basic export (returns boolean)
22-
// This is compatible with the base class interface
23-
const success = await signer.exportWallet();
24-
if (success) {
25-
console.log("Wallet export initiated successfully");
26-
// Note: This doesn't return the actual export bundle
27-
}
28-
29-
// Method 2: Export with result (recommended for React Native)
30-
// This returns the encrypted export bundle that needs to be decrypted
31-
const exportResult: ExportWalletResult =
32-
await signer.exportWalletWithResult();
33-
34-
// The export bundle is encrypted and needs to be decrypted
35-
// using the stamper's private key
36-
console.log("Export bundle received:", exportResult.exportBundle);
37-
console.log("Wallet address:", exportResult.address);
38-
console.log("Organization ID:", exportResult.orgId);
39-
40-
// IMPORTANT: Handle the export bundle securely
41-
// Options for handling the export bundle:
42-
// 1. Display in a secure modal/screen that prevents screenshots
43-
// 2. Store in secure device storage (iOS Keychain, Android Keystore)
44-
// 3. Allow user to copy to clipboard with security warnings
45-
46-
// Example: Show alert with security warning
24+
// Export as private key
25+
const result: ExportWalletResult = await signer.exportWalletWithResult({
26+
exportAs: "PRIVATE_KEY"
27+
});
28+
29+
console.log("Private key exported successfully");
30+
console.log("Address:", result.address);
31+
console.log("Private key:", result.privateKey);
32+
33+
// IMPORTANT: Handle the private key securely
4734
Alert.alert(
48-
"⚠️ Private Key Export",
49-
"Your private key export bundle has been generated. This is encrypted data that contains your private key.\n\n" +
50-
"Keep this information secure and never share it with anyone.",
35+
"⚠️ Private Key Exported",
36+
"Your private key has been exported. Keep this information extremely secure:\n\n" +
37+
`Address: ${result.address}\n\n` +
38+
"Never share your private key with anyone!",
5139
[
5240
{
5341
text: "I Understand",
5442
style: "default",
43+
onPress: () => {
44+
// Optionally copy to secure clipboard or save securely
45+
}
5546
},
56-
],
47+
]
5748
);
5849

59-
return exportResult;
50+
return result;
6051
} catch (error) {
61-
console.error("Failed to export wallet:", error);
52+
console.error("Failed to export private key:", error);
6253
Alert.alert(
6354
"Export Failed",
64-
"Unable to export wallet. Please ensure you are authenticated and try again.",
55+
"Unable to export wallet. Please ensure you are authenticated and try again."
6556
);
6657
throw error;
6758
}
6859
}
6960

70-
// Example of using the client directly
61+
/**
62+
* Example: Export seed phrase
63+
*/
64+
async function exportSeedPhrase() {
65+
const signer = RNAlchemySigner({
66+
client: {
67+
connection: {
68+
apiKey: "YOUR_API_KEY",
69+
},
70+
rpId: "your-app.com",
71+
},
72+
});
73+
74+
try {
75+
// Export as seed phrase
76+
const result: ExportWalletResult = await signer.exportWalletWithResult({
77+
exportAs: "SEED_PHRASE"
78+
});
79+
80+
console.log("Seed phrase exported successfully");
81+
console.log("Address:", result.address);
82+
console.log("Seed phrase:", result.seedPhrase);
83+
84+
Alert.alert(
85+
"🔐 Seed Phrase Exported",
86+
"Your recovery phrase has been exported. This is the ONLY way to recover your wallet:\n\n" +
87+
"• Write it down on paper\n" +
88+
"• Store it in a secure location\n" +
89+
"• Never share it with anyone\n" +
90+
"• Never store it digitally unless encrypted",
91+
[
92+
{
93+
text: "I've Secured My Phrase",
94+
style: "default",
95+
},
96+
]
97+
);
98+
99+
return result;
100+
} catch (error) {
101+
console.error("Failed to export seed phrase:", error);
102+
throw error;
103+
}
104+
}
105+
106+
/**
107+
* Example: Using the base exportWallet method (returns boolean)
108+
*/
109+
async function exportWalletBasic() {
110+
const signer = RNAlchemySigner({
111+
client: {
112+
connection: {
113+
apiKey: "YOUR_API_KEY",
114+
},
115+
rpId: "your-app.com",
116+
},
117+
});
118+
119+
try {
120+
// This method returns true/false but doesn't give you the actual key
121+
// Use exportWalletWithResult() to get the decrypted data
122+
const success = await signer.exportWallet({ exportAs: "PRIVATE_KEY" });
123+
124+
if (success) {
125+
console.log("Export completed successfully");
126+
// Note: You don't get the actual key with this method
127+
}
128+
129+
return success;
130+
} catch (error) {
131+
console.error("Export failed:", error);
132+
throw error;
133+
}
134+
}
135+
136+
/**
137+
* Example: Direct client usage
138+
*/
71139
async function exportWithClient() {
72140
const { RNSignerClient } = await import("@account-kit/react-native-signer");
73-
141+
74142
const client = new RNSignerClient({
75143
connection: {
76144
apiKey: "YOUR_API_KEY",
@@ -82,11 +150,34 @@ async function exportWithClient() {
82150
await client.initEmailAuth({
83151
email: "user@example.com",
84152
});
85-
86-
// After authentication...
87-
const exportResult = await client.exportWalletWithResult();
88-
89-
return exportResult;
153+
154+
// After authentication, export the wallet
155+
const result = await client.exportWalletWithResult({
156+
exportAs: "PRIVATE_KEY"
157+
});
158+
159+
return result;
90160
}
91161

92-
export { exportPrivateKey, exportWithClient };
162+
/**
163+
* Security Best Practices:
164+
*
165+
* 1. Never log private keys in production
166+
* 2. Use secure storage if you need to persist keys
167+
* 3. Implement screenshot prevention during export
168+
* 4. Add user authentication before allowing export
169+
* 5. Consider using biometric authentication
170+
* 6. Warn users about the risks of exporting keys
171+
* 7. Clear clipboard after copying sensitive data
172+
*
173+
* The export uses HPKE encryption with a locally generated P256 key pair,
174+
* ensuring that neither Turnkey nor your app backend can access the
175+
* decrypted private key - only the user's device has access.
176+
*/
177+
178+
export {
179+
exportPrivateKey,
180+
exportSeedPhrase,
181+
exportWalletBasic,
182+
exportWithClient
183+
};

account-kit/rn-signer/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
"dependencies": {
145145
"@aa-sdk/core": "^4.57.1",
146146
"@account-kit/signer": "^4.57.1",
147+
"@turnkey/crypto": "^2.5.0",
147148
"@turnkey/react-native-passkey-stamper": "^1.0.14",
148149
"uuid": "^11.1.0",
149150
"viem": "^2.29.2"

0 commit comments

Comments
 (0)