@renegade-fi/renegade-sdk 0.1.4 → 0.1.6

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.
@@ -13,6 +13,7 @@ import type { ExternalOrder } from "../index";
13
13
  import { http, createWalletClient } from "viem";
14
14
  import { privateKeyToAccount } from "viem/accounts";
15
15
  import { arbitrumSepolia } from "viem/chains";
16
+ import type { MalleableExternalMatchResponse } from "../src/types";
16
17
 
17
18
  // Get API credentials from environment variables
18
19
  const API_KEY = process.env.EXTERNAL_MATCH_KEY || "";
@@ -43,7 +44,6 @@ const walletClient = createWalletClient({
43
44
  });
44
45
 
45
46
  // Create the external match client
46
- console.log("API KEY", API_KEY);
47
47
  const client = ExternalMatchClient.newArbitrumSepoliaClient(API_KEY, API_SECRET);
48
48
 
49
49
  // Example order for USDC/WETH pair
@@ -54,6 +54,71 @@ const order: ExternalOrder = {
54
54
  quote_amount: BigInt(20_000_000), // 20 USDC
55
55
  };
56
56
 
57
+ /**
58
+ * Set a random base amount on the bundle and print the results
59
+ * @param bundle The malleable match bundle
60
+ */
61
+ function setRandomBaseAmount(bundle: MalleableExternalMatchResponse) {
62
+ // Print bundle info
63
+ console.log("Bundle info:");
64
+ const [minBase, maxBase] = bundle.baseBounds();
65
+ console.log(`Base bounds: ${minBase} - ${maxBase}`);
66
+
67
+ // Pick a random base amount and see the send and receive amounts at that base amount
68
+ const dummyBaseAmount = randomInRange(minBase, maxBase);
69
+ const dummySendAmount = bundle.sendAmountAtBase(dummyBaseAmount);
70
+ const dummyReceiveAmount = bundle.receiveAmountAtBase(dummyBaseAmount);
71
+ console.log(`Hypothetical base amount: ${dummyBaseAmount}`);
72
+ console.log(`Hypothetical send amount: ${dummySendAmount}`);
73
+ console.log(`Hypothetical receive amount: ${dummyReceiveAmount}`);
74
+
75
+ // Pick an actual base amount to swap with
76
+ const swappedBaseAmount = randomInRange(minBase, maxBase);
77
+
78
+ // Setting the base amount will return the receive amount at the new base
79
+ // You can also call sendAmount and receiveAmount to get the amounts at the
80
+ // currently set base amount
81
+ bundle.setBaseAmount(swappedBaseAmount);
82
+ const send = bundle.sendAmount();
83
+ const recv = bundle.receiveAmount();
84
+ console.log(`Swapped base amount: ${swappedBaseAmount}`);
85
+ console.log(`Send amount: ${send}`);
86
+ console.log(`Receive amount: ${recv}`);
87
+ }
88
+
89
+ /**
90
+ * Set a random quote amount on the bundle and print the results
91
+ * @param bundle The malleable match bundle
92
+ */
93
+ // biome-ignore lint/correctness/noUnusedVariables: User can choose to use this function in the example
94
+ function setRandomQuoteAmount(bundle: MalleableExternalMatchResponse) {
95
+ // Print bundle info
96
+ console.log("Bundle info:");
97
+ const [minQuote, maxQuote] = bundle.quoteBounds();
98
+ console.log(`Quote bounds: ${minQuote} - ${maxQuote}`);
99
+
100
+ // Pick a random base amount and see the send and receive amounts at that base amount
101
+ const dummyQuoteAmount = randomInRange(minQuote, maxQuote);
102
+ const dummySendAmount = bundle.sendAmountAtQuote(dummyQuoteAmount);
103
+ const dummyReceiveAmount = bundle.receiveAmountAtQuote(dummyQuoteAmount);
104
+ console.log(`Hypothetical quote amount: ${dummyQuoteAmount}`);
105
+ console.log(`Hypothetical send amount: ${dummySendAmount}`);
106
+ console.log(`Hypothetical receive amount: ${dummyReceiveAmount}`);
107
+
108
+ // Pick an actual base amount to swap with
109
+ const swappedQuoteAmount = randomInRange(minQuote, maxQuote);
110
+
111
+ // Setting the quote amount will return the receive amount at the new quote
112
+ // You can also call sendAmount and receiveAmount to get the amounts at the
113
+ // currently set quote amount
114
+ bundle.setQuoteAmount(swappedQuoteAmount);
115
+ const send = bundle.sendAmount();
116
+ const recv = bundle.receiveAmount();
117
+ console.log(`Swapped quote amount: ${swappedQuoteAmount}`);
118
+ console.log(`Send amount: ${send}`);
119
+ console.log(`Receive amount: ${recv}`);
120
+ }
121
+
57
122
  /**
58
123
  * Submit a transaction to the chain
59
124
  * @param settlementTx The settlement transaction
@@ -93,31 +158,10 @@ async function fullExample() {
93
158
  return;
94
159
  }
95
160
 
96
- // Print bundle info
97
- console.log("Bundle info:");
98
- const [minBase, maxBase] = bundle.baseBounds();
99
- console.log(`Base bounds: ${minBase} - ${maxBase}`);
100
-
101
- // Pick a random base amount and see the send and receive amounts at that base amount
102
- const dummyBaseAmount = randomInRange(minBase, maxBase);
103
- const dummySendAmount = bundle.sendAmountAtBase(dummyBaseAmount);
104
- const dummyReceiveAmount = bundle.receiveAmountAtBase(dummyBaseAmount);
105
- console.log(`Hypothetical base amount: ${dummyBaseAmount}`);
106
- console.log(`Hypothetical send amount: ${dummySendAmount}`);
107
- console.log(`Hypothetical receive amount: ${dummyReceiveAmount}`);
108
-
109
- // Pick an actual base amount to swap with
110
- const swappedBaseAmount = randomInRange(minBase, maxBase);
111
-
112
- // Setting the base amount will return the receive amount at the new base
113
- // You can also call sendAmount and receiveAmount to get the amounts at the
114
- // currently set base amount
115
- const _recv = bundle.setBaseAmount(swappedBaseAmount);
116
- const send = bundle.sendAmount();
117
- const recv = bundle.receiveAmount();
118
- console.log(`Swapped base amount: ${swappedBaseAmount}`);
119
- console.log(`Send amount: ${send}`);
120
- console.log(`Receive amount: ${recv}`);
161
+ // Set a base amount on the bundle
162
+ // Alternatively, you can set a quote amount on the bundle - see
163
+ // `setRandomQuoteAmount`
164
+ setRandomBaseAmount(bundle);
121
165
 
122
166
  // Step 3: Submit the transaction on-chain
123
167
  const txHash = await submitTransaction(bundle.match_bundle.settlement_tx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@renegade-fi/renegade-sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "A TypeScript client for interacting with the Renegade Darkpool API",
5
5
  "module": "index.ts",
6
6
  "type": "module",
@@ -12,7 +12,7 @@ export class FixedPoint {
12
12
  }
13
13
 
14
14
  /**
15
- * Multiply a fixed point number by a u128 and return the floor
15
+ * Multiply a fixed point number by a bigint and return the floor
16
16
  */
