# 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
Not authenticated
; } return (

User ID: {auth.tokenInfo?.sub}

Expires: {new Date(auth.tokenInfo.exp * 1000).toLocaleString()}

); } ``` ### Auth State The `auth` object contains: ```typescript { isAuthenticated: boolean; token: string | null; // Access token tokenInfo: { iss: string; // Issuer sub: string; // User identifier aud: string | string[]; // Audience (your provider address) exp: number; // Expiration timestamp iat: number; // Issued at timestamp nonce?: string; auth_time?: number; } | null; } ``` ### Available Methods ```typescript const { client, // Direct access to AlienSsoClient instance auth, // Authentication state queryClient, // React Query client instance generateDeeplink, // Generate authentication deep link pollAuth, // Poll for authentication status exchangeToken, // Exchange authorization code for tokens verifyAuth, // Verify current token (calls /oauth/userinfo) refreshToken, // Refresh access token logout, // Clear authentication state openModal, // Open built-in sign-in modal closeModal, // Close sign-in modal isModalOpen // Modal open state } = useAuth(); ``` ## Using Pre-built Components ### SignInButton A pre-styled button that opens the sign-in modal. ```typescript function LoginPage() { return (

Welcome

); } ``` ### SignInModal The modal is automatically rendered by `AlienSsoProvider` and handles the complete authentication flow including QR code display, polling, and token exchange. Control it via the `useAuth()` hook: ```typescript function CustomButton() { const { openModal } = useAuth(); return ; } ``` ## Token Refresh The SDK provides a `refreshToken` method for refreshing access tokens: ```typescript function MyComponent() { const { refreshToken, auth, logout } = useAuth(); async function handleApiCall() { // Check if token is expiring soon if (auth.tokenInfo && auth.tokenInfo.exp * 1000 < Date.now() + 60000) { const success = await refreshToken(); if (!success) { // Refresh failed, redirect to login return; } } // Make API call with fresh token const response = await fetch('/api/data', { headers: { Authorization: `Bearer ${auth.token}` } }); } } ``` ### Automatic Token Refresh with Axios For automatic token refresh on API calls, use an axios interceptor: ```typescript const { auth, logout, refreshToken } = useAuth(); const isRefreshing = useRef(false); const failedQueue = useRef([]); return useMemo(() => { const instance = axios.create({ baseURL: '/api', }); // Add token to requests instance.interceptors.request.use((config) => { if (auth.token) { config.headers.Authorization = `Bearer ${auth.token}`; } return config; }); // Handle 401 responses instance.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; if (error.response?.status !== 401 || originalRequest._retry) { return Promise.reject(error); } if (isRefreshing.current) { // Queue request while refreshing return new Promise((resolve, reject) => { failedQueue.current.push({ resolve, reject }); }).then(() => instance(originalRequest)); } originalRequest._retry = true; isRefreshing.current = true; try { const success = await refreshToken(); if (success) { failedQueue.current.forEach((p) => p.resolve()); failedQueue.current = []; return instance(originalRequest); } throw new Error('Refresh failed'); } catch (refreshError) { failedQueue.current.forEach((p) => p.reject(refreshError)); failedQueue.current = []; logout(); return Promise.reject(refreshError); } finally { isRefreshing.current = false; } } ); return instance; }, [auth.token, logout, refreshToken]); } ``` ## Custom Authentication Flow If you want to implement a custom UI instead of using the built-in modal: ```typescript function CustomAuth() { const { generateDeeplink, pollAuth, exchangeToken, auth } = useAuth(); const [deepLink, setDeepLink] = useState(null); const [pollingCode, setPollingCode] = useState(null); const handleSignIn = async () => { const response = await generateDeeplink(); setDeepLink(response.deep_link); setPollingCode(response.polling_code); }; useEffect(() => { if (!pollingCode) return; const interval = setInterval(async () => { const response = await pollAuth(pollingCode); if (response.status === 'authorized') { clearInterval(interval); await exchangeToken(response.authorization_code!); setDeepLink(null); setPollingCode(null); } else if (response.status === 'rejected' || response.status === 'expired') { clearInterval(interval); setDeepLink(null); setPollingCode(null); } }, 5000); return () => clearInterval(interval); }, [pollingCode, pollAuth, exchangeToken]); if (auth.isAuthenticated) { return
Authenticated as {auth.tokenInfo?.sub}
; } if (deepLink) { return (

Scan with Alien App

); } return ; } ``` ## Protected Routes Create a protected route component: ```typescript function ProtectedRoute({ children }: { children: React.ReactNode }) { const { auth } = useAuth(); if (!auth.isAuthenticated) { return ; } return <>{children}; } // Usage } /> ``` ## Complete Example ```typescript function App() { return ( ); } function Dashboard() { const { auth, logout, verifyAuth } = useAuth(); // Verify token on mount useEffect(() => { if (auth.token) { verifyAuth(); } }, []); if (!auth.isAuthenticated) { return (

