@renegade-fi/renegade-sdk 0.1.5 → 0.1.7
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/examples/base_sepolia_match.ts +117 -0
- package/examples/malleable_external_match.ts +70 -25
- package/package.json +1 -1
- package/src/client.ts +24 -0
- package/src/types/fixedPoint.ts +16 -1
- package/src/types/malleableMatch.ts +170 -10
- package/src/version.ts +2 -2
|
@@ -0,0 +1,117 @@
|
|
|
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 { ExternalMatchClient, OrderSide } from "../index";
|
|
8
|
+
import type { ExternalOrder } from "../index";
|
|
9
|
+
|
|
10
|
+
// Viem imports for on-chain transactions
|
|
11
|
+
import { http, createWalletClient } from "viem";
|
|
12
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
13
|
+
import { baseSepolia } from "viem/chains";
|
|
14
|
+
|
|
15
|
+
// Get API credentials from environment variables
|
|
16
|
+
const API_KEY = process.env.EXTERNAL_MATCH_KEY || "";
|
|
17
|
+
const API_SECRET = process.env.EXTERNAL_MATCH_SECRET || "";
|
|
18
|
+
const PRIVATE_KEY = process.env.PKEY || "";
|
|
19
|
+
const RPC_URL = process.env.RPC_URL || "https://sepolia.base.org";
|
|
20
|
+
|
|
21
|
+
// Validate API credentials
|
|
22
|
+
if (!API_KEY || !API_SECRET) {
|
|
23
|
+
console.error("Error: Missing API credentials");
|
|
24
|
+
console.error("Please set EXTERNAL_MATCH_KEY and EXTERNAL_MATCH_SECRET environment variables");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Validate wallet private key
|
|
29
|
+
if (!PRIVATE_KEY) {
|
|
30
|
+
console.error("Error: Missing private key");
|
|
31
|
+
console.error("Please set PKEY environment variable");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Set up wallet client for blockchain transactions
|
|
36
|
+
const privateKey = PRIVATE_KEY.startsWith("0x") ? PRIVATE_KEY : `0x${PRIVATE_KEY}`;
|
|
37
|
+
const walletClient = createWalletClient({
|
|
38
|
+
account: privateKeyToAccount(privateKey as `0x${string}`),
|
|
39
|
+
chain: baseSepolia,
|
|
40
|
+
transport: http(RPC_URL),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Create the external match client
|
|
44
|
+
console.log("API KEY", API_KEY);
|
|
45
|
+
const client = ExternalMatchClient.newBaseSepoliaClient(API_KEY, API_SECRET);
|
|
46
|
+
|
|
47
|
+
// Example order for USDC/WETH pair
|
|
48
|
+
const order: ExternalOrder = {
|
|
49
|
+
quote_mint: "0xD9961Bb4Cb27192f8dAd20a662be081f546b0E74", // USDC on testnet
|
|
50
|
+
base_mint: "0xb51a558c8E55DE1EE5391BDFe2aFA49968FC3B25", // cbBTC on testnet
|
|
51
|
+
side: OrderSide.BUY,
|
|
52
|
+
quote_amount: BigInt(20_000_000), // 20 USDC
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Submit a transaction to the chain
|
|
57
|
+
* @param settlementTx The settlement transaction
|
|
58
|
+
* @returns The transaction hash
|
|
59
|
+
*/
|
|
60
|
+
async function submitTransaction(settlementTx: any): Promise<`0x${string}`> {
|
|
61
|
+
console.log("Submitting transaction...");
|
|
62
|
+
|
|
63
|
+
const tx = await walletClient.sendTransaction({
|
|
64
|
+
to: settlementTx.to as `0x${string}`,
|
|
65
|
+
data: settlementTx.data as `0x${string}`,
|
|
66
|
+
value: settlementTx.value ? BigInt(settlementTx.value) : BigInt(0),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return tx;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Full example with on-chain submission
|
|
73
|
+
async function fullExample() {
|
|
74
|
+
try {
|
|
75
|
+
// Step 1: Request a quote
|
|
76
|
+
console.log("Requesting quote...");
|
|
77
|
+
const quote = await client.requestQuote(order);
|
|
78
|
+
|
|
79
|
+
if (!quote) {
|
|
80
|
+
console.log("No quote available");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log("Quote received!");
|
|
85
|
+
|
|
86
|
+
// Step 2: Assemble the quote into a match bundle
|
|
87
|
+
console.log("Assembling match...");
|
|
88
|
+
const bundle = await client.assembleQuote(quote);
|
|
89
|
+
|
|
90
|
+
if (!bundle) {
|
|
91
|
+
console.log("No match available");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log("Match assembled!");
|
|
96
|
+
|
|
97
|
+
// Step 3: Submit the transaction on-chain
|
|
98
|
+
const txHash = await submitTransaction(bundle.match_bundle.settlement_tx);
|
|
99
|
+
console.log(
|
|
100
|
+
"Transaction submitted:",
|
|
101
|
+
`${walletClient.chain.blockExplorers?.default.url}/tx/${txHash}`,
|
|
102
|
+
);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error("Error:", error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Run the examples
|
|
109
|
+
async function main() {
|
|
110
|
+
console.log("Running full example with on-chain submission...");
|
|
111
|
+
await fullExample();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Only run if this file is being executed directly
|
|
115
|
+
if (require.main === module) {
|
|
116
|
+
main().catch(console.error);
|
|
117
|
+
}
|
|
@@ -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 || "";
|
|
@@ -53,6 +54,71 @@ const order: ExternalOrder = {
|
|
|
53
54
|
quote_amount: BigInt(20_000_000), // 20 USDC
|
|
54
55
|
};
|
|
55
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
|
+
|
|
56
122
|
/**
|
|
57
123
|
* Submit a transaction to the chain
|
|
58
124
|
* @param settlementTx The settlement transaction
|
|
@@ -92,31 +158,10 @@ async function fullExample() {
|
|
|
92
158
|
return;
|
|
93
159
|
}
|
|
94
160
|
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// Pick a random base amount and see the send and receive amounts at that base amount
|
|
101
|
-
const dummyBaseAmount = randomInRange(minBase, maxBase);
|
|
102
|
-
const dummySendAmount = bundle.sendAmountAtBase(dummyBaseAmount);
|
|
103
|
-
const dummyReceiveAmount = bundle.receiveAmountAtBase(dummyBaseAmount);
|
|
104
|
-
console.log(`Hypothetical base amount: ${dummyBaseAmount}`);
|
|
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 swappedBaseAmount = randomInRange(minBase, maxBase);
|
|
110
|
-
|
|
111
|
-
// Setting the base amount will return the receive amount at the new base
|
|
112
|
-
// You can also call sendAmount and receiveAmount to get the amounts at the
|
|
113
|
-
// currently set base amount
|
|
114
|
-
const _recv = bundle.setBaseAmount(swappedBaseAmount);
|
|
115
|
-
const send = bundle.sendAmount();
|
|
116
|
-
const recv = bundle.receiveAmount();
|
|
117
|
-
console.log(`Swapped base amount: ${swappedBaseAmount}`);
|
|
118
|
-
console.log(`Send amount: ${send}`);
|
|
119
|
-
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);
|
|
120
165
|
|
|
121
166
|
// Step 3: Submit the transaction on-chain
|
|
122
167
|
const txHash = await submitTransaction(bundle.match_bundle.settlement_tx);
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -22,6 +22,8 @@ import { VERSION } from "./version";
|
|
|
22
22
|
// Constants for API URLs
|
|
23
23
|
const ARBITRUM_SEPOLIA_BASE_URL = "https://arbitrum-sepolia.auth-server.renegade.fi";
|
|
24
24
|
const ARBITRUM_ONE_BASE_URL = "https://arbitrum-one.auth-server.renegade.fi";
|
|
25
|
+
const BASE_SEPOLIA_BASE_URL = "https://base-sepolia.auth-server.renegade.fi";
|
|
26
|
+
const BASE_MAINNET_BASE_URL = "https://base-mainnet.auth-server.renegade.fi";
|
|
25
27
|
|
|
26
28
|
// Header constants
|
|
27
29
|
const RENEGADE_API_KEY_HEADER = "x-renegade-api-key";
|
|
@@ -290,6 +292,17 @@ export class ExternalMatchClient {
|
|
|
290
292
|
return new ExternalMatchClient(apiKey, apiSecret, ARBITRUM_SEPOLIA_BASE_URL);
|
|
291
293
|
}
|
|
292
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Create a new client configured for the Base Sepolia testnet.
|
|
297
|
+
*
|
|
298
|
+
* @param apiKey The API key for authentication
|
|
299
|
+
* @param apiSecret The API secret for request signing
|
|
300
|
+
* @returns A new ExternalMatchClient configured for Sepolia
|
|
301
|
+
*/
|
|
302
|
+
static newBaseSepoliaClient(apiKey: string, apiSecret: string): ExternalMatchClient {
|
|
303
|
+
return new ExternalMatchClient(apiKey, apiSecret, BASE_SEPOLIA_BASE_URL);
|
|
304
|
+
}
|
|
305
|
+
|
|
293
306
|
/**
|
|
294
307
|
* Create a new client configured for the Arbitrum One mainnet.
|
|
295
308
|
*
|
|
@@ -310,6 +323,17 @@ export class ExternalMatchClient {
|
|
|
310
323
|
return new ExternalMatchClient(apiKey, apiSecret, ARBITRUM_ONE_BASE_URL);
|
|
311
324
|
}
|
|
312
325
|
|
|
326
|
+
/**
|
|
327
|
+
* Create a new client configured for the Base mainnet.
|
|
328
|
+
*
|
|
329
|
+
* @param apiKey The API key for authentication
|
|
330
|
+
* @param apiSecret The API secret for request signing
|
|
331
|
+
* @returns A new ExternalMatchClient configured for mainnet
|
|
332
|
+
*/
|
|
333
|
+
static newBaseMainnetClient(apiKey: string, apiSecret: string): ExternalMatchClient {
|
|
334
|
+
return new ExternalMatchClient(apiKey, apiSecret, BASE_MAINNET_BASE_URL);
|
|
335
|
+
}
|
|
336
|
+
|
|
313
337
|
/**
|
|
314
338
|
* Request a quote for the given order.
|
|
315
339
|
*
|
package/src/types/fixedPoint.ts
CHANGED
|
@@ -12,7 +12,7 @@ export class FixedPoint {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Multiply a fixed point number by a
|
|
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
|
}
|
|
@@ -8,10 +8,20 @@ import {
|
|
|
8
8
|
type SettlementTransaction,
|
|
9
9
|
} from "./index";
|
|
10
10
|
|
|
11
|
-
/** The
|
|
12
|
-
const
|
|
13
|
-
/**
|
|
14
|
-
|
|
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;
|
|
15
25
|
/** The address used to represent the native asset */
|
|
16
26
|
const NATIVE_ASSET_ADDR = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
17
27
|
|
|
@@ -33,6 +43,17 @@ export class MalleableExternalMatchResponse {
|
|
|
33
43
|
* consistent
|
|
34
44
|
*/
|
|
35
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;
|
|
36
57
|
/**
|
|
37
58
|
* Whether the match was sponsored
|
|
38
59
|
*/
|
|
@@ -47,11 +68,13 @@ export class MalleableExternalMatchResponse {
|
|
|
47
68
|
gas_sponsored: boolean,
|
|
48
69
|
gas_sponsorship_info?: GasSponsorshipInfo,
|
|
49
70
|
base_amount?: bigint,
|
|
71
|
+
quote_amount?: bigint,
|
|
50
72
|
) {
|
|
51
73
|
this.match_bundle = match_bundle;
|
|
52
74
|
this.gas_sponsored = gas_sponsored;
|
|
53
75
|
this.gas_sponsorship_info = gas_sponsorship_info;
|
|
54
76
|
this.base_amount = base_amount;
|
|
77
|
+
this.quote_amount = quote_amount;
|
|
55
78
|
}
|
|
56
79
|
|
|
57
80
|
/**
|
|
@@ -62,9 +85,15 @@ export class MalleableExternalMatchResponse {
|
|
|
62
85
|
public setBaseAmount(baseAmount: bigint) {
|
|
63
86
|
this.checkBaseAmount(baseAmount);
|
|
64
87
|
|
|
88
|
+
const impliedQuoteAmount = this.quoteAmount(baseAmount);
|
|
89
|
+
|
|
65
90
|
// Set the calldata
|
|
66
91
|
this.setBaseAmountCalldata(baseAmount);
|
|
92
|
+
this.setQuoteAmountCalldata(impliedQuoteAmount);
|
|
93
|
+
|
|
94
|
+
// Set the quote and base amounts on the response
|
|
67
95
|
this.base_amount = baseAmount;
|
|
96
|
+
this.quote_amount = impliedQuoteAmount;
|
|
68
97
|
|
|
69
98
|
return this.receiveAmount();
|
|
70
99
|
}
|
|
@@ -72,18 +101,19 @@ export class MalleableExternalMatchResponse {
|
|
|
72
101
|
/**
|
|
73
102
|
* Set the calldata to use a given base amount
|
|
74
103
|
*/
|
|
75
|
-
|
|
104
|
+
private setBaseAmountCalldata(baseAmount: bigint) {
|
|
76
105
|
const calldataBytes = hexToBytes(this.match_bundle.settlement_tx.data as `0x${string}`);
|
|
77
106
|
|
|
78
107
|
// Padded to 32 bytes
|
|
79
|
-
const baseAmountBytes = numberToBytes(baseAmount, { size:
|
|
108
|
+
const baseAmountBytes = numberToBytes(baseAmount, { size: AMOUNT_CALLDATA_LENGTH });
|
|
80
109
|
|
|
81
110
|
const prefix = calldataBytes.slice(0, BASE_AMOUNT_OFFSET);
|
|
82
111
|
const suffix = calldataBytes.slice(
|
|
83
|
-
BASE_AMOUNT_OFFSET +
|
|
112
|
+
BASE_AMOUNT_OFFSET + AMOUNT_CALLDATA_LENGTH,
|
|
84
113
|
calldataBytes.length,
|
|
85
114
|
);
|
|
86
115
|
|
|
116
|
+
// Set the calldata and the tx value
|
|
87
117
|
const newCalldataBytes = concatBytes([prefix, baseAmountBytes, suffix]);
|
|
88
118
|
const newCalladata = bytesToHex(newCalldataBytes);
|
|
89
119
|
const value = this.isNativeEthSell() ? baseAmount : 0n;
|
|
@@ -101,6 +131,56 @@ export class MalleableExternalMatchResponse {
|
|
|
101
131
|
this.match_bundle = newMatchBundle;
|
|
102
132
|
}
|
|
103
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
|
+
|
|
173
|
+
const newMatchBundle = {
|
|
174
|
+
...this.match_bundle,
|
|
175
|
+
settlement_tx: {
|
|
176
|
+
...this.match_bundle.settlement_tx,
|
|
177
|
+
data: newCalladata,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
this.match_bundle = newMatchBundle;
|
|
182
|
+
}
|
|
183
|
+
|
|
104
184
|
/**
|
|
105
185
|
* Get the bounds on the base amount
|
|
106
186
|
*
|
|
@@ -113,6 +193,41 @@ export class MalleableExternalMatchResponse {
|
|
|
113
193
|
];
|
|
114
194
|
}
|
|
115
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
|
+
|
|
116
231
|
/**
|
|
117
232
|
* Get the receive amount at the currently set base amount
|
|
118
233
|
*/
|
|
@@ -127,6 +242,14 @@ export class MalleableExternalMatchResponse {
|
|
|
127
242
|
return this.computeReceiveAmount(baseAmount);
|
|
128
243
|
}
|
|
129
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
|
+
|
|
130
253
|
/**
|
|
131
254
|
* Get the send amount at the currently set base amount
|
|
132
255
|
*/
|
|
@@ -141,6 +264,14 @@ export class MalleableExternalMatchResponse {
|
|
|
141
264
|
return this.computeSendAmount(baseAmount);
|
|
142
265
|
}
|
|
143
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
|
+
|
|
144
275
|
/**
|
|
145
276
|
* Return whether the trade is a native ETH sell
|
|
146
277
|
*/
|
|
@@ -156,14 +287,31 @@ export class MalleableExternalMatchResponse {
|
|
|
156
287
|
* Check a base amount is in the valid range
|
|
157
288
|
*/
|
|
158
289
|
private checkBaseAmount(baseAmount: bigint) {
|
|
159
|
-
const min = this.
|
|
160
|
-
const max = this.match_bundle.match_result.max_base_amount;
|
|
290
|
+
const [min, max] = this.baseBounds();
|
|
161
291
|
|
|
162
292
|
if (baseAmount < min || baseAmount > max) {
|
|
163
293
|
throw new Error(`Base amount ${baseAmount} is not in the valid range ${min} - ${max}`);
|
|
164
294
|
}
|
|
165
295
|
}
|
|
166
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
|
+
|
|
167
315
|
/**
|
|
168
316
|
* Get the current receive amount at the given base amount
|
|
169
317
|
*
|
|
@@ -200,11 +348,19 @@ export class MalleableExternalMatchResponse {
|
|
|
200
348
|
return baseAmount;
|
|
201
349
|
}
|
|
202
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
|
+
|
|
203
359
|
/**
|
|
204
360
|
* Get the quote amount at the given base amount
|
|
205
361
|
*/
|
|
206
362
|
private quoteAmount(baseAmount: bigint) {
|
|
207
|
-
const price =
|
|
363
|
+
const price = this.getPriceFp();
|
|
208
364
|
return price.floorMulInt(baseAmount);
|
|
209
365
|
}
|
|
210
366
|
|
|
@@ -217,6 +373,10 @@ export class MalleableExternalMatchResponse {
|
|
|
217
373
|
}
|
|
218
374
|
return this.base_amount;
|
|
219
375
|
}
|
|
376
|
+
|
|
377
|
+
private getPriceFp() {
|
|
378
|
+
return new FixedPoint(BigInt(this.match_bundle.match_result.price_fp));
|
|
379
|
+
}
|
|
220
380
|
}
|
|
221
381
|
|
|
222
382
|
/**
|
package/src/version.ts
CHANGED