17
17
  floorMulInt(amount: bigint): bigint {
18
18
  const product = this.value * amount;
@@ -20,4 +20,19 @@ export class FixedPoint {
20
20
  const floored = product / FIXED_POINT_PRECISION_SHIFT;
21
21
  return floored;
22
22
  }
23
+
24
+ /**
25
+ * Divide a bigint by a fixed point number and return the ceiling
26
+ */
27
+ static ceilDivInt(amount: bigint, fp: FixedPoint): bigint {
28
+ const numerator = amount * FIXED_POINT_PRECISION_SHIFT;
29
+ const quotient = numerator / fp.value;
30
+ const remainder = numerator % fp.value;
31
+
32
+ if (remainder === BigInt(0)) {
33
+ return quotient;
34
+ }
35
+
36
+ return quotient + BigInt(1);
37
+ }
23
38
  }
@@ -1,4 +1,4 @@
1
- import { bytesToHex, concatBytes, hexToBytes, numberToBytes } from "viem/utils";
1
+ import { bytesToHex, concatBytes, hexToBytes, numberToBytes, numberToHex } from "viem/utils";
2
2
  import { FixedPoint } from "./fixedPoint";
3
3
  import {
4
4
  type ApiExternalAssetTransfer,
@@ -8,10 +8,22 @@ import {
8
8
  type SettlementTransaction,
9
9
  } from "./index";
10
10
 
11
- /** The offset of the base amount in the calldata */
12
- const BASE_AMOUNT_OFFSET = 4;
13
- /** The length of the base amount in the calldata */
14
- const BASE_AMOUNT_LENGTH = 32;
11
+ /** The length of an amount in the calldata, which is 32 bytes for a `uint256` */
12
+ const AMOUNT_CALLDATA_LENGTH = 32;
13
+ /**
14
+ * The offset of the quote amount in the calldata,
15
+ * which is `4` because it's the first calldata argument
16
+ * after the 4-byte function selector
17
+ */
18
+ const QUOTE_AMOUNT_OFFSET = 4;
19
+ /**
20
+ * The offset of the base amount in the calldata,
21
+ * which is `AMOUNT_CALLDATA_LENGTH` bytes after
22
+ * the quote amount as it is the next calldata argument
23
+ */
24
+ const BASE_AMOUNT_OFFSET = QUOTE_AMOUNT_OFFSET + AMOUNT_CALLDATA_LENGTH;
25
+ /** The address used to represent the native asset */
26
+ const NATIVE_ASSET_ADDR = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
15
27
 
16
28
  /**
17
29
  * The response type for requesting a malleable quote on an external order
@@ -31,6 +43,17 @@ export class MalleableExternalMatchResponse {
31
43
  * consistent
32
44
  */
33
45
  base_amount?: bigint;
46
+ /**
47
+ * The quote amount chosen for the match
48
+ *
49
+ * If `undefined`, the quote amount hasn't been selected and defaults to the
50
+ * quote amount implied by the maximum base amount and the price in the match result.
51
+ *
52
+ * This field is not meant for client use directly, rather it is set by
53
+ * operating on the type and allows the response type to stay internally
54
+ * consistent
55
+ */
56
+ quote_amount?: bigint;
34
57
  /**
35
58
  * Whether the match was sponsored
36
59
  */
@@ -45,11 +68,13 @@ export class MalleableExternalMatchResponse {
45
68
  gas_sponsored: boolean,
46
69
  gas_sponsorship_info?: GasSponsorshipInfo,
47
70
  base_amount?: bigint,
71
+ quote_amount?: bigint,
48
72
  ) {
49
73
  this.match_bundle = match_bundle;
50
74
  this.gas_sponsored = gas_sponsored;
51
75
  this.gas_sponsorship_info = gas_sponsorship_info;
52
76
  this.base_amount = base_amount;
77
+ this.quote_amount = quote_amount;
53
78
  }
54
79
 
55
80
  /**
@@ -60,9 +85,15 @@ export class MalleableExternalMatchResponse {
60
85
  public setBaseAmount(baseAmount: bigint) {
61
86
  this.checkBaseAmount(baseAmount);
62
87
 
88
+ const impliedQuoteAmount = this.quoteAmount(baseAmount);
89
+
63
90
  // Set the calldata
64
91
  this.setBaseAmountCalldata(baseAmount);
92
+ this.setQuoteAmountCalldata(impliedQuoteAmount);
93
+
94
+ // Set the quote and base amounts on the response
65
95
  this.base_amount = baseAmount;
96
+ this.quote_amount = impliedQuoteAmount;
66
97
 
67
98
  return this.receiveAmount();
68
99
  }
@@ -70,20 +101,75 @@ export class MalleableExternalMatchResponse {
70
101
  /**
71
102
  * Set the calldata to use a given base amount
72
103
  */
73
- public setBaseAmountCalldata(baseAmount: bigint) {
104
+ private setBaseAmountCalldata(baseAmount: bigint) {
74
105
  const calldataBytes = hexToBytes(this.match_bundle.settlement_tx.data as `0x${string}`);
75
106
 
76
107
  // Padded to 32 bytes
77
- const baseAmountBytes = numberToBytes(baseAmount, { size: BASE_AMOUNT_LENGTH });
108
+ const baseAmountBytes = numberToBytes(baseAmount, { size: AMOUNT_CALLDATA_LENGTH });
78
109
 
79
110
  const prefix = calldataBytes.slice(0, BASE_AMOUNT_OFFSET);
80
111
  const suffix = calldataBytes.slice(
81
- BASE_AMOUNT_OFFSET + BASE_AMOUNT_LENGTH,
112
+ BASE_AMOUNT_OFFSET + AMOUNT_CALLDATA_LENGTH,
82
113
  calldataBytes.length,
83
114
  );
84
115
 
116
+ // Set the calldata and the tx value
85
117
  const newCalldataBytes = concatBytes([prefix, baseAmountBytes, suffix]);
86
118
  const newCalladata = bytesToHex(newCalldataBytes);
119
+ const value = this.isNativeEthSell() ? baseAmount : 0n;
120
+ const valueHex = numberToHex(value);
121
+
122
+ const newMatchBundle = {
123
+ ...this.match_bundle,
124
+ settlement_tx: {
125
+ ...this.match_bundle.settlement_tx,
126
+ data: newCalladata,
127
+ value: valueHex,
128
+ },
129
+ };
130
+
131
+ this.match_bundle = newMatchBundle;
132
+ }
133
+
134
+ /**
135
+ * Set the `quote_amount` of the `match_result`
136
+ *
137
+ * @returns The amount received at the given `quote_amount`
138
+ */
139
+ public setQuoteAmount(quoteAmount: bigint) {
140
+ const impliedBaseAmount = this.baseAmount(quoteAmount);
141
+ this.checkQuoteAmount(quoteAmount, impliedBaseAmount);
142
+
143
+ // Set the calldata
144
+ this.setQuoteAmountCalldata(quoteAmount);
145
+ this.setBaseAmountCalldata(impliedBaseAmount);
146
+
147
+ // Set the quote and base amounts on the response
148
+ this.quote_amount = quoteAmount;
149
+ this.base_amount = impliedBaseAmount;
150
+
151
+ return this.receiveAmount();
152
+ }
153
+
154
+ /**
155
+ * Set the calldata to use a given quote amount
156
+ */
157
+ private setQuoteAmountCalldata(quoteAmount: bigint) {
158
+ const calldataBytes = hexToBytes(this.match_bundle.settlement_tx.data as `0x${string}`);
159
+
160
+ // Padded to 32 bytes
161
+ const quoteAmountBytes = numberToBytes(quoteAmount, { size: AMOUNT_CALLDATA_LENGTH });
162
+
163
+ const prefix = calldataBytes.slice(0, QUOTE_AMOUNT_OFFSET);
164
+ const suffix = calldataBytes.slice(
165
+ QUOTE_AMOUNT_OFFSET + AMOUNT_CALLDATA_LENGTH,
166
+ calldataBytes.length,
167
+ );
168
+
169
+ // Set the calldata and the tx value
170
+ const newCalldataBytes = concatBytes([prefix, quoteAmountBytes, suffix]);
171
+ const newCalladata = bytesToHex(newCalldataBytes);
172
+
87
173
  const newMatchBundle = {
88
174
  ...this.match_bundle,
89
175
  settlement_tx: {
@@ -107,6 +193,41 @@ export class MalleableExternalMatchResponse {
107
193
  ];
108
194
  }
109
195
 
196
+ /**
197
+ * Get the bounds on the quote amount
198
+ *
199
+ * Returns an array [min, max] inclusive
200
+ */
201
+ public quoteBounds(): [bigint, bigint] {
202
+ const [minBase, maxBase] = this.baseBounds();
203
+ const price = this.getPriceFp();
204
+
205
+ const minQuote = price.floorMulInt(minBase);
206
+ const maxQuote = price.floorMulInt(maxBase);
207
+
208
+ return [minQuote, maxQuote];
209
+ }
210
+
211
+ /**
212
+ * Get the bounds on the quote amount for a given base amount.
213
+ *
214
+ * For an explanation of these bounds, see:
215
+ * https://github.com/renegade-fi/renegade-contracts/blob/main/contracts-common/src/types/match.rs#L144-L174
216
+ */
217
+ public quoteBoundsForBase(baseAmount: bigint): [bigint, bigint] {
218
+ const [minQuote, maxQuote] = this.quoteBounds();
219
+
220
+ const price = this.getPriceFp();
221
+ const refQuote = price.floorMulInt(baseAmount);
222
+
223
+ const direction = this.match_bundle.match_result.direction;
224
+
225
+ const resolvedMinQuote = direction === OrderSide.BUY ? refQuote : minQuote;
226
+ const resolvedMaxQuote = direction === OrderSide.BUY ? maxQuote : refQuote;
227
+
228
+ return [resolvedMinQuote, resolvedMaxQuote];
229
+ }
230
+
110
231
  /**
111
232
  * Get the receive amount at the currently set base amount
112
233
  */
@@ -121,6 +242,14 @@ export class MalleableExternalMatchResponse {
121
242
  return this.computeReceiveAmount(baseAmount);
122
243
  }
123
244
 
245
+ /**
246
+ * Get the receive amount at the given quote amount
247
+ */
248
+ public receiveAmountAtQuote(quoteAmount: bigint) {
249
+ const baseAmount = this.baseAmount(quoteAmount);
250
+ return this.computeReceiveAmount(baseAmount);
251
+ }
252
+
124
253
  /**
125
254
  * Get the send amount at the currently set base amount
126
255
  */
@@ -135,18 +264,54 @@ export class MalleableExternalMatchResponse {
135
264
  return this.computeSendAmount(baseAmount);
136
265
  }
137
266
 
267
+ /**
268
+ * Get the send amount at the given quote amount
269
+ */
270
+ public sendAmountAtQuote(quoteAmount: bigint) {
271
+ const baseAmount = this.baseAmount(quoteAmount);
272
+ return this.computeSendAmount(baseAmount);
273
+ }
274
+
275
+ /**
276
+ * Return whether the trade is a native ETH sell
277
+ */
278
+ public isNativeEthSell(): boolean {
279
+ const matchRes = this.match_bundle.match_result;
280
+ const isSell = matchRes.direction === OrderSide.SELL;
281
+ const isBaseEth = matchRes.base_mint.toLowerCase() === NATIVE_ASSET_ADDR.toLowerCase();
282
+
283
+ return isBaseEth && isSell;
284
+ }
285
+
138
286
  /**
139
287
  * Check a base amount is in the valid range
140
288
  */
141
289
  private checkBaseAmount(baseAmount: bigint) {
142
- const min = this.match_bundle.match_result.min_base_amount;
143
- const max = this.match_bundle.match_result.max_base_amount;
290
+ const [min, max] = this.baseBounds();
144
291
 
145
292
  if (baseAmount < min || baseAmount > max) {
146
293
  throw new Error(`Base amount ${baseAmount} is not in the valid range ${min} - ${max}`);
147
294
  }
148
295
  }
149
296
 
297
+ /**
298
+ * Check a quote amount is in the valid range for a given base amount.
299
+ *
300
+ * This is true if the quote amount is within the bounds implied by the min
301
+ * and max base amounts given the price in the match results, and the
302
+ * quote amount does not imply a price improvement over the price in
303
+ * the match result.
304
+ */
305
+ private checkQuoteAmount(quoteAmount: bigint, baseAmount: bigint) {
306
+ const [min, max] = this.quoteBoundsForBase(baseAmount);
307
+
308
+ if (quoteAmount < min || quoteAmount > max) {
309
+ throw new Error(
310
+ `Quote amount ${quoteAmount} is not in the valid range ${min} - ${max}`,
311
+ );
312
+ }
313
+ }
314
+
150
315
  /**
151
316
  * Get the current receive amount at the given base amount
152
317
  *
@@ -183,11 +348,19 @@ export class MalleableExternalMatchResponse {
183
348
  return baseAmount;
184
349
  }
185
350
 
351
+ /**
352
+ * Get the base amount at the given quote amount
353
+ */
354
+ private baseAmount(quoteAmount: bigint) {
355
+ const price = this.getPriceFp();
356
+ return FixedPoint.ceilDivInt(quoteAmount, price);
357
+ }
358
+
186
359
  /**
187
360
  * Get the quote amount at the given base amount
188
361
  */
189
362
  private quoteAmount(baseAmount: bigint) {
190
- const price = new FixedPoint(BigInt(this.match_bundle.match_result.price_fp));
363
+ const price = this.getPriceFp();
191
364
  return price.floorMulInt(baseAmount);
192
365
  }
193
366
 
@@ -200,6 +373,10 @@ export class MalleableExternalMatchResponse {
200
373
  }
201
374
  return this.base_amount;
202
375
  }
376
+
377
+ private getPriceFp() {
378
+ return new FixedPoint(BigInt(this.match_bundle.match_result.price_fp));
379
+ }
203
380
  }
204
381
 
205
382
  /**
package/src/version.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * SDK version information
3
3
  * This file is automatically updated during the build process
4
- * Last updated: 2025-05-08T00:25:05Z
4
+ * Last updated: 2025-05-21T22:43:53Z
5
5
  */
6
6
 
7
- export const VERSION = '0.1.4';
7
+ export const VERSION = '0.1.6';