Welcome to My App

); } return (

Dashboard

User ID: {auth.tokenInfo?.sub}

Issuer: {auth.tokenInfo?.iss}

Expires: {new Date(auth.tokenInfo!.exp * 1000).toLocaleString()}

); } ``` ## TypeScript Support The React SDK is fully typed. Import types as needed: ```typescript AuthorizeResponse, PollResponse, TokenResponse, TokenInfo, } from '@alien_org/sso-sdk-core'; ``` ## Next Steps - **[API Reference - React](/sso-api-reference/api-reference-react)** - Complete API documentation - **[Demo App](https://github.com/alien-id/sso-sdk-js/tree/main/apps)** - Example implementation - **[Core Integration Guide](/sso-guide/core-integration)** - For vanilla JavaScript/TypeScript --- # OAuth2 Clients Integration Alien SSO is a fully OIDC-compliant identity provider. This means you can use **any standard OAuth 2.0 / OpenID Connect library** to integrate with it. ## Requirements Before integrating, ensure you have: - A registered provider from the [developer portal](https://dev.alien.org) with provider address - SSO base URL: `https://sso.alien-api.com` ## Key Configuration Notes | Setting | Value | |---------|-------| | Issuer URL | `https://sso.alien-api.com` | | Client ID | Your provider address | | Client Secret | Empty string (public client) | | Token Auth Method | `none` | | PKCE | Required (`S256`) | | Response Type | `code` | ## NextAuth.js (Auth.js) NextAuth.js (now Auth.js) is the most popular authentication library for Next.js applications. ### Basic Setup ```typescript // src/auth.ts 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", }, }, }], }) ``` ```typescript // src/app/api/auth/[...nextauth]/route.ts ``` ### With Refresh Tokens For long-lived sessions with automatic token refresh: ```typescript // src/auth.ts // Token refresh function async function refreshAccessToken(token: JWT): Promise { try { const response = await fetch("https://sso.alien-api.com/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: token.refreshToken as string, client_id: process.env.ALIEN_PROVIDER_ADDRESS!, }), }) const refreshedTokens = await response.json() if (!response.ok) { throw refreshedTokens } return { ...token, accessToken: refreshedTokens.access_token, accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000, refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, idToken: refreshedTokens.id_token ?? token.idToken, } } catch (error) { console.error("Error refreshing access token:", error) return { ...token, error: "RefreshAccessTokenError", } } } providers: [{ id: "alien", name: "Alien", type: "oidc", issuer: "https://sso.alien-api.com", clientId: process.env.ALIEN_PROVIDER_ADDRESS!, clientSecret: "", client: { token_endpoint_auth_method: "none", }, checks: ["pkce", "state"], authorization: { params: { scope: "openid", }, }, }], callbacks: { async jwt({ token, account }) { // Initial sign in if (account) { return { ...token, accessToken: account.access_token, accessTokenExpires: account.expires_at! * 1000, refreshToken: account.refresh_token, idToken: account.id_token, } } // Return previous token if the access token has not expired yet if (Date.now() < (token.accessTokenExpires as number)) { return token } // Access token has expired, try to refresh it return await refreshAccessToken(token) }, async session({ session, token }) { session.accessToken = token.accessToken as string session.error = token.error as string | undefined return session }, }, }) ``` ### Extend Session Types ```typescript // src/types/next-auth.d.ts declare module "next-auth" { interface Session { accessToken?: string error?: string } } declare module "next-auth/jwt" { interface JWT { accessToken?: string accessTokenExpires?: number refreshToken?: string idToken?: string error?: string } } ``` ### Handle Token Refresh Errors ```typescript // src/components/SessionProvider.tsx "use client" const { data: session } = useSession() useEffect(() => { if (session?.error === "RefreshAccessTokenError") { // Force sign in to resolve the error signIn("alien") } }, [session?.error]) return <>{children} } ``` ### Usage in Components ```typescript // src/app/page.tsx const session = await auth() if (!session) { return (
{ "use server" await signIn("alien") }}>
) } return (

Welcome, {session.user?.name}

User ID: {session.user?.id}

{ "use server" await signOut() }}>
) } ``` ### API Route with Token ```typescript // src/app/api/protected/route.ts const session = await auth() if (!session?.accessToken) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) } // Use accessToken for downstream API calls const response = await fetch("https://your-api.com/data", { headers: { Authorization: `Bearer ${session.accessToken}`, }, }) return NextResponse.json(await response.json()) } ``` --- ## Generic OAuth 2.0 Client For any language or framework, here's how to implement the OAuth 2.0 flow manually: ### 1. Discovery Document First, fetch the OIDC configuration: ```typescript const discovery = await fetch( "https://sso.alien-api.com/.well-known/openid-configuration" ) const config = await discovery.json() // config contains: // { // authorization_endpoint: "https://sso.alien-api.com/oauth/authorize", // token_endpoint: "https://sso.alien-api.com/oauth/token", // userinfo_endpoint: "https://sso.alien-api.com/oauth/userinfo", // jwks_uri: "https://sso.alien-api.com/oauth/jwks", // ... // } ``` ### 2. Generate PKCE Challenge ```typescript function generateCodeVerifier(): string { return crypto.randomBytes(32).toString("base64url") } function generateCodeChallenge(verifier: string): string { return crypto.createHash("sha256").update(verifier).digest("base64url") } const codeVerifier = generateCodeVerifier() const codeChallenge = generateCodeChallenge(codeVerifier) // Store codeVerifier in session for later use ``` ### 3. Authorization Request ```typescript const authUrl = new URL("https://sso.alien-api.com/oauth/authorize") authUrl.searchParams.set("response_type", "code") authUrl.searchParams.set("client_id", "your-provider-address") authUrl.searchParams.set("redirect_uri", "https://your-app.com/callback") authUrl.searchParams.set("scope", "openid") authUrl.searchParams.set("state", generateRandomState()) authUrl.searchParams.set("code_challenge", codeChallenge) authUrl.searchParams.set("code_challenge_method", "S256") // Redirect user to authUrl.toString() ``` ### 4. Token Exchange ```typescript async function exchangeCode(code: string, codeVerifier: string) { const response = await fetch("https://sso.alien-api.com/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "authorization_code", code, client_id: "your-provider-address", redirect_uri: "https://your-app.com/callback", code_verifier: codeVerifier, }), }) return response.json() // Returns: { access_token, id_token, refresh_token, expires_in, token_type } } ``` ### 5. Refresh Token ```typescript async function refreshToken(refreshToken: string) { const response = await fetch("https://sso.alien-api.com/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: "your-provider-address", }), }) return response.json() } ``` ### 6. Verify Token (UserInfo) ```typescript async function getUserInfo(accessToken: string) { const response = await fetch("https://sso.alien-api.com/oauth/userinfo", { headers: { Authorization: `Bearer ${accessToken}`, }, }) return response.json() // Returns: { sub: "user-session-address" } } ``` ### 7. Verify JWT Locally ```typescript const client = jwksClient({ jwksUri: "https://sso.alien-api.com/oauth/jwks", cache: true, rateLimit: true, }) async function verifyToken(token: string): Promise { const decoded = jwt.decode(token, { complete: true }) if (!decoded) throw new Error("Invalid token") const key = await client.getSigningKey(decoded.header.kid) const publicKey = key.getPublicKey() return jwt.verify(token, publicKey, { algorithms: ["RS256"], issuer: "https://sso.alien-api.com", audience: "your-provider-address", }) } ``` --- ## Python (Authlib) ```python from authlib.integrations.flask_client import OAuth oauth = OAuth() oauth.register( name='alien', client_id='your-provider-address', client_secret='', server_metadata_url='https://sso.alien-api.com/.well-known/openid-configuration', client_kwargs={ 'scope': 'openid', 'code_challenge_method': 'S256', 'token_endpoint_auth_method': 'none', }, ) # Login route @app.route('/login') def login(): redirect_uri = url_for('callback', _external=True) return oauth.alien.authorize_redirect(redirect_uri) # Callback route @app.route('/callback') def callback(): token = oauth.alien.authorize_access_token() user_info = oauth.alien.userinfo() # Store token and user_info in session return redirect('/dashboard') ``` --- ## Go (golang.org/x/oauth2) ```go package main "context" "golang.org/x/oauth2" ) var oauth2Config = &oauth2.Config{ ClientID: "your-provider-address", ClientSecret: "", // Public client Endpoint: oauth2.Endpoint{ AuthURL: "https://sso.alien-api.com/oauth/authorize", TokenURL: "https://sso.alien-api.com/oauth/token", }, RedirectURL: "http://localhost:8080/callback", Scopes: []string{"openid"}, } // Generate auth URL with PKCE func getAuthURL(state string, codeVerifier string) string { return oauth2Config.AuthCodeURL( state, oauth2.S256ChallengeOption(codeVerifier), ) } // Exchange code for token func exchangeToken(ctx context.Context, code, codeVerifier string) (*oauth2.Token, error) { return oauth2Config.Exchange( ctx, code, oauth2.VerifierOption(codeVerifier), ) } // Refresh token func refreshToken(ctx context.Context, token *oauth2.Token) (*oauth2.Token, error) { tokenSource := oauth2Config.TokenSource(ctx, token) return tokenSource.Token() } ``` --- ## Environment Variables For any integration, set these environment variables: ```bash # .env ALIEN_PROVIDER_ADDRESS=your-provider-address ALIEN_SSO_URL=https://sso.alien-api.com ``` ## Next Steps - **[SSO Introduction](/sso-guide/introduction)** - Overview and OIDC endpoints - **[Core Integration Guide](/sso-guide/core-integration)** - Use our JavaScript SDK - **[React Integration Guide](/sso-guide/react-integration)** - React SDK with hooks - **[API Reference](/sso-api-reference/api-reference-core)** - Complete SDK documentation --- # API Reference - Core Complete API documentation for `@alien_org/sso-sdk-core`. ## AlienSsoClient Main client class for Alien SSO authentication. ### Constructor ```typescript new AlienSsoClient(config: AlienSsoClientConfig) ``` **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `ssoBaseUrl` | string | Yes | Base URL of the SSO service | | `providerAddress` | string | Yes | Your provider address | | `pollingInterval` | number | No | Polling interval in ms (default: 5000) | **Example:** ```typescript const client = new AlienSsoClient({ ssoBaseUrl: 'https://sso.alien-api.com', providerAddress: 'your-provider-address', pollingInterval: 5000 }); ``` --- ## Authentication Methods ### generateDeeplink() Initiates OAuth2 authorization flow with `response_mode=json` for SPAs. ```typescript async generateDeeplink(): Promise ``` **HTTP Request:** ``` GET /oauth/authorize?response_type=code&response_mode=json&client_id={providerAddress}&scope=openid&code_challenge={challenge}&code_challenge_method=S256 ``` **Returns:** `AuthorizeResponse` ```typescript { deep_link: string; // Deep link for QR code or mobile redirect polling_code: string; // Code for polling authentication status expired_at: number; // Unix timestamp when session expires } ``` **Side Effects:** - Generates PKCE code verifier and challenge (S256) - Stores code verifier in `sessionStorage` **Example:** ```typescript const { deep_link, polling_code, expired_at } = await client.generateDeeplink(); console.log('Deep link:', deep_link); console.log('Expires:', new Date(expired_at * 1000)); ``` --- ### pollAuth() Polls for authentication completion status. ```typescript async pollAuth(pollingCode: string): Promise ``` **HTTP Request:** ``` POST /oauth/poll Content-Type: application/json Body: { "polling_code": "..." } ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `pollingCode` | string | Polling code from `generateDeeplink()` | **Returns:** `PollResponse` ```typescript { status: 'pending' | 'authorized' | 'rejected' | 'expired'; authorization_code?: string; // Only present when status is 'authorized' } ``` **Status Values:** | Status | Description | |--------|-------------| | `pending` | User hasn't completed authentication yet | | `authorized` | User approved, `authorization_code` available | | `rejected` | User denied authentication | | `expired` | Session expired | --- ### exchangeToken() Exchanges authorization code for tokens (OAuth2 token endpoint). ```typescript async exchangeToken(authorizationCode: string): Promise ``` **HTTP Request:** ``` POST /oauth/token Content-Type: application/x-www-form-urlencoded Body: grant_type=authorization_code&code={code}&client_id={providerAddress}&code_verifier={verifier} ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `authorizationCode` | string | Authorization code from `pollAuth()` | **Returns:** `TokenResponse` ```typescript { access_token: string; // JWT access token id_token: string; // JWT ID token with user claims refresh_token: string; // Opaque refresh token token_type: string; // "Bearer" expires_in: number; // Token lifetime in seconds } ``` **Side Effects:** - Retrieves code verifier from `sessionStorage` - Stores all tokens in `localStorage` - Clears code verifier from `sessionStorage` --- ### verifyAuth() Verifies authentication by calling the userinfo endpoint. Automatically refreshes token on 401. ```typescript async verifyAuth(): Promise ``` **HTTP Request:** ``` GET /oauth/userinfo Authorization: Bearer {access_token} ``` **Returns:** `UserInfoResponse | null` ```typescript { sub: string; // User identifier (session address) } ``` Returns `null` if not authenticated or token invalid. --- ### refreshAccessToken() Refreshes the access token using the stored refresh token. ```typescript async refreshAccessToken(): Promise ``` **HTTP Request:** ``` POST /oauth/token Content-Type: application/x-www-form-urlencoded Body: grant_type=refresh_token&refresh_token={token}&client_id={providerAddress} ``` **Returns:** `TokenResponse` (same as `exchangeToken()`) **Side Effects:** - Updates all tokens in `localStorage` - On failure, calls `logout()` to clear invalid tokens **Throws:** Error if no refresh token available or refresh fails --- ### withAutoRefresh() Executes a function with automatic token refresh on 401 error. ```typescript async withAutoRefresh( requestFn: () => Promise, maxRetries?: number ): Promise ``` **Parameters:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `requestFn` | function | - | Async function to execute | | `maxRetries` | number | 1 | Max retry attempts | **Example:** ```typescript const data = await client.withAutoRefresh(async () => { const res = await fetch('/api/data', { headers: { Authorization: `Bearer ${client.getAccessToken()}` } }); if (res.status === 401) { throw Object.assign(new Error('Unauthorized'), { response: { status: 401 } }); } return res.json(); }); ``` --- ### logout() Clears all stored authentication data. ```typescript logout(): void ``` **Side Effects:** - Removes `access_token`, `id_token`, `refresh_token`, `token_expiry` from `localStorage` - Removes `code_verifier` from `sessionStorage` --- ## Token Methods ### getAccessToken() Retrieves stored access token. ```typescript getAccessToken(): string | null ``` --- ### getIdToken() Retrieves stored ID token. ```typescript getIdToken(): string | null ``` --- ### getRefreshToken() Retrieves stored refresh token. ```typescript getRefreshToken(): string | null ``` --- ### hasRefreshToken() Checks if a refresh token is available. ```typescript hasRefreshToken(): boolean ``` --- ### getAuthData() Decodes JWT token and returns claims. Does NOT verify signature. ```typescript getAuthData(): TokenInfo | null ``` **Returns:** `TokenInfo | null` ```typescript { iss: string; // Issuer URL sub: string; // Subject (user identifier) aud: string | string[]; // Audience (your provider address) exp: number; // Expiration timestamp iat: number; // Issued at timestamp nonce?: string; // Nonce (if provided in authorize) auth_time?: number; // Authentication time } ``` **Note:** Returns `null` if token invalid, expired, or audience doesn't match. --- ### getSubject() Gets the subject (user identifier) from the token. ```typescript getSubject(): string | null ``` Shorthand for `getAuthData()?.sub`. --- ### isTokenExpired() Checks if the current token is expired based on `exp` claim. ```typescript isTokenExpired(): boolean ``` --- ### isAccessTokenExpired() Checks if the access token is expired or will expire within 5 minutes. ```typescript isAccessTokenExpired(): boolean ``` Uses stored expiry timestamp for efficiency. --- ## Types ### AlienSsoClientConfig ```typescript interface AlienSsoClientConfig { ssoBaseUrl: string; providerAddress: string; pollingInterval?: number; } ``` ### AuthorizeResponse ```typescript interface AuthorizeResponse { deep_link: string; polling_code: string; expired_at: number; } ``` ### PollResponse ```typescript interface PollResponse { status: 'pending' | 'authorized' | 'rejected' | 'expired'; authorization_code?: string; } ``` ### TokenResponse ```typescript interface TokenResponse { access_token: string; id_token: string; refresh_token: string; token_type: string; expires_in: number; } ``` ### UserInfoResponse ```typescript interface UserInfoResponse { sub: string; } ``` ### TokenInfo ```typescript interface TokenInfo { iss: string; sub: string; aud: string | string[]; exp: number; iat: number; nonce?: string; auth_time?: number; } ``` --- ## 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` | Expiry timestamp (ms) | ### sessionStorage | Key | Description | |-----|-------------| | `alien-sso_code_verifier` | PKCE code verifier | --- ## HTTP Endpoints Summary | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/oauth/authorize` | Start authorization flow | | POST | `/oauth/poll` | Poll for authorization status | | POST | `/oauth/token` | Exchange code or refresh token | | GET | `/oauth/userinfo` | Get user info | | GET | `/oauth/jwks` | Get public keys for JWT verification | | GET | `/.well-known/openid-configuration` | OIDC discovery document | --- ## Error Handling All async methods can throw errors: ```typescript try { await client.generateDeeplink(); } catch (error) { // Network error, invalid provider, or server error } try { await client.exchangeToken(code); } catch (error) { // Invalid code, expired code, or PKCE mismatch } try { await client.refreshAccessToken(); } catch (error) { // Refresh token expired or revoked // Client automatically calls logout() } ``` --- ## Next Steps - **[React API Reference](/sso-api-reference/api-reference-react)** - React SDK documentation - **[Core Integration Guide](/sso-guide/core-integration)** - Integration guide - **[Demo App](https://github.com/alien-id/sso-sdk-js/tree/main/apps)** - Example implementation --- # API Reference - React Complete API documentation for `@alien_org/sso-sdk-react`. ## AlienSsoProvider Context provider component that wraps your application and provides authentication state. ### Props ```typescript interface AlienSsoProviderProps { config: AlienSsoClientConfig; children: React.ReactNode; } ``` **Configuration:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `ssoBaseUrl` | string | Yes | Base URL of the SSO service | | `providerAddress` | string | Yes | Your provider address | | `pollingInterval` | number | No | Polling interval in ms (default: 5000) | **Example:** ```typescript function App() { return ( ); } ``` --- ## useAuth Hook Hook that provides access to authentication state and methods. Must be used within `AlienSsoProvider`. ```typescript function useAuth(): UseAuthReturn ``` ### Return Value ```typescript interface UseAuthReturn { client: AlienSsoClient; auth: AuthState; queryClient: QueryClient; generateDeeplink: () => Promise; pollAuth: (pollingCode: string) => Promise; exchangeToken: (authCode: string) => Promise; verifyAuth: () => Promise; refreshToken: () => Promise; logout: () => void; openModal: () => void; closeModal: () => void; isModalOpen: boolean; } ``` ### Properties #### client Direct access to the `AlienSsoClient` instance. ```typescript const { client } = useAuth(); const token = client.getAccessToken(); ``` #### auth Current authentication state. ```typescript interface AuthState { isAuthenticated: boolean; token: string | null; tokenInfo: TokenInfo | null; } ``` **TokenInfo (OIDC standard claims):** ```typescript interface TokenInfo { iss: string; // Issuer URL sub: string; // Subject (user identifier) aud: string | string[]; // Audience (provider address) exp: number; // Expiration timestamp iat: number; // Issued at timestamp nonce?: string; // Nonce if provided auth_time?: number; // Authentication time } ``` **Example:** ```typescript const { auth } = useAuth(); if (auth.isAuthenticated) { console.log('User ID:', auth.tokenInfo?.sub); console.log('Token:', auth.token); console.log('Expires:', new Date(auth.tokenInfo!.exp * 1000)); } ``` #### queryClient React Query client instance for advanced usage. ```typescript const { queryClient } = useAuth(); queryClient.invalidateQueries(['some-key']); ``` ### Methods #### generateDeeplink() Generates authentication deep link and polling code. ```typescript async generateDeeplink(): Promise ``` **Returns:** ```typescript { deep_link: string; // URL for QR code or redirect polling_code: string; // Code for polling status expired_at: number; // Unix timestamp } ``` --- #### pollAuth() Polls for authentication status. ```typescript async pollAuth(pollingCode: string): Promise ``` **Returns:** ```typescript { status: 'pending' | 'authorized' | 'rejected' | 'expired'; authorization_code?: string; // Only when status is 'authorized' } ``` --- #### exchangeToken() Exchanges authorization code for tokens. Updates auth state on success. ```typescript async exchangeToken(authCode: string): Promise ``` **Returns:** ```typescript { access_token: string; id_token: string; refresh_token: string; token_type: string; expires_in: number; } ``` **Side Effects:** - Updates `auth` state with new tokens - Stores tokens in localStorage --- #### verifyAuth() Verifies current access token by calling `/oauth/userinfo`. ```typescript async verifyAuth(): Promise ``` **Returns:** `true` if token is valid, `false` otherwise. **Side Effects:** - Updates `auth` state based on verification result - Automatically refreshes token on 401 (via underlying client) --- #### refreshToken() Refreshes the access token using the stored refresh token. ```typescript async refreshToken(): Promise ``` **Returns:** `true` if refresh succeeded, `false` if failed. **Side Effects:** - Updates `auth` state with new tokens on success - Clears auth state and calls logout on failure **Example:** ```typescript const { refreshToken, auth } = useAuth(); // Proactive refresh before token expires if (auth.tokenInfo && auth.tokenInfo.exp * 1000 < Date.now() + 60000) { const success = await refreshToken(); if (!success) { // Redirect to login } } ``` --- #### logout() Clears authentication state and storage. ```typescript logout(): void ``` **Side Effects:** - Updates `auth` state to unauthenticated - Removes all tokens from localStorage - Removes code verifier from sessionStorage --- #### openModal() / closeModal() Control the built-in sign-in modal. ```typescript openModal(): void closeModal(): void ``` **Example:** ```typescript const { openModal, closeModal, isModalOpen } = useAuth(); {isModalOpen && } ``` #### isModalOpen Boolean indicating if the sign-in modal is currently open. --- ## Components ### SignInButton Pre-styled button component that opens the sign-in modal. ```typescript function SignInButton(): JSX.Element ``` **Example:** ```typescript function LoginPage() { return (

