# Alien Developer Documentation
> Official documentation for integrating with the Alien Network - including SSO authentication, Solana integration, and Miniapps development.
---
# Overview
Welcome to the Alien dev portal. This guide helps you integrate
verification and authentication into your applications using the
Alien ID system. Alien enables you to verify the uniqueness and
humanness of your users, build persistent privacy-preserving
reputation and remove bots from your platform.
## Core Concepts
Before you start integrating, it's important to understand these
fundamental concepts:
- **[What is an Alien Session?](/what-is-alien-session)** — How
user authentication sessions work.
- **[What is an Alien Provider?](/what-is-alien-provider)** — How
to register your application on the Alien Network.
- **[Privacy PoP](https://blog.kirill.cc/p/privacy-pop)** — Our
article on privacy-preserving identity.
These concepts are the foundation for how Alien SSO works. We
recommend reading these guides before proceeding with integration.
## Who is this for?
This documentation is designed for:
- **Web developers** building SaaS applications, dashboards,
or content platforms
- **Blockchain developers** creating Solana dApps, DeFi
protocols, or NFT marketplaces
- **Mobile developers** integrating authentication into apps
with WebView components
- **Backend engineers** implementing secure, non-custodial
authentication systems
## What types of applications can integrate?
Alien Network supports various application types:
### Web Apps
- React, Next.js, Vue or Vanilla JavaScript applications.
- Traditional web2 apps that need secure verification.
- Applications that require OAuth2.0/OIDC-compatible identity.
- Services that need verified user claims (name/avatar).
### Solana Applications
- Decentralized applications (dApps) on Solana.
- DeFi protocols that require user verification.
- NFT marketplaces and gaming platforms.
- Any Solana program that needs on-chain session verification.
### Mobile Applications
- Native iOS/Android apps with WebView integration.
- Mini apps running inside the Alien native app.
- Hybrid applications that require deep link authentication.
## Which solution do you need?
Alien offers two approaches for verification and authentication
depending on your application type:
### Web Applications
Standard OAuth2.0/OIDC-compatible solution for web2 and web3
applications.
- Traditional OAuth-like flow.
- JWT access tokens for backend verification.
- Web application integration without blockchain dependencies.
- Compatible with any JavaScript/TypeScript project.
**[Learn more about Alien SSO →](/sso-guide/introduction)**
### Solana SSO
Blockchain-backed solution specifically for the Solana ecosystem.
- On-chain session attestations on Solana.
- Integration with Solana wallet adapters.
- Verifiable on-chain state.
- Building dApps that require proof of identity on Solana.
**[Learn more about Solana SSO →](/solana-sso-guide/introduction)**
### Alien Mini Apps
Build powerful, native-like applications that live directly inside
the Alien ecosystem.
- **Zero Friction:** No app store downloads or complex onboarding.
- **Instant Distribution:** Reach the entire Alien user base.
- **Seamless Payments:** Start receiving payments in minutes.
- **Secure Identity:** Privacy-preserving ID for zero-trust auth.
**[Start building for Alien →](/quickstart/create-miniapp)**
## Support
For additional support, get in touch on X,
[@aliendotorg](https://x.com/aliendotorg).
---
# What is an Alien Session?
An Alien Session is a unique, cryptographically-verified session created once by a user for your application (provider).
## Core Concept
When a user authenticates with your mini app, web app, or application through Alien SSO for the first time, a session is created once for that specific provider. Each session is created within the Alien Network's **Frame** architecture, in MPC-based TEEs.
This session represents a permanent, verified connection between the user (their Alien ID) and your application (the provider). The same session is reused for all subsequent authentications by that user with your application.
You can read the advanced reasoning on why Sessions are important by Alien's founder Kirill Avery: [Proving You're Human: How to Solve Privacy in the Era of AI](https://blog.kirill.cc/p/privacy-pop)
### Stored on Alien Network
All sessions are stored on the Alien Network as immutable, verifiable records. This ensures:
- **Transparency:** anyone can verify a session's authenticity on-chain.
- **Tamper-proof:** once created, sessions cannot be modified or forged.
- **Decentralized:** no central authority controls session data.
- **Privacy:** a user's personal data never leaves their device.
- **Permanent audit trail:** full history of session creation and lifecycle.
### Managed via Alien ID
Sessions are created and managed by users through their Alien ID:
- Users control which sessions to create and for which applications.
- Users can revoke sessions at any time through their Alien ID.
- All session actions require user authorization via their Alien ID.
- Private keys never leave the user's device.
## Unique for User and Provider
Each session is unique and tied to both the user's Alien ID and your specific provider. This means:
- The same user authenticating with your app will have a different session than when they authenticate with another app.
- Each session is isolated and cannot be reused across different providers.
- Sessions are tamper-proof and verified on-chain through cryptographic proofs.
## One Session = One Person
This architecture provides strong protection against bots and fake accounts.
### Bot Protection
- **Cryptographic verification:** each session is created in an execution layer within Frame TEEs and signed with the user's private key.
- **One identity per person:** the Alien Network ensures each person has only one Alien ID.
- **Session uniqueness:** bots cannot duplicate or forge sessions without access to the user's private keys.
### Sybil Attack Prevention
- **On-chain verification:** all sessions are verifiable on the blockchain.
- **Provider isolation:** sessions cannot be shared or transferred between different applications.
- **Proof of personhood:** each session is backed by an Alien ID.
## Session Lifecycle
### 1. Creation
When a user approves authentication in the Alien App:
- A session is created in an execution layer within Frame TEEs.
- A session address is generated.
- The session is recorded on-chain (for Solana SSO) or verified via JWT (for standard SSO).
### 2. Active
While the session is valid:
- Your app can verify the user.
- The user's approved claims (name, avatar) are available.
- The session cannot be tampered with or forged.
### 3. Revocation
Sessions remain active on-chain until revoked:
- Users can revoke sessions at any time through their Alien ID.
- Only the session creator (user) can revoke their sessions.
- Once revoked, a session cannot be restored.
## Benefits for Your Application
### Security
- **No password management:** users don't need passwords for your app.
- **Non-custodial:** you never have access to users' private keys.
- **Cryptographic proof:** each session is cryptographically verifiable.
### User Experience
- **Single sign-on:** users authenticate once across all Alien-integrated apps.
- **Privacy control:** users choose which claims to share with your app.
- **Mobile-friendly:** QR code or deep link authentication.
### Compliance
- **GDPR-friendly:** users control their own data.
- **Audit trail:** all sessions are verifiable on-chain.
- **Data minimization:** only approved claims are shared.
## Session vs User Identity
It's important to understand the distinction:
| Aspect | Session | User Identity |
|--------|---------|---------------|
| **Uniqueness** | Per provider, per authentication | Global across Alien Network |
| **Scope** | Your application only | All Alien-integrated apps |
| **Lifetime** | Until revoked | Permanent |
| **Purpose** | Authentication state | User's verified identity |
A single user has one Alien ID but can have multiple sessions across different applications.
## Next Steps
- [What is an Alien Provider?](/what-is-alien-provider) - Learn about provider registration.
- [SSO Guide](/sso-guide/introduction) - Integrate standard SSO sessions.
- [Solana SSO Guide](/solana-sso-guide/introduction) - Integrate on-chain sessions.
---
# What is an Alien Provider?
An Alien Provider is the on-chain representation of your application, mini app or web app on the Alien Network. It serves as your application's verified identity within the system.
## Core Concept
Before users can verify and authenticate with your application using Alien SSO, you must create a *Provider*: a blockchain-based entity that represents your application. A Provider has multiple roles:
- Your app's identity on the Alien Network.
- A trust anchor for users to verify they're connecting with your legitimate app.
- An access control mechanism that defines where and how authentication can occur.
### Stored on Alien Network
All providers are stored on **Alien Network** as permanent, verifiable entities. This ensures:
- **Immutability:** provider data cannot be tampered with or altered without authorization.
- **Transparency:** anyone can verify a provider's authenticity and configuration.
- **Decentralization:** no central registry controls provider information.
- **Privacy:** user data remains under user control and is never exposed on-chain.
- **Trust:** users can cryptographically verify they're connecting with legitimate applications.
### Managed with Alien ID
Providers are created and managed by developers through their Alien ID:
- Provider creation requires authorization via Alien ID in the dev portal.
- Only the provider's creator — identified by their Alien ID — can modify provider settings.
- All provider updates are signed with the creator's private key.
- Provider ownership is tied to the creator's Alien ID on-chain.
This ensures that only you, as the verified owner, can manage your application's provider configuration.
## Creating Your Provider
### Dev portal
All providers are created through the **Alien dev portal**:
1. **Access the dev portal**
- Access the [Alien dev portal](https://dev.alien.org).
- Sign in with your Alien ID.
2. **Create New Provider**
- Click "Create Provider".
- Scan QR code with the Alien App.
- Approve provider creation in the Alien App.
3. **Configure Provider Settings**
- **Name:** display name shown to users during authentication.
- **Provider URL:** the main URL of your application.
- **Allowed Origins:** a list of domains where authentication is permitted.
4. **Save and Deploy**
- The provider is registered on-chain.
- You will receive your Provider Address.
- You then use this address in your SDK configuration.
## Provider Lifecycle
1. **Creation**
- You set the provider's name, URL and allowed origins.
- Upload a logo and description.
- Optionally configure claim requirements.
2. **Integration**
- Use your provider address in SDK initialization.
- Implement an authentication flow.
- Test the integration with development origins.
3. **Production**
- Authentication requests are verified against the provider's metadata.
- Users authenticate through your verified provider.
- Sessions are created under your provider's namespace.
4. **Updates**
- Update allowed origins as needed.
- Modify your provider metadata as needed.
- All changes are recorded on-chain.
## Provider vs Session
It's important to understand this distinction:
| Aspect | Provider | Session |
|--------|----------|---------|
| What | Your application's identity | User's authentication identity |
| Scope | Global (all users) | Per user, per authentication |
| Created by | You (developer) | User (during authentication) |
| Stored | On-chain | On-chain |
**A useful analogy:** A provider is your app's passport, while sessions are entry visas for each user.
## Next Steps
- **[What is Alien Session?](/what-is-alien-session)** - Learn about user sessions.
- **[SSO Guide](/sso-guide/introduction)** - Integrate standard SSO sessions.
- **[Solana SSO Guide](/solana-sso-guide/introduction)** - Integrate on-chain sessions.
- **[Dev portal](https://dev.alien.org)** - Create your provider.
---
# Introduction
Alien SSO is a fully compliant **OAuth 2.0 / OpenID Connect (OIDC)** identity provider that enables non-custodial authentication using Alien ID. It provides secure, privacy-preserving sign-in flows backed by blockchain and Trusted Execution Environment (TEE).
## Who can use this?
Any application that supports OAuth 2.0 / OIDC:
- **Web applications** (React, Next.js, Vue, vanilla JavaScript)
- **Mobile apps** with WebView or native OAuth support
- **Backend services** requiring JWT token verification
- **Any OAuth 2.0 client** (NextAuth.js, Passport.js, Auth0 libraries, etc.)
## Key Features
- **Standard OAuth 2.0 / OIDC:** Compatible with any OAuth client library
- **PKCE required:** Secure authorization for public clients (SPAs, mobile apps)
- **JWT tokens:** RS256-signed access tokens and ID tokens
- **Refresh tokens:** Long-lived sessions with automatic token rotation
- **OIDC Discovery:** Auto-configuration via `/.well-known/openid-configuration`
## OIDC Endpoints
| Endpoint | URL |
|----------|-----|
| Discovery | `/.well-known/openid-configuration` |
| Authorization | `/oauth/authorize` |
| Token | `/oauth/token` |
| UserInfo | `/oauth/userinfo` |
| JWKS | `/oauth/jwks` |
## Supported Flows
| Feature | Value |
|---------|-------|
| Response Types | `code` |
| Response Modes | `query`, `json` |
| Grant Types | `authorization_code`, `refresh_token` |
| Token Auth | `none` (public client) |
| PKCE | Required (`S256`) |
| Signing | `RS256` |
## How it Works
```mermaid
sequenceDiagram
participant App as Your App
participant SSO as Alien SSO
participant Alien as Alien App
App->>SSO: 1. /oauth/authorize
SSO->>App: 2. QR code / deep link
Alien->>SSO: 3. User scans QR
Alien->>SSO: 4. User approves
SSO->>App: 5. Poll returns authorization_code
App->>SSO: 6. /oauth/token (exchange code)
SSO->>App: 7. access_token + id_token + refresh_token
```
## Integration Options
### Option 1: Use Our SDK (Recommended for SPAs)
We provide JavaScript/TypeScript SDKs with built-in QR code UI and polling:
```sh
# Core SDK for vanilla JS/TS
npm install @alien_org/sso-sdk-core
# React SDK with hooks and components
npm install @alien_org/sso-sdk-react
```
### Option 2: Use Any OAuth 2.0 Client
Since we're OIDC-compliant, you can use any standard OAuth library. See the **[OAuth2 Clients Guide](/sso-guide/oauth2-clients)** for detailed examples with refresh tokens.
**NextAuth.js (basic):**
```typescript
providers: [{
id: "alien",
name: "Alien",
type: "oidc",
issuer: "https://sso.alien-api.com",
clientId: process.env.ALIEN_PROVIDER_ADDRESS!,
clientSecret: "", // Public client - no secret needed
client: {
token_endpoint_auth_method: "none",
},
checks: ["pkce", "state"],
authorization: {
params: {
scope: "openid",
},
},
}],
})
```
**Any OIDC Client:**
```typescript
// Discovery URL provides all endpoints automatically
const discovery = await fetch("https://sso.alien-api.com/.well-known/openid-configuration")
const config = await discovery.json()
// config.authorization_endpoint = "/oauth/authorize"
// config.token_endpoint = "/oauth/token"
// etc.
```
## Token Format
Tokens are standard JWTs signed with RS256:
**ID Token Claims:**
```json
{
"iss": "https://sso.alien-api.com",
"sub": "user-session-address",
"aud": ["your-provider-address"],
"exp": 1234567890,
"iat": 1234567890,
"nonce": "optional-nonce",
"auth_time": 1234567890
}
```
**Access Token:** Same structure, used for API authentication.
**Refresh Token:** Opaque token for obtaining new access tokens.
## Packages
- **`@alien_org/sso-sdk-core`** - Core client for any JavaScript/TypeScript project
- **`@alien_org/sso-sdk-react`** - React hooks, components, and providers
## Next Steps
Choose your integration path:
- **[Core Integration Guide](/sso-guide/core-integration)** - For vanilla JavaScript/TypeScript or custom flows
- **[React Integration Guide](/sso-guide/react-integration)** - For React applications with pre-built components
- **[OAuth2 Clients Guide](/sso-guide/oauth2-clients)** - For NextAuth.js, Passport.js, and other OAuth2 libraries
- **[API Reference - Core](/sso-api-reference/api-reference-core)** - Complete SDK documentation
- **[API Reference - React](/sso-api-reference/api-reference-react)** - React SDK documentation
---
# Core Integration Guide
This guide shows how to integrate Alien SSO into any JavaScript/TypeScript project using the core SDK.
## Requirements
- A modern web browser with JavaScript enabled
- `localStorage` and `sessionStorage` support
- A registered provider from the [developer portal](https://dev.alien.org) with provider address
## Installation
```sh
npm install @alien_org/sso-sdk-core
```
## Setup
### Initialize the Client
```typescript
const client = new AlienSsoClient({
ssoBaseUrl: 'https://sso.alien-api.com',
providerAddress: 'your-provider-address'
});
```
### Configuration Options
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `ssoBaseUrl` | string | Yes | Base URL of the SSO service |
| `providerAddress` | string | Yes | Your provider address from developer portal |
| `pollingInterval` | number | No | Polling interval in ms (default: 5000) |
## Authentication Flow
### Step 1: Generate Deep Link
```typescript
const { deep_link, polling_code, expired_at } = await client.generateDeeplink();
// Display QR code with deep_link
displayQRCode(deep_link);
// Or redirect mobile users
window.location.href = deep_link;
```
The `generateDeeplink()` method:
- Sends `GET /oauth/authorize?response_type=code&response_mode=json&...`
- Generates PKCE code verifier/challenge (S256)
- Stores code verifier in `sessionStorage`
- Returns deep link for user authentication
### Step 2: Poll for Authorization
```typescript
const pollInterval = setInterval(async () => {
const response = await client.pollAuth(polling_code);
if (response.status === 'authorized') {
clearInterval(pollInterval);
// Proceed to token exchange
const tokens = await client.exchangeToken(response.authorization_code);
console.log('Authenticated!', tokens);
} else if (response.status === 'rejected') {
clearInterval(pollInterval);
console.error('User denied authentication');
} else if (response.status === 'expired') {
clearInterval(pollInterval);
console.error('Session expired');
}
// If status is 'pending', continue polling
}, 5000);
```
### Step 3: Exchange Code for Tokens
```typescript
const tokens = await client.exchangeToken(authorizationCode);
// Returns: { access_token, id_token, refresh_token, token_type, expires_in }
```
The `exchangeToken()` method:
- Sends `POST /oauth/token` with `grant_type=authorization_code`
- Retrieves code verifier from `sessionStorage`
- Stores all tokens in `localStorage`
- Returns the full token response
### Step 4: Get User Info
```typescript
const userInfo = await client.verifyAuth();
if (userInfo) {
console.log('User ID:', userInfo.sub);
}
```
The `verifyAuth()` method:
- Sends `GET /oauth/userinfo` with Bearer token
- Automatically refreshes token on 401 error
- Returns user info or `null` if not authenticated
## Token Management
### Get Stored Tokens
```typescript
// Access token (for API calls)
const accessToken = client.getAccessToken();
// ID token (contains user claims)
const idToken = client.getIdToken();
// Refresh token (for getting new access tokens)
const refreshToken = client.getRefreshToken();
```
### Get Token Claims
```typescript
const claims = client.getAuthData();
if (claims) {
console.log('Subject (user ID):', claims.sub);
console.log('Issuer:', claims.iss);
console.log('Audience:', claims.aud);
console.log('Expires at:', new Date(claims.exp * 1000));
console.log('Issued at:', new Date(claims.iat * 1000));
}
```
### Check Token Expiry
```typescript
// Check if token is expired
if (client.isTokenExpired()) {
console.log('Token expired');
}
// Check if token will expire soon (within 5 minutes)
if (client.isAccessTokenExpired()) {
console.log('Token expiring soon, should refresh');
}
```
### Get User Subject
```typescript
const userId = client.getSubject();
// Returns the 'sub' claim from the token
```
## Refresh Tokens
Access tokens expire after a configured time (default: 30 minutes). Use refresh tokens to get new access tokens without re-authentication.
### Manual Refresh
```typescript
try {
const newTokens = await client.refreshAccessToken();
console.log('New access token:', newTokens.access_token);
} catch (error) {
// Refresh failed - user needs to re-authenticate
client.logout();
}
```
### Automatic Refresh
The SDK provides a helper for automatic token refresh:
```typescript
// Wrap any authenticated request with auto-refresh
const result = await client.withAutoRefresh(async () => {
return await fetchProtectedResource();
});
```
This will:
1. Execute your function
2. If it fails with 401, automatically refresh the token
3. Retry your function with the new token
4. If refresh fails, throw the original error
### Example: API Calls with Auto-Refresh
```typescript
async function fetchUserData() {
return await client.withAutoRefresh(async () => {
const response = await fetch('/api/user', {
headers: {
Authorization: `Bearer ${client.getAccessToken()}`
}
});
if (response.status === 401) {
const error = new Error('Unauthorized');
error.response = { status: 401 };
throw error;
}
return response.json();
});
}
```
## Logout
```typescript
client.logout();
// Clears all tokens from localStorage and sessionStorage
```
## Complete Example
```typescript
const client = new AlienSsoClient({
ssoBaseUrl: 'https://sso.alien-api.com',
providerAddress: 'your-provider-address',
});
async function authenticate() {
try {
// Check if already authenticated
const userInfo = await client.verifyAuth();
if (userInfo) {
console.log('Already authenticated:', userInfo.sub);
return;
}
// Generate deep link
const { deep_link, polling_code } = await client.generateDeeplink();
// Display QR code
displayQRCode(deep_link);
// Poll for authorization
const pollInterval = setInterval(async () => {
const response = await client.pollAuth(polling_code);
if (response.status === 'authorized') {
clearInterval(pollInterval);
// Exchange code for tokens
const tokens = await client.exchangeToken(response.authorization_code);
console.log('Got tokens:', tokens);
// Get user info
const userInfo = await client.verifyAuth();
console.log('User:', userInfo?.sub);
hideQRCode();
} else if (response.status === 'rejected' || response.status === 'expired') {
clearInterval(pollInterval);
console.error('Authentication failed:', response.status);
hideQRCode();
}
}, 5000);
} catch (error) {
console.error('Authentication error:', error);
}
}
```
## Storage Keys
The SDK uses the following browser storage keys:
### localStorage
| Key | Description |
|-----|-------------|
| `alien-sso_access_token` | JWT access token |
| `alien-sso_id_token` | JWT ID token |
| `alien-sso_refresh_token` | Refresh token |
| `alien-sso_token_expiry` | Token expiry timestamp |
### sessionStorage
| Key | Description |
|-----|-------------|
| `alien-sso_code_verifier` | PKCE code verifier (cleared after exchange) |
## Error Handling
```typescript
try {
const { deep_link, polling_code } = await client.generateDeeplink();
} catch (error) {
// Network error, server error, or invalid provider
console.error('Failed to generate deep link:', error.message);
}
try {
const tokens = await client.exchangeToken(authorizationCode);
} catch (error) {
// Invalid code, expired code, or missing code_verifier
console.error('Token exchange failed:', error.message);
}
try {
await client.refreshAccessToken();
} catch (error) {
// Refresh token expired or revoked
console.error('Refresh failed:', error.message);
client.logout(); // Force re-authentication
}
```
## Next Steps
- **[API Reference - Core](/sso-api-reference/api-reference-core)** - Complete API documentation
- **[React Integration Guide](/sso-guide/react-integration)** - For React applications
- **[Demo App](https://github.com/alien-id/sso-sdk-js/tree/main/apps)** - Example implementation
---
# React Integration Guide
This guide shows how to integrate Alien SSO into React applications using the React SDK with pre-built hooks and components.
## Requirements
- React 19.1.1 or higher
- React DOM 19.1.1 or higher
- A registered provider from the [developer portal](https://dev.alien.org) with provider address
## Installation
```sh
npm install @alien_org/sso-sdk-react
```
The React SDK automatically includes `@alien_org/sso-sdk-core` as a dependency.
## Setup
### Wrap Your App with Provider
```typescript
function App() {
return (
);
}
```
### Configuration Options
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `ssoBaseUrl` | string | Yes | Base URL of the SSO service |
| `providerAddress` | string | Yes | Your provider address from developer portal |
| `pollingInterval` | number | No | Polling interval in ms (default: 5000) |
## Using the useAuth Hook
The `useAuth()` hook provides access to authentication state and methods.
```typescript
function Dashboard() {
const { auth, logout } = useAuth();
if (!auth.isAuthenticated) {
return
;
}
return auth.sessionAddress ? : ;
}
```
---
## Error Handling
All async methods can throw errors. Handle them appropriately:
```typescript
const { wallet, generateDeeplink, verifyAttestation } = useSolanaAuth();
try {
const { deep_link, polling_code } = await generateDeeplink(
wallet.publicKey.toBase58()
);
} catch (error) {
console.error('Failed to generate deep link:', error);
}
try {
const sessionAddress = await verifyAttestation(
wallet.publicKey.toBase58()
);
} catch (error) {
console.error('Attestation verification failed:', error);
}
```
---
## Next Steps
- **[API Reference - Core](/solana-sso-api-reference/api-reference-core)** - Complete API documentation for Core SDK.
- **[React Integration Guide](/solana-sso-guide/react-integration)** - For React applications.
- **[Demo App](https://github.com/alien-id/sso-sdk-js/tree/main/apps)** - Example implementation and source code.
---
# Demo App - Solana
Explore a complete working example of Alien Solana SSO integration in a React application.
## Live Demo
Try the demo app to see how Alien Solana SSO works in practice.
**[View Demo App →](https://alien-id.github.io/sso-sdk-js/example-solana-sso-app)**
## Repository
The complete source code is available in the `sso-sdk-js` monorepo.
**Repository:** [github.com/alien-id/sso-sdk-js/tree/main/apps](https://github.com/alien-id/sso-sdk-js/tree/main/apps)
## Running Locally
### Prerequisites
- Node.js 18 or higher.
- npm or pnpm.
- Solana wallet browser extension (Phantom, Solflare, etc.).
### Setup
```sh
# Clone the repository
git clone https://github.com/alien-id/sso-sdk-js.git
cd sso-sdk-js
# Install dependencies
npm install
# Build all packages (required before running demo)
npm run build
cd ./apps/example-solana-sso-app/
# Copy .env.example and fill data
cp .env.example .env
# Run the Solana SSO example app
npm run dev
```
The demo app will be available at `http://localhost:3000` (or another port if 3000 is occupied).
## Project Structure
```
apps/example-solana-sso-app/
├── src/
│ ├── App.tsx # Main application component
│ ├── main.tsx # Entry point with providers
│ ├── components/
│ │ ├── WalletConnection.tsx # Wallet connection component
│ │ ├── Dashboard.tsx # Protected dashboard
│ │ └── LoginPage.tsx # Login page with SolanaSignInButton
│ └── ...
├── package.json
└── vite.config.ts
```
## Next Steps
- **[Core Integration Guide](/solana-sso-guide/core-integration)** - For vanilla JavaScript/TypeScript projects.
- **[React Integration Guide](/solana-sso-guide/react-integration)** - For React applications.
- **[API Reference - React](/solana-sso-api-reference/api-reference-react)** - Complete API documentation for React SDK.
---
# Install Boilerplate
Get started with the Alien Mini App boilerplate. This template
includes authentication, payments, database, and the Alien SDK
pre-configured.
## Prerequisites
- [Bun](https://bun.sh/) runtime installed
- [Docker](https://www.docker.com/) for local PostgreSQL
- An Alien account with a
[Mini App registered](/quickstart/create-miniapp) in the Dev Portal
## Clone the Repository
```sh
git clone https://github.com/alien-id/miniapp-boilerplate my-miniapp
cd my-miniapp
```
## Install Dependencies
```sh
bun install
```
## Project Structure
```text
my-miniapp/
├── app/
│ ├── api/
│ │ ├── me/route.ts
│ │ ├── invoices/route.ts
│ │ ├── transactions/route.ts
│ │ └── webhooks/payment/route.ts
│ ├── store/page.tsx
│ ├── profile/page.tsx
│ ├── explore/page.tsx
│ ├── layout.tsx
│ ├── page.tsx
│ ├── providers.tsx
│ ├── error.tsx
│ └── global-error.tsx
├── features/
│ ├── auth/
│ │ ├── components/
│ │ │ └── connection-status.tsx
│ │ └── lib.ts
│ ├── user/
│ │ ├── components/
│ │ │ └── user-info.tsx
│ │ ├── dto.ts
│ │ ├── hooks/
│ │ │ └── use-current-user.ts
│ │ └── queries.ts
│ ├── payments/
│ │ ├── components/
│ │ │ └── diamond-store.tsx
│ │ ├── hooks/
│ │ │ └── use-diamond-purchase.ts
│ │ ├── constants.ts
│ │ ├── dto.ts
│ │ └── queries.ts
│ └── navigation/
│ └── components/
│ └── tab-bar.tsx
├── lib/
│ ├── db/
│ │ ├── index.ts
│ │ └── schema.ts
│ └── env.ts
├── drizzle/
├── scripts/
│ └── migrate.ts
├── docker-compose.yml
├── .env.example
└── package.json
```
## Environment Variables
Copy the template and fill in your values:
```sh
cp .env.example .env
```
| Variable | Description |
| --- | --- |
| `DATABASE_URL` | PostgreSQL connection string |
| `WEBHOOK_PUBLIC_KEY` | Ed25519 public key (hex) for webhooks |
| `NEXT_PUBLIC_RECIPIENT_ADDRESS` | Solana wallet for USDC payments |
| `NEXT_PUBLIC_ALIEN_RECIPIENT_ADDRESS` | Alien provider address |
Default `.env` for local development:
```env
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/miniapp
WEBHOOK_PUBLIC_KEY=
NEXT_PUBLIC_RECIPIENT_ADDRESS=
NEXT_PUBLIC_ALIEN_RECIPIENT_ADDRESS=
NODE_ENV=development
```
### Where to get the values
- **`NEXT_PUBLIC_RECIPIENT_ADDRESS`** — any Solana wallet address
you control. This is where USDC payments will be sent.
- **`NEXT_PUBLIC_ALIEN_RECIPIENT_ADDRESS`** — your **provider
address** from the
[Alien Dev Portal](https://dev.alien.org/). Found on the
Webhooks or Mini Apps pages.
- **`WEBHOOK_PUBLIC_KEY`** — the Ed25519 public key (hex-encoded)
provided by the Dev Portal when you
[register a webhook](/react-sdk/payments#webhook-setup).
Copy it immediately after creation — it is only shown once.
## Start the Database
```sh
docker compose up -d
```
## Run Migrations
```sh
bun run db:migrate
```
## Start Development Server
```sh
bun run dev
```
Open [http://localhost:3000](http://localhost:3000) to see the app.
## What's Included
| Package | Purpose |
| --- | --- |
| `@alien_org/react` | React hooks and context provider |
| `@alien_org/auth-client` | JWT verification for backend |
| `drizzle-orm` | Type-safe database ORM |
| `next` | React framework (v16) |
| `@tanstack/react-query` | Data fetching and caching |
| `zod` | Schema validation for API payloads |
| `tailwindcss` | Utility-first CSS |
## Database
PostgreSQL with Drizzle ORM. Local setup uses Docker
(`docker-compose.yml`).
### Schema
**Users** (`users`):
| Column | Type | Description |
| --- | --- | --- |
| `id` | UUID | Auto-generated primary key |
| `alienId` | TEXT (unique) | User's Alien ID from JWT `sub` |
| `createdAt` | TIMESTAMP | Set on first auth |
| `updatedAt` | TIMESTAMP | Updated on each auth |
**Payment Intents** (`payment_intents`):
| Column | Type | Description |
| --- | --- | --- |
| `id` | UUID | Auto-generated primary key |
| `invoice` | TEXT (unique) | Invoice identifier |
| `senderAlienId` | TEXT | Payer's Alien ID |
| `recipientAddress` | TEXT | Recipient wallet address |
| `amount` | TEXT | Amount in smallest units |
| `token` | TEXT | Token type (`USDC` / `ALIEN`) |
| `network` | TEXT | Network (`solana` / `alien`) |
| `productId` | TEXT | Product identifier |
| `status` | TEXT | `pending` / `completed` / `failed` |
| `createdAt` | TIMESTAMP | Set on creation |
**Transactions** (`transactions`):
| Column | Type | Description |
| --- | --- | --- |
| `id` | UUID | Auto-generated primary key |
| `senderAlienId` | TEXT | Payer's Alien ID |
| `recipientAddress` | TEXT | Recipient wallet address |
| `txHash` | TEXT | On-chain transaction hash |
| `status` | TEXT | `paid` / `failed` |
| `amount` | TEXT | Payment amount |
| `token` | TEXT | Token type |
| `network` | TEXT | Network |
| `invoice` | TEXT | Associated invoice |
| `test` | TEXT | `"true"` if test transaction |
| `payload` | JSONB | Full webhook payload for audit |
| `createdAt` | TIMESTAMP | Set on creation |
### Commands
```sh
bun run db:generate # Generate migration from schema
bun run db:migrate # Apply pending migrations
bun run db:push # Push schema directly (dev only)
bun run db:studio # Open Drizzle Studio GUI
```
To run migrations automatically on server start,
set `RUN_MIGRATIONS=true`.
## API Endpoints
### `GET /api/me`
Returns the authenticated user. Requires Bearer token.
```json
{
"id": "uuid",
"alienId": "user-alien-id",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}
```
### `POST /api/invoices`
Creates a payment intent. Requires Bearer token.
**Request body:**
```json
{
"recipientAddress": "wallet-or-provider-address",
"amount": "10000",
"token": "USDC",
"network": "solana",
"productId": "usdc-diamonds-10"
}
```
**Response:**
```json
{
"invoice": "inv-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"id": "uuid"
}
```
### `GET /api/transactions`
Returns the authenticated user's transaction history.
Requires Bearer token.
### `POST /api/webhooks/payment`
Receives payment status updates from the Alien platform.
Verifies the `x-webhook-signature` header using Ed25519 against
`WEBHOOK_PUBLIC_KEY`. See
[Payments — Webhook Setup](/react-sdk/payments#webhook-setup)
for details.
## Deployment
1. Set up a PostgreSQL database and configure `DATABASE_URL`
2. [Register a webhook](/react-sdk/payments#webhook-setup) in the
Alien Dev Portal pointing to
`https:///api/webhooks/payment`
3. Set `WEBHOOK_PUBLIC_KEY`,
`NEXT_PUBLIC_RECIPIENT_ADDRESS`, and
`NEXT_PUBLIC_ALIEN_RECIPIENT_ADDRESS`
4. Either run `bun run db:migrate` manually or set
`RUN_MIGRATIONS=true` for auto-migration on start
5. Deploy
## Next Steps
- [Create Mini App](/quickstart/create-miniapp) — Register in the Dev Portal
- [Testing](/quickstart/testing) — Test locally and in the Alien app
- [Authentication](/react-sdk/auth) — Learn how auth works
- [Payments](/react-sdk/payments) — Accept payments
---
# Create a Mini App
This guide walks you through creating a mini app in the Alien
Developer Portal.
## Prerequisites
- An Alien account
- A web application ready to be hosted (or a planned URL)
## Step 1: Access the Developer Portal
1. Navigate to [https://dev.alien.org/](https://dev.alien.org/)
2. Sign in with your Alien account
3. Select **Mini Apps** from the dashboard
## Step 2: Create a New Mini App
Click **Create Mini App** to open the configuration form.
### Required Fields
| Field | Description |
| --- | --- |
| **Name** | Display name shown to users |
| **Description** | Short summary of what your app does |
| **Logo** | Icon displayed in the Alien app (512x512px) |
| **URL** | Public URL where your mini app is hosted |
| **Allowed Origins** | Domains authorized for the bridge |
### Example Configuration
```text
Name: My Game
Description: A fun puzzle game built for Alien
URL: https://mygame.example.com
Allowed Origins: https://mygame.example.com
```
## Step 3: Understanding Your Slug
When you create a mini app, a unique **slug** is automatically
generated from your app's name. This slug identifies your mini
app in the Alien ecosystem.
Users can launch your app via deeplink:
`https://alien.app/miniapp/{slug}`
## Step 4: Test Your Mini App
Once created, you can test your mini app:
1. **Browser Testing:** Run your app in a regular browser to test
UI and logic. The Bridge SDK will log warnings but won't block
development.
2. **In-App Testing:** Copy the deeplink from your mini app
dashboard in the Developer Portal and open it on your device.
This launches your mini app in the Alien app's WebView
environment.
## Next Steps
- [Install Boilerplate](/quickstart/install-boilerplate) — Pre-configured template
- [Testing](/quickstart/testing) — Test locally and in the Alien app
- [React SDK](/react-sdk) — React hooks and components
- [Authentication](/react-sdk/auth) — Implement user authentication
- [Payments](/react-sdk/payments) — Accept payments
---
# Testing
This guide covers how to test your mini app during development, from
local browser testing to full integration testing in the Alien app.
## Local Development
During development, you can run your mini app in a regular browser.
The SDK operates in **development mode** when the bridge is unavailable
(i.e., `window.__ALIEN_AUTH_TOKEN__` and other host-injected globals
are not present).
```sh
bun dev
# or: npm run dev
```
**What works in the browser:**
- UI rendering and styling
- Application logic and state management
- API calls to your backend
- Navigation and routing
**What doesn't work:**
- Bridge communication with the host app
- Native features (payments, clipboard, link opening, haptic feedback)
The SDK logs warnings instead of throwing errors, so your app won't
crash. Bridge functions return graceful fallbacks.
```typescript
// Check if running in Alien app
if (isBridgeAvailable()) {
// Bridge methods will work
} else {
// Dev mode - bridge methods log warnings and return gracefully
}
```
## Mock Bridge
For testing bridge interactions without the Alien app, use
`createMockBridge`. It injects a simulated bridge and launch params
into window globals, allowing all bridge methods to work.
```ts
const mock = createMockBridge({
launchParams: {
authToken: 'test-token',
contractVersion: '1.0.0',
platform: 'ios',
},
handlers: {
'payment:request': (payload) => ({
status: 'paid',
txHash: 'mock-tx-123',
reqId: payload.reqId,
}),
},
delay: 100,
});
// Later: clean up
mock.cleanup();
```
The mock bridge provides default responses for all request-response
methods (`payment:request`, `clipboard:read`, wallet methods).
Set a handler to `false` to suppress the response entirely.
See [Bridge Reference — Mock Bridge](/react-sdk/bridge#mock-bridge)
for full documentation.
## Testing in the Alien App
To test the full integration with native features, launch your mini
app inside the Alien app.
### Steps
1. Deploy your app to a publicly accessible URL
2. Go to your mini app dashboard in the
[Developer Portal](https://dev.alien.org/)
3. Copy the deeplink for your mini app
4. Open the deeplink on your device — your mini app launches in
the Alien app
### Launch Parameters
When the Alien app loads your mini app, it provides launch parameters.
Use the `useLaunchParams` hook to access them:
```tsx
function App() {
const launchParams = useLaunchParams();
if (!launchParams) {
return
Running outside Alien App
;
}
return
Platform: {launchParams.platform}
;
}
```
**Available parameters:**
| Parameter | Type | Description |
| --- | --- | --- |
| `authToken` | `string \| undefined` | JWT for backend authentication |
| `contractVersion` | `Version \| undefined` | SDK protocol version |
| `hostAppVersion` | `string \| undefined` | Host app version |
| `platform` | `'ios' \| 'android' \| undefined` | Platform identifier |
| `startParam` | `string \| undefined` | Custom parameter from deeplink |
| `safeAreaInsets` | `SafeAreaInsets \| undefined` | Safe area insets |
| `displayMode` | `DisplayMode` | Display mode of the mini app |
## Deeplink Testing
Share deeplinks to launch your mini app directly:
```text
https://alien.app/miniapp/{slug}
```
With a custom start parameter:
```text
https://alien.app/miniapp/{slug}?startParam={value}
```
The `startParam` value is available via `useLaunchParams()`.
Use this for:
- Referral tracking
- Deep navigation to specific content
- Campaign attribution
- Pre-filling form data
**Example:**
```text
https://alien.app/miniapp/my-game?startParam=level-5
```
```tsx
function Game() {
const launchParams = useLaunchParams();
useEffect(() => {
if (launchParams?.startParam === 'level-5') {
navigateToLevel(5);
}
}, [launchParams?.startParam]);
// ...
}
```
## Debugging Tips
### Check Bridge Availability
```tsx
function DebugInfo() {
const { isBridgeAvailable } = useAlien();
if (isBridgeAvailable) {
console.log('Running in Alien app');
} else {
console.log('Running in browser (dev mode)');
}
// ...
}
```
### Log Launch Parameters
```tsx
function DebugInfo() {
const launchParams = useLaunchParams();
console.log('Launch params:', {
authToken: launchParams?.authToken ? 'present' : 'missing',
contractVersion: launchParams?.contractVersion,
hostVersion: launchParams?.hostAppVersion,
platform: launchParams?.platform,
startParam: launchParams?.startParam,
displayMode: launchParams?.displayMode,
});
// ...
}
```
### Remote Debugging
**iOS:** Use Safari Web Inspector to debug the WebView.
1. Enable "Web Inspector" in Safari settings on your device
2. Connect your device to your Mac
3. Open Safari > Develop > [Your Device] > [Your App]
**Android:** Use Chrome DevTools.
1. Enable USB debugging on your device
2. Connect your device to your computer
3. Open `chrome://inspect` in Chrome
4. Select your WebView under "Remote Target"
### Common Issues
| Issue | Solution |
| --- | --- |
| Bridge methods return undefined | Verify you're running inside the Alien app |
| Auth token missing | Check that your app is properly registered |
| Start param not received | Verify the deeplink format is correct |
## Next Steps
- [Authentication](/react-sdk/auth) — Implement user authentication
- [Payments](/react-sdk/payments) — Accept payments with webhook verification
- [React SDK](/react-sdk) — Full hooks reference
---
# React SDK
The `@alien_org/react` package provides React hooks for building
mini apps. It wraps the low-level bridge API with React-friendly
state management.
It also re-exports bridge utilities (`send`, `request`,
`isAvailable`, `createMockBridge`, etc.) and contract types
for convenience. See [Bridge Reference](/react-sdk/bridge)
for full details on the low-level API.
## Installation
```sh
npm install @alien_org/react
```
## Setup
Wrap your app with `AlienProvider`:
```tsx
function App() {
return (
);
}
```
The provider initializes the bridge connection, reads launch parameters,
sets safe area CSS variables, and sends `app:ready` to the host app.
See [App Lifecycle](/react-sdk/lifecycle) for details on why `app:ready`
matters.
### AlienProvider Props
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `autoReady` | `boolean` | `true` | Auto-send `app:ready` on mount |
| `interceptLinks` | `boolean` | `true` | Intercept external link clicks |
Set `autoReady={false}` to [defer the ready signal](/react-sdk/lifecycle#deferred).
## Hooks Reference
### useAlien
Access the core context values.
```tsx
const { authToken, contractVersion, isBridgeAvailable, ready } = useAlien();
```
| Property | Type | Description |
| --- | --- | --- |
| `authToken` | `string \| undefined` | JWT from host app |
| `contractVersion` | `Version \| undefined` | SDK protocol version |
| `isBridgeAvailable` | `boolean` | `true` if running in Alien app |
| `ready` | `() => void` | Signal the host that the miniapp is ready |
The `ready` function is only needed when `autoReady={false}`.
### useLaunchParams
Get all launch parameters.
```tsx
const params = useLaunchParams();
```
Returns `undefined` if launch params are unavailable
(e.g., running in a browser).
| Property | Type | Description |
| --- | --- | --- |
| `authToken` | `string \| undefined` | JWT from host app |
| `contractVersion` | `Version \| undefined` | SDK protocol version |
| `hostAppVersion` | `string \| undefined` | Host app version |
| `platform` | `'ios' \| 'android' \| undefined` | Platform |
| `startParam` | `string \| undefined` | Custom deeplink parameter |
| `safeAreaInsets` | `SafeAreaInsets \| undefined` | Safe area insets |
| `displayMode` | `DisplayMode` | Display mode (see below) |
**DisplayMode** values: `'standard'` (default), `'fullscreen'`,
`'immersive'`.
### useEvent
Subscribe to events from the host app. Cleanup is automatic on unmount.
```tsx
// Handle back button
useEvent('host.back.button:clicked', () => {
navigateBack();
});
```
### useMethod
Generic hook for request-response bridge methods.
Handles loading, error states, and version checking automatically.
```tsx
const { execute, data, error, isLoading, reset, supported } =
useMethod(
'payment:request',
'payment:response',
{ checkVersion: true }, // default
);
const { data, error } = await execute({
recipient: 'wallet-address',
amount: '1000000',
token: 'USDC',
network: 'solana',
invoice: 'order-123',
});
```
**Options (third argument):**
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `checkVersion` | `boolean` | `true` | Check version support before executing |
When `checkVersion` is `true` and the method is unsupported,
`execute()` sets `error` to a `MethodNotSupportedError` without
making a bridge call.
**Return values:**
| Property | Type | Description |
| --- | --- | --- |
| `execute` | `(params, opts?) => Promise<{ data, error }>` | Send method |
| `data` | `EventPayload \| undefined` | Response data |
| `error` | `Error \| undefined` | Error if failed |
| `isLoading` | `boolean` | Request in progress |
| `reset` | `() => void` | Reset state |
| `supported` | `boolean` | Method supported by host |
### useBackButton
Control the host app's native back button. The button is automatically
hidden on unmount.
```tsx
const { show, hide, isVisible, supported } = useBackButton(() => {
navigate(-1);
});
useEffect(() => {
show();
return () => hide();
}, [show, hide]);
```
| Property | Type | Description |
| --- | --- | --- |
| `show` | `() => void` | Show the back button |
| `hide` | `() => void` | Hide the back button |
| `isVisible` | `boolean` | Whether the back button is visible |
| `supported` | `boolean` | Whether back button is supported by host |
See [App Lifecycle](/react-sdk/lifecycle) for more details.
### usePayment
Specialized hook for payments with full state management.
See [Payments](/react-sdk/payments) for full documentation.
```tsx
const { pay, isPaid, isLoading, txHash, errorCode } = usePayment({
onPaid: (txHash) => console.log('Paid!', txHash),
onCancelled: () => console.log('Cancelled'),
onFailed: (code) => console.log('Failed:', code),
});
await pay({
recipient: 'wallet-address',
amount: '10000',
token: 'USDC',
network: 'solana',
invoice: 'order-123',
});
```
### useClipboard
Read and write to the system clipboard via the host app.
See [Clipboard](/react-sdk/clipboard) for full documentation.
```tsx
const { writeText, readText, isReading, errorCode, supported } = useClipboard();
```
### useHaptic
Trigger haptic feedback on the user's device. All methods are
fire-and-forget. See [Haptic Feedback](/react-sdk/haptic) for full
documentation.
```tsx
const { impactOccurred, notificationOccurred, selectionChanged, supported } = useHaptic();
impactOccurred('medium');
notificationOccurred('success');
selectionChanged();
```
| Property | Type | Description |
| --- | --- | --- |
| `impactOccurred` | `(style) => void` | Trigger impact feedback |
| `notificationOccurred` | `(type) => void` | Trigger notification feedback |
| `selectionChanged` | `() => void` | Trigger selection feedback |
| `supported` | `boolean` | Whether haptic methods are supported |
**Impact styles:** `'light'`, `'medium'`, `'heavy'`, `'soft'`, `'rigid'`
**Notification types:** `'success'`, `'warning'`, `'error'`
### useIsMethodSupported
Check if a method is supported by the host app version.
```tsx
const { supported, minVersion, contractVersion } = useIsMethodSupported(
'payment:request',
);
if (!supported) {
return
Update the Alien app (requires {minVersion})
;
}
```
### useLinkInterceptor
Intercept external link clicks and route them through the host app.
`AlienProvider` enables this by default.
See [Link Management](/react-sdk/links) for full documentation.
```tsx
useLinkInterceptor({ openMode: 'external' });
```
## Next Steps
- [App Lifecycle](/react-sdk/lifecycle) — `app:ready` and back button
- [Safe Area Insets](/react-sdk/safe-area) — Handle notches and home indicators
- [Authentication](/react-sdk/auth) — User authentication
- [Payments](/react-sdk/payments) — Accept payments
- [Clipboard](/react-sdk/clipboard) — System clipboard access
- [Haptic Feedback](/react-sdk/haptic) — Haptic feedback
- [Link Management](/react-sdk/links) — Open URLs through the host app
- [Bridge Reference](/react-sdk/bridge) — Low-level bridge API
- [Create Mini App](/quickstart/create-miniapp) — Register your mini app
- [Testing](/quickstart/testing) — Test your mini app
---
# Payments
Accept payments in your mini app using the Alien React SDK. Users can
pay with USDC, SOL (on Solana) or ALIEN tokens directly from their
wallet.
## Overview
The payment flow involves both frontend and backend:
1. **Frontend** creates an invoice on your backend, then opens the
Alien payment UI
2. **User** approves the payment; the transaction is broadcast
3. **Frontend** immediately receives the result (paid, cancelled, or
broadcast failed)
4. **Backend** later receives a webhook when the transaction is
confirmed on-chain, and fulfills the order
```mermaid
sequenceDiagram
participant User
participant Frontend as Your Frontend
participant Backend as Your Backend
participant Alien as Alien Platform
User->>Frontend: 1. Clicks "Buy"
Frontend->>Backend: 2. Create invoice
Backend-->>Frontend: 3. Returns invoice ID
Frontend->>Alien: 4. pay() opens payment UI
User->>Alien: 5. Approves & signs
Note over Alien: 6. Broadcast tx
Alien-->>Frontend: 7. Payment result (paid/cancelled/failed)
Note right of Frontend: Client knows immediately
Alien->>Backend: 8. Webhook POST (signed, after confirmation)
Note over Backend: 9. Verify signature & fulfill order
```
## Prerequisites
- `@alien_org/react` installed and `AlienProvider` wrapping your app
- A [webhook registered](#webhook-setup) in the Developer Portal
- A backend endpoint to receive and verify webhook callbacks
## Supported Tokens
| Token | Network | Decimals | Recipient |
| --- | --- | --- | --- |
| USDC | `solana` | 6 | Your Solana wallet address |
| SOL | `solana` | 9 | Your Solana wallet address |
| ALIEN | `alien` | 9 | Your provider address from the Dev Portal |
## Using the usePayment Hook
The `usePayment` hook provides full state management for payments.
```tsx
function CheckoutButton() {
const { authToken } = useAlien();
const {
pay,
status,
isLoading,
isPaid,
isCancelled,
isFailed,
txHash,
errorCode,
error,
reset,
supported,
} = usePayment({
onPaid: (txHash) => {
console.log('Payment successful:', txHash);
},
onCancelled: () => {
console.log('Payment cancelled by user');
},
onFailed: (errorCode, error) => {
console.error('Payment failed:', errorCode, error);
},
});
const handlePayment = async () => {
// Step 1: Create invoice on your backend
const res = await fetch('/api/invoices', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify({
recipientAddress: 'YOUR_WALLET_ADDRESS',
amount: '10000',
token: 'USDC',
network: 'solana',
productId: 'my-product',
}),
});
const { invoice } = await res.json();
// Step 2: Open Alien payment UI
await pay({
recipient: 'YOUR_WALLET_ADDRESS',
amount: '10000',
token: 'USDC',
network: 'solana',
invoice,
item: {
title: 'Premium Plan',
iconUrl: 'https://example.com/icon.png',
quantity: 1,
},
});
};
if (!supported) {
return
Payments not supported in this environment
;
}
return (
);
}
```
## Hook Options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `timeout` | `number` | `120000` | Payment timeout in ms (2 min) |
| `onPaid` | `(txHash: string) => void` | - | Called on success |
| `onCancelled` | `() => void` | - | Called on user cancel |
| `onFailed` | `(code, error?) => void` | - | Called on failure |
| `onStatusChange` | `(status) => void` | - | Called on any change |
## Hook Return Values
| Value | Type | Description |
| --- | --- | --- |
| `pay` | `(params) => Promise` | Initiates a payment |
| `status` | `PaymentStatus` | `'idle'`, `'loading'`, `'paid'`, etc. |
| `isLoading` | `boolean` | Payment is in progress |
| `isPaid` | `boolean` | Payment completed successfully |
| `isCancelled` | `boolean` | User cancelled the payment |
| `isFailed` | `boolean` | Payment failed |
| `txHash` | `string \| undefined` | Transaction hash (when paid) |
| `errorCode` | `PaymentErrorCode \| undefined` | Error code (when failed) |
| `error` | `Error \| undefined` | Error object (when failed) |
| `reset` | `() => void` | Reset state to idle |
| `supported` | `boolean` | Whether payments are supported |
## Payment Parameters
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `recipient` | `string` | Yes | Wallet address to receive payment |
| `amount` | `string` | Yes | Amount in token's smallest unit |
| `token` | `string` | Yes | `'USDC'`, `'SOL'`, `'ALIEN'`, or identifier |
| `network` | `string` | Yes | `'solana'` or `'alien'` |
| `invoice` | `string` | Yes | Unique payment identifier |
| `item` | `object` | No | Display info for approval screen |
| `item.title` | `string` | Yes* | Item title |
| `item.iconUrl` | `string` | Yes* | Item icon URL |
| `item.quantity` | `number` | Yes* | Item quantity |
| `test` | `PaymentTestScenario` | No | Test scenario string |
*Required if `item` is provided.
## Invoice
The `invoice` field is a memo string attached to the on-chain
transaction. It serves as a unique identifier that links the payment
to an order in your backend.
You can use **any format** for the invoice value — UUID, SHA-256 hash,
Nano ID, or any custom string — as long as it is unique per payment
and your backend can look it up when the webhook arrives.
> **Size limit:** The invoice is written to the transaction memo and
> **must be strictly less than 64 bytes** (512 bits). SHA-256 hashes
> (32 bytes raw), UUIDs (36 chars), and Nano IDs (21 chars) all fit
> comfortably within this limit.
Examples of valid invoice values:
```typescript
// UUID (36 bytes)
const invoice = crypto.randomUUID();
// SHA-256 as base64url (43 bytes) — fits well under 64
const invoice = createHash('sha256')
.update(orderId)
.digest('base64url');
// Nano ID (21 bytes)
const invoice = nanoid();
// Custom short ID
const invoice = `ord_${Date.now()}`;
```
## Error Codes
| Code | Description |
| --- | --- |
| `insufficient_balance` | User does not have enough tokens |
| `network_error` | Blockchain network issue |
| `unknown` | Unexpected error occurred |
## Webhook Setup
Before accepting payments, you must register a webhook in the
[Alien Dev Portal](https://dev.alien.org/):
1. Go to the [**Webhooks**](https://dev.alien.org/dashboard/webhooks)
page
2. Click **Create webhook**
3. Select your **Mini App** from the dropdown
4. Set the **Webhook URL** to your payment endpoint (any URL that can
receive POST requests):
```text
https:///your-webhook-path
```
If you're using the
[Next.js boilerplate](https://github.com/alien-id/miniapp-boilerplate),
this is `https:///api/webhooks/payment`.
5. Optionally add your **Solana pubkey** if you want to receive USDC
and SOL payments
6. Click **Create**
After creation, you will be shown your **webhook public key**
(Ed25519, hex-encoded). **Copy it immediately** — this is your
`WEBHOOK_PUBLIC_KEY` environment variable. It is only shown once.
If you need to rotate the public key, open your webhook in the Dev
Portal and click **Rotate key**. A new key will be generated without
recreating the webhook — just update `WEBHOOK_PUBLIC_KEY` on your
server.
### Webhook Payload
The Alien platform sends a `POST` request to your webhook URL with
the following JSON body:
```typescript
interface WebhookPayload {
invoice: string;
recipient: string;
status: 'finalized' | 'failed';
txHash?: string;
amount?: string;
token?: string;
network?: string;
test?: boolean;
}
```
### Webhook Signature Verification
> **Security: Always verify the webhook signature.** Without
> verification, any external party could send fake webhook requests
> to your endpoint and trick your backend into fulfilling orders
> that were never paid.
Every webhook request includes an `x-webhook-signature` header
containing an Ed25519 signature of the raw request body. You **must**
verify this signature before processing the payload — reject the
request if the signature is missing or invalid.
The public key used to verify signatures is shown **once** when you
[create your webhook](#webhook-setup) in the Dev Portal. Store it as
the `WEBHOOK_PUBLIC_KEY` environment variable on your server.
#### Verification Algorithm
1. Read the raw request body as a UTF-8 string
2. Get the `x-webhook-signature` header value (hex-encoded Ed25519
signature)
3. Import your `WEBHOOK_PUBLIC_KEY` (hex-encoded Ed25519 public key)
4. Verify the signature against the raw body using Ed25519
#### Implementation (Web Crypto API)
```typescript
async function verifySignature(
publicKeyHex: string,
signatureHex: string,
body: string,
): Promise {
// Import the Ed25519 public key from hex
const publicKey = await crypto.subtle.importKey(
'raw',
Buffer.from(publicKeyHex, 'hex'),
{ name: 'Ed25519' },
false,
['verify'],
);
// Verify the signature
return crypto.subtle.verify(
'Ed25519',
publicKey,
Buffer.from(signatureHex, 'hex'),
Buffer.from(body),
);
}
```
### Complete Webhook Handler
Here is a complete webhook handler example based on the
[Next.js boilerplate](https://github.com/alien-id/miniapp-boilerplate).
Adapt the route path and database logic to your own backend:
```typescript
async function verifySignature(
publicKeyHex: string,
signatureHex: string,
body: string,
): Promise {
const publicKey = await crypto.subtle.importKey(
'raw',
Buffer.from(publicKeyHex, 'hex'),
{ name: 'Ed25519' },
false,
['verify'],
);
return crypto.subtle.verify(
'Ed25519',
publicKey,
Buffer.from(signatureHex, 'hex'),
Buffer.from(body),
);
}
// 1. Read the raw body (must be read before parsing JSON)
const rawBody = await request.text();
// 2. Get the signature from headers
const signatureHex =
request.headers.get('x-webhook-signature') ?? '';
if (!signatureHex) {
return NextResponse.json(
{ error: 'Missing webhook signature' },
{ status: 401 },
);
}
try {
// 3. Verify the Ed25519 signature
const isValid = await verifySignature(
process.env.WEBHOOK_PUBLIC_KEY!,
signatureHex,
rawBody,
);
if (!isValid) {
return NextResponse.json(
{ error: 'Invalid signature' },
{ status: 401 },
);
}
// 4. Parse and process the payload
const payload = JSON.parse(rawBody);
// 5. Find the order by invoice and update status
if (payload.status === 'finalized') {
// Transaction confirmed on-chain — fulfill the order
await fulfillOrder(payload.invoice, payload.txHash);
} else if (payload.status === 'failed') {
// Transaction failed — handle accordingly
await markOrderFailed(payload.invoice);
}
return NextResponse.json({ success: true });
} catch (error) {
console.error('Webhook processing error:', error);
return NextResponse.json(
{ error: 'Failed to process webhook' },
{ status: 500 },
);
}
}
```
### Webhook Statuses
| Status | Description | Action |
| --- | --- | --- |
| `finalized` | Transaction confirmed on-chain | Fulfill order |
| `failed` | Transaction failed on-chain | Mark order as failed |
## Test Mode
Test payments without transferring real tokens by passing a test
scenario string. The `test` parameter accepts a `PaymentTestScenario`
string that simulates different outcomes:
```tsx
await pay({
recipient: 'YOUR_WALLET_ADDRESS',
amount: '10000',
token: 'USDC',
network: 'solana',
invoice: 'test-order-123',
test: 'paid', // Simulate successful payment
});
```
### Test Scenarios
| Scenario | Client sees | Webhook sent | Webhook status |
| --- | --- | --- | --- |
| `'paid'` | `paid` | Yes | `finalized` |
| `'paid:failed'` | `paid` | Yes | `failed` |
| `'cancelled'` | `cancelled` | No | - |
| `'error:insufficient_balance'` | `failed` | No | - |
| `'error:network_error'` | `failed` | No | - |
| `'error:unknown'` | `failed` | No | - |
- **`'paid'`** — Happy path. Client shows success, webhook reports
`finalized`.
- **`'paid:failed'`** — Client shows success, but the webhook reports
`failed`. Useful for testing rollback logic.
- **`'cancelled'`** — Simulates user cancellation. No webhook is sent.
- **`'error:*'`** — Pre-broadcast failures. No transaction is created
and no webhook is sent.
Test transactions are marked with `test: true` in the webhook payload.
## Handling Payment States
```tsx
function PaymentStatus() {
const {
status, isPaid, isFailed, isCancelled,
txHash, errorCode, reset,
} = usePayment();
if (isPaid) {
return (
;
}
return (
);
}
```
### Backend Invoice Route
Example using Next.js App Router (adapt to your framework).
This route authenticates the user, validates the request body,
verifies the product parameters match your catalog, and creates a
payment intent:
```typescript
const authClient = createAuthClient();
function extractBearerToken(
header: string | null,
): string | null {
return header?.startsWith('Bearer ')
? header.slice(7)
: null;
}
try {
const token = extractBearerToken(
request.headers.get('Authorization'),
);
if (!token) {
return NextResponse.json(
{ error: 'Missing authorization token' },
{ status: 401 },
);
}
// Verify JWT — `sub` is the user's Alien ID
const { sub } = await authClient.verifyToken(token);
const body = await request.json();
const {
recipientAddress, amount,
token: paymentToken, network, productId,
} = body;
// Validate that the product parameters match your catalog
// to prevent clients from sending arbitrary amounts
const product = PRODUCTS.find((p) => p.id === productId);
if (
!product ||
product.amount !== amount ||
product.token !== paymentToken ||
product.network !== network ||
product.recipientAddress !== recipientAddress
) {
return NextResponse.json(
{ error: 'Invalid product parameters' },
{ status: 400 },
);
}
// Any unique string under 64 bytes
const invoice = randomUUID();
// Save payment intent to your database
await db.paymentIntents.create({
invoice,
senderAlienId: sub,
recipientAddress,
amount,
token: paymentToken,
network,
productId,
status: 'pending',
});
return NextResponse.json({ invoice });
} catch (error) {
if (error instanceof JwtErrors.JWTExpired) {
return NextResponse.json(
{ error: 'Token expired' },
{ status: 401 },
);
}
if (error instanceof JwtErrors.JOSEError) {
return NextResponse.json(
{ error: 'Invalid token' },
{ status: 401 },
);
}
console.error('Failed to create invoice:', error);
return NextResponse.json(
{ error: 'Failed to create invoice' },
{ status: 500 },
);
}
}
```
> **Important:** Always validate that the product parameters (amount,
> token, network, recipient) match your server-side product catalog.
> Never trust client-submitted amounts directly.
## Next Steps
- [Install Boilerplate](/quickstart/install-boilerplate) — Pre-configured template
- [Authentication](/react-sdk/auth) — Authenticate users before checkout
- [Developer Portal](https://dev.alien.org/) — Configure webhooks
---
# Authentication
Authenticate users in your Alien mini app. The host app provides a JWT
token that identifies the user, which you can verify on your backend.
## How It Works
```mermaid
sequenceDiagram
participant Host as Alien App (Host)
participant Mini as Your Mini App
participant Backend as Your Backend
Host->>Mini: 1. Injects JWT via window globals
Note over Mini: 2. useAlien() reads token
Mini->>Backend: 3. Authorization: Bearer
Note over Backend: 4. Verify JWT with JWKS
Backend->>Mini: 5. Authenticated response
```
1. User opens your mini app inside the Alien app
2. Host app injects a JWT token via `window.__ALIEN_AUTH_TOKEN__`
3. `AlienProvider` reads the token and exposes it via `useAlien()`
4. Your mini app sends the token to your backend in the
`Authorization` header
5. Backend verifies the token using `@alien_org/auth-client`
(fetches public keys from JWKS)
6. Backend extracts the user's unique Alien ID from the `sub` claim
**Registration is implicit** — on first API call, the user is
automatically created in the database via a find-or-create pattern.
No signup flow needed.
## Frontend Setup
### Install the SDK
```sh
npm install @alien_org/react
```
### Configure the Provider
Wrap your app with `AlienProvider` to enable authentication:
```tsx
function App() {
return (
);
}
```
### Get the Auth Token
Use the `useAlien` hook to access the authentication token:
```tsx
function Profile() {
const { authToken, isBridgeAvailable } = useAlien();
if (!isBridgeAvailable) {
return
Open this app inside Alien to authenticate.
;
}
if (!authToken) {
return
Loading...
;
}
return
Authenticated
;
}
```
**Available context values:**
| Property | Type | Description |
| --- | --- | --- |
| `authToken` | `string \| undefined` | JWT token from the host app |
| `isBridgeAvailable` | `boolean` | `true` if running inside Alien app |
| `contractVersion` | `Version \| undefined` | SDK protocol version |
| `ready` | `() => void` | Signal the host app that the miniapp is ready |
The `ready` function is only needed when `autoReady` is `false`.
### Using Launch Parameters
For additional context about the launch environment,
use `useLaunchParams`:
```tsx
function App() {
const launchParams = useLaunchParams();
if (!launchParams) {
return
Running outside Alien App
;
}
return
Platform: {launchParams.platform}
;
}
```
| Parameter | Type | Description |
| --- | --- | --- |
| `authToken` | `string \| undefined` | JWT token |
| `contractVersion` | `Version \| undefined` | SDK protocol version |
| `platform` | `'ios' \| 'android' \| undefined` | Platform identifier |
| `startParam` | `string \| undefined` | Custom parameter from deeplink |
| `hostAppVersion` | `string \| undefined` | Version of the Alien app |
| `safeAreaInsets` | `SafeAreaInsets \| undefined` | Safe area insets |
| `displayMode` | `DisplayMode` | Display mode of the mini app |
## Backend Setup
### Install the Auth Client
```sh
npm install @alien_org/auth-client
```
### Verify Tokens
Create an auth client and verify incoming tokens:
```typescript
const authClient = createAuthClient();
async function verifyRequest(req: Request) {
const token = req.headers.get('Authorization')?.replace('Bearer ', '');
if (!token) {
throw new Error('Missing authorization token');
}
const tokenInfo = await authClient.verifyToken(token);
// tokenInfo.sub contains the user's unique Alien ID
return tokenInfo;
}
```
The auth client automatically fetches the public keys from the
JWKS endpoint:
```text
https://sso.alien-api.com/oauth/jwks
```
### Token Fields
Verified tokens contain these standard JWT claims:
| Field | Type | Description |
| --- | --- | --- |
| `iss` | `string` | Token issuer |
| `sub` | `string` | User's unique Alien ID |
| `aud` | `string \| string[]` | Token audience |
| `exp` | `number` | Expiration timestamp (Unix) |
| `iat` | `number` | Issued at timestamp (Unix) |
| `nonce` | `string \| undefined` | Nonce (if provided during auth) |
| `auth_time` | `number \| undefined` | Time of authentication (Unix) |
## Complete Example
### Frontend Component
```tsx
function UserProfile() {
const { authToken, isBridgeAvailable } = useAlien();
const [user, setUser] = useState<{ alienId: string } | null>(null);
const [error, setError] = useState(null);
useEffect(() => {
if (!authToken) return;
fetch('/api/me', {
headers: {
Authorization: `Bearer ${authToken}`,
},
})
.then((res) => {
if (!res.ok) throw new Error('Authentication failed');
return res.json();
})
.then(setUser)
.catch((err) => setError(err.message));
}, [authToken]);
if (!isBridgeAvailable) {
return
Please open this app inside Alien.
;
}
if (error) {
return
Error: {error}
;
}
if (!user) {
return
Loading...
;
}
return
Alien ID: {user.alienId}
;
}
function App() {
return (
);
}
```
### Backend API Route (Next.js)
```typescript
const authClient = createAuthClient();
const token = request.headers
.get('Authorization')
?.replace('Bearer ', '');
if (!token) {
return NextResponse.json(
{ error: 'Missing authorization token' },
{ status: 401 },
);
}
try {
const { sub } = await authClient.verifyToken(token);
// sub is the user's unique Alien ID
// Use find-or-create to register users implicitly
const user = await findOrCreateUser(sub);
return NextResponse.json({
id: user.id,
alienId: user.alienId,
createdAt: user.createdAt.toISOString(),
updatedAt: user.updatedAt.toISOString(),
});
} catch (error) {
if (error instanceof JwtErrors.JWTExpired) {
return NextResponse.json(
{ error: 'Token expired' },
{ status: 401 },
);
}
if (error instanceof JwtErrors.JOSEError) {
return NextResponse.json(
{ error: 'Invalid token' },
{ status: 401 },
);
}
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 },
);
}
}
```
### Express.js Middleware
```typescript
const authClient = createAuthClient();
interface AuthenticatedRequest extends Request {
user?: {
alienId: string;
exp: number;
iat: number;
};
}
async function authMiddleware(
req: AuthenticatedRequest,
res: Response,
next: NextFunction,
) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Missing authorization token' });
}
try {
const tokenInfo = await authClient.verifyToken(token);
req.user = {
alienId: tokenInfo.sub,
exp: tokenInfo.exp,
iat: tokenInfo.iat,
};
next();
} catch (error) {
if (error instanceof JwtErrors.JWTExpired) {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(401).json({ error: 'Invalid token' });
}
}
// Usage
app.get('/api/profile', authMiddleware, (req: AuthenticatedRequest, res) => {
res.json({ alienId: req.user?.alienId });
});
```
## Security Considerations
- **Always verify tokens on your backend.** Never trust the frontend.
- **Check token expiration.** The `exp` claim indicates when the token
expires. The auth client handles this automatically.
- **Use HTTPS.** Always transmit tokens over secure connections.
- **Do not log tokens.** Avoid logging full tokens in production.
- **Handle JWT errors.** Use `JwtErrors.JWTExpired` and
`JwtErrors.JOSEError` for specific error handling.
## Next Steps
- [Payments](/react-sdk/payments) — Accept payments in your mini app
- [Install Boilerplate](/quickstart/install-boilerplate) — Pre-configured template
---
# Bridge Reference
The `@alien_org/bridge` package provides low-level communication with the
Alien host app. Use this directly when you need fine-grained control or
aren't using React.
For React apps, prefer the higher-level hooks from `@alien_org/react`
([React SDK Overview](/react-sdk)).
## Installation
```sh
npm install @alien_org/bridge
```
## Core Functions
### send
Fire-and-forget method call. No response expected.
```ts
send('app:ready', {});
send('app:close', {});
send('clipboard:write', { text: 'Hello!' });
send('host.back.button:toggle', { visible: false });
send('link:open', { url: 'https://example.com' });
send('haptic:impact', { style: 'medium' });
```
### request
Send a method and wait for a response event. Returns a promise that
resolves with the response payload.
```ts
const response = await request(
'payment:request',
{ recipient, amount, token, network, invoice },
'payment:response',
{ timeout: 30000 },
);
```
**Options:**
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `timeout` | `number` | `30000` | Timeout in ms |
| `reqId` | `string` | auto | Request ID for correlation |
### on / off
Subscribe to events from the host app.
```ts
const handler = (payload) => {
console.log('Back button clicked');
};
on('host.back.button:clicked', handler);
// Later: unsubscribe
off('host.back.button:clicked', handler);
```
`on()` returns an unsubscribe function for convenience:
```ts
const unsubscribe = on('host.back.button:clicked', handler);
// Later:
unsubscribe();
```
### emit
Send an event (not a method). Rarely needed by mini apps.
```ts
emit('some:event', { data: 'value' });
```
### isBridgeAvailable
Check if running inside the Alien app.
```ts
if (isBridgeAvailable()) {
// Running in Alien app
} else {
// Browser dev mode
}
```
### isAvailable
Check if a specific method can be executed. Verifies bridge availability
and optionally checks version support.
```ts
if (isAvailable('payment:request', { version: contractVersion })) {
// Safe to call
}
```
### getLaunchParams
Get launch parameters injected by the host app.
Returns `undefined` if unavailable.
```ts
const params = getLaunchParams();
```
**Launch Parameters:**
| Parameter | Type | Description |
| --- | --- | --- |
| `authToken` | `string \| undefined` | JWT auth token |
| `contractVersion` | `Version \| undefined` | Contract version (semver) |
| `hostAppVersion` | `string \| undefined` | Host app version |
| `platform` | `'ios' \| 'android' \| undefined` | Platform |
| `startParam` | `string \| undefined` | Custom deeplink parameter |
| `safeAreaInsets` | `SafeAreaInsets \| undefined` | Safe area insets |
| `displayMode` | `DisplayMode` | Display mode (see below) |
**DisplayMode** values: `'standard'` (default), `'fullscreen'`,
`'immersive'`. Safe area inset values are in CSS pixels.
### retrieveLaunchParams
Strict version of `getLaunchParams` that throws `LaunchParamsError` if
params are unavailable.
```ts
try {
const params = retrieveLaunchParams();
} catch (error) {
if (error instanceof LaunchParamsError) {
console.log('Not running in Alien app');
}
}
```
### parseLaunchParams
Parse launch params from a JSON string.
```ts
const params = parseLaunchParams(jsonString);
```
### mockLaunchParamsForDev
Simulate launch params during development. Injects values into window
globals just like the host app would.
```ts
if (process.env.NODE_ENV === 'development') {
mockLaunchParamsForDev({
authToken: 'dev-token',
platform: 'ios',
contractVersion: '1.0.0',
displayMode: 'standard',
});
}
```
### clearMockLaunchParams
Remove mock launch params from window globals and sessionStorage.
```ts
clearMockLaunchParams();
```
### enableLinkInterceptor
Intercept external link clicks and route them through the bridge's
`link:open` method. Same-origin links are unaffected.
Returns an unsubscribe function.
```ts
const disable = enableLinkInterceptor({ openMode: 'external' });
// Later:
disable();
```
## Safe Variants
The bridge provides safe versions of `send` and `request` that never
throw. Instead, they return a `SafeResult` discriminated union.
```ts
type SafeResult = { ok: true; data: T } | { ok: false; error: Error };
```
### send.ifAvailable
Safe version of `send()`. Returns a `SafeResult`.
```ts
const result = send.ifAvailable(
'haptic:impact',
{ style: 'medium' },
{ version: contractVersion },
);
if (!result.ok) {
console.warn('Could not send:', result.error.message);
}
```
### request.ifAvailable
Safe version of `request()`. Returns a `Promise>`.
```ts
const result = await request.ifAvailable(
'payment:request',
{ recipient, amount, token, network, invoice },
'payment:response',
{ version: contractVersion },
);
if (result.ok) {
console.log('Payment:', result.data.status);
} else {
console.warn('Failed:', result.error.message);
}
```
Both safe variants accept an optional `version` option. When provided,
they check method support before executing and return a
`BridgeMethodUnsupportedError` if the method is not supported.
## Mock Bridge
For testing, use `createMockBridge` to simulate the host app environment.
It injects a mock bridge and launch params into window globals.
```ts
const mock = createMockBridge({
launchParams: {
authToken: 'test-token',
contractVersion: '1.0.0',
platform: 'ios',
},
handlers: {
'payment:request': (payload) => ({
status: 'paid',
txHash: 'mock-tx-123',
reqId: payload.reqId,
}),
},
delay: 100, // simulate async delay in ms
});
// Emit events to the miniapp
mock.emitEvent('host.back.button:clicked', {});
// Inspect method calls
const calls = mock.getCalls();
// Cleanup
mock.cleanup();
```
### Options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `launchParams` | `Partial` | see below | Mock launch params |
| `handlers` | `Record` | `{}` | Custom handlers |
| `delay` | `number` | `0` | Response delay in ms |
Default launch params: `authToken: 'mock-auth-token'`,
`contractVersion: '1.0.0'`, `platform: 'ios'`,
`displayMode: 'standard'`.
Set a handler to `false` to suppress the response entirely.
### Instance Methods
| Method | Description |
| --- | --- |
| `cleanup()` | Remove mock bridge and clear launch params |
| `emitEvent(name, payload)` | Emit an event to the miniapp |
| `getCalls()` | Get array of recorded method calls |
| `resetCalls()` | Clear recorded calls |
## Methods Reference
Methods are actions the mini app requests from the host app.
| Method | Payload | Description | Since |
| --- | --- | --- | --- |
| `app:ready` | `{}` | Signal mini app is ready | 0.0.9 |
| `app:close` | `{}` | Close the mini app | 1.0.0 |
| `host.back.button:toggle` | `{ visible }` | Show/hide back button | 1.0.0 |
| `payment:request` | See below | Request payment | 0.1.1 |
| `clipboard:write` | `{ text: string }` | Write to clipboard | 0.1.1 |
| `clipboard:read` | `{}` | Read from clipboard | 0.1.1 |
| `link:open` | `{ url, openMode? }` | Open a URL | 0.1.3 |
| `haptic:impact` | `{ style }` | Impact feedback | 0.2.4 |
| `haptic:notification` | `{ type }` | Notification feedback | 0.2.4 |
| `haptic:selection` | `{}` | Selection feedback | 0.2.4 |
| `wallet.solana:connect` | `{}` | Connect Solana wallet | 1.0.0 |
| `wallet.solana:disconnect` | `{}` | Disconnect wallet | 1.0.0 |
| `wallet.solana:sign.transaction` | `{ transaction }` | Sign tx | 1.0.0 |
| `wallet.solana:sign.message` | `{ message }` | Sign message | 1.0.0 |
| `wallet.solana:sign.send` | `{ transaction, ... }` | Sign+send | 1.0.0 |
### payment:request
```ts
{
recipient: string; // Wallet address
amount: string; // Amount in token's smallest unit
token: string; // 'USDC', 'ALIEN', or token identifier
network: string; // 'solana' or 'alien'
invoice: string; // Your order/invoice ID
item?: { // Optional display info
title: string;
iconUrl: string;
quantity: number;
};
test?: PaymentTestScenario; // Test mode scenario
}
```
**Test scenarios:** `'paid'`, `'paid:failed'`, `'cancelled'`,
`'error:insufficient_balance'`, `'error:network_error'`,
`'error:unknown'`
### link:open
```ts
{
url: string;
openMode?: 'external' | 'internal';
}
```
- `external` (default): Opens in system browser or appropriate app handler
- `internal`: Opens within the Alien app (other mini apps, webviews)
```ts
send('link:open', { url: 'https://example.com' });
send('link:open', { url: 'solana:...' });
send('link:open', { url: 'mailto:hi@example.com' });
send('link:open', {
url: 'https://alien.app/miniapp/other-app',
openMode: 'internal',
});
```
### haptic:impact
```ts
{
style: 'light' | 'medium' | 'heavy' | 'soft' | 'rigid';
}
```
### haptic:notification
```ts
{
type: 'success' | 'warning' | 'error';
}
```
### haptic:selection
```ts
{}
```
### wallet.solana:sign.transaction
```ts
{
transaction: string; // Base64-encoded serialized transaction
}
```
### wallet.solana:sign.message
```ts
{
message: string; // Base58-encoded message bytes
}
```
### wallet.solana:sign.send
```ts
{
transaction: string; // Base64-encoded
chain?: SolanaChain; // 'solana:mainnet' etc.
options?: {
skipPreflight?: boolean;
preflightCommitment?: SolanaCommitment;
commitment?: SolanaCommitment;
minContextSlot?: number;
maxRetries?: number;
};
}
```
**SolanaChain** values: `'solana:mainnet'`,
`'solana:devnet'`, `'solana:testnet'`.
**SolanaCommitment** values: `'processed'`,
`'confirmed'`, `'finalized'`.
## Events Reference
Events are notifications from the host app to the mini app.
| Event | Payload | Description | Since |
| --- | --- | --- | --- |
| `host.back.button:clicked` | `{}` | Back button pressed | 1.0.0 |
| `payment:response` | See below | Payment result | 0.1.1 |
| `clipboard:response` | See below | Clipboard read result | 0.1.1 |
| `wallet.solana:connect.response` | See below | Wallet connect result | 1.0.0 |
| `wallet.solana:sign.transaction.response` | See below | Sign tx | 1.0.0 |
| `wallet.solana:sign.message.response` | See below | Sign msg result | 1.0.0 |
| `wallet.solana:sign.send.response` | See below | Sign+send result | 1.0.0 |
### payment:response
```ts
{
reqId: string;
status: 'paid' | 'cancelled' | 'failed';
txHash?: string;
errorCode?: PaymentErrorCode;
}
```
**Error codes:** `insufficient_balance`, `network_error`, `unknown`
### clipboard:response
```ts
{
reqId: string;
text: string | null;
errorCode?: 'permission_denied' | 'unavailable';
}
```
### wallet.solana:connect.response
```ts
{
reqId: string;
publicKey?: string; // Base58-encoded
errorCode?: WalletSolanaErrorCode;
errorMessage?: string; // Human-readable error
}
```
### wallet.solana:sign.transaction.response
```ts
{
reqId: string;
signedTransaction?: string; // Base64-encoded
errorCode?: WalletSolanaErrorCode;
errorMessage?: string;
}
```
### wallet.solana:sign.message.response
```ts
{
reqId: string;
signature?: string; // Base58-encoded
publicKey?: string; // Base58-encoded
errorCode?: WalletSolanaErrorCode;
errorMessage?: string;
}
```
### wallet.solana:sign.send.response
```ts
{
reqId: string;
signature?: string; // Base58-encoded
errorCode?: WalletSolanaErrorCode;
errorMessage?: string;
}
```
### Wallet Error Codes
| Code | Name | Description |
| --- | --- | --- |
| `5000` | `USER_REJECTED` | User rejected the request |
| `-32602` | `INVALID_PARAMS` | Invalid request parameters |
| `-32603` | `INTERNAL_ERROR` | Internal wallet error |
| `8000` | `REQUEST_EXPIRED` | Request timed out |
## Error Handling
```ts
BridgeError,
BridgeTimeoutError,
BridgeUnavailableError,
BridgeWindowUnavailableError,
BridgeMethodUnsupportedError,
} from '@alien_org/bridge';
try {
const result = await request(
'payment:request',
params,
'payment:response',
);
} catch (error) {
if (error instanceof BridgeTimeoutError) {
// error.method, error.timeout
} else if (error instanceof BridgeMethodUnsupportedError) {
// error.method, error.contractVersion, error.minVersion
} else if (error instanceof BridgeUnavailableError) {
// Not running in Alien app
} else if (error instanceof BridgeWindowUnavailableError) {
// Window not available (SSR)
}
}
```
**Bridge errors** (from `@alien_org/bridge`):
| Error | Description |
| --- | --- |
| `BridgeError` | Base error class |
| `BridgeTimeoutError` | Request timed out |
| `BridgeUnavailableError` | Not in Alien app |
| `BridgeWindowUnavailableError` | Window undefined (SSR) |
| `BridgeMethodUnsupportedError` | Method not supported by host |
| `LaunchParamsError` | Launch params unavailable |
**React SDK errors** (from `@alien_org/react`):
| Error | Description |
| --- | --- |
| `ReactSDKError` | Base React SDK error |
| `MethodNotSupportedError` | Method unsupported by version |
`MethodNotSupportedError` is set by `useMethod` when
`checkVersion` is `true` and the method is not supported.
It has `method`, `contractVersion`, and `minVersion` properties.
## Dev Mode Behavior
When running in a browser (bridge unavailable):
- `send()` logs a warning, does nothing
- `request()` logs a warning, will eventually timeout
- `on()` logs a warning, returns no-op unsubscribe
- `isBridgeAvailable()` returns `false`
- `getLaunchParams()` returns `undefined`
- `send.ifAvailable()` returns `{ ok: false, error }`
- `request.ifAvailable()` returns `{ ok: false, error }`
Use `mockLaunchParamsForDev()` to simulate launch params,
or `createMockBridge()` for full bridge simulation.
## Next Steps
- [React SDK Overview](/react-sdk) — Higher-level React hooks
- [Safe Area Insets](/react-sdk/safe-area) — Handle notches and home indicators
- [Authentication](/react-sdk/auth) — User authentication
- [Payments](/react-sdk/payments) — Accept payments
- [Haptic Feedback](/react-sdk/haptic) — Haptic feedback
- [Quickstart](/quickstart/create-miniapp) — Create your first mini app
---