@pegaroute/sdk 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Simply Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,210 @@
1
- # Temporary Holding Version
1
+ # @pegaroute/sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TypeScript SDK for the Pegaroute cross-chain swap API.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install @pegaroute/sdk
9
+ ```
10
+
11
+ Requires Node.js 22.12+ (or Bun / Deno). The package is ESM only: use `import`, or `require()` from CommonJS, which
12
+ Node supports from 22.12. Where `require()` of ESM isn't available, CommonJS code can load it with
13
+ `const { Pegaroute } = await import("@pegaroute/sdk");`.
14
+
15
+ The client holds your API key, so **only use it on your backend**, never in browser code. Your frontend talks to your
16
+ backend, and has the user's wallet sign each swap with your wallet library of choice (e.g. viem). The SDK doesn't sign:
17
+ it returns what to sign, and the browser-safe `@pegaroute/sdk/types` (types and type guards, no client) helps your
18
+ frontend read it.
19
+
20
+ ## Quote and swap (backend)
21
+
22
+ ```ts
23
+ import { type CreateSwapParams, hasQuoteRouteFees, Pegaroute } from "@pegaroute/sdk";
24
+
25
+ const pr = new Pegaroute({ apiKey: process.env.PEGAROUTE_API_KEY!, baseUrl: process.env.PEGAROUTE_BASE_URL! });
26
+
27
+ const params = {
28
+ fromChain: "ETH",
29
+ fromToken: "ETH",
30
+ toChain: "BTC",
31
+ toToken: "BTC",
32
+ amount: "0.05",
33
+ senderAddress: "0xabc…",
34
+ destinationAddress: "bc1q…",
35
+ } satisfies CreateSwapParams;
36
+
37
+ // Routes are best first. No routes means no provider can quote the pair: see `quote.warnings`.
38
+ const quote = await pr.quotes.get(params);
39
+ const best = quote.routes[0];
40
+
41
+ if (best) {
42
+ // Show the user what the route costs. `{}` means no fee data, not a zero fee.
43
+ if (hasQuoteRouteFees(best.fees)) showCost(`$${best.fees.totalUsd} (${best.fees.totalBps} bps)`);
44
+
45
+ // Some routes add a fee in the source chain's native coin, which the wallet pays on top of `amount`.
46
+ if (best.additionalFee) showExtraFee(`${best.additionalFee.amount} ${best.additionalFee.token}`);
47
+
48
+ const swap = await pr.swaps.create({ ...params, quoteId: quote.quoteId, routeProvider: best.provider });
49
+
50
+ // Send `swap.transactionId` and `swap.execution` to the frontend for signing.
51
+ }
52
+ ```
53
+
54
+ `fees.totalUsd` is never negative. If the output is worth more than the input at reference prices, every fee is `0`,
55
+ so two routes showing `0` can still pay out different amounts: compare routes by `expectedOutput`. Only
56
+ `fees.breakdown.route` can be negative, when the provider's rate beats the reference price.
57
+
58
+ ## Sign (frontend, EVM with viem)
59
+
60
+ `swap.execution` says what the user's wallet must sign. For an EVM router call (e.g. THORChain):
61
+
62
+ ```ts
63
+ import { isExecutionFamily, isExecutionMode, type SwapExecution } from "@pegaroute/sdk/types";
64
+ import { type Address, createWalletClient, custom, type Hex } from "viem";
65
+
66
+ async function signSwap(execution: SwapExecution) {
67
+ if (!isExecutionFamily(execution, "evm") || !isExecutionMode(execution, "contract-call")) {
68
+ throw new Error("Not handled here");
69
+ }
70
+
71
+ const wallet = createWalletClient({ transport: custom(window.ethereum!) });
72
+ const [account] = await wallet.requestAddresses();
73
+
74
+ // If `execution.approval` is set, send the ERC-20 approve first (see examples/frontend/evm.ts).
75
+ const txHash = await wallet.sendTransaction({
76
+ account: account!,
77
+ chain: null,
78
+ to: execution.to as Address,
79
+ data: (execution.data ?? "0x") as Hex,
80
+ value: BigInt(execution.value?.baseUnits ?? 0),
81
+ });
82
+
83
+ // POST txHash to your backend.
84
+ }
85
+ ```
86
+
87
+ ## Other execution families and modes
88
+
89
+ EVM `contract-call` is one of several. `execution` is a union: switch on `family`, then `mode`, never on the provider.
90
+
91
+ ```ts
92
+ switch (execution.family) {
93
+ case "evm": // execution.mode: "contract-call" | "native-transfer" | "erc20-transfer"
94
+ case "utxo": // "payment-with-memo" | "serialized-tx" (PSBT)
95
+ case "cosmos": // "bank-send" | "msg-deposit" | "msg-execute"
96
+ // … "solana", "sui", "xrp", "stellar", "tron", "near", "hypercore", "cardano", "other"
97
+ }
98
+
99
+ // Or handle one mode across every family, e.g. a single "send to this deposit address" screen:
100
+ if (isExecutionMode(execution, "deposit-transfer")) showDeposit(execution.to, execution.amount, execution.memo);
101
+ ```
102
+
103
+ The guards `isExecutionFamily(execution, family)` and `isExecutionMode(execution, mode)` narrow `execution` to the
104
+ matching types. Each type documents what to sign.
105
+
106
+ ## Report the tx hash and track status (backend)
107
+
108
+ ```ts
109
+ await pr.swaps.submitTxHash(swap.transactionId, { txHash });
110
+
111
+ // Poll until the swap finishes (default: every 10 s, for up to 1 hour). Or use webhooks.
112
+ const final = await pr.swaps.waitForCompletion(swap.transactionId);
113
+ if (final.status === "fail") console.log(final.error?.userMessage, final.refund);
114
+ ```
115
+
116
+ ## Webhooks
117
+
118
+ **Registering webhooks:** done once in the Pegaroute UI. Set your endpoint URL on the integration and copy its
119
+ `whsec_…` secret (shown only when created or rotated).
120
+
121
+ ```ts
122
+ import { verifyWebhook, WebhookVerificationError } from "@pegaroute/sdk";
123
+
124
+ // Verify the raw body: parsing and re-serialising it breaks the signature.
125
+ const event = await verifyWebhook({
126
+ rawBody,
127
+ signature: request.headers.get("x-pegasus-signature"),
128
+ secret: process.env.PEGAROUTE_WEBHOOK_SECRET!,
129
+ });
130
+
131
+ if (event.event === "transaction.completed") {
132
+ // `event.data` is fully typed.
133
+ }
134
+ ```
135
+
136
+ `verifyWebhook` throws `WebhookVerificationError` for a missing or wrong signature: respond 401. Delivery is
137
+ at-least-once, so handle events idempotently. A delivery can be replayed, so confirm with `swaps.get` before acting on
138
+ funds.
139
+
140
+ ## Errors
141
+
142
+ Every API error is a `PegarouteError`, with a subclass per status (`NotFoundError`, `RateLimitError`, …).
143
+
144
+ ```ts
145
+ import { PegarouteError, ProviderChangedError } from "@pegaroute/sdk";
146
+
147
+ try {
148
+ await pr.swaps.create({ ...params, quoteId: quote.quoteId, routeProvider: best.provider });
149
+ } catch (error) {
150
+ if (error instanceof ProviderChangedError) {
151
+ // The quoted route is gone: show `error.newQuote` to the user, then create the swap with it.
152
+ } else if (error instanceof PegarouteError) {
153
+ console.log(error.code, error.status, error.userMessage); // `userMessage` is safe to show users
154
+ } else {
155
+ throw error;
156
+ }
157
+ }
158
+ ```
159
+
160
+ GET requests are retried on connection errors and on errors whose `retryable` is `true` (`maxRetries`, default 2).
161
+ `retryable` is Pegaroute's own flag, or, when the response doesn't carry one (e.g. a proxy's error page), `true` for
162
+ 429, 502, 503 and 504. POSTs are never retried.
163
+
164
+ ## Statuses
165
+
166
+ Pegaroute has two status sets. Use `status` (external) for business logic; `internalStatus` is for debugging.
167
+
168
+ | internal | external |
169
+ |---|---|
170
+ | pending | pending |
171
+ | submitted, executing, confirming | executing |
172
+ | completed | success |
173
+ | failed, refunded | fail |
174
+
175
+ | Call | Returns |
176
+ |---|---|
177
+ | `swaps.get`, `transactions.list`, webhooks | `status` (external) + `internalStatus` |
178
+ | `swaps.create` | `status` (external) |
179
+ | `swaps.submitTxHash` | internal (`"submitted"`) |
180
+ | `swaps.events` | internal (audit log) |
181
+ | `transactions.list({ status })` filter | takes internal values |
182
+
183
+ `toExternalStatus(internal)` and `isTerminalStatus(status)` are exported from both entry points.
184
+
185
+ ## Examples
186
+
187
+ [`examples/`](examples) has a minimal backend ([Hono](https://hono.dev)) and the full EVM signing example, including
188
+ the ERC-20 approval.
189
+
190
+ ## Development
191
+
192
+ ```sh
193
+ bun install
194
+ bun test # unit tests
195
+ bun run test:node # built package on real Node (needs Node ≥22.12, see .node-version)
196
+ bun run typecheck && bun run lint && bun run build
197
+ bun run check:package # builds (prepack command), then checks package exports and types
198
+ ```
199
+
200
+ CI runs all of the above, plus `typecheck:examples`, on every pull request. These are the checks contributors run.
201
+
202
+ End-to-end tests are for maintainers only: they need a local Pegaroute, started from the private Pegaroute repo
203
+ (`docker compose up -d --build`), and an operator login. Copy [`.env.example`](.env.example) to `.env` and fill it in;
204
+ it explains what each variable needs.
205
+
206
+ ```sh
207
+ bun run test:e2e # REST endpoints and webhooks
208
+ bun run test:e2e:examples # backend example + EVM signing (nothing is broadcast)
209
+ bun run typecheck:examples # examples against the built SDK
210
+ ```