npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@swype-org/checkout-mobile

v0.1.4

Published

Lightweight mobile checkout SDK — open a hosted Swype payment flow via in-app browser, handle deep-link completion, zero runtime dependencies

Readme

@swype-org/checkout-mobile

Note: For the best native UX (biometric passkey prompts with no browser chrome), use the platform-specific SDKs instead:

This SDK remains available as a fallback for React Native / Expo apps that prefer the simpler in-app browser integration without native bridging.

Lightweight mobile checkout SDK — open a hosted Swype payment flow via in-app browser, handle deep-link completion, zero runtime dependencies.

Quick Start

npm install @swype-org/checkout-mobile
import { MobileCheckout } from '@swype-org/checkout-mobile';

const checkout = new MobileCheckout({
  signer: 'https://api.merchant.com/sign-payment',
  callbackScheme: 'myapp',
  openUrl: (url) => openInAppBrowser(url),
});

// Listen for deep links and pass them to the SDK
onDeepLink((url) => checkout.handleDeepLink(url));

// Start a deposit
const { transfer } = await checkout.requestDeposit({
  amount: 50,
  chainId: 8453,
  address: '0x...',
  token: 'USDC',
});

console.log('Transfer complete:', transfer.id, transfer.status);

React Native Hook

A dedicated React Native entry point eliminates all boilerplate:

npm install @swype-org/checkout-mobile react
import { useSwypeMobileCheckout } from '@swype-org/checkout-mobile/react-native';
import * as Linking from 'expo-linking';
import * as WebBrowser from 'expo-web-browser';

function DepositButton() {
  const { status, result, error, displayMessage, requestDeposit, handleDeepLink } =
    useSwypeMobileCheckout({
      signer: 'https://api.merchant.com/sign-payment',
      callbackScheme: 'myapp',
      openUrl: (url) => WebBrowser.openBrowserAsync(url).then(() => {}),
    });

  useEffect(() => {
    const sub = Linking.addEventListener('url', ({ url }) => handleDeepLink(url));
    return () => sub.remove();
  }, [handleDeepLink]);

  return (
    <>
      <Button
        title={status === 'signer-loading' ? 'Preparing…' : 'Deposit $50'}
        disabled={status === 'signer-loading'}
        onPress={() =>
          requestDeposit({ amount: 50, chainId: 8453, address: '0x...', token: 'USDC' })
        }
      />
      {error && <Text>{displayMessage}</Text>}
      {result && <Text>Transfer {result.transfer.id} complete!</Text>}
    </>
  );
}

How It Works

Merchant App / SDK              Merchant Signer                Hosted Flow (in-app browser)
     │                               │                               │
     │  1. requestDeposit(request)   │                               │
     │──────────────────────────────►│                               │
     │                               │  2. signer(data) or POST URL  │
     │                               │  (includes callbackScheme     │
     │                               │   and webviewBaseUrl)         │
     │  3. { signature, payload, ...}│                               │
     │◄──────────────────────────────│                               │
     │                                                               │
     │  4. SDK builds URL, openUrl() → in-app browser                │
     │──────────────────────────────────────────────────────────────►│
     │                                                               │
     │  5. User completes payment in hosted flow                     │
     │                                                               │
     │  6. Redirect → myapp://swype/callback?transferId=...          │
     │◄─────────────────────────────────────────────────────────────│
     │                                                               │
     │  7. handleDeepLink(url) → Promise resolves with DepositResult │

Signer Contract

The signer config option controls how the SDK obtains a signed payment link. It accepts either a URL string or a custom function, giving you full control over authentication, HTTP method, and request shape. This is the same contract as @swype-org/checkout (web), except callbackScheme is set to the mobile app's URL scheme instead of null.

Using a URL string (simple)

When signer is a string, the SDK sends a POST with a JSON body to that URL and expects a SignerResponse back:

const checkout = new MobileCheckout({
  signer: 'https://api.merchant.com/sign-payment',
  callbackScheme: 'myapp',
  openUrl: (url) => WebBrowser.openBrowserAsync(url).then(() => {}),
});

Using a custom function (full control)

When signer is a function, the SDK calls it with a SignerRequest object and expects a Promise<SignerResponse>. Use this when you need control over the HTTP method, authentication, request transformation, or any other aspect of the signing call:

import type { SignerFunction } from '@swype-org/checkout-mobile';

