Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions docs/nitrolite/build/examples.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
---
title: Examples
description: Deployed Nitrolite example applications, their source repositories, stacks, and SDK usage.
sidebar_position: 4
---

# Examples

These deployed examples show different ways to build on Nitrolite. Use the live apps to understand the product flow, then inspect the source repositories for integration details.

:::note
Stack and SDK details reflect the linked repositories when this page was added. The repositories are the source of truth if an example changes after deployment.
:::

<div className="nitrolite-example-list">
<div className="card nitrolite-example-card">
<div className="nitrolite-example-card__layout">
<a className="nitrolite-example-card__media" href="https://cosign-demo-two.vercel.app/" target="_blank" rel="noopener noreferrer">
<img className="nitrolite-example-card__image" src="/img/nitrolite/examples/cosign-demo.png" alt="Co-Sign Checkout front page" />
</a>
<div className="nitrolite-example-card__content">
<div className="card__header">
<h2>Co-Sign Checkout</h2>
</div>
<div className="card__body">
<p className="nitrolite-example-card__summary">
A shared checkout demo where two participants create a cart, co-sign checkout actions,
move funds into an app-session cart, propose purchases, close the cart, and withdraw
remaining shared-wallet funds.
</p>
<div className="nitrolite-example-card__details">
<div>
<h3>Functionality</h3>
<ul>
<li>Two-party shared cart and approval flow.</li>
<li>Shared Wallet plus Checkout Cart app session.</li>
<li>Purchase proposals, cart close, and wallet withdrawal.</li>
<li>YUSD and YELLOW on Ethereum Sepolia.</li>
</ul>
</div>
<div>
<h3>Stack</h3>
<ul>
<li>Next.js, React, TypeScript, Tailwind CSS.</li>
<li>Supabase rooms, proposals, and events.</li>
<li>Vercel deployment with scheduled proposal expiration.</li>
<li>MetaMask SDK and viem.</li>
</ul>
</div>
<div>
<h3>SDKs</h3>
<ul>
<li><code>@yellow-org/sdk</code></li>
<li><code>@yellow-org/sdk-compat</code></li>
</ul>
</div>
</div>
</div>
<div className="card__footer">
<a className="button button--primary button--sm margin-right--sm" href="https://cosign-demo-two.vercel.app/" target="_blank" rel="noopener noreferrer">Live demo</a>
<a className="button button--secondary button--sm" href="https://github.com/layer-3/cosign-demo" target="_blank" rel="noopener noreferrer">Source</a>
</div>
</div>
</div>
</div>

<div className="card nitrolite-example-card">
<div className="nitrolite-example-card__layout">
<a className="nitrolite-example-card__media" href="https://nitrolite-go-example-production.up.railway.app/" target="_blank" rel="noopener noreferrer">
<img className="nitrolite-example-card__image" src="/img/nitrolite/examples/nitrolite-store.png" alt="Nitrolite Store front page" />
</a>
<div className="nitrolite-example-card__content">
<div className="card__header">
<h2>Nitrolite Store</h2>
</div>
<div className="card__body">
<p className="nitrolite-example-card__summary">
A content-store reference app with a Go backend and browser frontend. The shopper connects
MetaMask, opens a store app session, adds funds, buys catalog content, reads purchased
content, and withdraws the remaining balance.
</p>
<div className="nitrolite-example-card__details">
<div>
<h3>Functionality</h3>
<ul>
<li>MetaMask shopper identity with backend app signing.</li>
<li>Frontend-constructed app-session updates.</li>
<li>Catalog purchase and content gating.</li>
<li>YUSD and YELLOW pricing on Ethereum Sepolia.</li>
</ul>
</div>
<div>
<h3>Stack</h3>
<ul>
<li>Go service serving API and built web UI.</li>
<li>React, Vite, TypeScript frontend.</li>
<li>SQLite persistence, Docker, Railway.</li>
<li>MetaMask and viem in the browser.</li>
</ul>
</div>
<div>
<h3>SDKs</h3>
<ul>
<li><code>github.com/layer-3/nitrolite/sdk/go</code></li>
<li><code>@yellow-org/sdk</code></li>
<li><code>@yellow-org/sdk-compat</code></li>
</ul>
</div>
</div>
</div>
<div className="card__footer">
<a className="button button--primary button--sm margin-right--sm" href="https://nitrolite-go-example-production.up.railway.app/" target="_blank" rel="noopener noreferrer">Live demo</a>
<a className="button button--secondary button--sm" href="https://github.com/layer-3/nitrolite-store-example" target="_blank" rel="noopener noreferrer">Source</a>
</div>
</div>
</div>
</div>

