@renegade-fi/renegade-sdk 0.1.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) 2025 Renegade
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 ADDED
@@ -0,0 +1,31 @@
1
+ # Renegade External Match Client
2
+
3
+ A TypeScript client for interacting with the Renegade Darkpool's External Match API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Using npm
9
+ npm install renegade-sdk
10
+
11
+ # Using yarn
12
+ yarn add renegade-sdk
13
+
14
+ # Using bun
15
+ bun add renegade-sdk
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ See the [examples](examples) for usage examples.
21
+
22
+ ## Documentation
23
+
24
+ See the following resources for more information:
25
+ - [Examples From the Existing SDK](https://github.com/renegade-fi/typescript-sdk/tree/main/examples)
26
+ - [Renegade Docs](https://docs.renegade.fi/technical-reference/typescript-sdk#external-atomic-matching)
27
+ - [OpenAPI spec](https://github.com/renegade-fi/renegade-docs/blob/main/api-specs/external-match-openapi.yaml)
28
+
29
+ ## License
30
+
31
+ MIT
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Basic example of using the Renegade External Match Client
3
+ *
4
+ * This example demonstrates how to create a client, request a quote, assemble a match, and submit the transaction on-chain.
5
+ */
6
+
7
+ import {
8
+ ExternalMatchClient,
9
+ OrderSide,
10
+ } from '../index';
11
+ import type { ExternalOrder } from '../index';
12
+ import { stringifyBigJSON } from '../src/http';
13
+
14
+ // Viem imports for on-chain transactions
15
+ import { http, createWalletClient } from 'viem';
16
+ import { privateKeyToAccount } from 'viem/accounts';
17
+ import { arbitrumSepolia } from 'viem/chains';
18
+
19
+ // Get API credentials from environment variables
20
+ const API_KEY = process.env.EXTERNAL_MATCH_KEY || '';
21
+ const API_SECRET = process.env.EXTERNAL_MATCH_SECRET || '';
22
+ const PRIVATE_KEY = process.env.PKEY || '';
23
+ const RPC_URL = process.env.RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc';
24
+
25
+ // Validate API credentials
26
+ if (!API_KEY || !API_SECRET) {
27
+ console.error('Error: Missing API credentials');
28
+ console.error('Please set EXTERNAL_MATCH_KEY and EXTERNAL_MATCH_SECRET environment variables');
29
+ process.exit(1);
30
+ }
31
+
32
+ // Validate wallet private key
33
+ if (!PRIVATE_KEY) {
34
+ console.error('Error: Missing private key');
35
+ console.error('Please set PRIVATE_KEY environment variable');
36
+ process.exit(1);
37
+ }
38
+
39
+ // Set up wallet client for blockchain transactions
40
+ const privateKey = PRIVATE_KEY.startsWith('0x') ? PRIVATE_KEY : `0x${PRIVATE_KEY}`;
41
+ const walletClient = createWalletClient({
42
+ account: privateKeyToAccount(privateKey as `0x${string}`),
43
+ chain: arbitrumSepolia,
44
+ transport: http(RPC_URL),
45
+ });
46
+
47
+ // Create the external match client
48
+ console.log('API KEY', API_KEY);
49
+ const client = ExternalMatchClient.newSepoliaClient(API_KEY, API_SECRET);
50
+
51
+ // Example order for USDC/WETH pair
52
+ const order: ExternalOrder = {
53
+ quote_mint: '0xdf8d259c04020562717557f2b5a3cf28e92707d1', // USDC
54
+ base_mint: '0xc3414a7ef14aaaa9c4522dfc00a4e66e74e9c25a', // WETH
55
+ side: OrderSide.BUY,
56
+ quote_amount: BigInt(20_000_000), // 20 USDC
57
+ };
58
+
59
+ /**
60
+ * Submit a transaction to the chain
61
+ * @param settlementTx The settlement transaction
62
+ * @returns The transaction hash
63
+ */
64
+ async function submitTransaction(settlementTx: any): Promise<`0x${string}`> {
65
+ console.log('Submitting transaction...');
66
+
67
+ const tx = await walletClient.sendTransaction({
68
+ to: settlementTx.to as `0x${string}`,
69
+ data: settlementTx.data as `0x${string}`,
70
+ value: settlementTx.value ? BigInt(settlementTx.value) : BigInt(0),
71
+ });
72
+
73
+ return tx;
74
+ }
75
+
76
+ // Full example with on-chain submission
77
+ async function fullExample() {
78
+ try {
79
+ // Step 1: Request a quote
80
+ console.log('Requesting quote...');
81
+ const quote = await client.requestQuote(order);
82
+
83
+ if (!quote) {
84
+ console.log('No quote available');
85
+ return;
86
+ }
87
+
88
+ console.log('Quote received!');
89
+
90
+ // Step 2: Assemble the quote into a match bundle
91
+ console.log('Assembling match...');
92
+ const bundle = await client.assembleQuote(quote);
93
+
94
+ if (!bundle) {
95
+ console.log('No match available');
96
+ return;
97
+ }
98
+
99
+ console.log('Match assembled!');
100
+
101
+ // Step 3: Submit the transaction on-chain
102
+ const txHash = await submitTransaction(bundle.match_bundle.settlement_tx);
103
+ console.log(
104
+ 'Transaction submitted:',
105
+ `${walletClient.chain.blockExplorers?.default.url}/tx/${txHash}`
106
+ );
107
+ } catch (error) {
108
+ console.error('Error:', error);
109
+ }
110
+ }
111
+
112
+ // Run the examples
113
+ async function main() {
114
+ console.log('Running full example with on-chain submission...');
115
+ await fullExample();
116
+ }
117
+
118
+ // Only run if this file is being executed directly
119
+ if (require.main === module) {
120
+ main().catch(console.error);
121
+ }
package/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Renegade External Match Client
3
+ * A TypeScript client for interacting with the Renegade Darkpool API.
4
+ */
5
+
6
+ // Export main client
7
+ export { ExternalMatchClient, ExternalMatchClientError, RequestQuoteOptions, AssembleExternalMatchOptions } from './src/client';
8
+
9
+ // Export types
10
+ export type {
11
+ ApiExternalAssetTransfer,
12
+ ApiTimestampedPrice,
13
+ ApiExternalMatchResult,
14
+ FeeTake,
15
+ ExternalOrder,
16
+ ApiExternalQuote,
17
+ ApiSignedExternalQuote,
18
+ GasSponsorshipInfo,
19
+ SignedGasSponsorshipInfo,
20
+ SignedExternalQuote,
21
+ SettlementTransaction,
22
+ AtomicMatchApiBundle,
23
+ ExternalQuoteRequest,
24
+ ExternalQuoteResponse,
25
+ AssembleExternalMatchRequest,
26
+ ExternalMatchResponse
27
+ } from './src/types';
28
+
29
+ // Export enums
30
+ export { OrderSide } from './src/types';
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@renegade-fi/renegade-sdk",
3
+ "version": "0.1.0",
4
+ "description": "A TypeScript client for interacting with the Renegade Darkpool API",
5
+ "module": "index.ts",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "scripts": {
10
+ "build": "tsc -p tsconfig.build.json",
11
+ "update-version": "bash scripts/update-version.sh",
12
+ "prepublishOnly": "npm run update-version && npm run build"
13
+ },
14
+ "keywords": [
15
+ "renegade",
16
+ "darkpool",
17
+ "trading",
18
+ "api",
19
+ "client",
20
+ "cryptocurrency",
21
+ "defi"
22
+ ],
23
+ "author": "Renegade",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "@noble/hashes": "^1.7.1",
27
+ "json-bigint": "^1.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/bun": "latest",
31
+ "@types/json-bigint": "^1.0.4",
32
+ "typescript": "^5",
33
+ "viem": "^2.23.15"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/renegade-fi/typescript-external-match-client"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ }
42
+ }
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+
3
+ # Script to update the SDK version in the version.ts file
4
+ # This should be run as part of the prepublishOnly npm script
5
+
6
+ set -e
7
+
8
+ # Get the package version from package.json using jq
9
+ VERSION=$(jq -r '.version' package.json)
10
+
11
+ if [ -z "$VERSION" ]; then
12
+ echo "Error: Could not find version in package.json"
13
+ exit 1
14
+ fi
15
+
16
+ # Path to version.ts file
17
+ VERSION_FILE="src/version.ts"
18
+
19
+ # Generate version file content
20
+ cat > $VERSION_FILE << EOF
21
+ /**
22
+ * SDK version information
23
+ * This file is automatically updated during the build process
24
+ * Last updated: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
25
+ */
26
+
27
+ export const VERSION = '$VERSION';
28
+ EOF
29
+
30
+ echo "Successfully updated version to $VERSION in $VERSION_FILE"
package/src/client.ts ADDED
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Client for interacting with the Renegade external matching API.
3
+ *
4
+ * This client handles authentication and provides methods for requesting quotes,
5
+ * assembling matches, and executing trades.
6
+ */
7
+
8
+ import { RelayerHttpClient, RENEGADE_HEADER_PREFIX } from './http';
9
+ import { VERSION } from './version';
10
+ import type {
11
+ ExternalOrder,
12
+ SignedExternalQuote,
13
+ ExternalQuoteRequest,
14
+ ExternalQuoteResponse,
15
+ AssembleExternalMatchRequest,
16
+ ExternalMatchResponse,
17
+ ApiSignedExternalQuote,
18
+ AtomicMatchApiBundle
19
+ } from './types';
20
+
21
+ // Constants for API URLs
22
+ const SEPOLIA_BASE_URL = "https://testnet.auth-server.renegade.fi";
23
+ const MAINNET_BASE_URL = "https://mainnet.auth-server.renegade.fi";
24
+
25
+ // Header constants
26
+ const RENEGADE_API_KEY_HEADER = "x-renegade-api-key";
27
+ const RENEGADE_SDK_VERSION_HEADER = "x-renegade-sdk-version";
28
+
29
+ // API Routes
30
+ const REQUEST_EXTERNAL_QUOTE_ROUTE = "/v0/matching-engine/quote";
31
+ const ASSEMBLE_EXTERNAL_MATCH_ROUTE = "/v0/matching-engine/assemble-external-match";
32
+
33
+ // Query Parameters
34
+ const DISABLE_GAS_SPONSORSHIP_QUERY_PARAM = "disable_gas_sponsorship";
35
+ const GAS_REFUND_ADDRESS_QUERY_PARAM = "refund_address";
36
+ const REFUND_NATIVE_ETH_QUERY_PARAM = "refund_native_eth";
37
+
38
+ /**
39
+ * Get the SDK version string.
40
+ *
41
+ * @returns The SDK version prefixed with "typescript-v"
42
+ */
43
+ function getSdkVersion(): string {
44
+ return `typescript-v${VERSION}`;
45
+ }
46
+
47
+ /**
48
+ * Options for requesting a quote.
49
+ */
50
+ export class RequestQuoteOptions {
51
+ disableGasSponsorship: boolean = false;
52
+ gasRefundAddress?: string;
53
+ refundNativeEth: boolean = false;
54
+
55
+ /**
56
+ * Create a new instance of RequestQuoteOptions.
57
+ */
58
+ static new(): RequestQuoteOptions {
59
+ return new RequestQuoteOptions();
60
+ }
61
+
62
+ /**
63
+ * Set whether gas sponsorship should be disabled.
64
+ */
65
+ withGasSponsorshipDisabled(disableGasSponsorship: boolean): RequestQuoteOptions {
66
+ this.disableGasSponsorship = disableGasSponsorship;
67
+ return this;
68
+ }
69
+
70
+ /**
71
+ * Set the gas refund address.
72
+ */
73
+ withGasRefundAddress(gasRefundAddress: string): RequestQuoteOptions {
74
+ this.gasRefundAddress = gasRefundAddress;
75
+ return this;
76
+ }
77
+
78
+ /**
79
+ * Set whether to refund in native ETH.
80
+ */
81
+ withRefundNativeEth(refundNativeEth: boolean): RequestQuoteOptions {
82
+ this.refundNativeEth = refundNativeEth;
83
+ return this;
84
+ }
85
+
86
+ /**
87
+ * Build the request path with query parameters.
88
+ */
89
+ buildRequestPath(): string {
90
+ const params = new URLSearchParams();
91
+ params.set(DISABLE_GAS_SPONSORSHIP_QUERY_PARAM, this.disableGasSponsorship.toString());
92
+ if (this.gasRefundAddress) {
93
+ params.set(GAS_REFUND_ADDRESS_QUERY_PARAM, this.gasRefundAddress);
94
+ }
95
+
96
+ if (this.refundNativeEth) {
97
+ params.set(REFUND_NATIVE_ETH_QUERY_PARAM, this.refundNativeEth.toString());
98
+ }
99
+
100
+ return `${REQUEST_EXTERNAL_QUOTE_ROUTE}?${params.toString()}`;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Options for assembling an external match.
106
+ */
107
+ export class AssembleExternalMatchOptions {
108
+ doGasEstimation: boolean = false;
109
+ receiverAddress?: string;
110
+ updatedOrder?: ExternalOrder;
111
+ requestGasSponsorship: boolean = false;
112
+ gasRefundAddress?: string;
113
+
114
+ /**
115
+ * Create a new instance of AssembleExternalMatchOptions.
116
+ */
117
+ static new(): AssembleExternalMatchOptions {
118
+ return new AssembleExternalMatchOptions();
119
+ }
120
+
121
+ /**
122
+ * Set whether to do gas estimation.
123
+ */
124
+ withGasEstimation(doGasEstimation: boolean): AssembleExternalMatchOptions {
125
+ this.doGasEstimation = doGasEstimation;
126
+ return this;
127
+ }
128
+
129
+ /**
130
+ * Set the receiver address.
131
+ */
132
+ withReceiverAddress(receiverAddress: string): AssembleExternalMatchOptions {
133
+ this.receiverAddress = receiverAddress;
134
+ return this;
135
+ }
136
+
137
+ /**
138
+ * Set the updated order.
139
+ */
140
+ withUpdatedOrder(updatedOrder: ExternalOrder): AssembleExternalMatchOptions {
141
+ this.updatedOrder = updatedOrder;
142
+ return this;
143
+ }
144
+
145
+ /**
146
+ * Set whether to request gas sponsorship.
147
+ * @deprecated Request gas sponsorship when requesting a quote instead
148
+ */
149
+ withGasSponsorship(requestGasSponsorship: boolean): AssembleExternalMatchOptions {
150
+ this.requestGasSponsorship = requestGasSponsorship;
151
+ return this;
152
+ }
153
+
154
+ /**
155
+ * Set the gas refund address.
156
+ * @deprecated Request gas sponsorship when requesting a quote instead
157
+ */
158
+ withGasRefundAddress(gasRefundAddress: string): AssembleExternalMatchOptions {
159
+ this.gasRefundAddress = gasRefundAddress;
160
+ return this;
161
+ }
162
+
163
+ /**
164
+ * Build the request path with query parameters.
165
+ */
166
+ buildRequestPath(): string {
167
+ // If no query parameters are needed, return the base path
168
+ if (!this.requestGasSponsorship && !this.gasRefundAddress) {
169
+ return ASSEMBLE_EXTERNAL_MATCH_ROUTE;
170
+ }
171
+
172
+ const params = new URLSearchParams();
173
+ if (this.requestGasSponsorship) {
174
+ // We only write this query parameter if it was explicitly set
175
+ params.set(DISABLE_GAS_SPONSORSHIP_QUERY_PARAM, (!this.requestGasSponsorship).toString());
176
+ }
177
+
178
+ if (this.gasRefundAddress) {
179
+ params.set(GAS_REFUND_ADDRESS_QUERY_PARAM, this.gasRefundAddress);
180
+ }
181
+
182
+ return `${ASSEMBLE_EXTERNAL_MATCH_ROUTE}?${params.toString()}`;
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Error thrown by the ExternalMatchClient.
188
+ */
189
+ export class ExternalMatchClientError extends Error {
190
+ statusCode?: number;
191
+
192
+ constructor(message: string, statusCode?: number) {
193
+ super(message);
194
+ this.name = 'ExternalMatchClientError';
195
+ this.statusCode = statusCode;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Client for interacting with the Renegade external matching API.
201
+ */
202
+ export class ExternalMatchClient {
203
+ private apiKey: string;
204
+ private httpClient: RelayerHttpClient;
205
+
206
+ /**
207
+ * Initialize a new ExternalMatchClient.
208
+ *
209
+ * @param apiKey The API key for authentication
210
+ * @param apiSecret The API secret for request signing
211
+ * @param baseUrl The base URL of the Renegade API
212
+ */
213
+ constructor(apiKey: string, apiSecret: string, baseUrl: string) {
214
+ this.apiKey = apiKey;
215
+ this.httpClient = new RelayerHttpClient(baseUrl, apiSecret);
216
+ }
217
+
218
+ /**
219
+ * Create a new client configured for the Sepolia testnet.
220
+ *
221
+ * @param apiKey The API key for authentication
222
+ * @param apiSecret The API secret for request signing
223
+ * @returns A new ExternalMatchClient configured for Sepolia
224
+ */
225
+ static newSepoliaClient(apiKey: string, apiSecret: string): ExternalMatchClient {
226
+ return new ExternalMatchClient(apiKey, apiSecret, SEPOLIA_BASE_URL);
227
+ }
228
+
229
+ /**
230
+ * Create a new client configured for mainnet.
231
+ *
232
+ * @param apiKey The API key for authentication
233
+ * @param apiSecret The API secret for request signing
234
+ * @returns A new ExternalMatchClient configured for mainnet
235
+ */
236
+ static newMainnetClient(apiKey: string, apiSecret: string): ExternalMatchClient {
237
+ return new ExternalMatchClient(apiKey, apiSecret, MAINNET_BASE_URL);
238
+ }
239
+
240
+ /**
241
+ * Request a quote for the given order.
242
+ *
243
+ * @param order The order to request a quote for
244
+ * @returns A promise that resolves to a signed quote if one is available, null otherwise
245
+ * @throws ExternalMatchClientError if the request fails
246
+ */
247
+ async requestQuote(order: ExternalOrder): Promise<SignedExternalQuote | null> {
248
+ return this.requestQuoteWithOptions(order, RequestQuoteOptions.new());
249
+ }
250
+
251
+ /**
252
+ * Request a quote for the given order with custom options.
253
+ *
254
+ * @param order The order to request a quote for
255
+ * @param options Custom options for the quote request
256
+ * @returns A promise that resolves to a signed quote if one is available, null otherwise
257
+ * @throws ExternalMatchClientError if the request fails
258
+ */
259
+ async requestQuoteWithOptions(
260
+ order: ExternalOrder,
261
+ options: RequestQuoteOptions
262
+ ): Promise<SignedExternalQuote | null> {
263
+ const request: ExternalQuoteRequest = {
264
+ external_order: order
265
+ };
266
+
267
+ const path = options.buildRequestPath();
268
+ const headers = this.getHeaders();
269
+
270
+ try {
271
+ const response = await this.httpClient.post<ExternalQuoteResponse>(path, request, headers);
272
+
273
+ // Handle 204 No Content (no quotes available)
274
+ if (response.status === 204 || !response.data) {
275
+ return null;
276
+ }
277
+
278
+ const quoteResp = response.data;
279
+ const signedQuote: SignedExternalQuote = {
280
+ quote: quoteResp.signed_quote.quote,
281
+ signature: quoteResp.signed_quote.signature,
282
+ gas_sponsorship_info: quoteResp.gas_sponsorship_info
283
+ };
284
+
285
+ return signedQuote;
286
+ } catch (error: any) {
287
+ // Handle HTTP-related errors from fetch implementation
288
+ if (error.status === 204) {
289
+ return null;
290
+ }
291
+
292
+ throw new ExternalMatchClientError(
293
+ error.message || 'Failed to request quote',
294
+ error.status
295
+ );
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Assemble a quote into a match bundle with default options.
301
+ *
302
+ * @param quote The signed quote to assemble
303
+ * @returns A promise that resolves to a match response if assembly succeeds, null otherwise
304
+ * @throws ExternalMatchClientError if the request fails
305
+ */
306
+ async assembleQuote(quote: SignedExternalQuote): Promise<ExternalMatchResponse | null> {
307
+ return this.assembleQuoteWithOptions(quote, AssembleExternalMatchOptions.new());
308
+ }
309
+
310
+ /**
311
+ * Assemble a quote into a match bundle with custom options.
312
+ *
313
+ * @param quote The signed quote to assemble
314
+ * @param options Custom options for quote assembly
315
+ * @returns A promise that resolves to a match response if assembly succeeds, null otherwise
316
+ * @throws ExternalMatchClientError if the request fails
317
+ */
318
+ async assembleQuoteWithOptions(
319
+ quote: SignedExternalQuote,
320
+ options: AssembleExternalMatchOptions
321
+ ): Promise<ExternalMatchResponse | null> {
322
+ const signedQuote: ApiSignedExternalQuote = {
323
+ quote: quote.quote,
324
+ signature: quote.signature,
325
+ };
326
+
327
+ const request: AssembleExternalMatchRequest = {
328
+ do_gas_estimation: options.doGasEstimation,
329
+ receiver_address: options.receiverAddress,
330
+ signed_quote: signedQuote,
331
+ updated_order: options.updatedOrder,
332
+ gas_sponsorship_info: quote.gas_sponsorship_info,
333
+ };
334
+
335
+ const path = options.buildRequestPath();
336
+ const headers = this.getHeaders();
337
+
338
+ try {
339
+ const response = await this.httpClient.post<ExternalMatchResponse>(path, request, headers);
340
+
341
+ // Handle 204 No Content
342
+ if (response.status === 204 || !response.data) {
343
+ return null;
344
+ }
345
+
346
+ return response.data;
347
+ } catch (error: any) {
348
+ // Handle HTTP-related errors from fetch implementation
349
+ if (error.status === 204) {
350
+ return null;
351
+ }
352
+
353
+ throw new ExternalMatchClientError(
354
+ error.message || 'Failed to assemble quote',
355
+ error.status
356
+ );
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Get the headers required for API requests.
362
+ *
363
+ * @returns Headers containing the API key and SDK version
364
+ */
365
+ private getHeaders(): Record<string, string> {
366
+ return {
367
+ [RENEGADE_API_KEY_HEADER]: this.apiKey,
368
+ [RENEGADE_SDK_VERSION_HEADER]: getSdkVersion(),
369
+ };
370
+ }
371
+ }
package/src/http.ts ADDED
@@ -0,0 +1,238 @@
1
+ /**
2
+ * HTTP client for making authenticated requests to the Renegade relayer API.
3
+ * This client handles request signing and authentication using HMAC-SHA256.
4
+ */
5
+
6
+ import { sha256 } from '@noble/hashes/sha256';
7
+ import { hmac } from '@noble/hashes/hmac';
8
+ import { bytesToHex } from '@noble/hashes/utils';
9
+ import JSONBigInt from 'json-bigint';
10
+
11
+ // Constants for authentication
12
+ export const RENEGADE_HEADER_PREFIX = 'x-renegade';
13
+ export const RENEGADE_AUTH_HEADER = 'x-renegade-auth';
14
+ export const RENEGADE_AUTH_EXPIRATION_HEADER = 'x-renegade-auth-expiration';
15
+
16
+ // Authentication constants
17
+ const REQUEST_SIGNATURE_DURATION_MS = 10 * 1000; // 10 seconds in milliseconds
18
+
19
+ // Configure JSON-BigInt for parsing and stringifying
20
+ const jsonProcessor = JSONBigInt({
21
+ alwaysParseAsBig: true,
22
+ useNativeBigInt: true,
23
+ });
24
+
25
+ /**
26
+ * Parse JSON string that may contain BigInt values
27
+ */
28
+ export const parseBigJSON = (data: string) => {
29
+ try {
30
+ return jsonProcessor.parse(data);
31
+ } catch (error) {
32
+ // If parsing fails, return original data
33
+ console.error('Failed to parse JSON with BigInt', error);
34
+ return data;
35
+ }
36
+ };
37
+
38
+ /**
39
+ * Stringify object that may contain BigInt values
40
+ */
41
+ export const stringifyBigJSON = (data: any) => {
42
+ return jsonProcessor.stringify(data);
43
+ };
44
+
45
+ // Define interface for HTTP response similar to Axios response
46
+ export interface HttpResponse<T = any> {
47
+ data: T;
48
+ status: number;
49
+ statusText: string;
50
+ headers: Record<string, string>;
51
+ }
52
+
53
+ /**
54
+ * HTTP client for making authenticated requests to the Renegade relayer API.
55
+ */
56
+ export class RelayerHttpClient {
57
+ private baseUrl: string;
58
+ private authKey: Uint8Array;
59
+ private defaultHeaders: Record<string, string>;
60
+
61
+ /**
62
+ * Initialize a new RelayerHttpClient.
63
+ *
64
+ * @param baseUrl The base URL of the relayer API
65
+ * @param authKey The base64-encoded authentication key for request signing
66
+ */
67
+ constructor(baseUrl: string, authKey: string) {
68
+ this.baseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
69
+ this.authKey = this.decodeBase64(authKey);
70
+ this.defaultHeaders = {
71
+ 'Content-Type': 'application/json'
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Make a GET request with custom headers.
77
+ *
78
+ * @param path The API endpoint path
79
+ * @param headers Additional headers to include
80
+ * @returns The API response
81
+ */
82
+ public async get<T>(path: string, headers: Record<string, string> = {}): Promise<HttpResponse<T>> {
83
+ return this.request<T>('GET', path, undefined, headers);
84
+ }
85
+
86
+ /**
87
+ * Make a POST request with custom headers.
88
+ *
89
+ * @param path The API endpoint path
90
+ * @param data The request body to send
91
+ * @param headers Additional headers to include
92
+ * @returns The API response
93
+ */
94
+ public async post<T, D = any>(path: string, data: D, headers: Record<string, string> = {}): Promise<HttpResponse<T>> {
95
+ return this.request<T>('POST', path, data, headers);
96
+ }
97
+
98
+ /**
99
+ * Make an HTTP request with authentication.
100
+ *
101
+ * @param method The HTTP method
102
+ * @param path The API endpoint path
103
+ * @param data The request body data
104
+ * @param customHeaders Additional headers to include
105
+ * @returns The API response
106
+ */
107
+ private async request<T>(
108
+ method: string,
109
+ path: string,
110
+ data?: any,
111
+ customHeaders: Record<string, string> = {}
112
+ ): Promise<HttpResponse<T>> {
113
+ const urlPath = path.startsWith('/') ? path.slice(1) : path;
114
+ const url = new URL(urlPath, this.baseUrl);
115
+
116
+ // Prepare headers, and add authentication headers
117
+ const headers = { ...this.defaultHeaders, ...customHeaders };
118
+ const fullHeaders = this.addAuthHeaders(
119
+ url.pathname + url.search,
120
+ headers,
121
+ data
122
+ );
123
+
124
+ // Prepare request body
125
+ let body: string | undefined;
126
+ if (data) {
127
+ body = typeof data === 'string' ? data : stringifyBigJSON(data);
128
+ }
129
+
130
+ // Make the fetch request
131
+ const response = await fetch(url.toString(), {
132
+ method,
133
+ headers: fullHeaders,
134
+ body
135
+ });
136
+
137
+ // Read the response body
138
+ let responseData: any;
139
+ const contentType = response.headers.get('content-type') || '';
140
+ if (contentType.includes('application/json')) {
141
+ const text = await response.text();
142
+ responseData = parseBigJSON(text);
143
+ } else {
144
+ responseData = await response.text();
145
+ }
146
+
147
+ // Convert headers to a simple object
148
+ const responseHeaders: Record<string, string> = {};
149
+ response.headers.forEach((value, key) => {
150
+ responseHeaders[key] = value;
151
+ });
152
+
153
+ // Return a response object similar to Axios response
154
+ return {
155
+ data: responseData,
156
+ status: response.status,
157
+ statusText: response.statusText,
158
+ headers: responseHeaders
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Add authentication headers to a request.
164
+ */
165
+ private addAuthHeaders(
166
+ path: string,
167
+ headers: Record<string, string>,
168
+ data?: any
169
+ ): Record<string, string> {
170
+ // Add timestamp and expiry
171
+ const timestamp = Date.now();
172
+ const expiry = timestamp + REQUEST_SIGNATURE_DURATION_MS;
173
+ headers[RENEGADE_AUTH_EXPIRATION_HEADER] = expiry.toString();
174
+
175
+ // Compute the MAC signature and
176
+ const macDigest = this.computeRequestMac(path, headers, data);
177
+ headers[RENEGADE_AUTH_HEADER] = this.encodeBase64(Buffer.from(macDigest));
178
+ return headers;
179
+ }
180
+
181
+ /**
182
+ * Compute the HMAC-SHA256 MAC for a request.
183
+ *
184
+ * @param path The API endpoint path with query parameters
185
+ * @param headers The request headers
186
+ * @param data The request body data
187
+ * @returns The computed MAC digest
188
+ */
189
+ private computeRequestMac(
190
+ path: string,
191
+ headers: Record<string, string>,
192
+ data?: any
193
+ ): Uint8Array {
194
+ // Initialize MAC with auth key
195
+ const mac = hmac.create(sha256, this.authKey);
196
+
197
+ // Add path to signature
198
+ const pathBytes = new TextEncoder().encode(path);
199
+ mac.update(pathBytes);
200
+
201
+ // Add Renegade headers to signature
202
+ const renegadeHeaders = Object.entries(headers)
203
+ .filter(([key]) => key.toLowerCase().startsWith(RENEGADE_HEADER_PREFIX))
204
+ .filter(([key]) => key.toLowerCase() !== RENEGADE_AUTH_HEADER.toLowerCase())
205
+ .sort(([a], [b]) => a.localeCompare(b));
206
+
207
+ for (const [key, value] of renegadeHeaders) {
208
+ mac.update(new TextEncoder().encode(key));
209
+ mac.update(new TextEncoder().encode(value.toString()));
210
+ }
211
+
212
+ // Add body to signature
213
+ let body = '';
214
+ if (data) {
215
+ body = typeof data === 'string'
216
+ ? data
217
+ : stringifyBigJSON(data);
218
+ }
219
+ mac.update(new TextEncoder().encode(body));
220
+
221
+ // Return the digest
222
+ return mac.digest();
223
+ }
224
+
225
+ /**
226
+ * Decode a base64 string to a Uint8Array.
227
+ */
228
+ private decodeBase64(base64: string): Uint8Array {
229
+ return Buffer.from(base64, 'base64');
230
+ }
231
+
232
+ /**
233
+ * Encode a Uint8Array or Buffer to a base64 string.
234
+ */
235
+ private encodeBase64(data: Uint8Array | Buffer): string {
236
+ return Buffer.from(data).toString('base64').replace(/=+$/, '');
237
+ }
238
+ }
package/src/types.ts ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Type definitions for the Renegade Darkpool API.
3
+ */
4
+
5
+ export enum OrderSide {
6
+ BUY = "Buy",
7
+ SELL = "Sell",
8
+ }
9
+
10
+ export interface ApiExternalAssetTransfer {
11
+ mint: string;
12
+ amount: bigint;
13
+ }
14
+
15
+ export interface ApiTimestampedPrice {
16
+ price: string;
17
+ timestamp: bigint;
18
+ }
19
+
20
+ export interface ApiExternalMatchResult {
21
+ quote_mint: string;
22
+ base_mint: string;
23
+ quote_amount: bigint;
24
+ base_amount: bigint;
25
+ direction: OrderSide;
26
+ }
27
+
28
+ export interface FeeTake {
29
+ relayer_fee: bigint;
30
+ protocol_fee: bigint;
31
+ }
32
+
33
+ export interface ExternalOrder {
34
+ quote_mint: string;
35
+ base_mint: string;
36
+ side: OrderSide;
37
+ base_amount?: bigint;
38
+ quote_amount?: bigint;
39
+ exact_base_output?: bigint;
40
+ exact_quote_output?: bigint;
41
+ min_fill_size?: bigint;
42
+ }
43
+
44
+ export interface ApiExternalQuote {
45
+ order: ExternalOrder;
46
+ match_result: ApiExternalMatchResult;
47
+ fees: FeeTake;
48
+ send: ApiExternalAssetTransfer;
49
+ receive: ApiExternalAssetTransfer;
50
+ price: ApiTimestampedPrice;
51
+ timestamp: bigint;
52
+ }
53
+
54
+ export interface ApiSignedExternalQuote {
55
+ quote: ApiExternalQuote;
56
+ signature: string;
57
+ }
58
+
59
+ export interface GasSponsorshipInfo {
60
+ refund_amount: bigint;
61
+ refund_native_eth: boolean;
62
+ refund_address?: string;
63
+ }
64
+
65
+ export interface SignedGasSponsorshipInfo {
66
+ gas_sponsorship_info: GasSponsorshipInfo;
67
+ signature: string;
68
+ }
69
+
70
+ export interface SignedExternalQuote {
71
+ quote: ApiExternalQuote;
72
+ signature: string;
73
+ gas_sponsorship_info?: SignedGasSponsorshipInfo;
74
+ }
75
+
76
+ export interface SettlementTransaction {
77
+ tx_type: string;
78
+ to: string;
79
+ data: string;
80
+ value: string;
81
+ }
82
+
83
+ export interface AtomicMatchApiBundle {
84
+ match_result: ApiExternalMatchResult;
85
+ fees: FeeTake;
86
+ receive: ApiExternalAssetTransfer;
87
+ send: ApiExternalAssetTransfer;
88
+ settlement_tx: SettlementTransaction;
89
+ }
90
+
91
+ export interface ExternalQuoteRequest {
92
+ external_order: ExternalOrder;
93
+ }
94
+
95
+ export interface ExternalQuoteResponse {
96
+ signed_quote: ApiSignedExternalQuote;
97
+ gas_sponsorship_info?: SignedGasSponsorshipInfo;
98
+ }
99
+
100
+ export interface AssembleExternalMatchRequest {
101
+ do_gas_estimation?: boolean;
102
+ receiver_address?: string;
103
+ signed_quote: ApiSignedExternalQuote;
104
+ updated_order?: ExternalOrder;
105
+ gas_sponsorship_info?: SignedGasSponsorshipInfo;
106
+ }
107
+
108
+ export interface ExternalMatchResponse {
109
+ match_bundle: AtomicMatchApiBundle;
110
+ gas_sponsored: boolean;
111
+ gas_sponsorship_info?: GasSponsorshipInfo;
112
+ }
package/src/version.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * SDK version information
3
+ * This file is automatically updated during the build process
4
+ * Last updated: 2025-03-27T00:13:04Z
5
+ */
6
+
7
+ export const VERSION = '0.1.0';
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "declaration": true,
6
+ "noEmit": false,
7
+ "allowImportingTsExtensions": false
8
+ },
9
+ "include": [
10
+ "index.ts",
11
+ "src/**/*.ts"
12
+ ],
13
+ "exclude": [
14
+ "node_modules",
15
+ "**/*.test.ts"
16
+ ]
17
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["esnext"],
5
+ "target": "ESNext",
6
+ "module": "ESNext",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": true,
22
+
23
+ // Some stricter flags (disabled by default)
24
+ "noUnusedLocals": false,
25
+ "noUnusedParameters": false,
26
+ "noPropertyAccessFromIndexSignature": false
27
+ }
28
+ }