const checkout = new MobileCheckout({
  signer: async (data) => {
    const res = await fetch('https://api.merchant.com/sign-payment', {
      method: 'PATCH',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${getToken()}`,
      },
      body: JSON.stringify({ ...data, orderId: 'order-123' }),
    });
    if (!res.ok) throw new Error(`Signer error: ${res.status}`);
    return res.json();
  },
  callbackScheme: 'myapp',
  openUrl: (url) => WebBrowser.openBrowserAsync(url).then(() => {}),
});

SignerRequest (input)

The data the SDK passes to your signer (as the JSON body for URL mode, or as the function argument for function mode):

| Field | Type | Description | | --- | --- | --- | | amount | number | USD amount to deposit (always > 0). | | chainId | number | EVM chain ID for the destination (e.g. 8453 for Base). | | address | string | Destination wallet address (0x-prefixed, 40 hex chars). | | token | string | Token symbol on the destination chain (e.g. "USDC"). | | callbackScheme | string | URL scheme registered by the mobile app (e.g. "myapp"). The hosted flow redirects to {callbackScheme}://swype/callback?... on completion. | | url | string | Base webview URL the SDK will navigate to. Provided for logging/validation — your signer does not construct the final URL. | | version | string | Protocol version (currently "v1"). | | reference | string? | Merchant order or invoice ID for reconciliation. | | metadata | object? | Arbitrary key-value pairs forwarded from the merchant app. |

Example:

{
  "amount": 50,
  "chainId": 8453,
  "address": "0x...",
  "token": "USDC",
  "callbackScheme": "myapp",
  "url": "https://webview-app-staging.staging-swype.network",
  "version": "v1"
}

What the signer must do

  1. Validate the request fields.
  2. Generate an idempotency key (UUID) for this payment.
  3. Build a payload — a base64url-encoded JSON string containing the payment parameters:
{
  "amount": 50,
  "chainId": 8453,
  "address": "0x...",
  "token": "USDC",
  "idempotencyKey": "generated-uuid",
  "callbackScheme": "myapp",
  "expiresAt": "2026-03-07T12:00:00Z",
  "version": "v1"
}
  1. Sign the payload string with your merchant private key (SHA-256) and base64url-encode the signature.

SignerResponse (output)

The response your signer must return (as JSON for URL mode, or as the resolved value for function mode):

| Field | Type | Description | | --- | --- | --- | | merchantId | string | Your merchant UUID. | | payload | string | Base64url-encoded payment payload (see above). | | signature | string | Base64url-encoded signature of the payload string. | | expiresAt | string | ISO 8601 expiration timestamp for this payment link. | | preview | object | Echo of the payment parameters for client-side display. | | preview.amount | number | Deposit amount. | | preview.chainId | number | Destination chain ID. | | preview.address | string | Destination wallet address. | | preview.token | string | Destination token symbol. | | preview.idempotencyKey | string | The generated idempotency key. |

Example:

{
  "merchantId": "uuid",
  "payload": "base64url-encoded-payload",
  "signature": "base64url-encoded-signature",
  "expiresAt": "2026-03-07T12:00:00Z",
  "preview": {
    "amount": 50,
    "chainId": 8453,
    "address": "0x...",
    "token": "USDC",
    "idempotencyKey": "uuid"
  }
}

The SDK constructs the hosted flow URL by appending merchantId, payload, and signature as query parameters to the webviewBaseUrl, then opens it in the in-app browser. When the user completes payment the hosted flow redirects to {callbackScheme}://swype/callback?... and the SDK resolves the promise.

Configuration

const checkout = new MobileCheckout({
  // Required: URL string or custom async function (see "Signer Contract")
  signer: 'https://api.merchant.com/sign-payment',

  // Required: URL scheme registered by your mobile app (e.g. 'myapp')
  callbackScheme: 'myapp',

  // Required: function that opens a URL in an in-app browser
  openUrl: (url) => WebBrowser.openBrowserAsync(url).then(() => {}),

  // Optional: base URL of the hosted payment webview app.
  // Default: 'https://webview-app-staging.staging-swype.network'
  webviewBaseUrl: 'https://webview-app-staging.staging-swype.network',

  // Optional: path for the callback deep link (default: '/swype/callback')
  callbackPath: '/swype/callback',

  // Optional: max ms to wait for signer response (default: 15000)
  signerTimeoutMs: 15_000,

  // Optional: max ms for entire flow (signer + user completion)
  flowTimeoutMs: 300_000,

  // Optional: enable debug logging to console
  debug: false,
});

Deep Link Setup

Your mobile app must be configured to handle the callback URL scheme.

Expo / React Native

In app.json:

{
  "expo": {
    "scheme": "myapp"
  }
}

iOS (native)

Register your URL scheme in Info.plist:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
  </dict>
</array>

Android (native)

Add an intent filter in AndroidManifest.xml:

<activity ...>
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="myapp" />
  </intent-filter>
</activity>

Handling Deep Links

The SDK does not set up deep link listeners itself — this keeps it platform-agnostic. You must forward incoming URLs to handleDeepLink():

// Expo
import * as Linking from 'expo-linking';
Linking.addEventListener('url', ({ url }) => checkout.handleDeepLink(url));

// React Native (bare)
import { Linking } from 'react-native';
Linking.addEventListener('url', ({ url }) => checkout.handleDeepLink(url));

// iOS native (AppDelegate)
func application(_ app: UIApplication, open url: URL, options: ...) -> Bool {
    // bridge call to checkout.handleDeepLink(url.absoluteString)
}

// Android native (Activity)
override fun onNewIntent(intent: Intent) {
    intent.data?.toString()?.let { checkout.handleDeepLink(it) }
}

handleDeepLink() returns true if the URL was a Swype callback, false otherwise.

Observable Status

| Status | Meaning | | ---------------- | ------------------------------------------- | | idle | No active flow | | signer-loading | Calling the merchant signer endpoint | | browser-active | In-app browser is open, waiting for user | | completed | Transfer succeeded | | error | Something failed |

checkout.on('status-change', (status) => console.log('Status:', status));
checkout.status;   // current status
checkout.result;   // last DepositResult (when completed)
checkout.error;    // last CheckoutError (when error)
checkout.isActive; // true during signer-loading or browser-active

Error Handling

Every error is a CheckoutError with a machine-readable code:

| Code | Meaning | | ------------------------ | ----------------------------------------------- | | BROWSER_FAILED | Failed to open the in-app browser | | BROWSER_DISMISSED | User dismissed the browser before completing | | DEEP_LINK_INVALID | Callback deep link was malformed | | SIGNER_REQUEST_FAILED | Signer returned a non-2xx response | | SIGNER_NETWORK_ERROR | Network failure reaching the signer | | SIGNER_RESPONSE_INVALID| Signer response missing required fields | | SIGNER_TIMEOUT | Signer did not respond within signerTimeoutMs | | FLOW_TIMEOUT | Entire flow exceeded flowTimeoutMs | | INVALID_REQUEST | Bad input (amount, address, etc.) |

Use getDisplayMessage() for user-facing strings:

import { CheckoutError, getDisplayMessage } from '@swype-org/checkout-mobile';

try {
  await checkout.requestDeposit({ ... });
} catch (err) {
  if (err instanceof CheckoutError) {
    Alert.alert('Payment Error', getDisplayMessage(err));
  }
}

Events

checkout.on('complete', (result) => { /* DepositResult */ });
checkout.on('error', (error) => { /* CheckoutError */ });
checkout.on('dismiss', () => { /* browser dismissed */ });
checkout.on('status-change', (status) => { /* MobileCheckoutStatus */ });

Lifecycle

// Cancel the current flow and reset to idle
checkout.close();

// Tear down and release all resources (call on unmount)
checkout.destroy();

Comparison with Other Swype SDKs

| Aspect | @swype-org/checkout (web) | @swype-org/checkout-mobile | checkout-ios-sdk | checkout-android-sdk | | ----------------- | ------------------------------ | ---------------------------------- | -------------------------------- | -------------------------------- | | Platform | Browser | React Native / iOS / Android | iOS 16+ | Android 9+ (API 28) | | Language | TypeScript | TypeScript | Swift | Kotlin | | Passkey handling | Hosted flow (iframe) | Hosted flow (in-app browser) | Native ASAuthorization | Native Credential Manager | | UX | Modal iframe overlay | In-app browser with browser chrome | Direct biometric prompt | Direct biometric prompt | | Flow mechanism | iframe + postMessage | In-app browser + deep link | Direct API calls + passkey | Direct API calls + passkey | | Completion signal | postMessage | URL scheme callback | async return value | Coroutine return value | | Dependencies | None | None | None (Apple frameworks only) | Credential Manager, OkHttp |

TypeScript

All types are exported:

import type {
  MobileCheckoutConfig,
  MobileCheckoutStatus,
  DepositRequest,
  DepositResult,
  SignerFunction,
  SignerRequest,
  SignerResponse,
  TransferSummary,
} from '@swype-org/checkout-mobile';

import type { CheckoutErrorCode } from '@swype-org/checkout-mobile';