@yellow-org/sdk-compat
v1.4.0
Published
Curated migration layer preserving selected Nitrolite SDK v0.5.3 app-facing APIs over the v1 runtime.
Readme
Nitrolite Compat SDK
@yellow-org/sdk-compat is a migration layer that preserves selected Nitrolite SDK v0.5.3 app-facing APIs over the v1 runtime.
The off-chain broker was renamed from "Clearnode" to "Nitronode" in v1.3.0. See
MIGRATION-NITRONODE.md.
- Keep supported v0.5.3-style app-facing calls in your code.
- Run them through
@yellow-org/sdk-compat, backed by@yellow-org/sdk. - Treat it as a migration aid, not a drop-in replacement for the full published v0.5.3 package.
Compatibility Scope
@yellow-org/sdk-compat is intentionally narrower than the published v0.5.3 package surface.
- Preserved app-facing APIs: the
NitroliteClientfacade, selected auth helpers, app-session signing helpers, and many app-facing types remain available for supported migration paths. - Transitional helper surfaces: some legacy
create*Message/parse*Responseexports now emit real v1-compatible payloads inside the legacyreq/sigenvelope, while workflow-only helpers stay exported as fail-fast migration shims. - Unsupported full-package parity: low-level internals, broad root-export parity, and every legacy helper being runtime-faithful are not promised by this package.
Why
The v1 protocol changes wire format, authentication, WebSocket lifecycle, unit handling, asset resolution, and more. For apps built around the old surface, a direct migration can require scattered rewrites across transport, signing, and amount-handling paths.
The compat layer centralizes the supported migration paths into one package so app code can move to client-level methods incrementally instead of rewriting every call site at once.
Build Size
Measured on February 24, 2026 from the package directory using:
npm run build:prod
npm pack --dry-run --json| Metric | Size |
|---|---:|
| npm tarball (size) | 16,503 bytes (16.1 KB) |
| unpacked package (unpackedSize) | 73,292 bytes (71.6 KB) |
| compiled JS in dist/*.js | 38,146 bytes (37.3 KB) |
| type declarations in dist/*.d.ts | 20,293 bytes (19.8 KB) |
| total emitted runtime + types (.js + .d.ts) | 58,439 bytes (57.1 KB) |
Migration Guide
Step-by-step guides for migrating supported app-facing paths from v0.5.3:
- Overview & Quick Start — pattern changes, import swaps
- On-Chain Changes — deposits, withdrawals, channels
- Off-Chain Changes — auth, app sessions, transfers, ledger queries
Installation
npm install @yellow-org/sdk-compat
# peer dependencies
npm install @yellow-org/sdk viemQuick Start
1. Initialize the client
Replace new Client(ws, signer) with NitroliteClient.create():
import { NitroliteClient, blockchainRPCsFromEnv } from '@yellow-org/sdk-compat';
const client = await NitroliteClient.create({
wsURL: 'wss://nitronode.example.com/ws',
walletClient, // viem WalletClient with account
chainId: 11155111, // Sepolia
blockchainRPCs: blockchainRPCsFromEnv(),
});2. Deposit & create a channel
In v1, channel creation is implicit on deposit — no separate createChannel() call needed:
const tokenAddress = '0x6E2C4707DA119425DF2C722E2695300154652F56'; // USDC on Sepolia
const amount = 11_000_000n; // 11 USDC in raw units (6 decimals)
await client.deposit(tokenAddress as Address, amount);3. Query channels, balances, ledger entries
const channels = await client.getChannels();
const balances = await client.getBalances();
const entries = await client.getLedgerEntries();
const sessions = await client.getAppSessionsList();
const assets = await client.getAssetsList();4. Transfer off-chain
The compat transfer(destination, allocations) preserves the v0.5.3-style array-of-allocations signature. Each TransferAllocation.amount is a raw asset-unit string using the asset's canonical decimals. The compat layer divides by asset decimals before delegating to the v1 SDK's transfer(wallet, asset, Decimal):
// 5 USDC = 5_000_000 raw asset units when USDC has 6 asset decimals
await client.transfer(recipientAddress, [
{ asset: 'usdc', amount: '5000000' },
]);
// For direct SDK access with human-readable Decimal:
// await client.innerClient.transfer('0xRecip...', 'usdc', new Decimal(5));5. Close & clean up
await client.closeChannel();
await client.close();Method Cheat Sheet
Channel Operations
| Method | Description |
|---|---|
| deposit(token, amount) | Deposit to channel (creates if needed) |
| depositAndCreateChannel(token, amount) | Alias for deposit() |
| withdrawal(token, amount) | Withdraw from channel |
| closeChannel() | Close all open channels |
| resizeChannel({ allocate_amount, token }) | Resize an existing channel |
| challengeChannel({ state }) | Challenge a channel on-chain |
| acknowledge(tokenAddress) | Acknowledge a pending state or create a channel |
| checkTokenAllowance(chainId, tokenAddress) | Check ERC-20 allowance for the ChannelHub |
Queries
| Method | Description |
|---|---|
| getChannels() | List all ledger channels (open, closed, etc.) |
| getBalances(wallet?) | Get ledger balances |
| getLedgerEntries(wallet?) | Get transaction history |
| getAppSessionsList(wallet?, status?) | List app sessions (filter by 'open'/'closed') |
| getAssetsList() | List supported assets |
| getAccountInfo() | Aggregate balance + channel count |
| getConfig() | Node configuration |
| getBlockchains() | List supported blockchains |
| getEscrowChannel(escrowChannelId) | Query an escrow channel by ID |
| getChannelData(channelId) | Full channel + state for a specific channel |
| getLastAppSessionsListError() | Last getAppSessionsList() error message (if any) |
| getOpenChannels() | Read current-chain open channel IDs from the ChannelHub |
App Sessions
| Method | Description |
|---|---|
| createAppSession(definition, allocations, quorumSigs, opts?) | Create an app session with quorum signatures (optional ownerSig via opts) |
| closeAppSession(appSessionId, allocations, quorumSigs) | Close an app session with quorum signatures |
| submitAppState(params) | Submit state update (operate/deposit/withdraw/close) |
| getAppDefinition(appSessionId) | Get the definition for a session |
App-session allocation strings remain human-readable decimal strings such as '0.01'. They are not raw smallest-unit token strings.
App Session Signing Helpers
| Helper | Description |
|---|---|
| packCreateAppSessionHash(params) | Deterministic hash for createAppSession quorum signing |
| packSubmitAppStateHash(params) | Deterministic hash for submitAppState quorum signing |
| toWalletQuorumSignature(signature) | Prefixes wallet signature to compat app-session quorum format |
| toSessionKeyQuorumSignature(signature) | Prefixes app-session key signature (0xa2) to compat quorum format |
Session Key Operations
Both state.user_sig (wallet authorization) and state.session_key_sig (proof of
possession by the session-key holder) are required at submit time. The *Ownership
helpers produce session_key_sig.
| Method | Description |
|---|---|
| signChannelSessionKeyState(state) | Wallet user_sig over channel session-key state |
| signChannelSessionKeyOwnership(state, sessionKeySigner) | Session-key holder's session_key_sig for channel state |
| submitChannelSessionKeyState(state) | Register/submit a channel session-key state (both sigs required) |
| getLastChannelKeyStates(userAddress, sessionKey?, options?) | Fetch channel session-key states (active-only by default; { includeInactive: true } for expired/revoked) |
| signSessionKeyState(state) | Wallet user_sig over app session-key state |
| signAppSessionKeyOwnership(state, sessionKeySigner) | Session-key holder's session_key_sig for app state |
| submitSessionKeyState(state) | Register/submit an app-session key state (both sigs required) |
| getLastAppKeyStates(userAddress, sessionKey?, options?) | Fetch app-session key states (active-only by default; { includeInactive: true } for expired/revoked) |
| getLastKeyStates(userAddress, sessionKey?) | Deprecated — 0.5.x alias for getLastAppKeyStates; no includeInactive option |
Transfers
| Method | Description |
|---|---|
| transfer(destination, allocations) | Off-chain transfer to another participant |
TransferAllocation.amount remains a raw asset-unit string using the asset's canonical decimals, for example '5000000' for 5 USDC when USDC has 6 asset decimals.
Asset Resolution
| Method | Description |
|---|---|
| resolveToken(tokenAddress) | Look up asset info by token address |
| resolveAsset(symbol) | Look up asset info by symbol name |
| resolveAssetDisplay(tokenAddress, chainId?) | Get display-friendly symbol + decimals |
| getTokenDecimals(tokenAddress) | Get decimals for a token |
| formatAmount(tokenAddress, rawAmount) | Convert raw bigint → human-readable string |
| parseAmount(tokenAddress, humanAmount) | Convert human-readable string → raw bigint |
| findOpenChannel(tokenAddress, chainId?) | Find an open channel for a given token |
Legacy On-Chain Helpers
| Method | Description |
|---|---|
| approveTokens(tokenAddress, amount) | Approve the current-chain ChannelHub using raw token units |
| getTokenAllowance(tokenAddress) | Read current-chain ChannelHub allowance in raw token units |
| getTokenBalance(tokenAddress) | Read current-chain wallet token balance in raw token units |
Unsupported Legacy Gaps
These legacy methods remain intentionally unsupported because the v1 runtime no longer offers an honest one-to-one mapping:
createChannel(...)checkpointChannel(...)getAccountBalance(...)getChannelBalance(...)
Workflow-style RPC helpers such as createTransferMessage(...), createCreateChannelMessage(...), createCloseChannelMessage(...), and createResizeChannelMessage(...) are in the same category: they stay exported for migration, but now fail fast with migration guidance instead of silently approximating old behavior.
Lifecycle
| Method | Description |
|---|---|
| ping() | Health check |
| close() | Close the WebSocket connection |
| waitForClose() | Returns a promise that resolves when the connection is closed |
| refreshAssets() | Re-fetch the asset map from the nitronode |
Accessing the v1 SDK Directly
The underlying v1 Client is exposed for advanced use cases not covered by the compat surface:
const v1Client = client.innerClient;
await v1Client.getHomeChannel(wallet, 'usdc');Configuration
NitroliteClientConfig
interface NitroliteClientConfig {
wsURL: string; // Nitronode WebSocket URL
walletClient: WalletClient; // viem WalletClient with account
chainId: number; // Chain ID (e.g. 11155111 for Sepolia)
blockchainRPCs?: Record<number, string>; // Optional chain ID → RPC URL map
channelSessionKeySigner?: {
sessionKeyPrivateKey: Hex;
walletAddress: Address;
metadataHash: Hex;
authSig: Hex;
};
addresses?: ContractAddresses; // Deprecated — ignored, addresses come from get_config
challengeDuration?: bigint; // Deprecated — ignored
}Environment Variables
blockchainRPCsFromEnv() reads NEXT_PUBLIC_BLOCKCHAIN_RPCS:
NEXT_PUBLIC_BLOCKCHAIN_RPCS=11155111:https://rpc.sepolia.io,1:https://mainnet.infura.io/v3/KEYSigners
WalletStateSigner
A v0.5.3-compatible signer class that wraps a WalletClient. Actual state signing in v1 is handled internally by ChannelDefaultSigner; this class exists so existing store types compile:
import { WalletStateSigner } from '@yellow-org/sdk-compat';
const signer = new WalletStateSigner(walletClient);createECDSAMessageSigner
Creates a MessageSigner function from a private key, compatible with the v0.5.3 signing pattern:
import { createECDSAMessageSigner } from '@yellow-org/sdk-compat';
const sign = createECDSAMessageSigner(privateKey);
const signature = await sign(payload);Error Handling
The compat layer provides typed error classes for common failure modes:
| Error Class | Code | Description |
|---|---|---|
| CompatError | (varies) | Base class for all compat errors |
| AllowanceError | ALLOWANCE_INSUFFICIENT | Token approval needed |
| UserRejectedError | USER_REJECTED | User cancelled in wallet |
| InsufficientFundsError | INSUFFICIENT_FUNDS | Not enough balance |
| NotInitializedError | NOT_INITIALIZED | Client not connected |
| OngoingStateTransitionError | ONGOING_STATE_TRANSITION | Previous action still finalizing |
getUserFacingMessage(error)
Returns a human-friendly string suitable for UI display:
import { getUserFacingMessage, AllowanceError } from '@yellow-org/sdk-compat';
try {
await client.deposit(token, amount);
} catch (err) {
showToast(getUserFacingMessage(err));
// → "Transaction was rejected. Please approve the transaction in your wallet to continue."
}NitroliteClient.classifyError(error)
Converts raw SDK/wallet errors into the appropriate typed error:
try {
await client.deposit(token, amount);
} catch (err) {
const typed = NitroliteClient.classifyError(err);
if (typed instanceof AllowanceError) {
// prompt user to approve
}
}Event Polling
v0.5.3 used server-push WebSocket events. v1 uses a polling model. The EventPoller bridges this gap:
import { EventPoller } from '@yellow-org/sdk-compat';
const poller = new EventPoller(client, {
onChannelUpdate: (channels) => updateUI(channels),
onBalanceUpdate: (balances) => updateBalances(balances),
onAssetsUpdate: (assets) => updateAssets(assets),
onError: (err) => console.error(err),
}, 5000); // poll every 5 seconds
poller.start();
// Later:
poller.stop();
poller.setInterval(10000); // change intervalAmount conventions
The compat layer keeps the old amount conventions explicit instead of flattening them:
| Method group | Input type | Example: 100 tokens (18 decimals) |
|---|---|---|
| deposit, withdrawal | Raw bigint | 100_000_000_000_000_000_000n |
| transfer | Raw asset-unit string via TransferAllocation.amount | '100000000000000000000' |
| createAppSession, closeAppSession, submitAppState allocations | Human-readable decimal string | '100.0' |
For direct access to the v1 SDK's human-readable
DecimalAPI, useclient.innerClient.
RPC Stubs
The following functions remain exported primarily so legacy create*Message / parse*Response imports can keep compiling while an app migrates.
Some create* helpers emit real live-v1 method names and payload shapes inside the legacy req / sig envelope. Workflow-only helpers fail fast with migration guidance instead of returning fake wire payloads. parse* helpers only do lightweight normalization of known response shapes.
Prefer NitroliteClient methods directly for new integrations:
// Direct v1-compatible helper mappings:
createGetChannelsMessage, parseGetChannelsResponse,
createGetLedgerBalancesMessage, parseGetLedgerBalancesResponse,
parseGetLedgerEntriesResponse, createGetAppSessionsMessage, parseGetAppSessionsResponse,
createGetAppDefinitionMessage, parseGetAppDefinitionResponse,
createAppSessionMessage, parseCreateAppSessionResponse,
createCloseAppSessionMessage, parseCloseAppSessionResponse,
createSubmitAppStateMessage, parseSubmitAppStateResponse,
createPingMessage,
// Migration-only shims that fail fast:
createTransferMessage,
createCreateChannelMessage, parseCreateChannelResponse,
createCloseChannelMessage, parseCloseChannelResponse,
createResizeChannelMessage, parseResizeChannelResponse,
// Generic normalizers:
convertRPCToClientChannel, convertRPCToClientState,
parseAnyRPCResponse, NitroliteRPCAuth Helpers
Compat exports auth helpers for apps still using the v0.5.3 auth request/verify flow:
createAuthRequestMessage(params) // builds auth_request RPC message
createAuthVerifyMessage(signer, response) // signs and builds auth_verify RPC message
createAuthVerifyMessageWithJWT(jwt) // builds JWT-based auth_verify RPC message
createEIP712AuthMessageSigner(wallet, ...) // creates EIP-712 signer for auth_verify challengeTypes Reference
All legacy compat types are re-exported from @yellow-org/sdk-compat:
Enums
RPCMethod— RPC method names (Ping,GetConfig,GetChannels, etc.)RPCChannelStatus— Channel status values (Open,Closed,Resizing,Challenged,Closing)
Wire Types
MessageSigner—(payload: Uint8Array) => Promise<string>NitroliteRPCMessage—{ req: [number, string, any, number]; sig: string }RPCResponse—{ requestId, method, params }RPCBalance—{ asset, amount }RPCAsset—{ token, chainId, symbol, decimals }RPCChannelUpdate— Full channel update payloadRPCLedgerEntry— Ledger transaction entryAccountID— String alias for account identifiers
Channel Operation Types
ContractAddresses—{ custody, adjudicator }Allocation—{ destination, token, amount }FinalState— Final channel state with signaturesChannelData—{ lastValidState, stateData }CreateChannelResponseParams,CloseChannelResponseParamsResizeChannelRequestParamsTransferAllocation—{ asset, amount }
App Session Types
RPCAppDefinition—{ application, protocol, participants, weights, quorum, challenge, nonce }RPCAppSessionAllocation—{ participant, asset, amount }CloseAppSessionRequestParams
State Channel Primitives
Channel— Channel metadata (id, participants, adjudicator, challenge, nonce, version)State— Channel state (channelId, version, data, allocations)AppLogic<T>— Interface for custom app logic implementations
Nitronode Response Types
AccountInfo—{ balances: LedgerBalance[], channelCount: bigint }LedgerChannel— Full ledger channel record (id, participant, status, token, amount, chain_id, etc.)LedgerBalance—{ asset, amount }LedgerEntry— Ledger entry with credit/debitAppSession— App session recordClearNodeAsset—{ token, chainId, symbol, decimals }
Advanced Configuration
buildClientOptions
Converts a CompatClientConfig into v1 Option[] values passed to Client.create(). Useful if you need to customise the underlying SDK client beyond what NitroliteClient.create() exposes:
import { buildClientOptions, type CompatClientConfig } from '@yellow-org/sdk-compat';
const opts = buildClientOptions({
wsURL: 'wss://nitronode.example.com/ws',
blockchainRPCs: { 11155111: 'https://rpc.sepolia.io' },
});Next.js Integration Notes
When using the compat package in a Next.js app with Turbopack:
- Add to
transpilePackagesinnext.config.ts:
const nextConfig = {
transpilePackages: ['@yellow-org/sdk', '@yellow-org/sdk-compat'],
};- The package declares
"sideEffects": falsein itspackage.json, enabling tree-shaking of unused exports.
Peer Dependencies
| Package | Version |
|---|---|
| @yellow-org/sdk | >=1.2.0 |
| viem | ^2.0.0 |
License
MIT
