|
| 1 | +# Walutomat SDK |
| 2 | + |
| 3 | +Unofficial SDK clients for the [Walutomat API v2.0.0](https://api.walutomat.pl/v2.0.0/). |
| 4 | + |
| 5 | +Walutomat is a Polish peer-to-peer currency exchange platform operated by Currency One. This SDK provides typed clients for their REST API, covering wallet management, transfers, direct FX, and the P2P market order book. |
| 6 | + |
| 7 | +## Packages |
| 8 | + |
| 9 | +| Package | Description | |
| 10 | +| ------------------------------------------------ | ------------------------------------------ | |
| 11 | +| [`packages/typescript`](packages/typescript) | TypeScript client library | |
| 12 | +| [`packages/api-explorer`](packages/api-explorer) | Interactive API docs (OpenAPI + Scalar UI) | |
| 13 | + |
| 14 | +## Prerequisites |
| 15 | + |
| 16 | +- A Walutomat account with API access enabled |
| 17 | +- An RSA 4096-bit key pair (for signed endpoints) |
| 18 | + |
| 19 | +### Generate RSA keys |
| 20 | + |
| 21 | +```bash |
| 22 | +openssl genrsa -out private.key 4096 |
| 23 | +openssl rsa -in private.key -pubout -out public.key |
| 24 | +``` |
| 25 | + |
| 26 | +Upload `public.key` in the Walutomat User Panel under **Additional services > API Key**. Keep `private.key` safe — the SDK uses it to sign requests. |
| 27 | + |
| 28 | +## TypeScript SDK |
| 29 | + |
| 30 | +Zero dependencies. Uses native `fetch` and `node:crypto`. |
| 31 | + |
| 32 | +### Install |
| 33 | + |
| 34 | +```bash |
| 35 | +cd packages/typescript |
| 36 | +bun install |
| 37 | +``` |
| 38 | + |
| 39 | +### Usage |
| 40 | + |
| 41 | +```typescript |
| 42 | +import { createClient } from "walutomat-sdk"; |
| 43 | +import { readFileSync } from "node:fs"; |
| 44 | + |
| 45 | +const client = createClient({ |
| 46 | + apiKey: "your-api-key", |
| 47 | + privateKey: readFileSync("./private.key", "utf-8"), |
| 48 | + // sandbox: true, // use api.walutomat.dev |
| 49 | +}); |
| 50 | + |
| 51 | +// Wallet balances |
| 52 | +const balances = await client.account.getBalances(); |
| 53 | + |
| 54 | +// Exchange rates + execute a direct exchange |
| 55 | +const rate = await client.directFx.getRates({ currencyPair: "EURPLN" }); |
| 56 | +const { result } = await client.directFx.exchange({ |
| 57 | + submitId: crypto.randomUUID(), |
| 58 | + currencyPair: "EURPLN", |
| 59 | + buySell: "BUY", |
| 60 | + volume: "100.00", |
| 61 | + volumeCurrency: "EUR", |
| 62 | + ts: rate.ts, |
| 63 | +}); |
| 64 | + |
| 65 | +// P2P market order book (no auth required) |
| 66 | +const offers = await client.marketFx.getBestOffers({ currencyPair: "EURPLN" }); |
| 67 | + |
| 68 | +// Auto-paginate through operation history |
| 69 | +for await (const item of client.account.getHistoryIterator()) { |
| 70 | + console.log(item.operationAmount, item.currency); |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +### Tree-shakeable imports |
| 75 | + |
| 76 | +For smaller bundles, import individual endpoint functions instead of the full client: |
| 77 | + |
| 78 | +```typescript |
| 79 | +import { createHttpClient } from "walutomat-sdk"; |
| 80 | +import { getBalances } from "walutomat-sdk/account"; |
| 81 | +import { getBestOffers } from "walutomat-sdk/market-fx"; |
| 82 | + |
| 83 | +const http = createHttpClient({ apiKey: "...", privateKey: "..." }); |
| 84 | +const balances = await getBalances(http); |
| 85 | +``` |
| 86 | + |
| 87 | +### API coverage |
| 88 | + |
| 89 | +| Group | Endpoints | |
| 90 | +| ------------- | ------------------------------------------------------------------------------------------------------------------ | |
| 91 | +| **Account** | `getBalances`, `getHistory`, `getHistoryIterator`, `getHistoryMt940` | |
| 92 | +| **Transfers** | `getTransferStatus`, `createInternalTransfer`, `createIbanTransfer`, `createSepaTransfer`, `createNonIbanTransfer` | |
| 93 | +| **Direct FX** | `getRates`, `createExchange` | |
| 94 | +| **Market FX** | `getBestOffers`, `getBestOffersDetailed`, `getActiveOrders`, `getOrder`, `submitOrder`, `closeOrder` | |
| 95 | + |
| 96 | +### Error handling |
| 97 | + |
| 98 | +```typescript |
| 99 | +import { WalutomatApiError, WalutomatHttpError } from "walutomat-sdk/errors"; |
| 100 | + |
| 101 | +try { |
| 102 | + await client.transfers.createIban({ |
| 103 | + /* ... */ |
| 104 | + }); |
| 105 | +} catch (err) { |
| 106 | + if (err instanceof WalutomatApiError) { |
| 107 | + // API returned success: false |
| 108 | + console.error(err.errors); |
| 109 | + } else if (err instanceof WalutomatHttpError) { |
| 110 | + // HTTP error (429, 500, network failure, etc.) |
| 111 | + console.error(err.statusCode, err.responseBody); |
| 112 | + } |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +### Testing |
| 117 | + |
| 118 | +```bash |
| 119 | +cd packages/typescript |
| 120 | + |
| 121 | +# Unit tests |
| 122 | +bun run test |
| 123 | + |
| 124 | +# Integration tests (requires .env with credentials) |
| 125 | +bun run test:integration |
| 126 | +``` |
| 127 | + |
| 128 | +## API Explorer |
| 129 | + |
| 130 | +Interactive API documentation powered by [Scalar](https://scalar.com/) with a local signing proxy that lets you try all 16 endpoints directly from the browser. |
| 131 | + |
| 132 | +### Setup |
| 133 | + |
| 134 | +```bash |
| 135 | +cd packages/api-explorer |
| 136 | + |
| 137 | +# Create .env with your credentials |
| 138 | +cp .env.example .env |
| 139 | +# Edit .env — set WALUTOMAT_API_KEY and WALUTOMAT_PRIVATE_KEY_PATH |
| 140 | + |
| 141 | +bun start |
| 142 | +# Open http://localhost:3333 |
| 143 | +``` |
| 144 | + |
| 145 | +The signing proxy runs on `localhost:3333` and handles RSA signature computation, so Scalar's "Try it" feature works for every endpoint — including authenticated ones that require `X-API-Signature`. |
| 146 | + |
| 147 | +### How the proxy works |
| 148 | + |
| 149 | +Scalar sends requests to `http://localhost:3333/proxy/api/v2.0.0/...`. The proxy: |
| 150 | + |
| 151 | +1. Reads your API key and private key from `.env` |
| 152 | +2. Computes `X-API-Timestamp` and `X-API-Signature` headers |
| 153 | +3. Forwards the signed request to the real Walutomat API |
| 154 | +4. Returns the response with CORS headers |
| 155 | + |
| 156 | +### Keeping the spec in sync |
| 157 | + |
| 158 | +The OpenAPI spec (`packages/api-explorer/openapi.json`) is covered by a drift-detection test in the TypeScript package. Running `bun run test` checks that: |
| 159 | + |
| 160 | +- Every endpoint path + HTTP method in the source code has a matching entry in the spec |
| 161 | +- Response schema field names match the TypeScript interfaces |
| 162 | +- Enum values match the TypeScript union types |
| 163 | + |
| 164 | +If you add or change an endpoint in the SDK, the test will fail until you update the spec. |
| 165 | + |
| 166 | +## Authentication details |
| 167 | + |
| 168 | +The Walutomat API uses two-layer auth on all endpoints (except `GET /market_fx/best_offers`): |
| 169 | + |
| 170 | +1. **`X-API-Key`** — your API key |
| 171 | +2. **`X-API-Signature`** + **`X-API-Timestamp`** — RSA SHA-256 signature of `timestamp + endpointPath + bodyOrQuery` |
| 172 | + |
| 173 | +Both the signature and timestamp must always be provided together. The SDK handles this automatically when you provide a `privateKey`. |
| 174 | + |
| 175 | +## License |
| 176 | + |
| 177 | +[MIT](LICENSE) |
0 commit comments