giwa-react-native-wallet
v0.1.1
Published
GIWA Chain SDK for React Native - Expo and React Native CLI compatible
Downloads
216
Maintainers
Readme
giwa-react-native-wallet
GIWA Chain SDK for React Native - Expo and React Native CLI compatible
Features
✅ Available Now
| Feature | Hook | Description |
|---------|------|-------------|
| Wallet Management | useGiwaWallet | Create, recover, import, export wallets |
| Balance Query | useBalance | Check ETH and token balances |
| Transactions | useTransaction | Send ETH transactions |
| Token Operations | useTokens | ERC-20 token transfers and queries |
| Flashblocks | useFlashblocks | ~200ms fast preconfirmation |
| Dojang (EAS) | useDojang | On-chain attestation service |
| Faucet | useFaucet | Testnet ETH faucet |
| Network Info | useNetworkInfo | Network status and feature availability |
| Biometric Auth | useBiometricAuth | Face ID / Touch ID / Fingerprint |
| Secure Storage | - | iOS Keychain / Android Keystore |
🚧 Coming Soon (Contract Deployment Pending)
These features are fully implemented in the SDK, but require smart contract deployment by the GIWA team.
| Feature | Hook | Status | Official Docs |
|---------|------|--------|---------------|
| L1 Bridge | useBridge | ENS contracts not deployed | GIWA Docs |
| GIWA ID | useGiwaId | L1 Bridge contract not deployed | GIWA Docs |
💡 Note: L2 Standard Bridge (
l2StandardBridge) is available on OP Stack. Full L1↔L2 bridging requires L1 contract deployment.For bridge operations, you can use Superbridge in the meantime.
Installation
Expo
# npm
npm install giwa-react-native-wallet expo-secure-store expo-local-authentication react-native-get-random-values
# yarn
yarn add giwa-react-native-wallet expo-secure-store expo-local-authentication react-native-get-random-values
# pnpm
pnpm add giwa-react-native-wallet expo-secure-store expo-local-authentication react-native-get-random-valuesTip: You can also use
npx expo installfor automatic Expo SDK version compatibility.
React Native CLI
# npm
npm install giwa-react-native-wallet react-native-keychain react-native-get-random-values
# yarn
yarn add giwa-react-native-wallet react-native-keychain react-native-get-random-values
# pnpm
pnpm add giwa-react-native-wallet react-native-keychain react-native-get-random-values# iOS setup
cd ios && pod installQuick Start
1. Wrap your app with GiwaProvider
import { GiwaProvider } from "giwa-react-native-wallet";
export default function App() {
return (
<GiwaProvider>
{" "}
{/* config is optional, defaults to testnet */}
<YourApp />
</GiwaProvider>
);
}2. Create or recover a wallet
import { useGiwaWallet } from "giwa-react-native-wallet";
function WalletScreen() {
const { wallet, createWallet, recoverWallet } = useGiwaWallet();
// createWallet() - no parameters required
const handleCreate = async () => {
const { wallet, mnemonic } = await createWallet();
console.log("Address:", wallet.address);
// Save mnemonic securely!
};
return (
<View>
{wallet ? (
<Text>Address: {wallet.address}</Text>
) : (
<Button title="Create Wallet" onPress={handleCreate} />
)}
</View>
);
}3. Check balance
import { useBalance } from "giwa-react-native-wallet";
function BalanceScreen() {
// useBalance() - no parameters required, uses connected wallet
const { formattedBalance, refetch } = useBalance();
return (
<View>
<Text>Balance: {formattedBalance} ETH</Text>
<Button title="Refresh" onPress={refetch} />
</View>
);
}4. Send transactions
import { useTransaction } from "giwa-react-native-wallet";
function SendScreen() {
const { sendTransaction, waitForReceipt, isLoading } = useTransaction();
const handleSend = async () => {
const hash = await sendTransaction({
to: "0x...",
value: "0.1", // ETH
});
const receipt = await waitForReceipt(hash);
console.log("Confirmed in block:", receipt.blockNumber);
};
return (
<Button title="Send 0.1 ETH" onPress={handleSend} disabled={isLoading} />
);
}5. Use Flashblocks for fast confirmations
import { useFlashblocks } from "giwa-react-native-wallet";
function FastTransactionScreen() {
const { sendTransaction, getAverageLatency } = useFlashblocks();
const handleSend = async () => {
const { preconfirmation, result } = await sendTransaction({
to: "0x...",
value: BigInt("100000000000000000"), // 0.1 ETH in wei
});
console.log("Preconfirmed at:", preconfirmation.preconfirmedAt);
const receipt = await result.wait();
console.log("Confirmed at:", receipt.blockNumber);
console.log("Average latency:", getAverageLatency(), "ms");
};
return <Button title="Fast Send" onPress={handleSend} />;
}6. Resolve GIWA ID
import { useGiwaId } from "giwa-react-native-wallet";
function GiwaIdScreen() {
const { resolveAddress, resolveName } = useGiwaId();
const handleResolve = async () => {
// GIWA ID to address
const address = await resolveAddress("alice.giwa.id");
console.log("Address:", address);
// Address to GIWA ID
const name = await resolveName("0x...");
console.log("Name:", name);
};
return <Button title="Resolve" onPress={handleResolve} />;
}API Reference
Hooks
| Hook | Description | Status |
| ------------------ | --------------------------------------------------------- | ------ |
| useGiwaWallet | Wallet management (create, recover, import, export) | ✅ |
| useBalance | ETH balance queries | ✅ |
| useTransaction | Send ETH transactions | ✅ |
| useTokens | ERC-20 token operations | ✅ |
| useBridge | L1↔L2 bridge operations | 🚧 |
| useFlashblocks | Fast preconfirmation transactions | ✅ |
| useGiwaId | GIWA ID (ENS) resolution | 🚧 |
| useDojang | Attestation verification | ✅ |
| useFaucet | Testnet faucet | ✅ |
| useNetworkInfo | Network status and feature availability | ✅ |
| useBiometricAuth | Biometric authentication (Face ID, Touch ID, Fingerprint) | ✅ |
🚧 = Coming soon (contract deployment pending). See GIWA Docs for updates.
Configuration (All Optional)
// Minimal - all defaults
<GiwaProvider>
<App />
</GiwaProvider>
// With options (all optional)
<GiwaProvider
network="testnet" // optional, default: 'testnet'
initTimeout={10000} // optional, default: 10000 (ms)
onError={(e) => console.error(e)} // optional
>
<App />
</GiwaProvider>Custom Endpoints
You can override default network endpoints:
<GiwaProvider
config={{
network: 'testnet',
endpoints: {
rpcUrl: 'https://my-custom-rpc.example.com',
flashblocksRpcUrl: 'https://my-flashblocks-rpc.example.com',
flashblocksWsUrl: 'wss://my-flashblocks-ws.example.com',
explorerUrl: 'https://my-explorer.example.com',
},
}}
>Access endpoints at runtime:
import { useNetworkInfo } from "giwa-react-native-wallet";
function MyComponent() {
const { rpcUrl, flashblocksRpcUrl, flashblocksWsUrl, explorerUrl } =
useNetworkInfo();
// Use the resolved endpoints
}Network Selection
Check Network Status and Feature Availability
import { useNetworkInfo } from "giwa-react-native-wallet";
function NetworkStatus() {
const {
network,
isTestnet,
isReady,
hasWarnings,
warnings,
isFeatureAvailable,
unavailableFeatures,
} = useNetworkInfo();
return (
<View>
<Text>Network: {network}</Text>
<Text>Testnet: {isTestnet ? "Yes" : "No"}</Text>
<Text>Ready: {isReady ? "Yes" : "No"}</Text>
{hasWarnings && (
<View>
<Text>Warnings:</Text>
{warnings.map((w, i) => (
<Text key={i}>- {w}</Text>
))}
</View>
)}
{/* Check specific feature availability */}
{!isFeatureAvailable("giwaId") && (
<Text>GIWA ID is not available on this network</Text>
)}
</View>
);
}Network Warnings
When using mainnet with TBD (not yet deployed) contracts, the SDK will log warnings:
[GIWA SDK] Network "mainnet" has 4 warning(s):
1. [WARNING] Mainnet is not fully ready. 4 feature(s) unavailable.
2. [WARNING] bridge: L1 Bridge contract is TBD
3. [WARNING] giwaId: ENS Registry/Resolver contracts are TBD
4. [WARNING] dojang: EAS/Schema Registry contracts are TBD
[GIWA SDK] Consider using "testnet" for development and testing.Network Information
Testnet (GIWA Sepolia)
| Property | Value |
| --------------------- | ----------------------------------------- |
| Chain ID | 91342 |
| RPC URL | https://sepolia-rpc.giwa.io |
| Flashblocks RPC | https://sepolia-rpc-flashblocks.giwa.io |
| Flashblocks WebSocket | wss://sepolia-rpc-flashblocks.giwa.io |
| Block Explorer | https://sepolia-explorer.giwa.io |
| Currency | ETH |
Mainnet (🚧 Under Development)
⚠️ Mainnet is currently under development. Please use testnet.
| Property | Value | | -------------- | -------------------- | | Chain ID | - | | RPC URL | - | | Block Explorer | - | | Status | 🚧 Under Development |
Security
- Private keys and mnemonics are stored in iOS Keychain / Android Keystore
- Biometric authentication support for sensitive operations
- Secure storage required - SDK will not work without it
Testing
Setup
# Install dev dependencies
npm install --save-dev jest @testing-library/react-native @testing-library/react-hooks
# For TypeScript
npm install --save-dev @types/jest ts-jestJest Configuration
// jest.config.js
module.exports = {
preset: "react-native",
setupFilesAfterEnv: ["./jest.setup.js"],
transformIgnorePatterns: [
"node_modules/(?!(react-native|@react-native|giwa-react-native-wallet)/)",
],
moduleNameMapper: {
"^giwa-react-native-wallet$":
"<rootDir>/node_modules/giwa-react-native-wallet/dist/index.js",
},
};Mock Setup
// jest.setup.js
jest.mock("expo-secure-store", () => ({
setItemAsync: jest.fn(),
getItemAsync: jest.fn(),
deleteItemAsync: jest.fn(),
isAvailableAsync: jest.fn(() => Promise.resolve(true)),
WHEN_UNLOCKED_THIS_DEVICE_ONLY: "WHEN_UNLOCKED_THIS_DEVICE_ONLY",
}));
jest.mock("react-native-keychain", () => ({
setGenericPassword: jest.fn(() => Promise.resolve(true)),
getGenericPassword: jest.fn(() => Promise.resolve({ password: "test" })),
resetGenericPassword: jest.fn(() => Promise.resolve(true)),
getSupportedBiometryType: jest.fn(() => Promise.resolve("FaceID")),
ACCESS_CONTROL: { BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE: "BIOMETRY" },
ACCESSIBLE: { WHEN_UNLOCKED_THIS_DEVICE_ONLY: "WHEN_UNLOCKED" },
BIOMETRY_TYPE: { FACE_ID: "FaceID", TOUCH_ID: "TouchID" },
}));Unit Test Examples
Testing Wallet Creation
// __tests__/wallet.test.ts
import { renderHook, act } from "@testing-library/react-hooks";
import { useGiwaWallet } from "giwa-react-native-wallet";
import { GiwaProvider } from "giwa-react-native-wallet";
const wrapper = ({ children }) => (
<GiwaProvider config={{ network: "testnet" }}>{children}</GiwaProvider>
);
describe("useGiwaWallet", () => {
it("should create a new wallet", async () => {
const { result } = renderHook(() => useGiwaWallet(), { wrapper });
await act(async () => {
const { wallet, mnemonic } = await result.current.createWallet();
expect(wallet.address).toMatch(/^0x[a-fA-F0-9]{40}$/);
expect(mnemonic.split(" ")).toHaveLength(12);
});
});
it("should recover wallet from mnemonic", async () => {
const { result } = renderHook(() => useGiwaWallet(), { wrapper });
const testMnemonic =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
await act(async () => {
const wallet = await result.current.recoverWallet(testMnemonic);
expect(wallet.address).toBe("0x9858EfFD232B4033E47d90003D41EC34EcaEda94");
});
});
it("should throw error for invalid mnemonic", async () => {
const { result } = renderHook(() => useGiwaWallet(), { wrapper });
await expect(
result.current.recoverWallet("invalid mnemonic phrase")
).rejects.toThrow("Invalid recovery phrase.");
});
});Testing Balance Hook
// __tests__/balance.test.ts
import { renderHook, waitFor } from "@testing-library/react-hooks";
import { useBalance } from "giwa-react-native-wallet";
// Mock viem client
jest.mock("viem", () => ({
...jest.requireActual("viem"),
createPublicClient: () => ({
getBalance: jest.fn(() => Promise.resolve(BigInt("1000000000000000000"))),
}),
}));
describe("useBalance", () => {
it("should fetch ETH balance", async () => {
const { result } = renderHook(() => useBalance("0x1234..."), { wrapper });
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.formattedBalance).toBe("1.0");
expect(result.current.balance).toBe(BigInt("1000000000000000000"));
});
});Testing Token Transfer
// __tests__/tokens.test.ts
import { renderHook, act } from "@testing-library/react-hooks";
import { useTokens } from "giwa-react-native-wallet";
describe("useTokens", () => {
it("should transfer tokens", async () => {
const { result } = renderHook(() => useTokens(), { wrapper });
await act(async () => {
const hash = await result.current.transfer(
"0xTokenAddress...",
"0xRecipient...",
"100"
);
expect(hash).toMatch(/^0x[a-fA-F0-9]{64}$/);
});
});
it("should get token balance", async () => {
const { result } = renderHook(() => useTokens(), { wrapper });
await act(async () => {
const balance = await result.current.getBalance("0xTokenAddress...");
expect(balance.token.symbol).toBeDefined();
expect(balance.formattedBalance).toBeDefined();
});
});
});Testing GIWA ID Resolution
// __tests__/giwaId.test.ts
import { renderHook, act } from "@testing-library/react-hooks";
import { useGiwaId } from "giwa-react-native-wallet";
describe("useGiwaId", () => {
it("should resolve GIWA ID to address", async () => {
const { result } = renderHook(() => useGiwaId(), { wrapper });
await act(async () => {
const address = await result.current.resolveAddress("alice.giwa.id");
expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/);
});
});
it("should return null for non-existent GIWA ID", async () => {
const { result } = renderHook(() => useGiwaId(), { wrapper });
await act(async () => {
const address = await result.current.resolveAddress(
"nonexistent.giwa.id"
);
expect(address).toBeNull();
});
});
});Integration Test Example
// __tests__/integration/fullFlow.test.ts
import { renderHook, act } from "@testing-library/react-hooks";
import {
useGiwaWallet,
useBalance,
useTransaction,
} from "giwa-react-native-wallet";
describe("Full Wallet Flow", () => {
it("should complete full transaction flow", async () => {
// 1. Create wallet
const { result: walletResult } = renderHook(() => useGiwaWallet(), {
wrapper,
});
let walletAddress: string;
await act(async () => {
const { wallet } = await walletResult.current.createWallet();
walletAddress = wallet.address;
});
// 2. Check balance
const { result: balanceResult } = renderHook(
() => useBalance(walletAddress),
{ wrapper }
);
await waitFor(() => {
expect(balanceResult.current.balance).toBeDefined();
});
// 3. Send transaction (if balance > 0)
if (balanceResult.current.balance > 0n) {
const { result: txResult } = renderHook(() => useTransaction(), {
wrapper,
});
await act(async () => {
const hash = await txResult.current.sendTransaction({
to: "0xRecipient...",
value: "0.001",
});
expect(hash).toBeDefined();
});
}
});
});E2E Test with Detox (Optional)
// e2e/wallet.e2e.ts
describe("Wallet E2E", () => {
beforeAll(async () => {
await device.launchApp();
});
it("should create wallet and show address", async () => {
await element(by.id("create-wallet-button")).tap();
await expect(element(by.id("wallet-address"))).toBeVisible();
});
it("should show balance after wallet creation", async () => {
await expect(element(by.id("balance-display"))).toBeVisible();
});
});Running Tests
# Run all tests
npm test
# Run with coverage
npm test -- --coverage
# Run specific test file
npm test -- wallet.test.ts
# Run in watch mode
npm test -- --watchRequirements
- React >= 19.0.0
- React Native >= 0.77.0
- Expo SDK >= 53 (for Expo projects)
- expo-secure-store >= 15.0.0 (Expo)
- expo-local-authentication >= 14.0.0 (Expo, for biometrics)
- react-native-keychain >= 9.2.0 (React Native CLI)
- react-native-get-random-values >= 1.11.0
GIWA Official Resources
| Resource | URL | | ------------------------ | --------------------------------------------------------- | | GIWA Documentation | https://docs.giwa.io | | SDK Documentation | https://dev-eyoungmin.github.io/giwa-react-native-wallet/ | | Sample App (GitHub) | https://github.com/dev-eyoungmin/giwa-react-native-samples | | Testnet Faucet | https://faucet.giwa.io | | Block Explorer (Testnet) | https://sepolia-explorer.giwa.io | | Block Explorer (Mainnet) | - | | Bridge (Superbridge) | https://superbridge.app |
License
MIT