Welcome

); } ``` --- ### SignInModal Modal component for the authentication flow. Automatically rendered by `AlienSsoProvider`. This component: - Displays QR code with deep link - Handles polling automatically - Shows loading and error states - Exchanges token on successful authentication - Updates auth state when complete **Controlling the modal:** ```typescript const { openModal, closeModal } = useAuth(); openModal(); // Open modal closeModal(); // Close modal ``` --- ## Types ### AlienSsoClientConfig ```typescript interface AlienSsoClientConfig { ssoBaseUrl: string; providerAddress: string; pollingInterval?: number; } ``` ### AuthState ```typescript interface AuthState { isAuthenticated: boolean; token: string | null; tokenInfo: TokenInfo | null; } ``` ### TokenInfo ```typescript interface TokenInfo { iss: string; sub: string; aud: string | string[]; exp: number; iat: number; nonce?: string; auth_time?: number; } ``` ### AuthorizeResponse ```typescript interface AuthorizeResponse { deep_link: string; polling_code: string; expired_at: number; } ``` ### PollResponse ```typescript interface PollResponse { status: 'pending' | 'authorized' | 'rejected' | 'expired'; authorization_code?: string; } ``` ### TokenResponse ```typescript interface TokenResponse { access_token: string; id_token: string; refresh_token: string; token_type: string; expires_in: number; } ``` --- ## Usage Examples ### Basic Authentication ```typescript function App() { return ( ); } function Dashboard() { const { auth, logout } = useAuth(); if (!auth.isAuthenticated) { return ; } return (