<div className="card nitrolite-example-card">
<div className="nitrolite-example-card__layout">
<a className="nitrolite-example-card__media" href="https://nexus-testnet-sepolia-ten.vercel.app/" target="_blank" rel="noopener noreferrer">
<img className="nitrolite-example-card__image" src="/img/nitrolite/examples/nexus-p2p-transfer.png" alt="Nexus P2P Transfer front page" />
</a>
<div className="nitrolite-example-card__content">
<div className="card__header">
<h2>Nexus P2P Transfer</h2>
</div>
<div className="card__body">
<p className="nitrolite-example-card__summary">
A workshop-style content app that demonstrates wallet connection, Nitrolite session setup,
balance polling, and instant peer-to-peer support payments to post authors.
</p>
<div className="nitrolite-example-card__details">
<div>
<h3>Functionality</h3>
<ul>
<li>MetaMask connection and Sepolia network selection.</li>
<li>Nitrolite client session with balance polling.</li>
<li>Small support transfers to author wallets.</li>
<li>YUSD and YELLOW asset selection.</li>
</ul>
</div>
<div>
<h3>Stack</h3>
<ul>
<li>Preact with Hooks, TypeScript, and Vite.</li>
<li>CSS Modules for app styling.</li>
<li>viem for wallet access and amount conversion.</li>
<li>Vercel-hosted static frontend.</li>
</ul>
</div>
<div>
<h3>SDKs</h3>
<ul>
<li><code>@yellow-org/sdk</code></li>
<li><code>@yellow-org/sdk-compat</code></li>
</ul>
</div>
</div>
</div>
<div className="card__footer">
<a className="button button--primary button--sm margin-right--sm" href="https://nexus-testnet-sepolia-ten.vercel.app/" target="_blank" rel="noopener noreferrer">Live demo</a>
<a className="button button--secondary button--sm" href="https://github.com/erc7824/nitrolite-example/tree/final-p2p-transfer" target="_blank" rel="noopener noreferrer">Source</a>
</div>
</div>
</div>
</div>
</div>
174 changes: 174 additions & 0 deletions docs/nitrolite/build/sdk/typescript-compat/errors.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
title: "Errors and recovery (compat)"
description: Typed compat errors and recovery recipes for migrated apps
sidebar_position: 6
---

# Errors and Recovery

At app boundaries, classify raw wallet or SDK failures and show only UI-safe copy:

```typescript
import { NitroliteClient, getUserFacingMessage } from '@yellow-org/sdk-compat';

try {
await client.deposit(tokenAddress, 11_000_000n);
} catch (err) {
const typed = NitroliteClient.classifyError(err);
showToast(getUserFacingMessage(typed));
throw typed;
}
```

## Error Catalogue

| Error class | Code | Trigger | Recovery |
|---|---|---|---|
| `CompatError` | varies | Base compat failure | Log `error.code`; show `getUserFacingMessage(error)` |
| `AllowanceError` | `ALLOWANCE_INSUFFICIENT` | ChannelHub needs ERC-20 allowance | Approve, then retry |
| `UserRejectedError` | `USER_REJECTED` | Wallet rejected a signature or transaction | Show retry UI; do not auto-loop prompts |
| `InsufficientFundsError` | `INSUFFICIENT_FUNDS` | Not enough gas or token balance | Show funding guidance |
| `NotInitializedError` | `NOT_INITIALIZED` | Wallet/client is disconnected | Reconnect and recreate the client |
| `OngoingStateTransitionError` | `ONGOING_STATE_TRANSITION` | Previous transition still finalizing | Poll status, then retry |

