@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 +21 -0
- package/README.md +209 -2
- package/dist/index-ClDOR6-k.d.ts +1135 -0
- package/dist/index.d.ts +499 -0
- package/dist/index.js +901 -0
- package/dist/types-Dt7OyLsD.js +77 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -0
- package/package.json +63 -4
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
|
-
#
|
|
1
|
+
# @pegaroute/sdk
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
```
|