Welcome! User ID: {auth.tokenInfo?.sub}

); } ``` ### Protected Route ```typescript function ProtectedRoute({ children }: { children: React.ReactNode }) { const { auth } = useAuth(); if (!auth.isAuthenticated) { return ; } return <>{children}; } ``` ### Token Refresh on API Calls ```typescript function useApi() { const { auth, refreshToken, logout } = useAuth(); async function fetchWithAuth(url: string) { // Check if token is expiring within 1 minute if (auth.tokenInfo && auth.tokenInfo.exp * 1000 < Date.now() + 60000) { const success = await refreshToken(); if (!success) { logout(); throw new Error('Session expired'); } } return fetch(url, { headers: { Authorization: `Bearer ${auth.token}` } }); } return { fetchWithAuth }; } ``` ### Custom Sign-In Flow ```typescript function CustomSignIn() { const { generateDeeplink, pollAuth, exchangeToken, auth } = useAuth(); const [deepLink, setDeepLink] = useState(null); const [pollingCode, setPollingCode] = useState(null); const handleSignIn = async () => { const response = await generateDeeplink(); setDeepLink(response.deep_link); setPollingCode(response.polling_code); }; useEffect(() => { if (!pollingCode) return; const interval = setInterval(async () => { const response = await pollAuth(pollingCode); if (response.status === 'authorized') { clearInterval(interval); await exchangeToken(response.authorization_code!); setDeepLink(null); setPollingCode(null); } else if (response.status === 'rejected' || response.status === 'expired') { clearInterval(interval); setDeepLink(null); setPollingCode(null); } }, 5000); return () => clearInterval(interval); }, [pollingCode]); if (deepLink) { return ; } return ; } ``` ### Token Verification on Mount ```typescript function App() { const { verifyAuth, auth } = useAuth(); useEffect(() => { if (auth.token) { verifyAuth(); } }, []); // Rest of your app } ``` --- ## Error Handling All async methods can throw errors. Handle them appropriately: ```typescript const { generateDeeplink, exchangeToken, refreshToken } = useAuth(); try { const { deep_link, polling_code } = await generateDeeplink(); } catch (error) { console.error('Failed to generate deep link:', error); } try { await exchangeToken(authCode); } catch (error) { console.error('Token exchange failed:', error); } try { const success = await refreshToken(); if (!success) { // Handle refresh failure (user logged out automatically) } } catch (error) { console.error('Refresh error:', error); } ``` --- ## Next Steps - **[Core API Reference](/sso-api-reference/api-reference-core)** - Core SDK documentation - **[React Integration Guide](/sso-guide/react-integration)** - Integration guide - **[Demo App](https://github.com/alien-id/sso-sdk-js/tree/main/apps)** - Example implementation --- # Demo App Explore a complete working example of Alien SSO integration in a React application. ## Live Demo Try the demo app to see how Alien SSO works in practice: **[View Demo App →](https://alien-id.github.io/sso-sdk-js/example-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 ### 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-sso-app/ # Copy .env.example and fill data cp .env.example .env # Run the example SSO 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-sso-app/ ├── src/ │ ├── App.tsx # Main application component │ ├── main.tsx # Entry point with AlienSsoProvider │ ├── components/ │ │ ├── Dashboard.tsx # Protected dashboard component │ │ └── LoginPage.tsx # Login page with SignInButton │ └── ... ├── package.json └── vite.config.ts ``` ## Next Steps - **[Core Integration Guide](/sso-guide/core-integration)** - For vanilla JavaScript/TypeScript projects. - **[React Integration Guide](/sso-guide/react-integration)** - For React applications. - **[API Reference - React](/sso-api-reference/api-reference-react)** - Complete API documentation for React SDK. --- # Introduction Alien Solana SSO enables blockchain-backed verification and authentication for Solana applications using Alien ID with on-chain attestations. ## What is Solana SSO? Solana SSO is a decentralized solution specifically designed for the Solana ecosystem. Unlike standard SSO which uses JWT tokens, Solana SSO creates verifiable on-chain attestations that prove user verification directly on the Solana blockchain. ## Who should use Solana SSO? Solana SSO is ideal for: - **Solana dApps** requiring user verification - **DeFi protocols** needing identity verification - **NFT marketplaces and gaming platforms** on Solana - **Any Solana program** requiring session verification - **Applications** that need provable on-chain verification ## Key Features - **On-chain attestations:** Verification confirmed directly on Solana blockchain - **Wallet integration:** Seamless integration with Solana wallet adapters - **TEE-based security:** Execution layer based on TEE - **Session management:** Long-lived sessions with on-chain verification - **TypeScript-first:** Full type safety with Zod validation - **React integration:** Pre-built hooks and components for React apps ## Packages - **`@alien_org/solana-sso-sdk-core` -** Core client for any JavaScript/TypeScript project - **`@alien_org/solana-sso-sdk-react` -** React hooks, components, and providers for React applications ## How it works 1. **User connects Solana wallet -** User connects their Solana wallet to your dApp 2. **Click Sign In -** User clicks the Sign In button 3. **Check for attestation -** System checks if the wallet has an attestation on-chain 4. **Two scenarios:** - **Attestation exists →** User is verified immediately - **No attestation →** User creates attestation in Alien app and submits transaction on-chain 5. **Verification complete -** Once attestation exists on-chain, user is verified ## Installation ```sh # Core SDK for vanilla JavaScript/TypeScript npm install @alien_org/solana-sso-sdk-core # For React projects npm install @alien_org/solana-sso-sdk-react ``` ## Solana Programs Solana SSO interacts with three on-chain programs: - **Credential Signer Program:** `9cstDz8WWRAFaq1vVpTjfHz6tjgh6SJaqYFeZWi1pFHG` - **Session Registry Program:** `DeHa6pyZ2CFSbQQiNMm7FgoCXqmkX6tXG77C4Qycpta6` - **SAS (Solana Attestation Service):** `22zoJMtdu4tQc2PzL74ZUT7FrwgB1Udec8DdW4yw4BdG` These programs handle session creation, verification, and attestation storage on Solana. ### Solana Attestation Service Compatibility All attestations created by Alien Solana SSO are **fully compatible** with the Solana Attestation Service. Attestations are created using the official Solana Attestation Service programs. This means: - Attestations are created using the Solana Attestation Service on-chain programs - All attestations are discoverable and verifiable through [attest.solana.com](http://attest.solana.com) - Full interoperability with other Solana attestation-based applications - Built on Solana's official attestation infrastructure ### On-Chain Attestation Structure - **Session Entry:** Created in Session Registry program - **Solana Entry:** Maps Solana address to session - **Attestation:** Created in SAS (Solana Attestation Service) program - All linked via PDAs derived from credential, schema, and user public key ## Next Steps Choose your integration path based on your project: - **[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 - Core](/solana-sso-api-reference/api-reference-core)** - Complete API documentation for Core SDK. - **[API Reference - React](/solana-sso-api-reference/api-reference-react)** - Complete API documentation for React SDK. - **[Demo App](https://github.com/alien-id/sso-sdk-js/tree/main/apps)** - Example implementation and source code. --- # Core Integration Guide - Solana This guide shows how to integrate Alien Solana SSO verification and authentication into any JavaScript/TypeScript project using the core SDK. ## Requirements - A modern web browser with JavaScript enabled - localStorage and sessionStorage support - `@solana/web3.js` ^1.95.0 or higher - A registered provider from the [dev portal](https://dev.alien.org) with provider address ## Installation ```sh npm install @alien_org/solana-sso-sdk-core @solana/web3.js ``` ## Setup ### Initialize the Client ```typescript const client = new AlienSolanaSsoClient({ ssoBaseUrl: 'https://sso.alien-api.com', providerAddress: 'your-provider-address' }); ``` ### Configuration Options - **`ssoBaseUrl`** (required): Base URL of the SSO service - **`providerAddress`** (required): Your provider address from dev portal - **`pollingInterval`** (optional): Polling interval in milliseconds (default: 5000) - **`credentialSignerProgramId`** (optional): Credential Signer program ID - **`sessionRegistryProgramId`** (optional): Session Registry program ID - **`sasProgramId`** (optional): SAS (Solana Attestation Service) program ID - **`credentialAuthority`** (optional): Credential authority public key - **`credentialName`** (optional): Credential name - **`schemaName`** (optional): Schema name - **`schemaVersion`** (optional): Schema version ## Authentication Flow ### Step 1: Connect Wallet First, ensure the user has connected their Solana wallet: ```typescript // Get user's wallet public key (from wallet adapter or similar) const userPublicKey = new PublicKey('user-wallet-address'); const solanaAddress = userPublicKey.toBase58(); ``` ### Step 2: Check for Existing Attestation Before initiating the attestation creation flow, check if the wallet already has an attestation: ```typescript const sessionAddress = await client.getAttestation(solanaAddress); if (sessionAddress) { console.log('User is already authenticated! Session:', sessionAddress); // User is authenticated, proceed to app return; } // No attestation found, proceed with creation flow console.log('No attestation found, starting attestation creation...'); ``` ### Step 3: Generate Deep Link (If No Attestation) ```typescript const { deep_link, polling_code, expired_at } = await client.generateDeeplink(solanaAddress); // Display QR code with deep_link displayQRCode(deep_link); // Or redirect mobile users window.location.href = deep_link; ``` The `generateDeeplink()` method takes the user's Solana wallet address and returns: - `deep_link`: Link for QR code or mobile redirect - `polling_code`: Code for polling authentication status - `expired_at`: Unix timestamp when polling code expires ### Step 4: Poll for Authorization ```typescript let pollResponse; const pollInterval = setInterval(async () => { pollResponse = await client.pollAuth(polling_code); if (pollResponse.status === 'authorized') { clearInterval(pollInterval); // Proceed to transaction building console.log('Authorized! Session address:', pollResponse.session_address); } else if (pollResponse.status === 'rejected') { clearInterval(pollInterval); console.error('User rejected authentication'); } else if (pollResponse.status === 'expired') { clearInterval(pollInterval); console.error('Authentication expired'); } // If status is 'pending', continue polling }, 5000); ``` When status is `'authorized'`, the response includes: - `session_address`: Session identifier - `oracle_signature`: Signature bytes for verification - `oracle_public_key`: Oracle public key - `timestamp`: Unix timestamp - `expiry`: Expiry timestamp - `transaction`: Pre-built transaction (optional) ### Step 5: Build Attestation Transaction ```typescript // Create Solana connection const connection = new Connection('https://api.mainnet-beta.solana.com'); // Build attestation transaction const transaction = await client.buildCreateAttestationTransaction({ connection, payerPublicKey: userPublicKey, sessionAddress: pollResponse.session_address!, oracleSignature: new Uint8Array(pollResponse.oracle_signature!), oraclePublicKey: new PublicKey(pollResponse.oracle_public_key!), timestamp: pollResponse.timestamp!, expiry: pollResponse.expiry!, }); ``` ### Step 6: Sign and Send Transaction ```typescript // Sign transaction with user's wallet // (Implementation depends on your wallet integration) const signedTransaction = await wallet.signTransaction(transaction); // Send and confirm transaction const signature = await sendAndConfirmTransaction( connection, signedTransaction, [/* signers if needed */] ); console.log('Attestation created! Signature:', signature); ``` ### Step 7: Verify Attestation After the transaction is finalized, verify the on-chain attestation: ```typescript const sessionAddress = await client.getAttestation(solanaAddress); if (sessionAddress) { console.log('Attestation created successfully! Session:', sessionAddress); } else { console.log('Attestation not found, please try again'); } ``` ## Complete Example ```typescript const client = new AlienSolanaSsoClient({ ssoBaseUrl: 'https://sso.alien-api.com', providerAddress: 'your-provider-address', }); const connection = new Connection('https://api.mainnet-beta.solana.com'); async function authenticateUser(userWallet: any) { try { const userPublicKey = userWallet.publicKey; const solanaAddress = userPublicKey.toBase58(); // Check if already authenticated const existingSession = await client.getAttestation(solanaAddress); if (existingSession) { console.log('Already authenticated:', existingSession); return; } // Generate deep link const { deep_link, polling_code } = await client.generateDeeplink(solanaAddress); // 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); // Build attestation transaction const transaction = await client.buildCreateAttestationTransaction({ connection, payerPublicKey: userPublicKey, sessionAddress: response.session_address!, oracleSignature: new Uint8Array(response.oracle_signature!), oraclePublicKey: new PublicKey(response.oracle_public_key!), timestamp: response.timestamp!, expiry: response.expiry!, }); // Sign transaction const signedTransaction = await userWallet.signTransaction(transaction); // Send transaction const signature = await sendAndConfirmTransaction( connection, signedTransaction, [] ); console.log('Attestation created! Signature:', signature); // Verify attestation const sessionAddress = await client.getAttestation(solanaAddress); console.log('Verified session:', sessionAddress); 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); } } function displayQRCode(deepLink: string) { // Implementation to display QR code } function hideQRCode() { // Implementation to hide QR code } ``` ## PDA Derivation Utilities The SDK provides utility functions for deriving Program Derived Addresses (PDAs): ```typescript deriveProgramStatePda, deriveCredentialSignerPda, deriveSessionRegistryPda, deriveSessionEntryPda, deriveSolanaEntryPda, deriveAttestationPda, deriveCredentialPda, deriveSchemaPda, } from '@alien_org/solana-sso-sdk-core'; // Example: Derive attestation PDA const [attestationPda, bump] = deriveAttestationPda( credentialAddress, schemaAddress, userPublicKey, sasProgramId ); ``` ## Error Handling ```typescript try { const { deep_link, polling_code } = await client.generateDeeplink(solanaAddress); } catch (error) { console.error('Failed to generate deep link:', error); // Handle network error or server error } try { const transaction = await client.buildCreateAttestationTransaction({ connection, payerPublicKey: userPublicKey, sessionAddress: pollResponse.session_address!, oracleSignature: new Uint8Array(pollResponse.oracle_signature!), oraclePublicKey: new PublicKey(pollResponse.oracle_public_key!), timestamp: pollResponse.timestamp!, expiry: pollResponse.expiry!, }); } catch (error) { console.error('Transaction building failed:', error); // Could be due to missing program state or invalid parameters } ``` ## Solana Programs The SDK interacts with three on-chain programs: ### Credential Signer Program **ID:** `9cstDz8WWRAFaq1vVpTjfHz6tjgh6SJaqYFeZWi1pFHG` Manages credential signing and verification. ### Session Registry Program **ID:** `DeHa6pyZ2CFSbQQiNMm7FgoCXqmkX6tXG77C4Qycpta6` Stores session entries and maps Solana addresses to sessions. ### SAS (Solana Attestation Service) **ID:** `22zoJMtdu4tQc2PzL74ZUT7FrwgB1Udec8DdW4yw4BdG` Creates and manages on-chain attestations. ## 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. --- # React Integration Guide - Solana This guide shows how to integrate Alien Solana SSO into React applications using the React SDK with Solana wallet adapters. ## Requirements - React 19.1.1 or higher - React DOM 19.1.1 or higher - `@solana/web3.js` ^1.95.0 or higher - `@solana/wallet-adapter-react` ^0.15.0 or higher - A modern web browser with JavaScript enabled - localStorage and sessionStorage support - A registered provider from the [dev portal](https://dev.alien.org), with provider address ## Installation ```sh npm install @alien_org/solana-sso-sdk-react @solana/web3.js @solana/wallet-adapter-react ``` The React SDK requires Solana wallet adapter packages as peer dependencies. ## Setup ### Alien Solana SSO Provider Wrap your app with both wallet adapter providers and `AlienSolanaSsoProvider`: ```typescript function App() { return ( ); } ``` ### Configuration Options - **`ssoBaseUrl`** (required): Base URL of the SSO service - **`providerAddress`** (required): Your provider address from dev portal - **`pollingInterval`** (optional): Polling interval in milliseconds (default: 5000) - **`credentialSignerProgramId`** (optional): Credential Signer program ID - **`sessionRegistryProgramId`** (optional): Session Registry program ID - **`sasProgramId`** (optional): SAS program ID ## Using the useSolanaAuth Hook The `useSolanaAuth()` hook provides access to Solana authentication state and methods. ```typescript function Dashboard() { const { auth, wallet, logout } = useSolanaAuth(); if (!auth.sessionAddress) { return
Not authenticated
; } return (

Wallet: {wallet.publicKey?.toBase58()}

Session: {auth.sessionAddress}

); } ``` ### Auth State The `auth` object contains: ```typescript { sessionAddress: string | null; } ``` ### Available Properties and Methods ```typescript const { client, // Direct access to AlienSolanaSsoClient instance auth, // Authentication state wallet, // Solana wallet adapter connectionAdapter, // Solana connection from context queryClient, // React Query client instance generateDeeplink, // Generate authentication deep link pollAuth, // Poll for authentication status verifyAttestation, // Verify on-chain attestation logout, // Clear authentication state openModal, // Open built-in Solana sign-in modal closeModal, // Close sign-in modal isModalOpen // Modal open state } = useSolanaAuth(); ``` ## Using Pre-built Components ### SolanaSignInButton A pre-styled button that opens the Solana sign-in modal. ```typescript function LoginPage() { const { connected } = useWallet(); if (!connected) { return (

Connect Your Wallet

); } return (

Sign In with Alien

); } ``` ### SolanaSignInModal The modal is automatically rendered by `AlienSolanaSsoProvider` and handles: - QR code display - Polling for authentication - Transaction building and signing - On-chain attestation creation Control it via the `useSolanaAuth()` hook: ```typescript function CustomButton() { const { openModal } = useSolanaAuth(); return ; } ``` ## Complete Example ```typescript function App() { const endpoint = useMemo(() => clusterApiUrl('mainnet-beta'), []); return ( ); } function Dashboard() { const { connected } = useWallet(); const { auth, logout } = useSolanaAuth(); if (!connected) { return (

Connect Wallet

); } if (!auth.sessionAddress) { return (

Sign In

); } return (

Dashboard

Session: {auth.sessionAddress}

); } ``` ## Custom Authentication Flow If you want to implement a custom UI: ```typescript function CustomAuth() { const { wallet, generateDeeplink, pollAuth } = useSolanaAuth(); const [deepLink, setDeepLink] = useState(null); const [pollingCode, setPollingCode] = useState(null); const handleSignIn = async () => { if (!wallet.publicKey) return; const response = await generateDeeplink(wallet.publicKey.toBase58()); setDeepLink(response.deep_link); setPollingCode(response.polling_code); }; useEffect(() => { if (!pollingCode) return; const interval = setInterval(async () => { const response = await pollAuth(pollingCode); if (response.status === 'authorized') { clearInterval(interval); // The provider will automatically handle transaction creation and signing setDeepLink(null); setPollingCode(null); } else if (response.status === 'rejected' || response.status === 'expired') { clearInterval(interval); alert(`Authentication ${response.status}`); setDeepLink(null); setPollingCode(null); } }, 5000); return () => clearInterval(interval); }, [pollingCode, pollAuth]); if (deepLink) { return (

Scan QR Code

); } return ; } ``` ## Protected Routes Create a protected route component: ```typescript function ProtectedRoute({ children }: { children: React.ReactNode }) { const { connected } = useWallet(); const { auth } = useSolanaAuth(); if (!connected) { return ; } if (!auth.sessionAddress) { return ; } return <>{children}; } // Usage } /> ``` ## Grace Period Mechanism The Solana provider implements a 60-second grace period after attestation creation to handle RPC indexing delays: ```typescript // After successful attestation creation // Session address is cached for 60 seconds // verifyAttestation() returns cached value immediately // Background verification runs after grace period expires ``` Storage keys: - `alien-sso_solana_authed_address` - Authenticated Solana address - `alien-sso_session_address` - Session address - `alien-sso_attestation_created_at` - Timestamp of attestation creation ## Verifying Attestation on Mount ```typescript function App() { const { wallet } = useWallet(); const { verifyAttestation, auth } = useSolanaAuth(); const [isVerifying, setIsVerifying] = useState(true); useEffect(() => { const verify = async () => { if (wallet.publicKey) { await verifyAttestation(wallet.publicKey.toBase58()); } setIsVerifying(false); }; verify(); }, [wallet.publicKey]); if (isVerifying) { return
Verifying authentication...
; } return auth.sessionAddress ? : ; } ``` ## TypeScript Support The React SDK is fully typed: ```typescript const auth: SolanaAuthState = useSolanaAuth().auth; const client: AlienSolanaSsoClient = useSolanaAuth().client; ``` ## Next Steps - **[API Reference - React](/solana-sso-api-reference/api-reference-react)** - Complete API documentation for React SDK. - **[Demo App](https://github.com/alien-id/sso-sdk-js/tree/main/apps)** - Example implementation and source code. - **[Core Integration Guide](/solana-sso-guide/core-integration)** - For vanilla JavaScript/TypeScript projects. --- # API Reference - Core (Solana) Complete API documentation for `@alien_org/solana-sso-sdk-core`. ## AlienSolanaSsoClient Main client class for Alien Solana SSO authentication with on-chain attestations. ### Constructor ```typescript new AlienSolanaSsoClient(config: AlienSolanaSsoClientConfig) ``` **Parameters:** - `config.ssoBaseUrl` (string, required): Base URL of the SSO service - `config.providerAddress` (string, required): Your provider address - `config.pollingInterval` (number, optional): Polling interval in milliseconds (default: 5000) - `config.credentialSignerProgramId` (string, optional): Credential Signer program ID (default: `9cstDz8WWRAFaq1vVpTjfHz6tjgh6SJaqYFeZWi1pFHG`) - `config.sessionRegistryProgramId` (string, optional): Session Registry program ID (default: `DeHa6pyZ2CFSbQQiNMm7FgoCXqmkX6tXG77C4Qycpta6`) - `config.sasProgramId` (string, optional): SAS program ID (default: `22zoJMtdu4tQc2PzL74ZUT7FrwgB1Udec8DdW4yw4BdG`) - `config.credentialAuthority` (string, optional): Credential authority public key (default: `11111111111111111111111111111111`) - `config.credentialName` (string, optional): Credential name (default: `default_credential`) - `config.schemaName` (string, optional): Schema name (default: `default_schema`) - `config.schemaVersion` (number, optional): Schema version (default: `1`) **Example:** ```typescript const client = new AlienSolanaSsoClient({ ssoBaseUrl: 'https://sso.alien-api.com', providerAddress: 'your-provider-address', pollingInterval: 5000 }); ``` --- ## Methods ### generateDeeplink() Initiates attestation creation flow by generating a deep link and polling code. Use this when the wallet doesn't have an attestation yet. ```typescript async generateDeeplink(solanaAddress: string): Promise ``` **Parameters:** - `solanaAddress` (string): User's Solana wallet public key (base58 string) **Returns:** `Promise` ```typescript { deep_link: string; // Deep link for QR code or mobile redirect polling_code: string; // Code for polling authentication status expired_at: number; // Unix timestamp when polling code expires } ``` **Example:** ```typescript const userPublicKey = wallet.publicKey.toBase58(); const { deep_link, polling_code, expired_at } = await client.generateDeeplink(userPublicKey); console.log('Deep link:', deep_link); console.log('Polling code:', polling_code); ``` **Throws:** - Network errors if request fails. - Validation errors if the response doesn't match schema. --- ### pollAuth() Polls for attestation creation completion status. ```typescript async pollAuth(pollingCode: string): Promise ``` **Parameters:** - `pollingCode` (string): The polling code from `generateDeeplink()` **Returns:** `Promise` ```typescript { status: 'pending' | 'authorized' | 'rejected' | 'expired'; transaction?: string; // Base64-encoded pre-built transaction oracle_signature?: number[]; // Signature bytes for verification oracle_public_key?: string; // Oracle public key (base58) timestamp?: number; // Unix timestamp expiry?: number; // Expiry timestamp session_address?: string; // Session identifier } ``` **Status Values:** - `pending`: User hasn't completed attestation creation yet - `authorized`: User approved attestation creation, transaction data is available - `rejected`: User denied attestation creation - `expired`: Polling code expired **Example:** ```typescript const response = await client.pollAuth(polling_code); if (response.status === 'authorized') { console.log('Session address:', response.session_address); console.log('Oracle signature:', response.oracle_signature); console.log('Timestamp:', response.timestamp); } ``` **Throws:** - Network errors if request fails. - Validation errors if the response doesn't match schema. --- ### getAttestation() Checks if a Solana wallet has an attestation on-chain and retrieves the session address. This is the primary method to verify if a user is authenticated. ```typescript async getAttestation(solanaAddress: string): Promise ``` **Parameters:** - `solanaAddress` (string): User's Solana wallet public key (base58 string) **Returns:** `Promise` - Session address or `null` if not found **Example:** ```typescript const sessionAddress = await client.getAttestation(wallet.publicKey.toBase58()); if (sessionAddress) { console.log('User is authenticated! Session:', sessionAddress); // User already has attestation, skip attestation creation flow } else { console.log('No attestation found, need to create one'); // Start attestation creation flow with generateDeeplink() } ``` **Throws:** - Network errors if request fails --- ### buildCreateAttestationTransaction() Builds Solana transaction to create on-chain attestation. ```typescript async buildCreateAttestationTransaction(params: BuildAttestationParams): Promise ``` **Parameters:** ```typescript interface BuildAttestationParams { connection: Connection; // Solana RPC connection payerPublicKey: PublicKey; // User's wallet public key sessionAddress: string; // Session address from poll response oracleSignature: Uint8Array; // Oracle signature from poll response oraclePublicKey: PublicKey; // Oracle public key from poll response timestamp: number; // Timestamp from poll response expiry: number; // Expiry from poll response } ``` **Returns:** `Promise` - Unsigned Solana transaction ready to be signed and sent **Process:** 1. Fetches on-chain program state to get credential and schema addresses 2. Derives all necessary PDAs 3. Creates Ed25519 signature verification instruction 4. Creates attestation instruction 5. Returns combined transaction **Example:** ```typescript const connection = new Connection('https://api.mainnet-beta.solana.com'); const transaction = await client.buildCreateAttestationTransaction({ connection, payerPublicKey: wallet.publicKey, sessionAddress: pollResponse.session_address!, oracleSignature: new Uint8Array(pollResponse.oracle_signature!), oraclePublicKey: new PublicKey(pollResponse.oracle_public_key!), timestamp: pollResponse.timestamp!, expiry: pollResponse.expiry!, }); // Sign and send transaction const signedTx = await wallet.signTransaction(transaction); const signature = await connection.sendRawTransaction(signedTx.serialize()); ``` **Throws:** - Error if program state account not found - Error if credential or schema not found - Network errors if RPC request fails --- ## PDA Derivation Utilities Functions for deriving Program Derived Addresses (PDAs). ### deriveProgramStatePda() ```typescript function deriveProgramStatePda(programId: PublicKey): [PublicKey, number] ``` Derives the program state PDA. **Returns:** `[PublicKey, number]` - PDA address and bump seed --- ### deriveCredentialSignerPda() ```typescript function deriveCredentialSignerPda(programId: PublicKey): [PublicKey, number] ``` Derives the credential signer PDA. --- ### deriveSessionRegistryPda() ```typescript function deriveSessionRegistryPda(programId: PublicKey): [PublicKey, number] ``` Derives the session registry PDA. --- ### deriveSessionEntryPda() ```typescript function deriveSessionEntryPda( sessionAddress: string, programId: PublicKey ): [PublicKey, number] ``` Derives the session entry PDA for a specific session. **Parameters:** - `sessionAddress` (string): Session identifier - `programId` (PublicKey): Session Registry program ID --- ### deriveSolanaEntryPda() ```typescript function deriveSolanaEntryPda( userPublicKey: PublicKey, programId: PublicKey ): [PublicKey, number] ``` Derives the Solana entry PDA that maps a Solana address to a session. **Parameters:** - `userPublicKey` (PublicKey): User's Solana wallet public key - `programId` (PublicKey): Session Registry program ID --- ### deriveAttestationPda() ```typescript function deriveAttestationPda( credentialAddress: PublicKey, schemaAddress: PublicKey, userPublicKey: PublicKey, sasProgramId: PublicKey ): [PublicKey, number] ``` Derives the attestation PDA. **Parameters:** - `credentialAddress` (PublicKey): Credential PDA - `schemaAddress` (PublicKey): Schema PDA - `userPublicKey` (PublicKey): User's wallet public key - `sasProgramId` (PublicKey): SAS program ID --- ### deriveCredentialPda() ```typescript function deriveCredentialPda( authorityPublicKey: PublicKey, credentialName: string, programId: PublicKey ): [PublicKey, number] ``` Derives the credential PDA. **Parameters:** - `authorityPublicKey` (PublicKey): Credential authority public key - `credentialName` (string): Credential name - `programId` (PublicKey): Credential Signer program ID --- ### deriveSchemaPda() ```typescript function deriveSchemaPda( authorityPublicKey: PublicKey, schemaName: string, schemaVersion: number, programId: PublicKey ): [PublicKey, number] ``` Derives the schema PDA. **Parameters:** - `authorityPublicKey` (PublicKey): Schema authority public key - `schemaName` (string): Schema name - `schemaVersion` (number): Schema version - `programId` (PublicKey): Credential Signer program ID --- ## Types ### AlienSolanaSsoClientConfig ```typescript interface AlienSolanaSsoClientConfig { ssoBaseUrl: string; providerAddress: string; pollingInterval?: number; credentialSignerProgramId?: string; sessionRegistryProgramId?: string; sasProgramId?: string; credentialAuthority?: string; credentialName?: string; schemaName?: string; schemaVersion?: number; } ``` ### SolanaLinkResponse ```typescript interface SolanaLinkResponse { deep_link: string; polling_code: string; expired_at: number; } ``` ### SolanaPollResponse ```typescript interface SolanaPollResponse { status: 'pending' | 'authorized' | 'rejected' | 'expired'; transaction?: string; oracle_signature?: number[]; oracle_public_key?: string; timestamp?: number; expiry?: number; session_address?: string; } ``` ### BuildAttestationParams ```typescript interface BuildAttestationParams { connection: Connection; payerPublicKey: PublicKey; sessionAddress: string; oracleSignature: Uint8Array; oraclePublicKey: PublicKey; timestamp: number; expiry: number; } ``` --- ## Solana Programs ### Credential Signer Program **Program ID:** `9cstDz8WWRAFaq1vVpTjfHz6tjgh6SJaqYFeZWi1pFHG` Manages credential signing and schema definitions. ### Session Registry Program **Program ID:** `DeHa6pyZ2CFSbQQiNMm7FgoCXqmkX6tXG77C4Qycpta6` Stores session entries and maps Solana addresses to sessions. ### SAS (Solana Attestation Service) **Program ID:** `22zoJMtdu4tQc2PzL74ZUT7FrwgB1Udec8DdW4yw4BdG` Creates and manages on-chain attestations linking credentials, schemas, and user addresses. --- ## Error Handling All async methods can throw errors. Always wrap in try-catch: ```typescript try { const { deep_link, polling_code } = await client.generateDeeplink(solanaAddress); } catch (error) { console.error('Failed to generate deep link:', error); } try { const transaction = await client.buildCreateAttestationTransaction({ connection, payerPublicKey: wallet.publicKey, sessionAddress: pollResponse.session_address!, oracleSignature: new Uint8Array(pollResponse.oracle_signature!), oraclePublicKey: new PublicKey(pollResponse.oracle_public_key!), timestamp: pollResponse.timestamp!, expiry: pollResponse.expiry!, }); } catch (error) { console.error('Transaction building failed:', error); } ``` --- ## Complete Example ```typescript const client = new AlienSolanaSsoClient({ ssoBaseUrl: 'https://sso.alien-api.com', providerAddress: 'your-provider-address', }); const connection = new Connection('https://api.mainnet-beta.solana.com'); async function authenticateUser(wallet: any) { const solanaAddress = wallet.publicKey.toBase58(); // Check existing attestation const existingSession = await client.getAttestation(solanaAddress); if (existingSession) { console.log('Already authenticated:', existingSession); return; } // Generate deep link const { deep_link, polling_code } = await client.generateDeeplink(solanaAddress); displayQRCode(deep_link); // Poll for authorization const pollInterval = setInterval(async () => { const response = await client.pollAuth(polling_code); if (response.status === 'authorized') { clearInterval(pollInterval); // Build attestation transaction const transaction = await client.buildCreateAttestationTransaction({ connection, payerPublicKey: wallet.publicKey, sessionAddress: response.session_address!, oracleSignature: new Uint8Array(response.oracle_signature!), oraclePublicKey: new PublicKey(response.oracle_public_key!), timestamp: response.timestamp!, expiry: response.expiry!, }); // Sign and send const signedTx = await wallet.signTransaction(transaction); const signature = await sendAndConfirmTransaction(connection, signedTx, []); console.log('Attestation created! Signature:', signature); // Verify const sessionAddress = await client.getAttestation(solanaAddress); console.log('Verified session:', sessionAddress); hideQRCode(); } else if (response.status === 'rejected' || response.status === 'expired') { clearInterval(pollInterval); console.error('Authentication failed:', response.status); hideQRCode(); } }, 5000); } ``` --- ## Next Steps - **[API Reference - React](/solana-sso-api-reference/api-reference-react)** - Complete API documentation for React SDK. - **[Core Integration Guide](/solana-sso-guide/core-integration)** - For vanilla JavaScript/TypeScript projects. - **[Demo App](https://github.com/alien-id/sso-sdk-js/tree/main/apps)** - Example implementation and source code. --- # API Reference - React (Solana) Complete API documentation for `@alien_org/solana-sso-sdk-react`. ## AlienSolanaSsoProvider Context provider component that wraps your application and provides Solana authentication state. ### Props ```typescript interface AlienSolanaSsoProviderProps { config: AlienSolanaSsoClientConfig; children: React.ReactNode; } ``` **Configuration:** - `config.ssoBaseUrl` (string, required): Base URL of the SSO service - `config.providerAddress` (string, required): Your provider address - `config.pollingInterval` (number, optional): Polling interval in milliseconds (default: 5000) - `config.credentialSignerProgramId` (string, optional): Credential Signer program ID - `config.sessionRegistryProgramId` (string, optional): Session Registry program ID - `config.sasProgramId` (string, optional): SAS program ID - `config.credentialAuthority` (string, optional): Credential authority public key - `config.credentialName` (string, optional): Credential name - `config.schemaName` (string, optional): Schema name - `config.schemaVersion` (number, optional): Schema version **Example:** ```typescript function App() { return ( ); } ``` --- ## useSolanaAuth Hook Hook that provides access to Solana authentication state and methods. Must be used within `AlienSolanaSsoProvider` and Solana wallet adapter providers. ```typescript function useSolanaAuth(): UseSolanaAuthReturn ``` ### Return Value ```typescript interface UseSolanaAuthReturn { client: AlienSolanaSsoClient; auth: SolanaAuthState; wallet: SolanaWalletAdapter; connectionAdapter: SolanaConnectionAdapter; queryClient: QueryClient; generateDeeplink: (solanaAddress: string) => Promise; pollAuth: (pollingCode: string) => Promise; verifyAttestation: (solanaAddress: string) => Promise; logout: () => void; openModal: () => void; closeModal: () => void; isModalOpen: boolean; } ``` ### Properties #### client Direct access to the `AlienSolanaSsoClient` instance. ```typescript const { client } = useSolanaAuth(); const sessionAddress = await client.getAttestation(walletAddress); ``` #### auth Current Solana authentication state. ```typescript interface SolanaAuthState { sessionAddress: string | null; } ``` **Example:** ```typescript const { auth } = useSolanaAuth(); if (auth.sessionAddress) { console.log('User is authenticated! Session:', auth.sessionAddress); } ``` #### wallet Solana wallet adapter from context (from `@solana/wallet-adapter-react`). ```typescript const { wallet } = useSolanaAuth(); if (wallet.publicKey) { console.log('Wallet connected:', wallet.publicKey.toBase58()); } // Sign transaction const signedTx = await wallet.signTransaction(transaction); ``` #### connectionAdapter Solana connection adapter from context (from `@solana/wallet-adapter-react`). ```typescript const { connectionAdapter } = useSolanaAuth(); const balance = await connectionAdapter.connection.getBalance(wallet.publicKey); ``` #### queryClient React Query client instance for advanced usage. ```typescript const { queryClient } = useSolanaAuth(); queryClient.invalidateQueries(['some-key']); ``` ### Methods #### generateDeeplink() Generates attestation creation deep link and polling code for a Solana address. Use this when the wallet doesn't have an attestation yet. ```typescript async generateDeeplink(solanaAddress: string): Promise ``` **Parameters:** - `solanaAddress` (string): User's Solana wallet public key (base58 string) **Returns:** ```typescript { deep_link: string; polling_code: string; expired_at: number; } ``` **Example:** ```typescript const { wallet, generateDeeplink } = useSolanaAuth(); const handleSignIn = async () => { if (!wallet.publicKey) return; const { deep_link, polling_code } = await generateDeeplink( wallet.publicKey.toBase58() ); console.log('Deep link:', deep_link); }; ``` #### pollAuth() Polls for attestation creation status. ```typescript async pollAuth(pollingCode: string): Promise ``` **Parameters:** - `pollingCode` (string): Polling code from `generateDeeplink()` **Returns:** ```typescript { status: 'pending' | 'authorized' | 'rejected' | 'expired'; transaction?: string; oracle_signature?: number[]; oracle_public_key?: string; timestamp?: number; expiry?: number; session_address?: string; } ``` **Example:** ```typescript const { pollAuth } = useSolanaAuth(); const response = await pollAuth(polling_code); if (response.status === 'authorized') { console.log('Session address:', response.session_address); } ``` #### verifyAttestation() Checks if a Solana wallet has an attestation on-chain. This is the primary method to verify if a user is authenticated. ```typescript async verifyAttestation(solanaAddress: string): Promise ``` **Parameters:** - `solanaAddress` (string): User's Solana wallet public key (base58 string) **Returns:** `Promise` - Session address or `null` if not found **Grace Period:** - Returns cached session address immediately within 60-second grace period after attestation creation - After grace period, verifies on-chain attestation **Example:** ```typescript const { wallet, verifyAttestation } = useSolanaAuth(); const sessionAddress = await verifyAttestation(wallet.publicKey.toBase58()); if (sessionAddress) { console.log('User is authenticated:', sessionAddress); // User already has attestation, skip attestation creation flow } else { console.log('No attestation found, need to create one'); // Start attestation creation flow } ``` #### logout() Clears authentication state and storage. ```typescript logout(): void ``` **Side Effects:** - Updates `auth` state to remove session address - Removes session data from localStorage - Clears grace period cache **Example:** ```typescript const { logout } = useSolanaAuth(); ``` #### openModal() / closeModal() Control the built-in Solana sign-in modal. ```typescript openModal(): void closeModal(): void ``` **Example:** ```typescript const { openModal, closeModal, isModalOpen } = useSolanaAuth(); {isModalOpen && } ``` #### isModalOpen Boolean indicating if the sign-in modal is currently open. ```typescript const { isModalOpen } = useSolanaAuth(); console.log('Modal open:', isModalOpen); ``` --- ## Components ### SolanaSignInButton Pre-styled button component that opens the Solana sign-in modal. ```typescript function SolanaSignInButton(): JSX.Element ``` **Example:** ```typescript function LoginPage() { const { connected } = useWallet(); if (!connected) { return (

Connect Your Wallet

); } return (

Sign In with Alien

); } ``` **Styling:** The button has default styles but can be customized via CSS: ```css button.alien-solana-sso-signin-button { /* Your custom styles */ } ``` --- ### SolanaSignInModal Modal component for the attestation creation flow. Automatically rendered by `AlienSolanaSsoProvider`. This component: - Checks if attestation already exists (if yes, authenticates immediately) - Displays QR code with deep link (if no attestation) - Handles polling automatically - Builds attestation transaction - Prompts user to sign transaction - Creates on-chain attestation - Shows loading and error states **Controlling the modal:** ```typescript const { openModal, closeModal } = useSolanaAuth(); // Open modal openModal(); // Close modal closeModal(); ``` --- ## Types ### AlienSolanaSsoClientConfig ```typescript interface AlienSolanaSsoClientConfig { ssoBaseUrl: string; providerAddress: string; pollingInterval?: number; credentialSignerProgramId?: string; sessionRegistryProgramId?: string; sasProgramId?: string; credentialAuthority?: string; credentialName?: string; schemaName?: string; schemaVersion?: number; } ``` ### SolanaAuthState ```typescript interface SolanaAuthState { sessionAddress: string | null; } ``` ### SolanaLinkResponse ```typescript interface SolanaLinkResponse { deep_link: string; polling_code: string; expired_at: number; } ``` ### SolanaPollResponse ```typescript interface SolanaPollResponse { status: 'pending' | 'authorized' | 'rejected' | 'expired'; transaction?: string; oracle_signature?: number[]; oracle_public_key?: string; timestamp?: number; expiry?: number; session_address?: string; } ``` --- ## Storage Keys The SDK uses the following localStorage keys: - **`alien-sso_solana_authed_address`**: Stores which wallet address is authenticated (used to restore auth state on page reload) - **`alien-sso_session_address`**: Cached session address - **`alien-sso_attestation_created_at`**: Timestamp of attestation creation (for 60-second grace period to handle RPC indexing delays) --- ## Grace Period Mechanism The provider implements a 60-second grace period after attestation creation to handle RPC indexing delays: **How it works:** 1. After successful attestation creation, session address is cached 2. Grace period timestamp is stored in localStorage 3. During 60 seconds, `verifyAttestation()` returns cached value immediately 4. After 60 seconds, verification runs against on-chain state 5. If on-chain verification fails after grace period, session is cleared **Example:** ```typescript // After attestation is created // Session address is available immediately const sessionAddress = await verifyAttestation(walletAddress); // Returns cached value within 60 seconds // After 60 seconds const sessionAddress = await verifyAttestation(walletAddress); // Queries on-chain attestation ``` --- ## Usage Examples ### Basic Authentication Flow ```typescript function App() { const endpoint = clusterApiUrl('mainnet-beta'); return ( ); } function Dashboard() { const { connected } = useWallet(); const { auth, logout } = useSolanaAuth(); if (!connected) { return (