## Allowance

Compat token helpers use token addresses and raw token units:

```typescript
import { AllowanceError, NitroliteClient, getUserFacingMessage } from '@yellow-org/sdk-compat';

try {
await client.deposit(tokenAddress, 11_000_000n);
} catch (err) {
const typed = NitroliteClient.classifyError(err);
showToast(getUserFacingMessage(typed));

if (typed instanceof AllowanceError) {
await client.approveTokens(tokenAddress, 11_000_000n);
await client.deposit(tokenAddress, 11_000_000n);
}
}
```

When you fall through to native v1, use `client.innerClient.approveToken(chainId, asset, amount)` with `Decimal`:

```typescript
import Decimal from 'decimal.js';

try {
await client.innerClient.checkpoint('usdc');
} catch (err) {
showToast(getUserFacingMessage(err));
await client.innerClient.approveToken(11155111n, 'usdc', new Decimal(11));
await client.innerClient.checkpoint('usdc');
}
```

## Prompt and Reconnect

For `UserRejectedError`, show `getUserFacingMessage(error)` and expose an explicit retry button. Do not auto-loop wallet prompts. For `NotInitializedError`, reconnect the wallet and recreate `NitroliteClient` before retrying.

## Ongoing Transition

Use `EventPoller` to wait for channel status to settle before enabling another write:

```typescript
import {
EventPoller,
OngoingStateTransitionError,
NitroliteClient,
getUserFacingMessage,
} from '@yellow-org/sdk-compat';

try {
await client.deposit(tokenAddress, 11_000_000n);
} catch (err) {
const typed = NitroliteClient.classifyError(err);
showToast(getUserFacingMessage(typed));

if (typed instanceof OngoingStateTransitionError) {
const poller = new EventPoller(client, {
onChannelUpdate: (channels) => {
if (!channels.some((channel) => channel.status === 'resizing')) {
poller.stop();
enableRetry();
}
},
onError: (pollError) => showToast(getUserFacingMessage(pollError)),
}, 3000);
poller.start();
}
}
```

## Insufficient Funds

```typescript
import { InsufficientFundsError, NitroliteClient, getUserFacingMessage } from '@yellow-org/sdk-compat';

try {
await client.deposit(tokenAddress, 11_000_000n);
} catch (err) {
const typed = NitroliteClient.classifyError(err);
showToast(getUserFacingMessage(typed));

if (typed instanceof InsufficientFundsError) {
const tokenBalance = await client.getTokenBalance(tokenAddress);
renderFundingHelp({ tokenBalance, chainId: 11155111 });
}
}
```

## Missing RPC URL

On-chain helpers throw `No RPC URL configured for chain ...` when `blockchainRPCs` is missing. Fix the client config with `blockchainRPCs: { 80002: process.env.POLYGON_AMOY_RPC_URL! }`.

```typescript
try {
await client.getOpenChannels();
} catch (err) {
showToast(getUserFacingMessage(err));
if (err instanceof Error && err.message.includes('No RPC URL configured')) promptForRpcUrl();
}
```

## Unsupported Asset or Wrong Amount Unit

```typescript
try {
await client.transfer(destination, [{ asset: 'usdc', amount: '5000000' }]);
} catch (err) {
showToast(getUserFacingMessage(err));

if (err instanceof Error && err.message.includes('Unknown asset')) {
renderSupportedAssets(await client.getAssetsList());
}
}
```

If a transfer uses `5.0`, fix it before retrying. Compat transfer uses raw asset-unit strings. Keep the [amount-units table](./overview#amount-units) next to migrated payment code reviews.

## Failed Checkpoint

Retry with capped backoff only after approval and state preconditions are satisfied. Keep the visible message behind `getUserFacingMessage(err)`:

```typescript
let lastError: unknown;

for (const waitMs of [1000, 3000, 5000]) {
try {
await client.innerClient.checkpoint('usdc');
lastError = undefined;
break;
} catch (err) {
lastError = err;
showToast(getUserFacingMessage(err));
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
}

if (lastError) {
throw lastError;
}
```
Loading
Loading