React Native Shield SDK
React Native uses the same @akedly/shield npm package as web. The library automatically detects the React Native environment and switches to batched main-thread PoW solving (no Web Worker).
Installation
Installation
npm install @akedly/shield
For Turnstile support, you also need a WebView package:
npm install react-native-webview
Key Differences from Web
| Feature | Web | React Native |
|---|---|---|
| PoW Solver | Web Worker (off-thread) | Batched main-thread with setTimeout(fn, 0) |
| UI during PoW | Fully responsive | Responsive (batched yields to event loop) |
| Turnstile | getTurnstileToken() (hidden DOM widget) | WebView pointing to bridge page |
| Import | Same | Same |
The solvePow function works identically -- it detects the environment and falls back to batched solving automatically. No configuration needed.
For Turnstile, getTurnstileToken() is browser-only. In React Native, use a WebView pointing to the Turnstile bridge page at turnstile.akedly.io.
Complete Example
Never ship your API key to React Native
JS bundles in React Native apps are inspectable — anything shipped is public. Keep APIKey and pipelineID on your backend and expose a thin proxy (/auth/akedly/challenge, /auth/akedly/send, /auth/akedly/verify) that the RN app calls. The Backend tab below shows the minimal Node.js server; the React Native tab shows the matching client.
Optional: per-end-user-IP rate limiting
Per-IP rate limiting is opt-in. If you want it, forward the end user's IP via the x-end-user-ip header — the IP that hit your backend from the mobile device, not your server's IP. The Express example below uses req.ip, which only returns the real client IP after app.set('trust proxy', 1) is configured for your reverse proxy (Cloudflare, AWS ALB, Nginx, Fly.io). Do not read the device's public IP from within the RN bundle — clients cannot reliably know their own public IP and can lie. Skip this entirely if you don't need per-IP limiting. See the V1.2 API reference for Next.js, Flask, and PHP extraction patterns.
React Native Component
// Express proxy — deploy this on your server, not inside the RN bundle.
import express from 'express';
const app = express();
app.use(express.json());
// Optional — only needed if you want per-end-user-IP rate limiting.
// Drop this line and the x-end-user-ip header below if you don't.
// Makes req.ip the real client IP behind a reverse proxy (1 = trust first hop).
app.set('trust proxy', 1);
app.get('/auth/akedly/challenge', async (_req, res) => {
const r = await fetch(
`https://api.akedly.io/api/v1.2/transactions/challenge` +
`?APIKey=${process.env.AKEDLY_API_KEY}` +
`&pipelineID=${process.env.AKEDLY_PIPELINE_ID}`
);
res.status(r.status).json(await r.json());
});
app.post('/auth/akedly/send', async (req, res) => {
const { phoneNumber, powSolution, turnstileToken } = req.body;
const r = await fetch('https://api.akedly.io/api/v1.2/transactions/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-end-user-ip': req.ip,
},
body: JSON.stringify({
APIKey: process.env.AKEDLY_API_KEY,
pipelineID: process.env.AKEDLY_PIPELINE_ID,
verificationAddress: { phoneNumber },
powSolution,
turnstileToken,
}),
});
res.status(r.status).json(await r.json());
});
app.post('/auth/akedly/verify', async (req, res) => {
const { transactionReqID, otp } = req.body;
const r = await fetch('https://api.akedly.io/api/v1.2/transactions/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transactionReqID, otp }),
});
res.status(r.status).json(await r.json());
});
Expo Considerations
If using Expo, install the WebView via:
npx expo install react-native-webview
The @akedly/shield package works with Expo without any additional configuration. The PoW solver uses JavaScript-only crypto (no native modules required).
For managed Expo projects, the Turnstile WebView approach works out of the box since react-native-webview is supported in the Expo Go client.