Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/widget-usdh-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@usdh-kit/widget': minor
---

Add `USDHMigration`, an exit widget that converts a HyperCore USDH balance back to USDC as USDH is sunset on Hyperliquid.
180 changes: 67 additions & 113 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<!--
npm-dependent badges, intentionally commented until @usdh-kit/sdk and
@usdh-kit/widget are published. Uncomment as part of the first npm
@usdh-kit/widget are published. Uncomment only as part of an approved npm
publish PR.

[![@usdh-kit/sdk](https://img.shields.io/npm/v/@usdh-kit/sdk?style=flat&color=000000&label=%40usdh-kit%2Fsdk)](https://www.npmjs.com/package/@usdh-kit/sdk)
Expand All @@ -16,57 +16,58 @@
[![Bundle](https://img.shields.io/bundlephobia/minzip/@usdh-kit/sdk?style=flat&color=000000&label=sdk%20gzipped)](https://bundlephobia.com/package/@usdh-kit/sdk)
-->

USDH sunset migration kit for Hyperliquid.

TypeScript SDK for USDH on Hyperliquid.

USDH is the native stablecoin on Hyperliquid, issued by Bridge and designed by Native Markets, with 50% of reserve revenue routed to the Hyperliquid Assistance Fund. `@usdh-kit/sdk` ships the retail-side plumbing (pair resolution, signing, transport) so apps and bots can convert into USDH without writing the Hyperliquid action layer themselves. `@usdh-kit/widget` is an embeddable React component on top of the SDK so dapps can drop in a swap form in a few lines.
USDH is being sunset in favour of USDC. The final purpose of `usdh-kit` is to
help users migrate remaining HyperCore USDH balances back to USDC, while keeping
the original USDH integration work available as legacy reference code.

- **Source:** [github.com/sumfxn/usdh-kit](https://github.com/sumfxn/usdh-kit)
- **Issues:** [github.com/sumfxn/usdh-kit/issues](https://github.com/sumfxn/usdh-kit/issues)
- **USDH:** [usdh.com](https://usdh.com) (issued by [Bridge](https://bridge.xyz), designed by [Native Markets](https://www.nativemarkets.com))
- **Hyperliquid:** [hyperliquid.xyz](https://hyperliquid.xyz) · [docs](https://hyperliquid.gitbook.io/hyperliquid-docs)
- **Hyperliquid:** [hyperliquid.xyz](https://hyperliquid.xyz) / [docs](https://hyperliquid.gitbook.io/hyperliquid-docs)

## Status

Pre-release. Public API is unstable until `1.0.0`.

What works today:
This repo is in sunset/migration mode:

- `getQuote()` and `swap()` for `USDC → USDH` and `USDH → USDC` end to end
- `bridgeToCore()` for moving USDC from HyperEVM to HyperCore, with credit polling
- `bridgeFromCore()` for moving linked USDC/USDH spot assets from HyperCore to HyperEVM
- `getHypercoreBalance()` for spendable HyperCore balances (`total - hold`)
- `getRoute()` / `preflightSwap()` to choose direct HyperCore swap vs HyperEVM bridge
- `bridgeAndSwap()` for the common route → bridge → swap retail flow
- Hyperliquid agent-wallet support for browser-safe L1 order signing
- React widget with built-in source-chain selection (HyperEVM bridge or direct HyperCore swap), short-lived trading sessions, friendly errors, and full theming via CSS variables
- `@usdh-kit/widget` exports `USDHMigration`, a wallet-gated `USDH -> USDC` exit widget.
- `@usdh-kit/sdk` keeps the USDH quote, swap, bridge, and signing helpers needed for migration.
- `USDHSwap` and `bridgeAndSwap()` remain available only as legacy compatibility surfaces.
- HIP-4 and outcome helpers remain historical/read-only reference work, not the future product surface.
- Future USDC or HIP-4 tooling should live in a separate repo/package with a clean name and API.
- No release, merge, or npm publish is implied without explicit approval.

Deferred to follow-up PRs:
## Migration Widget

- USDT pricing and swap (USDT/USDC/USDH double-hop)
- Multi-chain source via LiFi/Squid (Ethereum, Arbitrum, Base)
```tsx
// app/layout.tsx
import '@usdh-kit/widget/styles.css'

## Install
// app/page.tsx
import { USDHMigration } from '@usdh-kit/widget'

```sh
pnpm add @usdh-kit/sdk
export default function Page() {
return <USDHMigration network="mainnet" />
}
```

For the React widget:
The migration widget:

```sh
pnpm add @usdh-kit/widget wagmi viem @tanstack/react-query react react-dom
```
- reads the connected wallet through wagmi;
- shows the user's HyperCore USDH balance;
- reads the live `USDH/USDC` book before wallet writes;
- never fakes a `1:1` receive estimate when the quote is unavailable;
- asks for a short-lived Hyperliquid trading session before submitting the `USDH -> USDC` order;
- does not bridge the resulting USDC out to HyperEVM.

The widget depends on `@usdh-kit/sdk` internally. Install the SDK separately
only when your app imports SDK APIs directly.

## SDK quickstart
## SDK Migration Flow

```ts
import { approveAgent, createUsdhKit } from '@usdh-kit/sdk'

// Browser apps should use an approved Hyperliquid agent wallet for L1 orders.
await approveAgent({
network: 'mainnet',
signer: masterWalletSigner,
Expand All @@ -83,110 +84,63 @@ const kit = createUsdhKit({
slippageBps: 30,
})

// quote
const amount = 11_000_000n // 11 USDC; Hyperliquid spot orders must be >10 USDC

const quote = await kit.getQuote({ from: 'USDC', amount })
console.log(`would receive ~${quote.estimatedReceived} USDH`)
const amount = 11_000_000n // 11 USDH; Hyperliquid spot orders must be >10 USDH

// move USDC from HyperEVM to HyperCore (skip if already on HC)
const bridge = await kit.bridgeToCore({ asset: 'USDC', amount })
const quote = await kit.getQuote({ from: 'USDH', to: 'USDC', amount })
console.log(`would receive ~${quote.estimatedReceived} USDC`)

// swap on HyperCore via IOC limit at mid + slippageBps
const result = await kit.swap({ from: 'USDC', amount })
console.log(`got ${result.received} USDH for ${result.spent} USDC`)
console.log(`realised slippage: ${result.slippageBps}bps`)

// reverse direction on HyperCore
const reverse = await kit.swap({ from: 'USDH', to: 'USDC', amount: 11_000_000n })
console.log(`got ${reverse.received} USDC`)

// or let the SDK route, bridge if needed, then swap
const routed = await kit.bridgeAndSwap({
from: 'USDC',
amount,
onProgress: (event) => console.log(event.phase),
})
console.log(`route: ${routed.route.sourceChain}`)
console.log(`order: ${routed.swap.orderId}`)
const result = await kit.swap({ from: 'USDH', to: 'USDC', amount })
console.log(`got ${result.received} USDC for ${result.spent} USDH`)
```

`swap()` submits an IOC limit order priced from the current mid: `USDC -> USDH` buys up to `mid + slippageBps`, while `USDH -> USDC` sells down to `mid - slippageBps`. The returned `result.slippageBps` is the realised slippage versus mid.

## Widget quickstart
`swap()` submits an IOC limit order priced from the current mid. The sunset path
sells `USDH -> USDC` down to `mid - slippageBps`. Legacy `USDC -> USDH`
acquisition remains in the package for existing integrators, but it is no longer
the migration path.

The widget reads the connected wallet from wagmi. Wrap your tree in `WagmiProvider` and `QueryClientProvider` (e.g. via ConnectKit or RainbowKit), import the stylesheet once at your app root, then drop the component in.
## Packages

```tsx
// app/layout.tsx (Next.js)
import '@usdh-kit/widget/styles.css'

// app/page.tsx
import { USDHSwap } from '@usdh-kit/widget'

export default function Page() {
return <USDHSwap network="mainnet" />
}
```sh
pnpm add @usdh-kit/sdk
pnpm add @usdh-kit/widget wagmi viem @tanstack/react-query react react-dom
```

The widget defaults to `theme="auto"` (follows the user's system). Force a palette with `<USDHSwap network="mainnet" theme="dark" />` or `<USDHSwap network="mainnet" theme="light" />`. Override any colour token from your own stylesheet — see [docs/theming.md](./docs/theming.md).

On first use, the widget asks the connected wallet to approve a short-lived Hyperliquid trading session. That agent signs only the HyperCore USDH order. If the route starts on HyperEVM, the connected wallet still submits the USDC approval/deposit transactions required for the Circle bridge.

<img src="./docs/assets/widget-dark.png" alt="USDH swap widget dark mode" width="480" />

## Use cases
## Archive Contents

A few real flows the SDK is shaped for today. Runnable examples are still on the roadmap; until they land, treat these as the integration targets to copy into your own app.
The repo also preserves the original USDH work:

- **End-to-end CLI** — bridge + quote + swap from a private key on the command line. Smallest possible integration.
- **Discord bot** — slash command that quotes `USDC → USDH`, confirms the route, then calls `bridgeAndSwap()` with progress updates.
- **Subscription billing** — collect or rebalance USDC into USDH from a merchant wallet after a payment webhook.
- **Treasury rebalance** — scheduled job that converts a fraction of HyperCore USDC above a floor into USDH. Designed for cron.
- HyperCore/HyperEVM balance and bridge helpers;
- USDH spot pair discovery;
- Hyperliquid agent-wallet signing;
- legacy `USDC -> USDH` swap and bridge flows;
- experimental read-only HIP-4/outcome helpers;
- a demo registry for migration and historical component references.

## Features (V1)

- `USDC → USDH` quote and swap via the canonical HL spot pair
- `USDH → USDC` reverse swap on HyperCore
- HyperEVM → HyperCore bridge with credit polling (`bridgeToCore`)
- HyperCore → HyperEVM bridge-out for linked USDC/USDH spot assets (`bridgeFromCore`)
- HyperCore balance, route/preflight helpers plus `bridgeAndSwap()` orchestration
- Experimental read-only outcome market metadata, books, and mids
- USDH-only spot order helpers for placing, cancelling, and reading USDH-pair orders
- Wallet-agnostic `Signer` interface (works with viem, ethers, Privy, Turnkey, raw private key)
- Approved Hyperliquid agent wallet flow for browser apps (`approveAgent`, `accountAddress`)
- Read-only `InfoClient` (spotMeta, outcomeMeta, spot clearinghouse state, L2 book, allMids) for consumers building custom UIs
- Typed error hierarchy rooted at `UsdhKitError`, including `BridgeAndSwapError` phase/cause context and `isBridgeAndSwapError()` for orchestration failures
- `friendlyError()` helper to map SDK errors to short, copy-safe strings
- React widget (`@usdh-kit/widget`) with light, dark and auto theming (WCAG AA defaults, CSS variables for integrator overrides)
- npm provenance on every release
- Mainnet and testnet support, no signing on read paths
Those surfaces are kept for compatibility and reference. They are not a roadmap
for turning `usdh-kit` into a USDC, generic spot, or HIP-4 SDK.

## Docs

- [docs/architecture.md](./docs/architecture.md) — what the SDK does under the hood, in the order it does it (msgpack, signing, bridge polling, error model).
- [docs/agent-wallets.md](./docs/agent-wallets.md) — secure signing patterns for builders: backend agent, browser trading session, externally managed signer.
- [docs/bridge-and-swap.md](./docs/bridge-and-swap.md) — the full USDC HyperEVM → USDH HyperCore flow, including wallet prompts and recovery.
- [docs/glossary.md](./docs/glossary.md) — Hyperliquid terms used across the SDK and widget (HyperEVM vs HyperCore, IOC, system address, weiDecimals, …).
- [docs/theming.md](./docs/theming.md) — widget CSS variable list, override patterns, SSR-flash mitigation, Tailwind setup.
- [docs/troubleshooting.md](./docs/troubleshooting.md) — common errors with concrete fixes (`MissingEvmWalletError`, `BridgeTimeoutError`, "borders render bright white", …).
- [docs/README.md](./docs/README.md) - docs index
- [docs/architecture.md](./docs/architecture.md) - SDK internals and legacy architecture
- [docs/agent-wallets.md](./docs/agent-wallets.md) - secure signing patterns
- [docs/bridge-and-swap.md](./docs/bridge-and-swap.md) - legacy acquisition flow and migration notes
- [docs/glossary.md](./docs/glossary.md) - Hyperliquid terms used in the repo
- [docs/roadmap.md](./docs/roadmap.md) - sunset roadmap and non-goals
- [docs/theming.md](./docs/theming.md) - widget CSS variables and theming
- [docs/troubleshooting.md](./docs/troubleshooting.md) - common errors and recovery notes

## Runtime support
## Runtime Support

- Node.js >= 18.18 (native `fetch`, `AbortController`, `bigint`)
- Node.js >= 18.18
- Bun >= 1.1
- Modern evergreen browsers (Chrome >= 107, Safari >= 16, Firefox >= 104)
- Edge runtimes (Cloudflare Workers, Vercel Edge)

Consumers targeting older environments must downlevel via their bundler.

## Why USDH

Hyperliquid's primary stable was USDC, bridged from Arbitrum. USDH is native, fully reserved (cash plus US Treasuries), and routes 50% of reserve revenue to the Assistance Fund instead of an issuer. Apps that hold or pay out stables on Hyperliquid have a reason to prefer USDH; this SDK removes the friction.
- Modern evergreen browsers
- Edge runtimes with `fetch`, `AbortController`, and `bigint`

## Contributing

See [`CONTRIBUTING.md`](./CONTRIBUTING.md). Security disclosures: [`SECURITY.md`](./SECURITY.md).
See [`CONTRIBUTING.md`](./CONTRIBUTING.md). Security disclosures:
[`SECURITY.md`](./SECURITY.md).

## License

Expand Down
1 change: 1 addition & 0 deletions apps/demo/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
2 changes: 1 addition & 1 deletion apps/demo/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import '../../../../packages/widget/src/styles.css'

export const metadata: Metadata = {
title: 'usdh-kit demo',
description: 'Swap stables into USDH on Hyperliquid, end-to-end demo',
description: 'Migrate remaining USDH back to USDC on Hyperliquid.',
}

export const viewport: Viewport = {
Expand Down
2 changes: 1 addition & 1 deletion apps/demo/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function Home() {
<ConnectButton />
</header>
<p className="mt-1 text-sm text-neutral-600 dark:text-neutral-400">
Swap stables into USDH on Hyperliquid.
Migrate remaining USDH back to USDC on Hyperliquid.
</p>
<div className="mt-5">
<SwapSection />
Expand Down
4 changes: 2 additions & 2 deletions apps/demo/src/components/ComponentDocsShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function ComponentDocsShell({ activeSlug, children }: ComponentDocsShellP
<SheetContent className="w-[calc(100vw-1rem)] max-w-80">
<SheetHeader>
<SheetTitle>Components</SheetTitle>
<SheetDescription>USDH surfaces for builder apps.</SheetDescription>
<SheetDescription>USDH sunset migration surfaces.</SheetDescription>
</SheetHeader>
<Separator />
<div className="px-3 py-4">
Expand Down Expand Up @@ -103,7 +103,7 @@ export function ComponentDocsShell({ activeSlug, children }: ComponentDocsShellP
Registry
</div>
<p className="mt-1 text-sm leading-6 text-neutral-500 dark:text-neutral-400">
Copyable USDH product patterns.
USDH sunset migration and legacy references.
</p>
</div>
<SidebarSearch value={navQuery} onChange={setNavQuery} />
Expand Down
6 changes: 6 additions & 0 deletions apps/demo/src/components/ComponentPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const UsdhWidgetPreview = dynamic<SnapshotPreviewProps>(
() => import('./registry/previews/widget').then((module) => module.UsdhWidgetPreview),
{ loading: PreviewLoading },
)
const UsdhMigrationPreview = dynamic<StaticPreviewProps>(
() => import('./registry/previews/widget').then((module) => module.UsdhMigrationPreview),
{ loading: PreviewLoading },
)
const MarketBoardPreview = dynamic<MarketPreviewProps>(
() => import('./registry/previews/market-board').then((module) => module.MarketBoardPreview),
{ loading: PreviewLoading },
Expand Down Expand Up @@ -70,6 +74,8 @@ export function ComponentPreview({
switch (slug) {
case 'usdh-widget':
return <UsdhWidgetPreview snapshot={snapshot} size={size} previewId={previewId} />
case 'usdh-migration':
return <UsdhMigrationPreview size={size} previewId={previewId} />
case 'market-board':
return (
<MarketBoardPreview
Expand Down
7 changes: 4 additions & 3 deletions apps/demo/src/components/ComponentRegistryExperience.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
visibleComponentEntries,
} from '../lib/component-registry'

const categories = ['All', 'Widget', 'USDH', 'HIP-4', 'Trading'] as const
const categories = ['All', 'Widget'] as const
type CategoryFilter = (typeof categories)[number]

export function ComponentRegistryExperience() {
Expand Down Expand Up @@ -47,10 +47,11 @@ export function ComponentRegistryExperience() {
<header className="space-y-4">
<div className="max-w-[680px]">
<h1 className="max-w-[19rem] break-words text-2xl font-semibold tracking-normal text-neutral-950 text-balance dark:text-neutral-50 sm:max-w-[720px] sm:text-3xl">
USDH components for app builders.
USDH sunset migration kit.
</h1>
<p className="mt-3 max-w-[21rem] break-words text-sm leading-6 text-neutral-600 dark:text-neutral-400 sm:max-w-2xl">
Copyable USDH and HIP-4 patterns with preview, code, contract, and examples.
A migration widget for remaining USDH balances, with the legacy swap kept only for
compatibility.
</p>
</div>
</header>
Expand Down
4 changes: 2 additions & 2 deletions apps/demo/src/components/SwapSection.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use client'

import { USDHSwap } from '@usdh-kit/widget'
import { USDHMigration } from '@usdh-kit/widget'

export function SwapSection() {
return (
<section className="flex justify-center">
<USDHSwap network="mainnet" />
<USDHMigration network="mainnet" />
</section>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function QuoteSummary({
<div>
<div className="text-sm font-semibold">Quote summary</div>
<p className="mt-1 text-sm text-neutral-500">
Read-only context for a USDC to USDH quote.
Read-only context for a USDH to USDC migration quote.
</p>
</div>
<span className="inline-flex items-center gap-1 rounded-md border border-neutral-200 bg-neutral-50 px-2 py-1 text-xs font-medium text-neutral-600 dark:border-neutral-800 dark:bg-neutral-900/60 dark:text-neutral-300">
Expand Down
Loading
Loading