diff --git a/.gitignore b/.gitignore index 0da5e94f..4e240029 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ node_modules tsconfig.tsbuildinfo *.vitest-temp.json **/.env -.npmrc \ No newline at end of file +.npmrc +CLAUDE*.md diff --git a/examples/external-match/base-sepolia/package.json b/examples/external-match/base-sepolia/package.json index da5a0024..e3620a69 100644 --- a/examples/external-match/base-sepolia/package.json +++ b/examples/external-match/base-sepolia/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/renegade-sdk": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/examples/external-match/basic/package.json b/examples/external-match/basic/package.json index e4a05e55..fe8f80d3 100644 --- a/examples/external-match/basic/package.json +++ b/examples/external-match/basic/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/renegade-sdk": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/examples/external-match/direct-malleable-match/index.ts b/examples/external-match/direct-malleable-match/index.ts index 8d6600c2..c3882b3c 100644 --- a/examples/external-match/direct-malleable-match/index.ts +++ b/examples/external-match/direct-malleable-match/index.ts @@ -39,68 +39,63 @@ if (!matchResponse) { } console.log("Received malleable external match response", { - gasSponsored: matchResponse.gas_sponsored, + gasSponsored: matchResponse.gas_sponsorship_info != null, gasSponsorshipInfo: matchResponse.gas_sponsorship_info, }); // --- Malleable Bundle Manipulation --- // -// Print bundle info +// Print bundle info (v2 uses input/output terminology) console.log("\nBundle bounds:"); -const [minBase, maxBase] = matchResponse.baseBounds(); -const [minQuote, maxQuote] = matchResponse.quoteBounds(); -console.log(`Base bounds: ${minBase} - ${maxBase}`); -console.log(`Quote bounds: ${minQuote} - ${maxQuote}`); +const [minInput, maxInput] = matchResponse.inputBounds(); +const [minOutput, maxOutput] = matchResponse.outputBounds(); +console.log(`Input bounds: ${minInput} - ${maxInput}`); +console.log(`Output bounds: ${minOutput} - ${maxOutput}`); -// Set a specific base amount on the bundle +// Set a specific input amount on the bundle // This modifies the settlement transaction calldata to use the specified amount -const targetBaseAmount = minBase + (maxBase - minBase) / BigInt(2); -const receiveAmount = matchResponse.setBaseAmount(targetBaseAmount); +const targetInputAmount = minInput + (maxInput - minInput) / BigInt(2); +const receiveAmount = matchResponse.setInputAmount(targetInputAmount); const sendAmount = matchResponse.sendAmount(); -console.log(`\nSet base amount: ${targetBaseAmount}`); +console.log(`\nSet input amount: ${targetInputAmount}`); console.log(`Send amount: ${sendAmount}`); console.log(`Receive amount: ${receiveAmount}`); -// Alternatively, you can set a quote amount instead: -// const targetQuoteAmount = minQuote + (maxQuote - minQuote) / BigInt(2); -// matchResponse.setQuoteAmount(targetQuoteAmount); - const bundle = matchResponse.match_bundle; const tx = bundle.settlement_tx; // --- Allowance Check --- // -const isSell = bundle.match_result.direction === OrderSide.SELL; -const address = isSell - ? (bundle.match_result.base_mint as `0x${string}`) - : (bundle.match_result.quote_mint as `0x${string}`); -// Use the send amount that was set via setBaseAmount (or max if not set) +// The input token is what we send; skip ERC20 approval for native ETH sells +const inputMint = bundle.match_result.input_mint as `0x${string}`; const amount = sendAmount; // This is the amount that will actually be sent const spender = tx.to as `0x${string}`; -console.log("\nChecking allowance..."); - -const allowance = await publicClient.readContract({ - address, - abi: erc20Abi, - functionName: "allowance", - args: [owner, spender], -}); +if (!matchResponse.isNativeEthSell()) { + console.log("\nChecking allowance..."); -if (allowance < amount) { - console.log("Allowance is less than amount, approving..."); - const approveTx = await walletClient.writeContract({ - address, + const allowance = await publicClient.readContract({ + address: inputMint, abi: erc20Abi, - functionName: "approve", - args: [spender, amount], + functionName: "allowance", + args: [owner, spender], }); - console.log("Submitting approve transaction..."); - await publicClient.waitForTransactionReceipt({ - hash: approveTx, - }); - console.log("Successfully submitted approve transaction", approveTx); + + if (allowance < amount) { + console.log("Allowance is less than amount, approving..."); + const approveTx = await walletClient.writeContract({ + address: inputMint, + abi: erc20Abi, + functionName: "approve", + args: [spender, amount], + }); + console.log("Submitting approve transaction..."); + await publicClient.waitForTransactionReceipt({ + hash: approveTx, + }); + console.log("Successfully submitted approve transaction", approveTx); + } } // --- Submit Bundle --- // @@ -110,6 +105,7 @@ console.log("\nSubmitting bundle..."); const hash = await walletClient.sendTransaction({ to: tx.to as `0x${string}`, data: tx.data as `0x${string}`, + value: BigInt(tx.value ?? "0x0"), type: "eip1559", }); diff --git a/examples/external-match/direct-malleable-match/package.json b/examples/external-match/direct-malleable-match/package.json index f192d5e6..029a655d 100644 --- a/examples/external-match/direct-malleable-match/package.json +++ b/examples/external-match/direct-malleable-match/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/renegade-sdk": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/examples/external-match/direct-match/package.json b/examples/external-match/direct-match/package.json index b05cdbb4..c226148c 100644 --- a/examples/external-match/direct-match/package.json +++ b/examples/external-match/direct-match/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/renegade-sdk": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/examples/external-match/exact-output/package.json b/examples/external-match/exact-output/package.json index 24d6bf02..f7ef29b9 100644 --- a/examples/external-match/exact-output/package.json +++ b/examples/external-match/exact-output/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/renegade-sdk": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/examples/external-match/exchange-metadata/package.json b/examples/external-match/exchange-metadata/package.json index 185f7f2d..95a6e230 100644 --- a/examples/external-match/exchange-metadata/package.json +++ b/examples/external-match/exchange-metadata/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/renegade-sdk": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/examples/external-match/in-kind-gas-sponsorship/package.json b/examples/external-match/in-kind-gas-sponsorship/package.json index 5a9acb4a..344ee718 100644 --- a/examples/external-match/in-kind-gas-sponsorship/package.json +++ b/examples/external-match/in-kind-gas-sponsorship/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/renegade-sdk": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/examples/external-match/malleable/helpers.ts b/examples/external-match/malleable/helpers.ts index 5c3f02ab..4b5e91be 100644 --- a/examples/external-match/malleable/helpers.ts +++ b/examples/external-match/malleable/helpers.ts @@ -1,33 +1,34 @@ -import type { MalleableExternalMatchResponse } from "@renegade-fi/node"; +import type { MalleableExternalMatchResponse } from "@renegade-fi/renegade-sdk"; /** - * Set a random base amount on the bundle and print the results + * Set a random input amount on the bundle and print the results * @param bundle The malleable match bundle */ -export function setRandomBaseAmount(bundle: MalleableExternalMatchResponse) { +export function setRandomInputAmount(bundle: MalleableExternalMatchResponse) { // Print bundle info - console.log("Bundle info:"); - const [minBase, maxBase] = bundle.baseBounds(); - console.log(`Base bounds: ${minBase} - ${maxBase}`); - - // Pick a random base amount and see the send and receive amounts at that base amount - const dummyBaseAmount = randomInRange(minBase, maxBase); - const dummySendAmount = bundle.sendAmountAtBase(dummyBaseAmount); - const dummyReceiveAmount = bundle.receiveAmountAtBase(dummyBaseAmount); - console.log(`Hypothetical base amount: ${dummyBaseAmount}`); - console.log(`Hypothetical send amount: ${dummySendAmount}`); + console.log("\nBundle info:"); + const [minInput, maxInput] = bundle.inputBounds(); + const [minOutput, maxOutput] = bundle.outputBounds(); + console.log(`Input bounds: ${minInput} - ${maxInput}`); + console.log(`Output bounds: ${minOutput} - ${maxOutput}`); + + // Pick a hypothetical input amount and see the receive amount + const dummyInputAmount = randomInRange(minInput, maxInput); + const dummyReceiveAmount = bundle.receiveAmountAtInput(dummyInputAmount); + console.log(`Hypothetical input amount: ${dummyInputAmount}`); + console.log(`Hypothetical send amount: ${dummyInputAmount}`); console.log(`Hypothetical receive amount: ${dummyReceiveAmount}`); - // Pick an actual base amount to swap with - const swappedBaseAmount = randomInRange(minBase, maxBase); + // Pick an actual input amount to swap with + const swappedInputAmount = randomInRange(minInput, maxInput); - // Setting the base amount will return the receive amount at the new base - // You can also call sendAmount and receiveAmount to get the amounts at the - // currently set base amount - bundle.setBaseAmount(swappedBaseAmount); + // Setting the input amount returns the receive amount at the new input + // You can also call sendAmount() and receiveAmount() to get the amounts + // at the currently set input amount + bundle.setInputAmount(swappedInputAmount); const send = bundle.sendAmount(); const recv = bundle.receiveAmount(); - console.log(`Swapped base amount: ${swappedBaseAmount}`); + console.log(`Swapped input amount: ${swappedInputAmount}`); console.log(`Send amount: ${send}`); console.log(`Receive amount: ${recv}`); } @@ -36,36 +37,3 @@ export function setRandomBaseAmount(bundle: MalleableExternalMatchResponse) { function randomInRange(min: bigint, max: bigint): bigint { return min + BigInt(Math.floor(Math.random() * (Number(max) - Number(min)))); } - -/** - * Set a random quote amount on the bundle and print the results - * @param bundle The malleable match bundle - */ -// biome-ignore lint/correctness/noUnusedVariables: User can choose to use this function in the example -function setRandomQuoteAmount(bundle: MalleableExternalMatchResponse) { - // Print bundle info - console.log("Bundle info:"); - const [minQuote, maxQuote] = bundle.quoteBounds(); - console.log(`Quote bounds: ${minQuote} - ${maxQuote}`); - - // Pick a random base amount and see the send and receive amounts at that base amount - const dummyQuoteAmount = randomInRange(minQuote, maxQuote); - const dummySendAmount = bundle.sendAmountAtQuote(dummyQuoteAmount); - const dummyReceiveAmount = bundle.receiveAmountAtQuote(dummyQuoteAmount); - console.log(`Hypothetical quote amount: ${dummyQuoteAmount}`); - console.log(`Hypothetical send amount: ${dummySendAmount}`); - console.log(`Hypothetical receive amount: ${dummyReceiveAmount}`); - - // Pick an actual base amount to swap with - const swappedQuoteAmount = randomInRange(minQuote, maxQuote); - - // Setting the quote amount will return the receive amount at the new quote - // You can also call sendAmount and receiveAmount to get the amounts at the - // currently set quote amount - bundle.setQuoteAmount(swappedQuoteAmount); - const send = bundle.sendAmount(); - const recv = bundle.receiveAmount(); - console.log(`Swapped quote amount: ${swappedQuoteAmount}`); - console.log(`Send amount: ${send}`); - console.log(`Receive amount: ${recv}`); -} diff --git a/examples/external-match/malleable/index.ts b/examples/external-match/malleable/index.ts index 94620773..18dd275f 100644 --- a/examples/external-match/malleable/index.ts +++ b/examples/external-match/malleable/index.ts @@ -1,6 +1,6 @@ -import { ExternalMatchClient } from "@renegade-fi/node"; -import { API_KEY, API_SECRET, chainId, walletClient } from "./env"; -import { setRandomBaseAmount } from "./helpers"; +import { ExternalMatchClient, OrderSide } from "@renegade-fi/renegade-sdk"; +import { API_KEY, API_SECRET, walletClient } from "./env"; +import { setRandomInputAmount } from "./helpers"; if (!API_KEY) { throw new Error("API_KEY is not set"); @@ -10,50 +10,51 @@ if (!API_SECRET) { throw new Error("API_SECRET is not set"); } -const client = ExternalMatchClient.new({ - apiKey: API_KEY, - apiSecret: API_SECRET, - chainId, -}); +const client = ExternalMatchClient.newArbitrumSepoliaClient(API_KEY, API_SECRET); const WETH_ADDRESS = "0xc3414a7ef14aaaa9c4522dfc00a4e66e74e9c25a"; const USDC_ADDRESS = "0xdf8d259c04020562717557f2b5a3cf28e92707d1"; const quoteAmount = BigInt(2_000_000); // 2 USDC -const side = "buy"; +const side = OrderSide.BUY; const order = { - base: WETH_ADDRESS, - quote: USDC_ADDRESS, + base_mint: WETH_ADDRESS, + quote_mint: USDC_ADDRESS, side, - quoteAmount, + quote_amount: quoteAmount, } as const; console.log("Fetching quote..."); -const quote = await client.getQuote({ - order, -}); +const quote = await client.requestQuote(order); -console.log("Assmbling quote..."); +if (!quote) { + console.error("No quote available, exiting..."); + process.exit(1); +} -const bundle = await client.assembleMalleableQuote({ - quote, -}); +console.log("Assembling malleable quote..."); + +const bundle = await client.assembleMalleableQuote(quote); + +if (!bundle) { + console.error("No bundle available, exiting..."); + process.exit(1); +} -// Set a base amount on the bundle -// Alternatively, you can set a quote amount on the bundle - see -// `setRandomQuoteAmount` in `helpers.ts` -setRandomBaseAmount(bundle); +// Set a random input amount on the bundle +setRandomInputAmount(bundle); -const tx = bundle.match_bundle.settlement_tx; +const tx = bundle.settlementTx(); // --- Submit Bundle --- // -console.log("Submitting bundle..."); +console.log("\nSubmitting bundle..."); const hash = await walletClient.sendTransaction({ - to: tx.to, - data: tx.data, + to: tx.to as `0x${string}`, + data: tx.data as `0x${string}`, + value: BigInt(tx.value ?? "0x0"), type: "eip1559", }); diff --git a/examples/external-match/malleable/package.json b/examples/external-match/malleable/package.json index 50b11620..ade76a80 100644 --- a/examples/external-match/malleable/package.json +++ b/examples/external-match/malleable/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/node": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/examples/external-match/native-gas-sponshorship/package.json b/examples/external-match/native-gas-sponshorship/package.json index 5971c8e5..ec0c2b51 100644 --- a/examples/external-match/native-gas-sponshorship/package.json +++ b/examples/external-match/native-gas-sponshorship/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/renegade-sdk": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/examples/external-match/order-book-depth-all-pairs/package.json b/examples/external-match/order-book-depth-all-pairs/package.json index 6e012911..673973db 100644 --- a/examples/external-match/order-book-depth-all-pairs/package.json +++ b/examples/external-match/order-book-depth-all-pairs/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/renegade-sdk": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/examples/external-match/order-book-depth/package.json b/examples/external-match/order-book-depth/package.json index b4be2289..898bca76 100644 --- a/examples/external-match/order-book-depth/package.json +++ b/examples/external-match/order-book-depth/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/renegade-sdk": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/examples/external-match/supported-tokens/package.json b/examples/external-match/supported-tokens/package.json index b7cf205f..db5eb051 100644 --- a/examples/external-match/supported-tokens/package.json +++ b/examples/external-match/supported-tokens/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/renegade-sdk": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/examples/external-match/token-prices/package.json b/examples/external-match/token-prices/package.json index c9a537e4..9973101f 100644 --- a/examples/external-match/token-prices/package.json +++ b/examples/external-match/token-prices/package.json @@ -6,7 +6,7 @@ "start": "tsx index.ts" }, "dependencies": { - "@renegade-fi/renegade-sdk": "latest", + "@renegade-fi/renegade-sdk": "workspace:*", "viem": "latest" }, "devDependencies": { diff --git a/packages/external-match/src/client.ts b/packages/external-match/src/client.ts index c2d3e4c6..6e57ab35 100644 --- a/packages/external-match/src/client.ts +++ b/packages/external-match/src/client.ts @@ -7,60 +7,67 @@ import { type HttpResponse, RelayerHttpClient } from "./http.js"; import { - type ApiSignedExternalQuote, - type AssembleExternalMatchRequest, ExchangeMetadataResponse, - type ExternalMatchRequest, - ExternalMatchResponse, + type ExternalMatchResponse, type ExternalOrder, - type ExternalQuoteRequest, - type ExternalQuoteResponse, - GetDepthForAllPairsResponse, + type GetDepthForAllPairsResponse, MalleableExternalMatchResponse, - OrderBookDepth, - SignedExternalQuote, + type OrderBookDepth, + type SignedExternalQuote, type SupportedTokensResponse, - type TokenPrice, type TokenPricesResponse, } from "./types/index.js"; +import type { + AssembleExternalMatchRequestV2, + ExternalOrderV2, + GetMarketDepthByMintResponse, + GetMarketDepthsResponse, + GetMarketsResponse, +} from "./types/v2Types.js"; +import { + deserializeMarketDepthResponse, + deserializeMarketDepthsResponse, + deserializeMarketsResponse, + deserializeMatchResponseV2, + deserializeQuoteResponseV2, + serializeAssembleRequestV2, + serializeQuoteRequestV2, +} from "./types/v2Types.js"; +import { + marketDepthsToV1, + marketDepthToV1, + marketsToSupportedTokens, + marketsToTokenPrices, + v1OrderToV2, + v1QuoteToV2, + v2QuoteToV1, + v2ResponseToV1NonMalleable, +} from "./v1Conversions.js"; import { VERSION } from "./version.js"; -// Constants for API URLs -const ARBITRUM_SEPOLIA_BASE_URL = "https://arbitrum-sepolia.auth-server.renegade.fi"; -const ARBITRUM_ONE_BASE_URL = "https://arbitrum-one.auth-server.renegade.fi"; -const BASE_SEPOLIA_BASE_URL = "https://base-sepolia.auth-server.renegade.fi"; -const BASE_MAINNET_BASE_URL = "https://base-mainnet.auth-server.renegade.fi"; +// Constants for auth server URLs +const ARBITRUM_SEPOLIA_BASE_URL = "https://arbitrum-sepolia.v2.auth-server.renegade.fi"; +const ARBITRUM_ONE_BASE_URL = "https://arbitrum-one.v2.auth-server.renegade.fi"; +const BASE_SEPOLIA_BASE_URL = "https://base-sepolia.v2.auth-server.renegade.fi"; +const BASE_MAINNET_BASE_URL = "https://base-mainnet.v2.auth-server.renegade.fi"; -const ARBITRUM_SEPOLIA_RELAYER_URL = "https://arbitrum-sepolia.relayer.renegade.fi"; -const ARBITRUM_ONE_RELAYER_URL = "https://arbitrum-one.relayer.renegade.fi"; -const BASE_SEPOLIA_RELAYER_URL = "https://base-sepolia.relayer.renegade.fi"; -const BASE_MAINNET_RELAYER_URL = "https://base-mainnet.relayer.renegade.fi"; +// Constants for relayer URLs +const ARBITRUM_SEPOLIA_RELAYER_URL = "https://arbitrum-sepolia.v2.relayer.renegade.fi"; +const ARBITRUM_ONE_RELAYER_URL = "https://arbitrum-one.v2.relayer.renegade.fi"; +const BASE_SEPOLIA_RELAYER_URL = "https://base-sepolia.v2.relayer.renegade.fi"; +const BASE_MAINNET_RELAYER_URL = "https://base-mainnet.v2.relayer.renegade.fi"; // Header constants const RENEGADE_API_KEY_HEADER = "x-renegade-api-key"; const RENEGADE_SDK_VERSION_HEADER = "x-renegade-sdk-version"; -// API Routes -const REQUEST_EXTERNAL_QUOTE_ROUTE = "/v0/matching-engine/quote"; -const ASSEMBLE_EXTERNAL_MATCH_ROUTE = "/v0/matching-engine/assemble-external-match"; -/** - * The route used to assemble an external match into a malleable bundle - */ -const ASSEMBLE_MALLEABLE_EXTERNAL_MATCH_ROUTE = - "/v0/matching-engine/assemble-malleable-external-match"; -const REQUEST_EXTERNAL_MATCH_ROUTE = "/v0/matching-engine/request-external-match"; -/** - * The route used to request a malleable external match directly - */ -const REQUEST_MALLEABLE_EXTERNAL_MATCH_ROUTE = - "/v0/matching-engine/request-malleable-external-match"; -const ORDER_BOOK_DEPTH_ROUTE = "/v0/order_book/depth"; -/** Returns the supported tokens list */ -const SUPPORTED_TOKENS_ROUTE = "/v0/supported-tokens"; -/** Returns the token prices */ -const TOKEN_PRICES_ROUTE = "/v0/token-prices"; -/** Returns the exchange metadata */ -const EXCHANGE_METADATA_ROUTE = "/v0/exchange-metadata"; +// V2 API Routes +const GET_QUOTE_ROUTE = "/v2/external-matches/get-quote"; +const ASSEMBLE_MATCH_BUNDLE_ROUTE = "/v2/external-matches/assemble-match-bundle"; +const GET_MARKETS_ROUTE = "/v2/markets"; +const GET_MARKET_DEPTH_BY_MINT_ROUTE = "/v2/markets"; // /{mint}/depth appended dynamically +const GET_MARKETS_DEPTH_ROUTE = "/v2/markets/depth"; +const GET_EXCHANGE_METADATA_ROUTE = "/v2/metadata/exchange"; // Query Parameters const DISABLE_GAS_SPONSORSHIP_QUERY_PARAM = "disable_gas_sponsorship"; @@ -69,8 +76,6 @@ const REFUND_NATIVE_ETH_QUERY_PARAM = "refund_native_eth"; /** * Get the SDK version string. - * - * @returns The SDK version prefixed with "typescript-v" */ function getSdkVersion(): string { return `typescript-v${VERSION}`; @@ -84,40 +89,25 @@ export class RequestQuoteOptions { gasRefundAddress?: string; refundNativeEth = false; - /** - * Create a new instance of RequestQuoteOptions. - */ static new(): RequestQuoteOptions { return new RequestQuoteOptions(); } - /** - * Set whether gas sponsorship should be disabled. - */ withGasSponsorshipDisabled(disableGasSponsorship: boolean): this { this.disableGasSponsorship = disableGasSponsorship; return this; } - /** - * Set the gas refund address. - */ withGasRefundAddress(gasRefundAddress: string): this { this.gasRefundAddress = gasRefundAddress; return this; } - /** - * Set whether to refund in native ETH. - */ withRefundNativeEth(refundNativeEth: boolean): this { this.refundNativeEth = refundNativeEth; return this; } - /** - * Build the request path with query parameters. - */ buildRequestPath(): string { const params = new URLSearchParams(); params.set(DISABLE_GAS_SPONSORSHIP_QUERY_PARAM, this.disableGasSponsorship.toString()); @@ -129,7 +119,7 @@ export class RequestQuoteOptions { params.set(REFUND_NATIVE_ETH_QUERY_PARAM, this.refundNativeEth.toString()); } - return `${REQUEST_EXTERNAL_QUOTE_ROUTE}?${params.toString()}`; + return `${GET_QUOTE_ROUTE}?${params.toString()}`; } } @@ -143,56 +133,35 @@ export class RequestExternalMatchOptions { doGasEstimation = false; receiverAddress?: string; - /** - * Create a new instance of RequestExternalMatchOptions. - */ static new(): RequestExternalMatchOptions { return new RequestExternalMatchOptions(); } - /** - * Set whether gas sponsorship should be disabled. - */ withGasSponsorshipDisabled(disableGasSponsorship: boolean): this { this.disableGasSponsorship = disableGasSponsorship; return this; } - /** - * Set the gas refund address. - */ withGasRefundAddress(gasRefundAddress: string): this { this.gasRefundAddress = gasRefundAddress; return this; } - /** - * Set whether to refund in native ETH. - */ withRefundNativeEth(refundNativeEth: boolean): this { this.refundNativeEth = refundNativeEth; return this; } - /** - * Set whether the relayer should include gas estimation in the response. - */ withGasEstimation(doGasEstimation: boolean): this { this.doGasEstimation = doGasEstimation; return this; } - /** - * Set the receiver address for the match. - */ withReceiverAddress(receiverAddress: string): this { this.receiverAddress = receiverAddress; return this; } - /** - * Build the request path with query parameters. - */ buildRequestPath(): string { const params = new URLSearchParams(); params.set(DISABLE_GAS_SPONSORSHIP_QUERY_PARAM, this.disableGasSponsorship.toString()); @@ -206,28 +175,8 @@ export class RequestExternalMatchOptions { const query = params.toString(); return query.length > 0 - ? `${REQUEST_EXTERNAL_MATCH_ROUTE}?${query}` - : REQUEST_EXTERNAL_MATCH_ROUTE; - } - - /** - * Build the request path for malleable external match with query parameters. - */ - buildMalleableRequestPath(): string { - const params = new URLSearchParams(); - params.set(DISABLE_GAS_SPONSORSHIP_QUERY_PARAM, this.disableGasSponsorship.toString()); - if (this.gasRefundAddress) { - params.set(GAS_REFUND_ADDRESS_QUERY_PARAM, this.gasRefundAddress); - } - - if (this.refundNativeEth) { - params.set(REFUND_NATIVE_ETH_QUERY_PARAM, this.refundNativeEth.toString()); - } - - const query = params.toString(); - return query.length > 0 - ? `${REQUEST_MALLEABLE_EXTERNAL_MATCH_ROUTE}?${query}` - : REQUEST_MALLEABLE_EXTERNAL_MATCH_ROUTE; + ? `${ASSEMBLE_MATCH_BUNDLE_ROUTE}?${query}` + : ASSEMBLE_MATCH_BUNDLE_ROUTE; } } @@ -236,92 +185,30 @@ export class RequestExternalMatchOptions { */ export class AssembleExternalMatchOptions { doGasEstimation = false; - allowShared = false; receiverAddress?: string; updatedOrder?: ExternalOrder; - requestGasSponsorship = false; - gasRefundAddress?: string; - /** - * Create a new instance of AssembleExternalMatchOptions. - */ static new(): AssembleExternalMatchOptions { return new AssembleExternalMatchOptions(); } - /** - * Set whether to do gas estimation. - */ withGasEstimation(doGasEstimation: boolean): AssembleExternalMatchOptions { this.doGasEstimation = doGasEstimation; return this; } - /** - * Set whether to allow shared gas sponsorship. - */ - withAllowShared(allowShared: boolean): AssembleExternalMatchOptions { - this.allowShared = allowShared; - return this; - } - - /** - * Set the receiver address. - */ withReceiverAddress(receiverAddress: string): AssembleExternalMatchOptions { this.receiverAddress = receiverAddress; return this; } - /** - * Set the updated order. - */ withUpdatedOrder(updatedOrder: ExternalOrder): AssembleExternalMatchOptions { this.updatedOrder = updatedOrder; return this; } - /** - * Set whether to request gas sponsorship. - * @deprecated Request gas sponsorship when requesting a quote instead - */ - withGasSponsorship(requestGasSponsorship: boolean): AssembleExternalMatchOptions { - this.requestGasSponsorship = requestGasSponsorship; - return this; - } - - /** - * Set the gas refund address. - * @deprecated Request gas sponsorship when requesting a quote instead - */ - withGasRefundAddress(gasRefundAddress: string): AssembleExternalMatchOptions { - this.gasRefundAddress = gasRefundAddress; - return this; - } - - /** - * Build the request path with query parameters. - */ buildRequestPath(): string { - // If no query parameters are needed, return the base path - if (!this.requestGasSponsorship && !this.gasRefundAddress) { - return ASSEMBLE_EXTERNAL_MATCH_ROUTE; - } - - const params = new URLSearchParams(); - if (this.requestGasSponsorship) { - // We only write this query parameter if it was explicitly set - params.set( - DISABLE_GAS_SPONSORSHIP_QUERY_PARAM, - (!this.requestGasSponsorship).toString(), - ); - } - - if (this.gasRefundAddress) { - params.set(GAS_REFUND_ADDRESS_QUERY_PARAM, this.gasRefundAddress); - } - - return `${ASSEMBLE_EXTERNAL_MATCH_ROUTE}?${params.toString()}`; + return ASSEMBLE_MATCH_BUNDLE_ROUTE; } } @@ -329,36 +216,9 @@ export class AssembleExternalMatchOptions { * Options for assembling a malleable external match. */ export class AssembleMalleableExternalMatchOptions extends AssembleExternalMatchOptions { - /** - * Create a new instance of AssembleExternalMatchOptions. - */ static override new(): AssembleMalleableExternalMatchOptions { return new AssembleMalleableExternalMatchOptions(); } - /** - * Build the request path with query parameters. - */ - override buildRequestPath(): string { - // If no query parameters are needed, return the base path - if (!this.requestGasSponsorship && !this.gasRefundAddress) { - return ASSEMBLE_MALLEABLE_EXTERNAL_MATCH_ROUTE; - } - - const params = new URLSearchParams(); - if (this.requestGasSponsorship) { - // We only write this query parameter if it was explicitly set - params.set( - DISABLE_GAS_SPONSORSHIP_QUERY_PARAM, - (!this.requestGasSponsorship).toString(), - ); - } - - if (this.gasRefundAddress) { - params.set(GAS_REFUND_ADDRESS_QUERY_PARAM, this.gasRefundAddress); - } - - return `${ASSEMBLE_MALLEABLE_EXTERNAL_MATCH_ROUTE}?${params.toString()}`; - } } /** @@ -408,43 +268,49 @@ export class ExternalMatchClientError extends Error { } } +/** + * Build a v2 assemble request for a direct order. + */ +function buildDirectOrderRequest( + v2Order: ExternalOrderV2, + options: { doGasEstimation: boolean; receiverAddress?: string }, +): AssembleExternalMatchRequestV2 { + return { + do_gas_estimation: options.doGasEstimation, + receiver_address: options.receiverAddress, + order: { + type: "direct-order", + external_order: v2Order, + }, + }; +} + /** * Client for interacting with the Renegade external matching API. */ export class ExternalMatchClient { private apiKey: string; private httpClient: RelayerHttpClient; - private relayerHttpClient: RelayerHttpClient; + private relayerHttpClient?: RelayerHttpClient; /** * Initialize a new ExternalMatchClient. * * @param apiKey The API key for authentication * @param apiSecret The API secret for request signing - * @param baseUrl The base URL of the Renegade API + * @param baseUrl The base URL of the auth server API + * @param relayerBaseUrl The base URL of the relayer API (for market endpoints) */ - constructor(apiKey: string, apiSecret: string, baseUrl: string, relayerUrl: string) { + constructor(apiKey: string, apiSecret: string, baseUrl: string, relayerBaseUrl?: string) { this.apiKey = apiKey; this.httpClient = new RelayerHttpClient(baseUrl, apiSecret); - this.relayerHttpClient = new RelayerHttpClient(relayerUrl); - } - - /** - * Create a new client configured for the Arbitrum Sepolia testnet. - * - * @deprecated Use {@link ExternalMatchClient.newArbitrumSepoliaClient} instead - */ - static newSepoliaClient(apiKey: string, apiSecret: string): ExternalMatchClient { - return ExternalMatchClient.newArbitrumSepoliaClient(apiKey, apiSecret); + if (relayerBaseUrl) { + this.relayerHttpClient = new RelayerHttpClient(relayerBaseUrl, apiSecret); + } } /** * Create a new client configured for the Arbitrum Sepolia testnet. - * - * @param apiKey The API key for authentication - * @param apiSecret The API secret for request signing - * @param relayerUrl The relayer URL for the client - * @returns A new ExternalMatchClient configured for Sepolia */ static newArbitrumSepoliaClient(apiKey: string, apiSecret: string): ExternalMatchClient { return new ExternalMatchClient( @@ -457,10 +323,6 @@ export class ExternalMatchClient { /** * Create a new client configured for the Base Sepolia testnet. - * - * @param apiKey The API key for authentication - * @param apiSecret The API secret for request signing - * @returns A new ExternalMatchClient configured for Sepolia */ static newBaseSepoliaClient(apiKey: string, apiSecret: string): ExternalMatchClient { return new ExternalMatchClient( @@ -473,19 +335,6 @@ export class ExternalMatchClient { /** * Create a new client configured for the Arbitrum One mainnet. - * - * @deprecated Use {@link ExternalMatchClient.newArbitrumOneClient} instead - */ - static newMainnetClient(apiKey: string, apiSecret: string): ExternalMatchClient { - return ExternalMatchClient.newArbitrumOneClient(apiKey, apiSecret); - } - - /** - * Create a new client configured for the Arbitrum One mainnet. - * - * @param apiKey The API key for authentication - * @param apiSecret The API secret for request signing - * @returns A new ExternalMatchClient configured for mainnet */ static newArbitrumOneClient(apiKey: string, apiSecret: string): ExternalMatchClient { return new ExternalMatchClient( @@ -498,10 +347,6 @@ export class ExternalMatchClient { /** * Create a new client configured for the Base mainnet. - * - * @param apiKey The API key for authentication - * @param apiSecret The API secret for request signing - * @returns A new ExternalMatchClient configured for mainnet */ static newBaseMainnetClient(apiKey: string, apiSecret: string): ExternalMatchClient { return new ExternalMatchClient( @@ -512,12 +357,10 @@ export class ExternalMatchClient { ); } + // --- Quote methods (v1 signature, v2 internally) --- + /** * Request a quote for the given order. - * - * @param order The order to request a quote for - * @returns A promise that resolves to a signed quote if one is available, null otherwise - * @throws ExternalMatchClientError if the request fails */ async requestQuote(order: ExternalOrder): Promise { return this.requestQuoteWithOptions(order, RequestQuoteOptions.new()); @@ -525,29 +368,73 @@ export class ExternalMatchClient { /** * Request a quote for the given order with custom options. - * - * @param order The order to request a quote for - * @param options Custom options for the quote request - * @returns A promise that resolves to a signed quote if one is available, null otherwise - * @throws ExternalMatchClientError if the request fails */ async requestQuoteWithOptions( order: ExternalOrder, options: RequestQuoteOptions, ): Promise { validateExternalOrder(order); - const request: ExternalQuoteRequest = { - external_order: order, - }; + const v2Order = v1OrderToV2(order); + const body = serializeQuoteRequestV2({ external_order: v2Order }); const path = options.buildRequestPath(); const headers = this.getHeaders(); - const response = await this.httpClient.post(path, request, headers); + const response = await this.httpClient.post(path, body, headers); - return this.handleOptionalResponse(response, SignedExternalQuote.deserialize); + return this.handleOptionalResponse(response, (data) => { + const v2Response = deserializeQuoteResponseV2(data); + return v2QuoteToV1(v2Response, order); + }); + } + + // --- Assemble methods (v1 signature, v2 internally) --- + + /** + * Assemble a quote into a match bundle with default options. + */ + async assembleQuote(quote: SignedExternalQuote): Promise { + return this.assembleQuoteWithOptions(quote, AssembleExternalMatchOptions.new()); } + /** + * Assemble a quote into a match bundle with custom options. + */ + async assembleQuoteWithOptions( + quote: SignedExternalQuote, + options: AssembleExternalMatchOptions, + ): Promise { + if (options.updatedOrder) { + validateExternalOrder(options.updatedOrder); + } + + const direction = quote.quote.order.side; + const v2SignedQuote = v1QuoteToV2(quote); + + const request: AssembleExternalMatchRequestV2 = { + do_gas_estimation: options.doGasEstimation, + receiver_address: options.receiverAddress, + order: { + type: "quoted-order", + signed_quote: v2SignedQuote, + updated_order: options.updatedOrder ? v1OrderToV2(options.updatedOrder) : undefined, + }, + }; + + const body = serializeAssembleRequestV2(request); + const path = ASSEMBLE_MATCH_BUNDLE_ROUTE; + const headers = this.getHeaders(); + + const response = await this.httpClient.post(path, body, headers); + + return this.handleOptionalResponse(response, (data) => { + const v2Resp = deserializeMatchResponseV2(data); + return v2ResponseToV1NonMalleable(v2Resp, direction); + }); + } + + // --- Direct match methods (v1 signature, v2 internally) --- + /** * Request an external match directly with default options. */ @@ -563,26 +450,25 @@ export class ExternalMatchClient { options: RequestExternalMatchOptions, ): Promise { validateExternalOrder(order); - const request: ExternalMatchRequest = { - do_gas_estimation: options.doGasEstimation, - receiver_address: options.receiverAddress, - external_order: order, - }; + const v2Order = v1OrderToV2(order); + const request = buildDirectOrderRequest(v2Order, options); + const body = serializeAssembleRequestV2(request); const path = options.buildRequestPath(); const headers = this.getHeaders(); - const response = await this.httpClient.post(path, request, headers); + const response = await this.httpClient.post(path, body, headers); - return this.handleOptionalResponse(response, ExternalMatchResponse.deserialize); + return this.handleOptionalResponse(response, (data) => { + const v2Resp = deserializeMatchResponseV2(data); + return v2ResponseToV1NonMalleable(v2Resp, order.side); + }); } + // --- Malleable match methods (v1 input, v2 response — breaking) --- + /** * Request a malleable external match directly with default options. - * - * @param order The order to request a malleable match for - * @returns A promise that resolves to a malleable match response if one is available, null otherwise - * @throws ExternalMatchClientError if the request fails */ async requestMalleableExternalMatch( order: ExternalOrder, @@ -595,89 +481,26 @@ export class ExternalMatchClient { /** * Request a malleable external match directly with custom options. - * - * @param order The order to request a malleable match for - * @param options Custom options for the malleable match request - * @returns A promise that resolves to a malleable match response if one is available, null otherwise - * @throws ExternalMatchClientError if the request fails */ async requestMalleableExternalMatchWithOptions( order: ExternalOrder, options: RequestExternalMatchOptions, ): Promise { validateExternalOrder(order); - const request: ExternalMatchRequest = { - do_gas_estimation: options.doGasEstimation, - receiver_address: options.receiverAddress, - external_order: order, - }; - - const path = options.buildMalleableRequestPath(); - const headers = this.getHeaders(); - - const response = await this.httpClient.post( - path, - request, - headers, - ); - - return this.handleOptionalResponse(response, MalleableExternalMatchResponse.deserialize); - } - - /** - * Assemble a quote into a match bundle with default options. - * - * @param quote The signed quote to assemble - * @returns A promise that resolves to a match response if assembly succeeds, null otherwise - * @throws ExternalMatchClientError if the request fails - */ - async assembleQuote(quote: SignedExternalQuote): Promise { - return this.assembleQuoteWithOptions(quote, AssembleExternalMatchOptions.new()); - } - - /** - * Assemble a quote into a match bundle with custom options. - * - * @param quote The signed quote to assemble - * @param options Custom options for quote assembly - * @returns A promise that resolves to a match response if assembly succeeds, null otherwise - * @throws ExternalMatchClientError if the request fails - */ - async assembleQuoteWithOptions( - quote: SignedExternalQuote, - options: AssembleExternalMatchOptions, - ): Promise { - if (options.updatedOrder) { - validateExternalOrder(options.updatedOrder); - } - const signedQuote: ApiSignedExternalQuote = { - quote: quote.quote, - signature: quote.signature, - deadline: quote.deadline, - }; - - const request: AssembleExternalMatchRequest = { - do_gas_estimation: options.doGasEstimation, - allow_shared: options.allowShared, - receiver_address: options.receiverAddress, - signed_quote: signedQuote, - updated_order: options.updatedOrder, - }; + const v2Order = v1OrderToV2(order); + const request = buildDirectOrderRequest(v2Order, options); + const body = serializeAssembleRequestV2(request); const path = options.buildRequestPath(); const headers = this.getHeaders(); - const response = await this.httpClient.post(path, request, headers); + const response = await this.httpClient.post(path, body, headers); - return this.handleOptionalResponse(response, ExternalMatchResponse.deserialize); + return this.handleOptionalResponse(response, MalleableExternalMatchResponse.deserialize); } /** * Assemble a quote into a malleable match bundle with default options. - * - * @param quote The signed quote to assemble - * @returns A promise that resolves to a match response if assembly succeeds, null otherwise - * @throws ExternalMatchClientError if the request fails */ async assembleMalleableQuote( quote: SignedExternalQuote, @@ -698,145 +521,113 @@ export class ExternalMatchClient { if (options.updatedOrder) { validateExternalOrder(options.updatedOrder); } - const signedQuote: ApiSignedExternalQuote = { - quote: quote.quote, - signature: quote.signature, - deadline: quote.deadline, - }; - const request: AssembleExternalMatchRequest = { + const v2SignedQuote = v1QuoteToV2(quote); + + const request: AssembleExternalMatchRequestV2 = { do_gas_estimation: options.doGasEstimation, - allow_shared: options.allowShared, receiver_address: options.receiverAddress, - signed_quote: signedQuote, - updated_order: options.updatedOrder, + order: { + type: "quoted-order", + signed_quote: v2SignedQuote, + updated_order: options.updatedOrder ? v1OrderToV2(options.updatedOrder) : undefined, + }, }; - const path = options.buildRequestPath(); + const body = serializeAssembleRequestV2(request); + const path = ASSEMBLE_MATCH_BUNDLE_ROUTE; const headers = this.getHeaders(); - const response = await this.httpClient.post( - path, - request, - headers, - ); + const response = await this.httpClient.post(path, body, headers); return this.handleOptionalResponse(response, MalleableExternalMatchResponse.deserialize); } + // --- New v2 market methods --- + /** - * Get order book depth for a given base token mint. - * - * @param mint The base token mint address - * @returns A promise that resolves to the order book depth - * @throws ExternalMatchClientError if the request fails + * Get all tradable markets. */ - async getOrderBookDepth(mint: string): Promise { - const path = `${ORDER_BOOK_DEPTH_ROUTE}/${mint}`; - const headers = this.getHeaders(); + async getMarkets(): Promise { + const client = this.relayerHttpClient ?? this.httpClient; + const response = await client.get(GET_MARKETS_ROUTE); - try { - const response = await this.httpClient.get(path, headers); - if (response.status !== 200 || !response.data) { - throw new ExternalMatchClientError( - "Failed to get order book depth", - response.status, - ); - } - return this.handleOptionalResponse(response, OrderBookDepth.deserialize); - } catch (error: any) { - throw new ExternalMatchClientError( - error.message || "Failed to get order book depth", - error.status, - ); + if (response.status !== 200 || !response.data) { + throw new ExternalMatchClientError("Failed to get markets", response.status); } + + return deserializeMarketsResponse(response.data); } /** - * Get order book depth for all pairs - * @returns A promise that resolves to the order book depth for all pairs - * @throws ExternalMatchClientError if the request fails + * Get market depth for a given base token mint. */ - async getOrderBookDepthAllPairs(): Promise { - const path = `${ORDER_BOOK_DEPTH_ROUTE}`; + async getMarketDepth(mint: string): Promise { + const path = `${GET_MARKET_DEPTH_BY_MINT_ROUTE}/${mint}/depth`; const headers = this.getHeaders(); - try { - const response = await this.httpClient.get(path, headers); - if (response.status !== 200 || !response.data) { - throw new ExternalMatchClientError( - "Failed to get order book depth", - response.status, - ); - } - return this.handleOptionalResponse(response, GetDepthForAllPairsResponse.deserialize); - } catch (error: any) { - throw new ExternalMatchClientError( - error.message || "Failed to get order book depth", - error.status, - ); + const response = await this.httpClient.get(path, headers); + + if (response.status !== 200 || !response.data) { + throw new ExternalMatchClientError("Failed to get market depth", response.status); } + + return deserializeMarketDepthResponse(response.data); } /** - * Get a list of supported tokens for external matches + * Get market depth for all pairs. */ - async getSupportedTokens(): Promise { - const path = `${SUPPORTED_TOKENS_ROUTE}`; + async getMarketDepthsAllPairs(): Promise { const headers = this.getHeaders(); + const response = await this.httpClient.get(GET_MARKETS_DEPTH_ROUTE, headers); - try { - const response = await this.relayerHttpClient.get( - path, - headers, - ); - if (response.status !== 200 || !response.data) { - throw new ExternalMatchClientError( - "Failed to get supported tokens", - response.status, - ); - } - return response.data; - } catch (error: any) { - throw new ExternalMatchClientError( - error.message || "Failed to get supported tokens", - error.status, - ); + if (response.status !== 200 || !response.data) { + throw new ExternalMatchClientError("Failed to get market depths", response.status); } + + return deserializeMarketDepthsResponse(response.data); } + // --- Deprecated v1 methods (shimmed through v2) --- + /** - * Get a list of token prices + * @deprecated Use getMarkets() instead + */ + async getSupportedTokens(): Promise { + const resp = await this.getMarkets(); + return marketsToSupportedTokens(resp); + } + + /** + * @deprecated Use getMarkets() instead */ async getTokenPrices(): Promise { - const path = `${TOKEN_PRICES_ROUTE}`; - const headers = this.getHeaders(); + const resp = await this.getMarkets(); + return marketsToTokenPrices(resp); + } - try { - const response = await this.relayerHttpClient.get(path, headers); - if (response.status !== 200 || !response.data) { - throw new ExternalMatchClientError("Failed to get token prices", response.status); - } - return { - ...response.data, - token_prices: response.data.token_prices.map((tokenPrice: TokenPrice) => ({ - ...tokenPrice, - price: Number.parseFloat(tokenPrice.price.toString()), - })), - }; - } catch (error: any) { - throw new ExternalMatchClientError( - error.message || "Failed to get token prices", - error.status, - ); - } + /** + * @deprecated Use getMarketDepth() instead + */ + async getOrderBookDepth(mint: string): Promise { + const resp = await this.getMarketDepth(mint); + return marketDepthToV1(resp); + } + + /** + * @deprecated Use getMarketDepthsAllPairs() instead + */ + async getOrderBookDepthAllPairs(): Promise { + const resp = await this.getMarketDepthsAllPairs(); + return marketDepthsToV1(resp); } /** * Get exchange metadata including chain ID, settlement contract address, and supported tokens */ async getExchangeMetadata(): Promise { - const path = `${EXCHANGE_METADATA_ROUTE}`; + const path = GET_EXCHANGE_METADATA_ROUTE; const headers = this.getHeaders(); const response = await this.httpClient.get(path, headers); @@ -846,15 +637,8 @@ export class ExternalMatchClient { ) as ExchangeMetadataResponse; } - /** - * Handle an optional HTTP response, returning null for 204, deserializing for 200, - * and throwing an error for other status codes. - * - * @param response The HTTP response - * @param deserialize Function to deserialize the response data - * @returns The deserialized response or null for 204 - * @throws ExternalMatchClientError for non-200/204 status codes - */ + // --- Private helpers --- + private handleOptionalResponse( response: HttpResponse, deserialize: (data: any) => T, @@ -871,24 +655,12 @@ export class ExternalMatchClient { throw new ExternalMatchClientError(errorMessage, response.status); } - /** - * Extract error message from an error response. - * Per OpenAPI spec, error responses follow the ErrorResponse schema with an "error" field. - * - * @param data The response data (may be ErrorResponse object, string, or other) - * @returns The extracted error message - */ private extractErrorMessage(data: any): string { if (data && typeof data === "object" && "error" in data && typeof data.error === "string") { return data.error; } return typeof data === "string" ? data : JSON.stringify(data); } - /** - * Get the headers required for API requests. - * - * @returns Headers containing the API key and SDK version - */ private getHeaders(): Record { return { diff --git a/packages/external-match/src/http.ts b/packages/external-match/src/http.ts index c144930a..aef1fb54 100644 --- a/packages/external-match/src/http.ts +++ b/packages/external-match/src/http.ts @@ -57,8 +57,6 @@ export class RelayerHttpClient { } this.defaultHeaders = { "Content-Type": "application/json", - // Ask the server to encode all numeric values as strings. - Accept: "application/json; number=string", }; } diff --git a/packages/external-match/src/index.ts b/packages/external-match/src/index.ts index 9b05ec6d..1b0512ce 100644 --- a/packages/external-match/src/index.ts +++ b/packages/external-match/src/index.ts @@ -26,6 +26,7 @@ export type { ExternalQuoteRequest, ExternalQuoteResponse, FeeTake, + FeeTakeRate, GasSponsorshipInfo, SettlementTransaction, SignedGasSponsorshipInfo, @@ -40,3 +41,13 @@ export { OrderSide, SignedExternalQuote, } from "./types/index.js"; + +// Export new v2 market types +export type { + DepthSide, + GetMarketDepthByMintResponse, + GetMarketDepthsResponse, + GetMarketsResponse, + MarketDepth, + MarketInfo, +} from "./types/v2Types.js"; diff --git a/packages/external-match/src/types/fixedPoint.ts b/packages/external-match/src/types/fixedPoint.ts index 720b6f3c..525772c7 100644 --- a/packages/external-match/src/types/fixedPoint.ts +++ b/packages/external-match/src/types/fixedPoint.ts @@ -21,6 +21,13 @@ export class FixedPoint { return floored; } + /** + * Convert the fixed point value to a floating point number + */ + toF64(): number { + return Number(this.value) / Number(FIXED_POINT_PRECISION_SHIFT); + } + /** * Divide a bigint by a fixed point number and return the ceiling */ diff --git a/packages/external-match/src/types/index.ts b/packages/external-match/src/types/index.ts index b4fc6f99..21f64758 100644 --- a/packages/external-match/src/types/index.ts +++ b/packages/external-match/src/types/index.ts @@ -2,6 +2,8 @@ * Type definitions for the Renegade Darkpool API. */ +import type { ApiSignedQuoteV2 } from "./v2Types.js"; + export enum OrderSide { BUY = "Buy", SELL = "Sell", @@ -70,7 +72,7 @@ export interface GasSponsorshipInfo { export interface SignedGasSponsorshipInfo { gas_sponsorship_info: GasSponsorshipInfo; - signature: string; + signature?: string; } export class SignedExternalQuote { @@ -78,17 +80,21 @@ export class SignedExternalQuote { signature: string; deadline: bigint; gas_sponsorship_info?: SignedGasSponsorshipInfo; + /** @internal Stored for round-tripping through assemble */ + _innerV2Quote?: ApiSignedQuoteV2; constructor( quote: ApiExternalQuote, signature: string, deadline: bigint, gas_sponsorship_info?: SignedGasSponsorshipInfo, + innerV2Quote?: ApiSignedQuoteV2, ) { this.quote = quote; this.signature = signature; this.deadline = deadline; this.gas_sponsorship_info = gas_sponsorship_info; + this._innerV2Quote = innerV2Quote; } static deserialize(data: ExternalQuoteResponse): SignedExternalQuote { @@ -326,15 +332,21 @@ export interface TokenPricesResponse { export class ExchangeMetadataResponse { chain_id: number; settlement_contract_address: string; + executor_address: string; + relayer_fee_recipient: string; supported_tokens: ApiToken[]; constructor( chain_id: number, settlement_contract_address: string, + executor_address: string, + relayer_fee_recipient: string, supported_tokens: ApiToken[], ) { this.chain_id = chain_id; this.settlement_contract_address = settlement_contract_address; + this.executor_address = executor_address; + this.relayer_fee_recipient = relayer_fee_recipient; this.supported_tokens = supported_tokens; } @@ -342,6 +354,8 @@ export class ExchangeMetadataResponse { return new ExchangeMetadataResponse( Number(data.chain_id), data.settlement_contract_address, + data.executor_address, + data.relayer_fee_recipient, data.supported_tokens.map((token: any) => ({ address: token.address, symbol: token.symbol, diff --git a/packages/external-match/src/types/malleableMatch.ts b/packages/external-match/src/types/malleableMatch.ts index 2f191ff4..ff78ef50 100644 --- a/packages/external-match/src/types/malleableMatch.ts +++ b/packages/external-match/src/types/malleableMatch.ts @@ -1,32 +1,25 @@ import { bytesToHex, concatBytes, hexToBytes, numberToBytes, numberToHex } from "viem/utils"; import { FixedPoint } from "./fixedPoint.js"; -import { - type ApiExternalAssetTransfer, - type FeeTakeRate, - type GasSponsorshipInfo, - OrderSide, - type SettlementTransaction, +import type { + ApiExternalAssetTransfer, + FeeTakeRate, + GasSponsorshipInfo, + SettlementTransaction, } from "./index.js"; /** The length of an amount in the calldata, which is 32 bytes for a `uint256` */ const AMOUNT_CALLDATA_LENGTH = 32; /** - * The offset of the quote amount in the calldata, + * The offset of the input amount in the calldata, * which is `4` because it's the first calldata argument * after the 4-byte function selector */ -const QUOTE_AMOUNT_OFFSET = 4; -/** - * The offset of the base amount in the calldata, - * which is `AMOUNT_CALLDATA_LENGTH` bytes after - * the quote amount as it is the next calldata argument - */ -const BASE_AMOUNT_OFFSET = QUOTE_AMOUNT_OFFSET + AMOUNT_CALLDATA_LENGTH; +const INPUT_AMOUNT_OFFSET = 4; /** The address used to represent the native asset */ const NATIVE_ASSET_ADDR = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; /** - * The response type for requesting a malleable quote on an external order + * The response type for requesting a malleable match on an external order (v2) */ export class MalleableExternalMatchResponse { /** @@ -34,30 +27,16 @@ export class MalleableExternalMatchResponse { */ match_bundle: MalleableAtomicMatchApiBundle; /** - * The base amount chosen for the match - * - * If `undefined`, the base amount hasn't been selected and defaults to the order's maximum base amount. - * - * This field is not meant for client use directly, rather it is set by - * operating on the type and allows the response type to stay internally - * consistent - */ - base_amount?: bigint; - /** - * The quote amount chosen for the match + * The input amount chosen for the match * - * If `undefined`, the quote amount hasn't been selected and defaults to the - * quote amount implied by the maximum base amount and the price in the match result. + * If `undefined`, the input amount hasn't been selected and defaults to + * the order's maximum input amount. * * This field is not meant for client use directly, rather it is set by * operating on the type and allows the response type to stay internally * consistent */ - quote_amount?: bigint; - /** - * Whether the match was sponsored - */ - gas_sponsored: boolean; + input_amount?: bigint; /** * The gas sponsorship info, if the match was sponsored */ @@ -65,27 +44,22 @@ export class MalleableExternalMatchResponse { constructor( match_bundle: MalleableAtomicMatchApiBundle, - gas_sponsored: boolean, gas_sponsorship_info?: GasSponsorshipInfo, - base_amount?: bigint, - quote_amount?: bigint, + input_amount?: bigint, ) { this.match_bundle = match_bundle; - this.gas_sponsored = gas_sponsored; this.gas_sponsorship_info = gas_sponsorship_info; - this.base_amount = base_amount; - this.quote_amount = quote_amount; + this.input_amount = input_amount; } static deserialize(data: any): MalleableExternalMatchResponse { - const matchBundle = { + const matchBundle: MalleableAtomicMatchApiBundle = { match_result: { - quote_mint: data.match_bundle.match_result.quote_mint, - base_mint: data.match_bundle.match_result.base_mint, + input_mint: data.match_bundle.match_result.input_mint, + output_mint: data.match_bundle.match_result.output_mint, price_fp: data.match_bundle.match_result.price_fp, - min_base_amount: BigInt(data.match_bundle.match_result.min_base_amount), - max_base_amount: BigInt(data.match_bundle.match_result.max_base_amount), - direction: data.match_bundle.match_result.direction, + min_input_amount: BigInt(data.match_bundle.match_result.min_input_amount), + max_input_amount: BigInt(data.match_bundle.match_result.max_input_amount), }, fee_rates: data.match_bundle.fee_rates, max_receive: { @@ -104,13 +78,21 @@ export class MalleableExternalMatchResponse { mint: data.match_bundle.min_send.mint, amount: BigInt(data.match_bundle.min_send.amount), }, - settlement_tx: data.match_bundle.settlement_tx, - deadline: BigInt(data.match_bundle.deadline), + settlement_tx: { + tx_type: + data.match_bundle.settlement_tx.type ?? + data.match_bundle.settlement_tx.tx_type ?? + "0x0", + to: data.match_bundle.settlement_tx.to, + data: data.match_bundle.settlement_tx.input ?? data.match_bundle.settlement_tx.data, + value: data.match_bundle.settlement_tx.value ?? "0x0", + gas: data.match_bundle.settlement_tx.gas, + }, + deadline: Number(data.match_bundle.deadline), }; return new MalleableExternalMatchResponse( matchBundle, - data.gas_sponsored, data.gas_sponsorship_info ? { refund_amount: BigInt(data.gas_sponsorship_info.refund_amount), @@ -118,254 +100,149 @@ export class MalleableExternalMatchResponse { refund_address: data.gas_sponsorship_info.refund_address, } : undefined, - data.base_amount ? BigInt(data.base_amount) : undefined, + data.input_amount != null ? BigInt(data.input_amount) : undefined, ); } /** - * Set the `base_amount` of the `match_result` - * - * @returns The amount received at the given `base_amount` + * Get a copy of the settlement transaction */ - public setBaseAmount(baseAmount: bigint) { - this.checkBaseAmount(baseAmount); - - const impliedQuoteAmount = this.quoteAmount(baseAmount); - - // Set the calldata - this.setBaseAmountCalldata(baseAmount); - this.setQuoteAmountCalldata(impliedQuoteAmount); - - // Set the quote and base amounts on the response - this.base_amount = baseAmount; - this.quote_amount = impliedQuoteAmount; - - return this.receiveAmount(); + public settlementTx(): SettlementTransaction { + return { ...this.match_bundle.settlement_tx }; } /** - * Set the calldata to use a given base amount - */ - private setBaseAmountCalldata(baseAmount: bigint) { - const calldataBytes = hexToBytes(this.match_bundle.settlement_tx.data as `0x${string}`); - - // Padded to 32 bytes - const baseAmountBytes = numberToBytes(baseAmount, { size: AMOUNT_CALLDATA_LENGTH }); - - const prefix = calldataBytes.slice(0, BASE_AMOUNT_OFFSET); - const suffix = calldataBytes.slice( - BASE_AMOUNT_OFFSET + AMOUNT_CALLDATA_LENGTH, - calldataBytes.length, - ); - - // Set the calldata and the tx value - const newCalldataBytes = concatBytes([prefix, baseAmountBytes, suffix]); - const newCalladata = bytesToHex(newCalldataBytes); - const value = this.isNativeEthSell() ? baseAmount : 0n; - const valueHex = numberToHex(value); - - const newMatchBundle = { - ...this.match_bundle, - settlement_tx: { - ...this.match_bundle.settlement_tx, - data: newCalladata, - value: valueHex, - }, - }; - - this.match_bundle = newMatchBundle; - } - - /** - * Set the `quote_amount` of the `match_result` + * Set the input amount of the match result * - * @returns The amount received at the given `quote_amount` + * @returns The receive amount (output net of fees) at the given input amount */ - public setQuoteAmount(quoteAmount: bigint) { - const impliedBaseAmount = this.baseAmount(quoteAmount); - this.checkQuoteAmount(quoteAmount, impliedBaseAmount); + public setInputAmount(inputAmount: bigint): bigint { + this.checkInputAmount(inputAmount); // Set the calldata - this.setQuoteAmountCalldata(quoteAmount); - this.setBaseAmountCalldata(impliedBaseAmount); + this.setInputAmountCalldata(inputAmount); - // Set the quote and base amounts on the response - this.quote_amount = quoteAmount; - this.base_amount = impliedBaseAmount; + // Set the input amount on the response + this.input_amount = inputAmount; return this.receiveAmount(); } /** - * Set the calldata to use a given quote amount - */ - private setQuoteAmountCalldata(quoteAmount: bigint) { - const calldataBytes = hexToBytes(this.match_bundle.settlement_tx.data as `0x${string}`); - - // Padded to 32 bytes - const quoteAmountBytes = numberToBytes(quoteAmount, { size: AMOUNT_CALLDATA_LENGTH }); - - const prefix = calldataBytes.slice(0, QUOTE_AMOUNT_OFFSET); - const suffix = calldataBytes.slice( - QUOTE_AMOUNT_OFFSET + AMOUNT_CALLDATA_LENGTH, - calldataBytes.length, - ); - - // Set the calldata and the tx value - const newCalldataBytes = concatBytes([prefix, quoteAmountBytes, suffix]); - const newCalladata = bytesToHex(newCalldataBytes); - - const newMatchBundle = { - ...this.match_bundle, - settlement_tx: { - ...this.match_bundle.settlement_tx, - data: newCalladata, - }, - }; - - this.match_bundle = newMatchBundle; - } - - /** - * Get the bounds on the base amount + * Get the bounds on the input amount * * Returns an array [min, max] inclusive */ - public baseBounds(): [bigint, bigint] { + public inputBounds(): [bigint, bigint] { return [ - this.match_bundle.match_result.min_base_amount, - this.match_bundle.match_result.max_base_amount, + this.match_bundle.match_result.min_input_amount, + this.match_bundle.match_result.max_input_amount, ]; } /** - * Get the bounds on the quote amount + * Get the bounds on the output amount * * Returns an array [min, max] inclusive */ - public quoteBounds(): [bigint, bigint] { - const [minBase, maxBase] = this.baseBounds(); + public outputBounds(): [bigint, bigint] { + const [minInput, maxInput] = this.inputBounds(); const price = this.getPriceFp(); - const minQuote = price.floorMulInt(minBase); - const maxQuote = price.floorMulInt(maxBase); + const minOutput = price.floorMulInt(minInput); + const maxOutput = price.floorMulInt(maxInput); - return [minQuote, maxQuote]; + return [minOutput, maxOutput]; } /** - * Get the bounds on the quote amount for a given base amount. - * - * For an explanation of these bounds, see: - * https://github.com/renegade-fi/renegade-contracts/blob/main/contracts-common/src/types/match.rs#L144-L174 + * Get the receive amount at the currently set input amount */ - public quoteBoundsForBase(baseAmount: bigint): [bigint, bigint] { - const [minQuote, maxQuote] = this.quoteBounds(); - - const price = this.getPriceFp(); - const refQuote = price.floorMulInt(baseAmount); - - const direction = this.match_bundle.match_result.direction; - - const resolvedMinQuote = direction === OrderSide.BUY ? refQuote : minQuote; - const resolvedMaxQuote = direction === OrderSide.BUY ? maxQuote : refQuote; - - return [resolvedMinQuote, resolvedMaxQuote]; + public receiveAmount(): bigint { + return this.computeReceiveAmount(this.currentInputAmount()); } /** - * Get the receive amount at the currently set base amount + * Get the receive amount at the given input amount */ - public receiveAmount() { - return this.computeReceiveAmount(this.currentBaseAmount()); + public receiveAmountAtInput(inputAmount: bigint): bigint { + return this.computeReceiveAmount(inputAmount); } /** - * Get the receive amount at the given base amount + * Get the send amount at the currently set input amount */ - public receiveAmountAtBase(baseAmount: bigint) { - return this.computeReceiveAmount(baseAmount); + public sendAmount(): bigint { + return this.currentInputAmount(); } /** - * Get the receive amount at the given quote amount + * Return whether the trade is a native ETH sell (input token is native ETH) */ - public receiveAmountAtQuote(quoteAmount: bigint) { - const baseAmount = this.baseAmount(quoteAmount); - return this.computeReceiveAmount(baseAmount); + public isNativeEthSell(): boolean { + return ( + this.match_bundle.match_result.input_mint.toLowerCase() === + NATIVE_ASSET_ADDR.toLowerCase() + ); } /** - * Get the send amount at the currently set base amount + * Set the calldata to use a given input amount */ - public sendAmount() { - return this.computeSendAmount(this.currentBaseAmount()); - } + private setInputAmountCalldata(inputAmount: bigint) { + const calldataBytes = hexToBytes(this.match_bundle.settlement_tx.data as `0x${string}`); - /** - * Get the send amount at the given base amount - */ - public sendAmountAtBase(baseAmount: bigint) { - return this.computeSendAmount(baseAmount); - } + // Padded to 32 bytes + const inputAmountBytes = numberToBytes(inputAmount, { size: AMOUNT_CALLDATA_LENGTH }); - /** - * Get the send amount at the given quote amount - */ - public sendAmountAtQuote(quoteAmount: bigint) { - const baseAmount = this.baseAmount(quoteAmount); - return this.computeSendAmount(baseAmount); - } + const prefix = calldataBytes.slice(0, INPUT_AMOUNT_OFFSET); + const suffix = calldataBytes.slice( + INPUT_AMOUNT_OFFSET + AMOUNT_CALLDATA_LENGTH, + calldataBytes.length, + ); - /** - * Return whether the trade is a native ETH sell - */ - public isNativeEthSell(): boolean { - const matchRes = this.match_bundle.match_result; - const isSell = matchRes.direction === OrderSide.SELL; - const isBaseEth = matchRes.base_mint.toLowerCase() === NATIVE_ASSET_ADDR.toLowerCase(); + const newCalldataBytes = concatBytes([prefix, inputAmountBytes, suffix]); + const newCalldata = bytesToHex(newCalldataBytes); - return isBaseEth && isSell; + // If the trade is a native ETH sell, set the tx value to the input amount + const value = this.isNativeEthSell() ? inputAmount : 0n; + const valueHex = numberToHex(value); + + this.match_bundle = { + ...this.match_bundle, + settlement_tx: { + ...this.match_bundle.settlement_tx, + data: newCalldata, + value: valueHex, + }, + }; } /** - * Check a base amount is in the valid range + * Check an input amount is in the valid range */ - private checkBaseAmount(baseAmount: bigint) { - const [min, max] = this.baseBounds(); + private checkInputAmount(inputAmount: bigint) { + const [min, max] = this.inputBounds(); - if (baseAmount < min || baseAmount > max) { - throw new Error(`Base amount ${baseAmount} is not in the valid range ${min} - ${max}`); + if (inputAmount < min || inputAmount > max) { + throw new Error( + `Input amount ${inputAmount} is not in the valid range ${min} - ${max}`, + ); } } /** - * Check a quote amount is in the valid range for a given base amount. - * - * This is true if the quote amount is within the bounds implied by the min - * and max base amounts given the price in the match results, and the - * quote amount does not imply a price improvement over the price in - * the match result. + * Get the output amount at the given input amount */ - private checkQuoteAmount(quoteAmount: bigint, baseAmount: bigint) { - const [min, max] = this.quoteBoundsForBase(baseAmount); - - if (quoteAmount < min || quoteAmount > max) { - throw new Error( - `Quote amount ${quoteAmount} is not in the valid range ${min} - ${max}`, - ); - } + private outputAmount(inputAmount: bigint): bigint { + return this.getPriceFp().floorMulInt(inputAmount); } /** - * Get the current receive amount at the given base amount - * - * This is net of fees + * Compute the receive amount (output net of fees) at the given input amount */ - private computeReceiveAmount(baseAmount: bigint) { - const matchRes = this.match_bundle.match_result; - let preSponsoredAmount = - matchRes.direction === OrderSide.BUY ? baseAmount : this.quoteAmount(baseAmount); + private computeReceiveAmount(inputAmount: bigint): bigint { + let preSponsoredAmount = this.outputAmount(inputAmount); // Account for fees const totalFee = @@ -383,89 +260,48 @@ export class MalleableExternalMatchResponse { } /** - * Get the current send amount at the given base amount + * Get the current input amount */ - private computeSendAmount(baseAmount: bigint) { - const matchRes = this.match_bundle.match_result; - if (matchRes.direction === OrderSide.BUY) { - return this.quoteAmount(baseAmount); + private currentInputAmount(): bigint { + if (this.input_amount != null) { + return this.input_amount; } - return baseAmount; - } - - /** - * Get the base amount at the given quote amount - */ - private baseAmount(quoteAmount: bigint) { - const price = this.getPriceFp(); - return FixedPoint.ceilDivInt(quoteAmount, price); - } - - /** - * Get the quote amount at the given base amount - */ - private quoteAmount(baseAmount: bigint) { - const price = this.getPriceFp(); - return price.floorMulInt(baseAmount); + return this.match_bundle.match_result.max_input_amount; } - /** - * Get the current base amount - */ - private currentBaseAmount() { - if (!this.base_amount) { - return this.match_bundle.match_result.max_base_amount; - } - return this.base_amount; - } - - private getPriceFp() { + private getPriceFp(): FixedPoint { return new FixedPoint(BigInt(this.match_bundle.match_result.price_fp)); } } /** - * A bounded match result + * A bounded match result (v2, input/output terminology) */ -interface ApiBoundedMatchResult { - /** - * The mint of the quote token in the matched asset pair - */ - quote_mint: string; - /** - * The mint of the base token in the matched asset pair - */ - base_mint: string; - /** - * The price at which the match executes - */ +interface ApiBoundedMatchResultV2 { + /** The mint of the input token */ + input_mint: string; + /** The mint of the output token */ + output_mint: string; + /** The price at which the match executes (output per input, fixed point) */ price_fp: string; - /** - * The minimum base amount of the match - */ - min_base_amount: bigint; - /** - * The maximum base amount of the match - */ - max_base_amount: bigint; - /** - * The direction of the match - */ - direction: OrderSide; + /** The minimum input amount of the match */ + min_input_amount: bigint; + /** The maximum input amount of the match */ + max_input_amount: bigint; } /** - * An atomic match settlement bundle using a malleable match result + * An atomic match settlement bundle using a malleable match result (v2) * - * A malleable match result is one in which the exact `base_amount` swapped + * A malleable match result is one in which the exact `input_amount` swapped * is not known at the time the proof is generated, and may be changed up until * it is submitted on-chain. Instead, a bounded match result gives a - * `min_base_amount` and a `max_base_amount`, between which the `base_amount` - * may take any value + * `min_input_amount` and a `max_input_amount`, between which the + * `input_amount` may take any value */ interface MalleableAtomicMatchApiBundle { /** The match result */ - match_result: ApiBoundedMatchResult; + match_result: ApiBoundedMatchResultV2; /** The fees owed by the external party */ fee_rates: FeeTakeRate; /** The maximum amount that the external party will receive */ @@ -479,5 +315,5 @@ interface MalleableAtomicMatchApiBundle { /** The transaction which settles the match on-chain */ settlement_tx: SettlementTransaction; /** The deadline of the match */ - deadline: bigint; + deadline: number; } diff --git a/packages/external-match/src/types/v2Types.ts b/packages/external-match/src/types/v2Types.ts new file mode 100644 index 00000000..89674aef --- /dev/null +++ b/packages/external-match/src/types/v2Types.ts @@ -0,0 +1,392 @@ +/** + * Internal v2 API types and serialization/deserialization helpers. + * + * These types define the wire format for v2 API communication. + * They are NOT exported from the package entry point. + */ + +import type { + ApiExternalAssetTransfer, + ApiToken, + FeeTake, + FeeTakeRate, + GasSponsorshipInfo, + SettlementTransaction, +} from "./index.js"; + +// --- Order --- + +export interface ExternalOrderV2 { + input_mint: string; + output_mint: string; + input_amount: bigint; + output_amount: bigint; + use_exact_output_amount: boolean; + min_fill_size: bigint; +} + +// --- Quote response types --- + +export interface ApiTimestampedPriceFp { + price: string; + timestamp: number; +} + +export interface ApiExternalMatchResultV2 { + input_mint: string; + output_mint: string; + input_amount: bigint; + output_amount: bigint; + price_fp: ApiTimestampedPriceFp; +} + +export interface ApiExternalQuoteV2 { + order: ExternalOrderV2; + match_result: ApiExternalMatchResultV2; + fees: FeeTake; + send: ApiExternalAssetTransfer; + receive: ApiExternalAssetTransfer; + price: { price: string; timestamp: number }; + timestamp: number; +} + +export interface ApiSignedQuoteV2 { + quote: ApiExternalQuoteV2; + signature: string; + deadline: number; +} + +export interface ExternalQuoteResponseV2 { + signed_quote: ApiSignedQuoteV2; + gas_sponsorship_info?: GasSponsorshipInfo; +} + +// --- Assemble request --- + +export type AssemblyType = + | { + type: "quoted-order"; + signed_quote: ApiSignedQuoteV2; + updated_order?: ExternalOrderV2 | null; + } + | { type: "direct-order"; external_order: ExternalOrderV2 }; + +export interface AssembleExternalMatchRequestV2 { + do_gas_estimation: boolean; + receiver_address?: string | null; + order: AssemblyType; +} + +// --- Match response (always malleable from v2 API) --- + +export interface ApiBoundedMatchResultV2 { + input_mint: string; + output_mint: string; + price_fp: string; + min_input_amount: bigint; + max_input_amount: bigint; +} + +export interface MalleableAtomicMatchApiBundleV2 { + match_result: ApiBoundedMatchResultV2; + fee_rates: FeeTakeRate; + max_receive: ApiExternalAssetTransfer; + min_receive: ApiExternalAssetTransfer; + max_send: ApiExternalAssetTransfer; + min_send: ApiExternalAssetTransfer; + settlement_tx: SettlementTransaction; + deadline: number; +} + +export interface ExternalMatchResponseV2 { + match_bundle: MalleableAtomicMatchApiBundleV2; + input_amount?: bigint | null; + gas_sponsorship_info?: GasSponsorshipInfo | null; +} + +// --- Market types (public) --- + +export interface MarketInfo { + base: ApiToken; + quote: ApiToken; + price: { price: string; timestamp: number }; + internal_match_fee_rates: FeeTakeRate; + external_match_fee_rates: FeeTakeRate; +} + +export interface DepthSide { + total_quantity: bigint; + total_quantity_usd: number; +} + +export interface MarketDepth { + market: MarketInfo; + buy: DepthSide; + sell: DepthSide; +} + +export interface GetMarketsResponse { + markets: MarketInfo[]; +} + +export interface GetMarketDepthByMintResponse { + market_depth: MarketDepth; +} + +export interface GetMarketDepthsResponse { + market_depths: MarketDepth[]; +} + +// --- Serialization helpers (BigInt → string for v2 wire format) --- + +function serializeOrderV2(order: ExternalOrderV2): Record { + return { + input_mint: order.input_mint, + output_mint: order.output_mint, + input_amount: order.input_amount.toString(), + output_amount: order.output_amount.toString(), + use_exact_output_amount: order.use_exact_output_amount, + min_fill_size: order.min_fill_size.toString(), + }; +} + +function serializeMatchResultV2(mr: ApiExternalMatchResultV2): Record { + return { + input_mint: mr.input_mint, + output_mint: mr.output_mint, + input_amount: mr.input_amount.toString(), + output_amount: mr.output_amount.toString(), + price_fp: mr.price_fp, + }; +} + +function serializeFeeTake(fees: FeeTake): Record { + return { + relayer_fee: fees.relayer_fee.toString(), + protocol_fee: fees.protocol_fee.toString(), + }; +} + +function serializeAssetTransfer(t: ApiExternalAssetTransfer): Record { + return { + mint: t.mint, + amount: t.amount.toString(), + }; +} + +function serializeQuoteV2(quote: ApiExternalQuoteV2): Record { + return { + order: serializeOrderV2(quote.order), + match_result: serializeMatchResultV2(quote.match_result), + fees: serializeFeeTake(quote.fees), + send: serializeAssetTransfer(quote.send), + receive: serializeAssetTransfer(quote.receive), + price: quote.price, + timestamp: quote.timestamp, + }; +} + +function serializeApiSignedQuoteV2(sq: ApiSignedQuoteV2): Record { + return { + quote: serializeQuoteV2(sq.quote), + signature: sq.signature, + deadline: sq.deadline, + }; +} + +export function serializeQuoteRequestV2(req: { + external_order: ExternalOrderV2; +}): Record { + return { + external_order: serializeOrderV2(req.external_order), + }; +} + +export function serializeAssembleRequestV2( + req: AssembleExternalMatchRequestV2, +): Record { + let order: Record; + if (req.order.type === "quoted-order") { + order = { + type: "quoted-order", + signed_quote: serializeApiSignedQuoteV2(req.order.signed_quote), + updated_order: req.order.updated_order + ? serializeOrderV2(req.order.updated_order) + : undefined, + }; + } else { + order = { + type: "direct-order", + external_order: serializeOrderV2(req.order.external_order), + }; + } + + return { + do_gas_estimation: req.do_gas_estimation, + receiver_address: req.receiver_address ?? undefined, + order, + }; +} + +// --- Deserialization helpers (string → BigInt from API JSON) --- + +function deserializeOrderV2(data: any): ExternalOrderV2 { + return { + input_mint: data.input_mint, + output_mint: data.output_mint, + input_amount: BigInt(data.input_amount), + output_amount: BigInt(data.output_amount), + use_exact_output_amount: data.use_exact_output_amount, + min_fill_size: BigInt(data.min_fill_size), + }; +} + +function deserializeMatchResultV2(data: any): ApiExternalMatchResultV2 { + return { + input_mint: data.input_mint, + output_mint: data.output_mint, + input_amount: BigInt(data.input_amount), + output_amount: BigInt(data.output_amount), + price_fp: { + price: data.price_fp.price, + timestamp: Number(data.price_fp.timestamp), + }, + }; +} + +function deserializeFeeTake(data: any): FeeTake { + return { + relayer_fee: BigInt(data.relayer_fee), + protocol_fee: BigInt(data.protocol_fee), + }; +} + +function deserializeAssetTransfer(data: any): ApiExternalAssetTransfer { + return { + mint: data.mint, + amount: BigInt(data.amount), + }; +} + +function deserializeGasSponsorshipInfo(data: any): GasSponsorshipInfo | undefined { + if (!data) return undefined; + return { + refund_amount: BigInt(data.refund_amount), + refund_native_eth: data.refund_native_eth, + refund_address: data.refund_address, + }; +} + +export function deserializeQuoteResponseV2(data: any): ExternalQuoteResponseV2 { + const sq = data.signed_quote; + const q = sq.quote; + return { + signed_quote: { + quote: { + order: deserializeOrderV2(q.order), + match_result: deserializeMatchResultV2(q.match_result), + fees: deserializeFeeTake(q.fees), + send: deserializeAssetTransfer(q.send), + receive: deserializeAssetTransfer(q.receive), + price: q.price, + timestamp: Number(q.timestamp), + }, + signature: sq.signature, + deadline: Number(sq.deadline), + }, + gas_sponsorship_info: deserializeGasSponsorshipInfo(data.gas_sponsorship_info), + }; +} + +function deserializeFeeTakeRate(data: any): FeeTakeRate { + return { + relayer_fee_rate: data.relayer_fee_rate, + protocol_fee_rate: data.protocol_fee_rate, + }; +} + +function deserializeBoundedMatchResultV2(data: any): ApiBoundedMatchResultV2 { + return { + input_mint: data.input_mint, + output_mint: data.output_mint, + price_fp: data.price_fp, + min_input_amount: BigInt(data.min_input_amount), + max_input_amount: BigInt(data.max_input_amount), + }; +} + +/** + * Normalize an Alloy TransactionRequest into our SettlementTransaction format. + * + * Alloy uses `input` for calldata; our v1 type uses `data`. + */ +function deserializeSettlementTx(raw: any): SettlementTransaction { + return { + tx_type: raw.type ?? raw.tx_type ?? "0x0", + to: raw.to, + data: raw.input ?? raw.data, + value: raw.value ?? "0x0", + gas: raw.gas, + }; +} + +export function deserializeMatchResponseV2(data: any): ExternalMatchResponseV2 { + const mb = data.match_bundle; + return { + match_bundle: { + match_result: deserializeBoundedMatchResultV2(mb.match_result), + fee_rates: deserializeFeeTakeRate(mb.fee_rates), + max_receive: deserializeAssetTransfer(mb.max_receive), + min_receive: deserializeAssetTransfer(mb.min_receive), + max_send: deserializeAssetTransfer(mb.max_send), + min_send: deserializeAssetTransfer(mb.min_send), + settlement_tx: deserializeSettlementTx(mb.settlement_tx), + deadline: Number(mb.deadline), + }, + input_amount: data.input_amount != null ? BigInt(data.input_amount) : undefined, + gas_sponsorship_info: deserializeGasSponsorshipInfo(data.gas_sponsorship_info), + }; +} + +function deserializeMarketInfo(data: any): MarketInfo { + return { + base: data.base, + quote: data.quote, + price: data.price, + internal_match_fee_rates: deserializeFeeTakeRate(data.internal_match_fee_rates), + external_match_fee_rates: deserializeFeeTakeRate(data.external_match_fee_rates), + }; +} + +function deserializeDepthSide(data: any): DepthSide { + return { + total_quantity: BigInt(data.total_quantity), + total_quantity_usd: Number(data.total_quantity_usd), + }; +} + +function deserializeMarketDepth(data: any): MarketDepth { + return { + market: deserializeMarketInfo(data.market), + buy: deserializeDepthSide(data.buy), + sell: deserializeDepthSide(data.sell), + }; +} + +export function deserializeMarketsResponse(data: any): GetMarketsResponse { + return { + markets: data.markets.map(deserializeMarketInfo), + }; +} + +export function deserializeMarketDepthResponse(data: any): GetMarketDepthByMintResponse { + return { + market_depth: deserializeMarketDepth(data.market_depth), + }; +} + +export function deserializeMarketDepthsResponse(data: any): GetMarketDepthsResponse { + return { + market_depths: data.market_depths.map(deserializeMarketDepth), + }; +} diff --git a/packages/external-match/src/v1Conversions.ts b/packages/external-match/src/v1Conversions.ts new file mode 100644 index 00000000..e07ad215 --- /dev/null +++ b/packages/external-match/src/v1Conversions.ts @@ -0,0 +1,372 @@ +/** + * Conversion functions between v1 and v2 external match types. + * + * Ported from Rust SDK's v1_conversions.rs. + */ + +import { hexToBytes } from "viem/utils"; +import { FixedPoint } from "./types/fixedPoint.js"; +import { + type ApiExternalAssetTransfer, + type ApiExternalMatchResult, + type ApiExternalQuote, + type ApiTimestampedPrice, + type DepthSideInfo, + ExternalMatchResponse, + type ExternalOrder, + type FeeTake, + type GasSponsorshipInfo, + GetDepthForAllPairsResponse, + OrderBookDepth, + OrderSide, + SignedExternalQuote, + type SupportedTokensResponse, + type TokenPricesResponse, +} from "./types/index.js"; +import type { + ApiSignedQuoteV2, + ExternalMatchResponseV2, + ExternalOrderV2, + ExternalQuoteResponseV2, + GetMarketDepthByMintResponse, + GetMarketDepthsResponse, + GetMarketsResponse, + MarketDepth, +} from "./types/v2Types.js"; + +/** The offset of the input amount in the calldata, after the 4-byte function selector */ +const INPUT_AMOUNT_OFFSET = 4; +/** The length of an amount in calldata (32 bytes for a `uint256`) */ +const AMOUNT_CALLDATA_LENGTH = 32; + +// ------------------------- +// | ExternalOrder v1 → v2 | +// ------------------------- + +export function v1OrderToV2(order: ExternalOrder): ExternalOrderV2 { + if (order.side === OrderSide.BUY) { + // Buy: input=quote, output=base + let input_amount = 0n; + let output_amount = 0n; + let use_exact_output_amount = false; + + if (order.quote_amount != null && order.quote_amount !== 0n) { + input_amount = order.quote_amount; + } else if (order.base_amount != null && order.base_amount !== 0n) { + output_amount = order.base_amount; + } else if (order.exact_base_output != null && order.exact_base_output !== 0n) { + output_amount = order.exact_base_output; + use_exact_output_amount = true; + } else if (order.exact_quote_output != null && order.exact_quote_output !== 0n) { + input_amount = order.exact_quote_output; + use_exact_output_amount = true; + } + + return { + input_mint: order.quote_mint, + output_mint: order.base_mint, + input_amount, + output_amount, + use_exact_output_amount, + min_fill_size: order.min_fill_size ?? 0n, + }; + } + + // Sell: input=base, output=quote + let input_amount = 0n; + let output_amount = 0n; + let use_exact_output_amount = false; + + if (order.base_amount != null && order.base_amount !== 0n) { + input_amount = order.base_amount; + } else if (order.quote_amount != null && order.quote_amount !== 0n) { + output_amount = order.quote_amount; + } else if (order.exact_quote_output != null && order.exact_quote_output !== 0n) { + output_amount = order.exact_quote_output; + use_exact_output_amount = true; + } else if (order.exact_base_output != null && order.exact_base_output !== 0n) { + input_amount = order.exact_base_output; + use_exact_output_amount = true; + } + + return { + input_mint: order.base_mint, + output_mint: order.quote_mint, + input_amount, + output_amount, + use_exact_output_amount, + min_fill_size: order.min_fill_size ?? 0n, + }; +} + +// -------------------------------------------------- +// | SignedExternalQuoteV2 → v1 SignedExternalQuote | +// -------------------------------------------------- + +function invertPriceString(priceStr: string): string { + const price = Number.parseFloat(priceStr); + if (Number.isNaN(price) || price === 0) { + return "0"; + } + return (1.0 / price).toString(); +} + +export function v2QuoteToV1( + v2Response: ExternalQuoteResponseV2, + originalOrder: ExternalOrder, +): SignedExternalQuote { + const v2Quote = v2Response.signed_quote.quote; + const direction = originalOrder.side; + + // Map v2 input/output to v1 base/quote based on direction + let quote_mint: string; + let base_mint: string; + let quote_amount: bigint; + let base_amount: bigint; + + if (direction === OrderSide.BUY) { + // Buy: input=quote, output=base + quote_mint = v2Quote.match_result.input_mint; + base_mint = v2Quote.match_result.output_mint; + quote_amount = v2Quote.match_result.input_amount; + base_amount = v2Quote.match_result.output_amount; + } else { + // Sell: input=base, output=quote + quote_mint = v2Quote.match_result.output_mint; + base_mint = v2Quote.match_result.input_mint; + quote_amount = v2Quote.match_result.output_amount; + base_amount = v2Quote.match_result.input_amount; + } + + const v1MatchResult: ApiExternalMatchResult = { + quote_mint, + base_mint, + quote_amount, + base_amount, + direction, + }; + + // Convert the price from v2's output/input to v1's quote/base + // For Sell: output=quote, input=base → output/input = quote/base ✓ + // For Buy: output=base, input=quote → invert to get quote/base + const v1Price: ApiTimestampedPrice = + direction === OrderSide.BUY + ? { + price: invertPriceString(v2Quote.price.price), + timestamp: BigInt(v2Quote.price.timestamp), + } + : { price: v2Quote.price.price, timestamp: BigInt(v2Quote.price.timestamp) }; + + const v1Quote: ApiExternalQuote = { + order: originalOrder, + match_result: v1MatchResult, + fees: v2Quote.fees, + send: v2Quote.send, + receive: v2Quote.receive, + price: v1Price, + timestamp: BigInt(v2Quote.timestamp), + }; + + // Build v1 gas sponsorship info (no signature in v2) + const gasSponsorshipInfo = v2Response.gas_sponsorship_info + ? { gas_sponsorship_info: v2Response.gas_sponsorship_info } + : undefined; + + // Store the original v2 ApiSignedQuote for round-tripping + const innerV2Quote: ApiSignedQuoteV2 = v2Response.signed_quote; + + return new SignedExternalQuote( + v1Quote, + v2Response.signed_quote.signature, + BigInt(v2Response.signed_quote.deadline), + gasSponsorshipInfo, + innerV2Quote, + ); +} + +// -------------------------------------------------- +// | v1 SignedExternalQuote → v2 for round-tripping | +// -------------------------------------------------- + +export function v1QuoteToV2(v1Quote: SignedExternalQuote): ApiSignedQuoteV2 { + if (!v1Quote._innerV2Quote) { + throw new Error( + "Cannot convert v1 quote to v2: missing inner v2 quote. " + + "This quote was not created from a v2 response.", + ); + } + return v1Quote._innerV2Quote; +} + +// ---------------------------------------------- +// | ExternalMatchResponseV2 → v1 Non-Malleable | +// ---------------------------------------------- + +function decodeInputAmountFromCalldata(settlementTxData: string): bigint { + const data = hexToBytes(settlementTxData as `0x${string}`); + const end = INPUT_AMOUNT_OFFSET + AMOUNT_CALLDATA_LENGTH; + + if (data.length < end) { + throw new Error("Invalid calldata: too short to decode input amount"); + } + + const inputSlice = data.slice(INPUT_AMOUNT_OFFSET, end); + + // Decode big-endian uint256 → bigint + let value = 0n; + for (const byte of inputSlice) { + value = (value << 8n) | BigInt(byte); + } + return value; +} + +export function v2ResponseToV1NonMalleable( + v2Resp: ExternalMatchResponseV2, + direction: OrderSide, +): ExternalMatchResponse { + const matchResult = v2Resp.match_bundle.match_result; + const priceFp = new FixedPoint(BigInt(matchResult.price_fp)); + + // Decode input amount from calldata + const inputAmount = decodeInputAmountFromCalldata(v2Resp.match_bundle.settlement_tx.data); + const outputAmount = priceFp.floorMulInt(inputAmount); + + // Map v2 input/output to v1 base/quote based on direction + let quote_mint: string; + let base_mint: string; + let quote_amount: bigint; + let base_amount: bigint; + + if (direction === OrderSide.BUY) { + // Buy: input=quote, output=base + quote_mint = matchResult.input_mint; + base_mint = matchResult.output_mint; + quote_amount = inputAmount; + base_amount = outputAmount; + } else { + // Sell: input=base, output=quote + quote_mint = matchResult.output_mint; + base_mint = matchResult.input_mint; + quote_amount = outputAmount; + base_amount = inputAmount; + } + + const v1MatchResult: ApiExternalMatchResult = { + quote_mint, + base_mint, + quote_amount, + base_amount, + direction, + }; + + // Compute fees from fee_rates and output amount + const relayerFeeRate = new FixedPoint(BigInt(v2Resp.match_bundle.fee_rates.relayer_fee_rate)); + const protocolFeeRate = new FixedPoint(BigInt(v2Resp.match_bundle.fee_rates.protocol_fee_rate)); + const totalFeeRate = new FixedPoint(relayerFeeRate.value + protocolFeeRate.value); + + const totalFee = totalFeeRate.floorMulInt(outputAmount); + const relayerFee = relayerFeeRate.floorMulInt(outputAmount); + const protocolFee = totalFee - relayerFee; + + const fees: FeeTake = { relayer_fee: relayerFee, protocol_fee: protocolFee }; + + // Compute receive/send from direction + // The receive token is the output token; fees are subtracted from receive + let receive: ApiExternalAssetTransfer; + let send: ApiExternalAssetTransfer; + + if (direction === OrderSide.BUY) { + receive = { mint: base_mint, amount: base_amount - totalFee }; + send = { mint: quote_mint, amount: quote_amount }; + } else { + receive = { mint: quote_mint, amount: quote_amount - totalFee }; + send = { mint: base_mint, amount: base_amount }; + } + + const gasSponsored = v2Resp.gas_sponsorship_info != null; + const gasSponsorshipInfo: GasSponsorshipInfo | undefined = + v2Resp.gas_sponsorship_info ?? undefined; + + return new ExternalMatchResponse( + { + match_result: v1MatchResult, + fees, + receive, + send, + settlement_tx: v2Resp.match_bundle.settlement_tx, + deadline: BigInt(v2Resp.match_bundle.deadline), + }, + gasSponsored, + gasSponsorshipInfo, + ); +} + +// ------------------------------- +// | Market response conversions | +// ------------------------------- + +export function marketsToSupportedTokens(resp: GetMarketsResponse): SupportedTokensResponse { + const seen = new Set(); + const tokens: { address: string; symbol: string }[] = []; + + for (const market of resp.markets) { + if (!seen.has(market.base.address)) { + seen.add(market.base.address); + tokens.push(market.base); + } + if (!seen.has(market.quote.address)) { + seen.add(market.quote.address); + tokens.push(market.quote); + } + } + + return { tokens }; +} + +export function marketsToTokenPrices(resp: GetMarketsResponse): TokenPricesResponse { + const token_prices = resp.markets.map((m) => ({ + base_token: m.base.address, + quote_token: m.quote.address, + price: Number.parseFloat(m.price.price), + })); + return { token_prices }; +} + +function marketDepthToV1Single(depth: MarketDepth): OrderBookDepth { + const price = Number.parseFloat(depth.market.price.price); + const relayerFeeRate = new FixedPoint( + BigInt(depth.market.external_match_fee_rates.relayer_fee_rate), + ); + const protocolFeeRate = new FixedPoint( + BigInt(depth.market.external_match_fee_rates.protocol_fee_rate), + ); + + const buy: DepthSideInfo = { + total_quantity: depth.buy.total_quantity, + total_quantity_usd: depth.buy.total_quantity_usd, + }; + const sell: DepthSideInfo = { + total_quantity: depth.sell.total_quantity, + total_quantity_usd: depth.sell.total_quantity_usd, + }; + + return new OrderBookDepth( + depth.market.base.address, + price, + depth.market.price.timestamp, + buy, + sell, + { + relayer_fee_rate: relayerFeeRate.toF64(), + protocol_fee_rate: protocolFeeRate.toF64(), + }, + ); +} + +export function marketDepthToV1(resp: GetMarketDepthByMintResponse): OrderBookDepth { + return marketDepthToV1Single(resp.market_depth); +} + +export function marketDepthsToV1(resp: GetMarketDepthsResponse): GetDepthForAllPairsResponse { + return new GetDepthForAllPairsResponse(resp.market_depths.map(marketDepthToV1Single)); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc4d902f..8e3f1e36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -223,8 +223,8 @@ importers: examples/external-match/base-sepolia: dependencies: '@renegade-fi/renegade-sdk': - specifier: latest - version: 0.1.12(typescript@5.5.4)(zod@4.0.5) + specifier: workspace:* + version: link:../../../packages/external-match viem: specifier: latest version: 2.37.4(typescript@5.5.4)(zod@4.0.5) @@ -239,8 +239,8 @@ importers: examples/external-match/basic: dependencies: '@renegade-fi/renegade-sdk': - specifier: latest - version: 0.1.17(typescript@5.5.4)(zod@4.0.5) + specifier: workspace:* + version: link:../../../packages/external-match viem: specifier: latest version: 2.37.4(typescript@5.5.4)(zod@4.0.5) @@ -255,8 +255,8 @@ importers: examples/external-match/direct-malleable-match: dependencies: '@renegade-fi/renegade-sdk': - specifier: latest - version: 0.1.16(typescript@5.9.3)(zod@4.0.5) + specifier: workspace:* + version: link:../../../packages/external-match viem: specifier: latest version: 2.39.0(typescript@5.9.3)(zod@4.0.5) @@ -271,8 +271,8 @@ importers: examples/external-match/direct-match: dependencies: '@renegade-fi/renegade-sdk': - specifier: latest - version: 0.1.12(typescript@5.5.4)(zod@4.0.5) + specifier: workspace:* + version: link:../../../packages/external-match viem: specifier: latest version: 2.39.0(typescript@5.5.4)(zod@4.0.5) @@ -284,11 +284,27 @@ importers: specifier: ^5 version: 5.5.4 - examples/external-match/exchange-metadata: + examples/external-match/exact-output: dependencies: '@renegade-fi/renegade-sdk': + specifier: workspace:* + version: link:../../../packages/external-match + viem: specifier: latest - version: 0.1.17(typescript@5.9.3)(zod@4.0.5) + version: 2.46.2(typescript@5.9.3)(zod@4.0.5) + devDependencies: + tsx: + specifier: ^4 + version: 4.20.6 + typescript: + specifier: ^5 + version: 5.9.3 + + examples/external-match/exchange-metadata: + dependencies: + '@renegade-fi/renegade-sdk': + specifier: workspace:* + version: link:../../../packages/external-match viem: specifier: latest version: 2.39.3(typescript@5.9.3)(zod@4.0.5) @@ -303,8 +319,8 @@ importers: examples/external-match/in-kind-gas-sponsorship: dependencies: '@renegade-fi/renegade-sdk': - specifier: latest - version: 0.1.12(typescript@5.5.4)(zod@4.0.5) + specifier: workspace:* + version: link:../../../packages/external-match viem: specifier: latest version: 2.37.4(typescript@5.5.4)(zod@4.0.5) @@ -318,9 +334,9 @@ importers: examples/external-match/malleable: dependencies: - '@renegade-fi/node': - specifier: latest - version: 0.6.18(@tanstack/query-core@5.52.0)(@types/react@18.3.4)(react@18.3.1)(viem@2.37.4(typescript@5.5.4)(zod@4.0.5))(ws@8.18.3) + '@renegade-fi/renegade-sdk': + specifier: workspace:* + version: link:../../../packages/external-match viem: specifier: latest version: 2.37.4(typescript@5.5.4)(zod@4.0.5) @@ -335,8 +351,8 @@ importers: examples/external-match/native-gas-sponshorship: dependencies: '@renegade-fi/renegade-sdk': - specifier: latest - version: 0.1.12(typescript@5.5.4)(zod@4.0.5) + specifier: workspace:* + version: link:../../../packages/external-match viem: specifier: latest version: 2.37.4(typescript@5.5.4)(zod@4.0.5) @@ -351,8 +367,8 @@ importers: examples/external-match/order-book-depth: dependencies: '@renegade-fi/renegade-sdk': - specifier: latest - version: 0.1.17(typescript@5.5.4)(zod@4.0.5) + specifier: workspace:* + version: link:../../../packages/external-match viem: specifier: latest version: 2.37.4(typescript@5.5.4)(zod@4.0.5) @@ -367,8 +383,8 @@ importers: examples/external-match/order-book-depth-all-pairs: dependencies: '@renegade-fi/renegade-sdk': - specifier: latest - version: 0.1.17(typescript@5.9.3)(zod@4.0.5) + specifier: workspace:* + version: link:../../../packages/external-match viem: specifier: latest version: 2.39.3(typescript@5.9.3)(zod@4.0.5) @@ -383,8 +399,8 @@ importers: examples/external-match/supported-tokens: dependencies: '@renegade-fi/renegade-sdk': - specifier: latest - version: 0.1.12(typescript@5.5.4)(zod@4.0.5) + specifier: workspace:* + version: link:../../../packages/external-match viem: specifier: latest version: 2.37.4(typescript@5.5.4)(zod@4.0.5) @@ -399,8 +415,8 @@ importers: examples/external-match/token-prices: dependencies: '@renegade-fi/renegade-sdk': - specifier: latest - version: 0.1.12(typescript@5.5.4)(zod@4.0.5) + specifier: workspace:* + version: link:../../../packages/external-match viem: specifier: latest version: 2.37.4(typescript@5.5.4)(zod@4.0.5) @@ -1064,15 +1080,6 @@ packages: peerDependencies: viem: 2.x - '@renegade-fi/renegade-sdk@0.1.12': - resolution: {integrity: sha512-I03XxqZoAdssin3FHBxdWgqcf9sSC7kT5LkzeSo4MrJInW2dATMxTuZNNanyqj29WYEPFQL5+g8J9AESoZq/DA==} - - '@renegade-fi/renegade-sdk@0.1.16': - resolution: {integrity: sha512-2d05V/p2csdEFqaWFc/4jS7z9wtmlvYGjvADHNQQMGDXfxvMuT60vrAJVYCjeCccezaWvESxIR5iJnEVJQYvsg==} - - '@renegade-fi/renegade-sdk@0.1.17': - resolution: {integrity: sha512-QS/9khcqBi4x7IsQyk9T0tR6/9IbuLsrhlZ6F0NTgo/WtyFUF73M81OHHjBVrnkdTmVOwb0m1OaQc+8JhJmsAA==} - '@renegade-fi/token@0.0.32': resolution: {integrity: sha512-ck1MEze8sSHFry82pUKTsgMuoqgSIx5ue0PxN0gdYIH/cLORz+1XLzr7k4WJXdITvazl0CBf/3+uGXJ/eAyT2w==} @@ -1161,6 +1168,17 @@ packages: zod: optional: true + abitype@1.2.3: + resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -1445,6 +1463,14 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + ox@0.12.4: + resolution: {integrity: sha512-+P+C7QzuwPV8lu79dOwjBKfB2CbnbEXe/hfyyrff1drrO1nOOj3Hc87svHfcW1yneRr3WXaKr6nz11nq+/DF9Q==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + ox@0.8.7: resolution: {integrity: sha512-W1f0FiMf9NZqtHPEDEAEkyzZDwbIKfmH2qmQx8NNiQ/9JhxrSblmtLJsSfTtQG5YKowLOnBlLVguCyxm/7ztxw==} peerDependencies: @@ -1702,6 +1728,14 @@ packages: typescript: optional: true + viem@2.46.2: + resolution: {integrity: sha512-w8Qv5Vyo7TfXcH3vgmxRa1NRvzJCDy2aSGSRsJn3503nC/qVbgEQ+n3aj/CkqWXbloudZh97h5o5aQrQSVGy0w==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + webauthn-p256@0.0.5: resolution: {integrity: sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg==} @@ -2123,50 +2157,6 @@ snapshots: - react - ws - '@renegade-fi/renegade-sdk@0.1.12(typescript@5.5.4)(zod@4.0.5)': - dependencies: - '@noble/hashes': 1.8.0 - json-bigint: 1.0.0 - viem: 2.39.0(typescript@5.5.4)(zod@4.0.5) - transitivePeerDependencies: - - bufferutil - - typescript - - utf-8-validate - - zod - - '@renegade-fi/renegade-sdk@0.1.16(typescript@5.9.3)(zod@4.0.5)': - dependencies: - '@noble/hashes': 1.8.0 - json-bigint: 1.0.0 - viem: 2.39.3(typescript@5.9.3)(zod@4.0.5) - transitivePeerDependencies: - - bufferutil - - typescript - - utf-8-validate - - zod - - '@renegade-fi/renegade-sdk@0.1.17(typescript@5.5.4)(zod@4.0.5)': - dependencies: - '@noble/hashes': 1.8.0 - json-bigint: 1.0.0 - viem: 2.39.3(typescript@5.5.4)(zod@4.0.5) - transitivePeerDependencies: - - bufferutil - - typescript - - utf-8-validate - - zod - - '@renegade-fi/renegade-sdk@0.1.17(typescript@5.9.3)(zod@4.0.5)': - dependencies: - '@noble/hashes': 1.8.0 - json-bigint: 1.0.0 - viem: 2.39.3(typescript@5.9.3)(zod@4.0.5) - transitivePeerDependencies: - - bufferutil - - typescript - - utf-8-validate - - zod - '@renegade-fi/token@0.0.32(@tanstack/query-core@5.52.0)(@types/react@18.3.4)(react@18.3.1)(viem@2.37.4(typescript@5.5.4)(zod@4.0.5))(ws@8.18.3)': dependencies: '@renegade-fi/core': 0.9.13(@tanstack/query-core@5.52.0)(@types/react@18.3.4)(react@18.3.1)(viem@2.37.4(typescript@5.5.4)(zod@4.0.5))(ws@8.18.3) @@ -2261,6 +2251,11 @@ snapshots: typescript: 5.9.3 zod: 4.0.5 + abitype@1.2.3(typescript@5.9.3)(zod@4.0.5): + optionalDependencies: + typescript: 5.9.3 + zod: 4.0.5 + ansi-colors@4.1.3: {} ansi-regex@5.0.1: {} @@ -2554,6 +2549,21 @@ snapshots: outdent@0.5.0: {} + ox@0.12.4(typescript@5.9.3)(zod@4.0.5): + dependencies: + '@adraffy/ens-normalize': 1.11.0 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.9.3)(zod@4.0.5) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - zod + ox@0.8.7(typescript@5.9.3)(zod@3.23.8): dependencies: '@adraffy/ens-normalize': 1.11.0 @@ -2858,32 +2868,32 @@ snapshots: - utf-8-validate - zod - viem@2.39.3(typescript@5.5.4)(zod@4.0.5): + viem@2.39.3(typescript@5.9.3)(zod@4.0.5): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.1.0(typescript@5.5.4)(zod@4.0.5) + abitype: 1.1.0(typescript@5.9.3)(zod@4.0.5) isows: 1.0.7(ws@8.18.3) - ox: 0.9.6(typescript@5.5.4)(zod@4.0.5) + ox: 0.9.6(typescript@5.9.3)(zod@4.0.5) ws: 8.18.3 optionalDependencies: - typescript: 5.5.4 + typescript: 5.9.3 transitivePeerDependencies: - bufferutil - utf-8-validate - zod - viem@2.39.3(typescript@5.9.3)(zod@4.0.5): + viem@2.46.2(typescript@5.9.3)(zod@4.0.5): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.1.0(typescript@5.9.3)(zod@4.0.5) + abitype: 1.2.3(typescript@5.9.3)(zod@4.0.5) isows: 1.0.7(ws@8.18.3) - ox: 0.9.6(typescript@5.9.3)(zod@4.0.5) + ox: 0.12.4(typescript@5.9.3)(zod@4.0.5) ws: 8.18.3 optionalDependencies: typescript: 5.9.3