npm package: @l2pass/swapkit-sdk
Description: TypeScript SDK for the L2Pass SwapKit API
Latest version: 0.2.1

# @l2pass/swapkit-sdk

TypeScript client for the **L2Pass SwapKit** partner HTTP API: typed requests, helpers, and webhook signature verification.

## Documentation

- **Interactive reference** (HTTP API + this SDK guide): [api.l2pass.com/docs](https://api.l2pass.com/docs)
- **Package on npm**: [@l2pass/swapkit-sdk](https://www.npmjs.com/package/@l2pass/swapkit-sdk)
- **Examples**: [`examples/basic-swap.ts`](./examples/basic-swap.ts), [`examples/webhook-handler.ts`](./examples/webhook-handler.ts)

## Installation

```bash
npm install @l2pass/swapkit-sdk
```

## Quick start

Use **`createSwapKit`** for grouped methods (`kit.assets.list()`, `kit.swap.quote()`, …). Each call returns the same `{ data, error, response }` shape as [`openapi-fetch`](https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-fetch).

```typescript
import { createSwapKit, AnalyticsPeriod, SwapStatus } from "@l2pass/swapkit-sdk";

const kit = createSwapKit({
  apiKey: "sk_live_...",
  // baseUrl optional; default https://api.l2pass.com
});

const { data: assets } = await kit.assets.list();
const originAsset = assets?.find((a) => a.id === "ethereum:USDC");
const destAsset = assets?.find((a) => a.id === "arbitrum:USDC");
if (!originAsset || !destAsset) throw new Error("Assets unavailable");

const { data: estimate } = await kit.swap.estimate({
  origin_asset: originAsset.id,
  dest_asset: destAsset.id,
  amount: "100000000",
  decimals: 6,
  slippage_bps: 25,
  recipient: "0x742d35cc6634c0532925a3b844bc9e7595f2bd18",
});

const { data: quote } = await kit.swap.quote({
  origin_asset: originAsset.id,
  dest_asset: destAsset.id,
  amount: "100000000",
  decimals: 6,
  slippage_bps: 25,
  recipient: "0x742d35cc6634c0532925a3b844bc9e7595f2bd18",
  refund_to: "0x9642438c800E4616B977cF52AA1310725754c807",
});

const { data: status } = await kit.swap.getStatus(quote!.quote_id);
if (status?.status === SwapStatus.INCOMPLETE_DEPOSIT) {
  console.log(status.deposited_amount, status.min_amount_in);
}

const { data: analytics } = await kit.partner.getAnalytics({
  period: AnalyticsPeriod.DAYS_7,
});
```

### Throw on error

Pass **`throwOnError: true`** to fail fast with **`SwapKitError`**:

```typescript
const kit = createSwapKit({ apiKey: "...", throwOnError: true });
const { data: me } = await kit.partner.getProfile();
```

### Cancellation / timeouts

Pass [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) as the last argument:

```typescript
const c = new AbortController();
setTimeout(() => c.abort(), 10_000);
const { data } = await kit.assets.list({ signal: c.signal });
```

## Versioning

The package follows [Semantic Versioning](https://semver.org/). See [`CHANGELOG.md`](CHANGELOG.md).

Export **`SDK_VERSION`** reflects the running package version (also used for the default `User-Agent`).

## Constants

| Export | Use |
|--------|-----|
| `SwapStatus` | Swap lifecycle (`SwapStatus.PENDING`, …); type `SwapStatusCode` |
| `TransactionListStatus` | `GET /v1/partner/transactions?status=`; type `TransactionListStatusCode` |
| `AnalyticsPeriod` | Analytics `period` (`AnalyticsPeriod.DAYS_7`, …); type `AnalyticsPeriodCode` |
| `WebhookEventType` | Webhook `event_type`; type `WebhookEventTypeCode` |
| `assetId(chain, symbol)` | Builds `chain:symbol`; prefer ids from `kit.assets.list()` |

## Low-level client

**`createSwapKitClient`** exposes typed **`GET` / `POST`** with path strings (same types as the facade, optional [`openapi-fetch` middleware](https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-fetch#middleware)).

```typescript
import { createSwapKitClient } from "@l2pass/swapkit-sdk";

const client = createSwapKitClient({
  apiKey: "sk_...",
  baseUrl: "https://api.l2pass.com", // optional
  userAgent: "@l2pass/swapkit-sdk/custom", // optional; "" omits User-Agent
});

const { data } = await client.GET("/v1/assets");
```

**`kit.rawClient`** on the high-level instance is the same low-level client.

### HTTP routes (reference)

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/v1/assets` | List supported stablecoin assets |
| `POST` | `/v1/swap/estimate` | Swap estimate (dry quote) |
| `POST` | `/v1/swap/quote` | Binding quote with deposit address |
| `GET` | `/v1/swap/status/{quoteId}` | Swap status and transitions |
| `POST` | `/v1/partner/rotate-key` | Rotate API key |
| `GET` | `/v1/partner/me` | Partner profile |
| `GET` | `/v1/partner/transactions` | Paginated transactions |
| `GET` | `/v1/partner/analytics` | Aggregated analytics |

`GET /v1/partner/me` includes **`affiliate_recipient`**. If it is unset, **estimate** and **quote** may fail until it is configured in the **partner dashboard** (or via L2Pass support).

## `verifyWebhookSignature(payload, signature, secret, options?)`

Verifies the `X-Webhook-Signature` header (constant-time HMAC-SHA256, same format as the API).

```typescript
import { verifyWebhookSignature } from "@l2pass/swapkit-sdk";

app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
  try {
    const isValid = verifyWebhookSignature(
      req.body,
      req.headers["x-webhook-signature"] as string,
      process.env.WEBHOOK_SECRET!,
      { tolerance: 300 }
    );
    if (!isValid) return res.status(401).json({ error: "Invalid signature" });
    const event = JSON.parse(req.body.toString());
    res.status(200).json({ received: true });
  } catch {
    res.status(401).json({ error: "Verification failed" });
  }
});
```

Use the **raw** request body (e.g. `express.raw()`), not re-serialized JSON.

## `SwapKitError` and error codes

Typed API errors (including with `throwOnError: true`). See **`ErrorCodes`**.

| Code | HTTP | Description |
|------|------|-------------|
| `INVALID_ASSET` | 400 | Unknown asset id |
| `INVALID_AMOUNT` | 400 | Invalid amount |
| `INVALID_SLIPPAGE` | 400 | Slippage out of range |
| `INVALID_RECIPIENT` | 400 | Invalid recipient |
| `MISSING_REFUND_ADDRESS` | 400 | Refund address required |
| `INVALID_QUOTE_ID` | 400 | Bad quote UUID |
| `QUOTE_NOT_FOUND` | 404 | Quote not found |
| `UPSTREAM_VALIDATION` | 400 | Upstream rejected request |
| `UPSTREAM_ERROR` | 502 | Upstream provider error |
| `UNAUTHORIZED` | 401 | Bad or missing API key |
| `RATE_LIMITED` | 429 | Rate limited |
| `INVALID_REQUEST` | 400 | Bad JSON or parameters |
| `INTERNAL` | 500 | Server error (message may include configuration hints) |

## Types

Named exports mirror API schemas; `paths` and `components` are re-exported for advanced typing.

```typescript
import type {
  Asset,
  SwapRequest,
  SwapEstimateResponse,
  SwapQuoteResponse,
  SwapStatusResponse,
  Partner,
  paths,
  components,
} from "@l2pass/swapkit-sdk";
```

## License

MIT