Connect Wallet

); } if (!auth.sessionAddress) { return (

Sign In

); } return (

Dashboard

Session: {auth.sessionAddress}

); } ``` ### Protected Route ```typescript function ProtectedRoute({ children }: { children: React.ReactNode }) { const { connected } = useWallet(); const { auth } = useSolanaAuth(); if (!connected) { return ; } if (!auth.sessionAddress) { return ; } return <>{children}; } ``` ### Verify Attestation on Mount ```typescript function App() { const { publicKey } = useWallet(); const { verifyAttestation, auth } = useSolanaAuth(); const [isVerifying, setIsVerifying] = useState(true); useEffect(() => { const verify = async () => { if (publicKey) { await verifyAttestation(publicKey.toBase58()); } setIsVerifying(false); }; verify(); }, [publicKey]); if (isVerifying) { return
Verifying authentication...
; } 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 (

Payment Successful

Transaction: {txHash}

); } if (isFailed) { return (

Payment Failed

Error: {errorCode}

); } if (isCancelled) { return (

Payment Cancelled

); } return

Status: {status}

; } ``` ## Complete Example A full purchase flow with invoice creation, payment, and webhook handling. ### Frontend (React) ```tsx function BuyButton({ product }) { const { authToken } = useAlien(); const payment = usePayment({ onPaid: (txHash) => console.log('Paid!', txHash), onCancelled: () => console.log('Cancelled'), onFailed: (code) => console.log('Failed:', code), }); const handlePurchase = useCallback(async () => { if (!authToken) return; // 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: product.recipientAddress, amount: product.amount, token: product.token, network: product.network, productId: product.id, }), }); const { invoice } = await res.json(); // 2. Open payment UI await payment.pay({ recipient: product.recipientAddress, amount: product.amount, token: product.token, network: product.network, invoice, item: { title: product.name, iconUrl: product.iconUrl, quantity: product.quantity, }, }); }, [authToken, payment, product]); if (!payment.supported) { return

Payments not available

; } 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 ---