Skip to content
Merged
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
3 changes: 2 additions & 1 deletion docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@
"guides/privacy-best-practices",
"guides/stellar-fees",
"guides/stellar-troubleshooting",
"guides/spectre-stellar-cookbook"
"guides/spectre-stellar-cookbook",
"guides/integrations/react-native"
]
},
{
Expand Down
282 changes: 282 additions & 0 deletions guides/integrations/react-native.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
---
title: "React Native Integration"
description: "Configure the Wraith SDK in Expo and bare React Native applications"
---

The `@wraith-protocol/sdk-react` package allows you to integrate managed AI agents and stealth payments directly into mobile applications.

Because React Native and Expo environments do not support Node.js built-ins or standard browser web-cryptography APIs out of the box, you must configure a secure cryptographic shim and a persistent storage adapter.

---

## Installation

Install the required packages. Depending on whether you are using **Expo** or **Bare React Native**, install the corresponding dependencies:

<CodeGroup>
```bash Expo
npm install @wraith-protocol/sdk @wraith-protocol/sdk-react @react-native-async-storage/async-storage expo-crypto
```

```bash Bare React Native
npm install @wraith-protocol/sdk @wraith-protocol/sdk-react @react-native-async-storage/async-storage react-native-get-random-values buffer
```
</CodeGroup>

---

## 1. Cryptographic Shimming

The Wraith SDK relies on secure random bytes for elliptic curve operations (such as deriving ephemeral stealth keys). Since `crypto.getRandomValues` is not standard in React Native, you must register a shim at the **absolute entry point** of your application (usually `index.js` or `App.js`).

### Expo Setup

For Expo projects, use `expo-crypto` to supply the random byte generator.

Add the following shim to your entry file before any other imports:

```typescript
// App.js (or index.js) - Place this at the very top
import * as Crypto from "expo-crypto";

if (typeof global.crypto !== "object") {
global.crypto = {} as any;
}

if (typeof global.crypto.getRandomValues !== "function") {
global.crypto.getRandomValues = <T extends ArrayBufferView | null>(array: T): T => {
if (!array) {
throw new Error("crypto.getRandomValues: array cannot be null");
}
const bytes = Crypto.getRandomBytes(array.byteLength);
const view = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
view.set(bytes);
return array;
};
}
```

### Bare React Native Setup

For bare React Native projects, use `react-native-get-random-values` and polyfill Node's `Buffer` global.

Add these imports to the very top of your entry file:

```typescript
// index.js - Place this at the very top
import "react-native-get-random-values";
import { Buffer } from "buffer";

global.Buffer = global.Buffer || Buffer;
```

---

## 2. AsyncStorage Adapter

By default, the React SDK uses the browser's `localStorage` to persist active agent sessions and cached state. In a mobile environment, you must wire up `@react-native-async-storage/async-storage` as a custom storage adapter.

Define the storage adapter following the SDK's storage interface:

```typescript
import AsyncStorage from "@react-native-async-storage/async-storage";

export const asyncStorageAdapter = {
getItem: async (key: string): Promise<string | null> => {
try {
return await AsyncStorage.getItem(key);
} catch (error) {
console.error("Wraith storage error (getItem):", error);
return null;
}
},
setItem: async (key: string, value: string): Promise<void> => {
try {
await AsyncStorage.setItem(key, value);
} catch (error) {
console.error("Wraith storage error (setItem):", error);
}
},
removeItem: async (key: string): Promise<void> => {
try {
await AsyncStorage.removeItem(key);
} catch (error) {
console.error("Wraith storage error (removeItem):", error);
}
},
};
```

---

## 3. Wiring up the Provider

Wrap your application root inside `WraithProvider`, passing your API key and the `asyncStorageAdapter`:

```tsx
import React from "react";
import { WraithProvider } from "@wraith-protocol/sdk-react";
import { asyncStorageAdapter } from "./storage";
import { MainScreen } from "./MainScreen";

export default function App() {
return (
<WraithProvider
apiKey="wraith_live_abc123"
storage={asyncStorageAdapter}
>
<MainScreen />
</WraithProvider>
);
}
```

---

## Full Send + Receive Example

Here is a complete screen component demonstrating how to connect to an agent, fund it, send a stealth payment, and scan for incoming payments.

```tsx
import React, { useState } from "react";
import { StyleSheet, Text, View, Button, ActivityIndicator, Alert } from "react-native";
import { useWraith, useAgent } from "@wraith-protocol/sdk-react";
import { Chain } from "@wraith-protocol/sdk";

export function MainScreen() {
const { client } = useWraith();
const [agentId, setAgentId] = useState<string | null>(null);
const [loading, setLoading] = useState(false);

// Hook to interact with the active agent once connected
const { agent, chat, getBalance } = useAgent(agentId);
const [balance, setBalance] = useState<string>("0");

// Step 1: Create or connect to an agent
const handleConnectAgent = async () => {
setLoading(true);
try {
// Sign the registration message using your app's wallet adapter
const message = "Sign to initialize Wraith Agent";
const signature = "0x..."; // Obtain from wallet signature (e.g. WalletConnect/Expo)

const newAgent = await client.createAgent({
name: "mobile_user",
chain: Chain.Ethereum,
wallet: "0xYourWalletAddress",
signature: signature,
message: message,
});

setAgentId(newAgent.info.id);
Alert.alert("Success", `Connected to ${newAgent.info.name}.wraith`);
} catch (error: any) {
Alert.alert("Connection Error", error.message);
} finally {
setLoading(false);
}
};

// Step 2: Send a stealth payment
const handleSendPayment = async () => {
if (!agent) return;
setLoading(true);
try {
const res = await chat("send 0.05 ETH to bob.wraith");
Alert.alert("Payment Sent", res.response);

// Update balance
const newBalance = await getBalance();
setBalance(newBalance.native);
} catch (error: any) {
Alert.alert("Payment Error", error.message);
} finally {
setLoading(false);
}
};

// Step 3: Scan for incoming payments
const handleScanPayments = async () => {
if (!agent) return;
setLoading(true);
try {
const res = await chat("scan for payments");
Alert.alert("Scan Completed", res.response);

// Update balance
const newBalance = await getBalance();
setBalance(newBalance.native);
} catch (error: any) {
Alert.alert("Scan Error", error.message);
} finally {
setLoading(false);
}
};

return (
<View style={styles.container}>
<Text style={styles.title}>Wraith Mobile Agent</Text>

{loading && <ActivityIndicator size="large" color="#0000ff" style={styles.loader} />}

{!agentId ? (
<Button title="Connect / Create Agent" onPress={handleConnectAgent} disabled={loading} />
) : (
<View style={styles.agentBox}>
<Text style={styles.agentText}>Agent: mobile_user.wraith</Text>
<Text style={styles.balanceText}>Balance: {balance} ETH</Text>

<View style={styles.buttonRow}>
<Button title="Send Stealth Payment" onPress={handleSendPayment} disabled={loading} />
<View style={styles.spacer} />
<Button title="Scan for Payments" onPress={handleScanPayments} disabled={loading} />
</View>
</View>
)}
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
padding: 20,
backgroundColor: "#f5f5f5",
},
title: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 20,
},
loader: {
marginBottom: 20,
},
agentBox: {
padding: 20,
backgroundColor: "#ffffff",
borderRadius: 8,
width: "100%",
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
agentText: {
fontSize: 18,
marginBottom: 10,
},
balanceText: {
fontSize: 16,
color: "#666",
marginBottom: 20,
},
buttonRow: {
marginTop: 10,
},
spacer: {
height: 10,
},
});
Loading