Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/external-match/exact-output/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Exact External Match Output Example

```bash
bun run index.ts
```

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/renegade-fi/typescript-sdk/tree/main/examples/external-match/exact-output)
27 changes: 27 additions & 0 deletions examples/external-match/exact-output/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { arbitrumSepolia } from "viem/chains";

const chainId = arbitrumSepolia.id;
const privateKey = process.env.PKEY;
if (!privateKey) {
throw new Error("PKEY is not set");
}
const account = privateKeyToAccount(privateKey as `0x${string}`);
const owner = account.address;

const API_KEY = process.env.API_KEY;
const API_SECRET = process.env.API_SECRET;

const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http(),
});

const walletClient = createWalletClient({
account,
chain: arbitrumSepolia,
transport: http(),
});

export { account, chainId, publicClient, walletClient, API_KEY, API_SECRET, owner };
93 changes: 93 additions & 0 deletions examples/external-match/exact-output/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { ExternalMatchClient, OrderSide } from "@renegade-fi/renegade-sdk";
import { erc20Abi } from "viem";
import { API_KEY, API_SECRET, owner, publicClient, walletClient } from "./env";

if (!API_KEY) {
throw new Error("API_KEY is not set");
}

if (!API_SECRET) {
throw new Error("API_SECRET is not set");
}

const client = ExternalMatchClient.newArbitrumSepoliaClient(API_KEY, API_SECRET);

const WETH_ADDRESS = "0xc3414a7ef14aaaa9c4522dfc00a4e66e74e9c25a";
const USDC_ADDRESS = "0xdf8d259c04020562717557f2b5a3cf28e92707d1";

// Use exact_quote_output to specify the exact amount of USDC to receive from a sell.
// Unlike quote_amount (which specifies a maximum input), exact_quote_output guarantees
// the exact output amount with no slippage.
const order = {
base_mint: WETH_ADDRESS,
quote_mint: USDC_ADDRESS,
side: OrderSide.SELL,
exact_quote_output: BigInt(2_000_000), // Exactly 2 USDC output
};

console.log("Fetching quote with exact output...");

const quote = await client.requestQuote(order);

if (!quote) {
console.error("No quote available, exiting...");
process.exit(1);
}

console.log("Assembling quote...");

const bundle = await client.assembleQuote(quote);

if (!bundle) {
console.error("No bundle available, exiting...");
process.exit(1);
}

const tx = bundle.match_bundle.settlement_tx;

// --- Allowance Check --- //

const isSell = bundle.match_bundle.match_result.direction === "Sell";
const address = isSell
? (bundle.match_bundle.match_result.base_mint as `0x${string}`)
: (bundle.match_bundle.match_result.quote_mint as `0x${string}`);
const amount = isSell
? bundle.match_bundle.match_result.base_amount
: bundle.match_bundle.match_result.quote_amount;
const spender = tx.to as `0x${string}`;

console.log("Checking allowance...");

const allowance = await publicClient.readContract({
address,
abi: erc20Abi,
functionName: "allowance",
args: [owner, spender],
});

if (allowance < amount) {
console.log("Allowance is less than amount, approving...");
const approveTx = await walletClient.writeContract({
address,
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 --- //

console.log("Submitting bundle...");

const hash = await walletClient.sendTransaction({
to: tx.to as `0x${string}`,
data: tx.data as `0x${string}`,
type: "eip1559",
});

console.log("Successfully submitted transaction", hash);
16 changes: 16 additions & 0 deletions examples/external-match/exact-output/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "example-external-match-exact-output",
"private": true,
"type": "module",
"scripts": {
"start": "tsx index.ts"
},
"dependencies": {
"@renegade-fi/renegade-sdk": "latest",
"viem": "latest"
},
"devDependencies": {
"tsx": "^4",
"typescript": "^5"
}
}
19 changes: 19 additions & 0 deletions examples/external-match/exact-output/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["src"]
}
92 changes: 46 additions & 46 deletions packages/core/src/utils.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,50 @@
/* tslint:disable */
/* eslint-disable */
/**
* @param {string} seed
* @returns {any}
*/
export function derive_blinder_share(seed: string): any;
/**
* @param {string} seed
* @returns {any}
*/
export function wallet_id(seed: string): any;
/**
* @param {string} wallet_str
* @returns {any}
*/
export function wallet_nullifier(wallet_str: string): any;
/**
* @param {Function} sign_message
* @returns {Promise<any>}
*/
export function generate_wallet_secrets(sign_message: Function): Promise<any>;
/**
* @param {string} seed
* @param {bigint} nonce
* @returns {any}
*/
export function derive_sk_root_from_seed(seed: string, nonce: bigint): any;
/**
* @param {string} seed
* @param {bigint} nonce
* @returns {any}
*/
export function get_pk_root(seed: string, nonce: bigint): any;
/**
* @param {string | undefined} [seed]
* @param {bigint | undefined} [nonce]
* @param {string | undefined} [public_key]
* @returns {any[]}
*/
export function get_pk_root_scalars(seed?: string, nonce?: bigint, public_key?: string): any[];
/**
* @param {string} seed
* @returns {any}
*/
export function get_symmetric_key(seed: string): any;
/**
* @param {string} wallet_id
* @param {string} blinder_seed
* @param {string} share_seed
Expand All @@ -9,7 +53,7 @@
* @param {string} symmetric_key
* @returns {Promise<any>}
*/
export function find_external_wallet(wallet_id: string, blinder_seed: string, share_seed: string, pk_root: string, sk_match: string, symmetric_key: string): Promise<any>;
export function create_external_wallet(wallet_id: string, blinder_seed: string, share_seed: string, pk_root: string, sk_match: string, symmetric_key: string): Promise<any>;
/**
* @param {string} seed
* @returns {any}
Expand Down Expand Up @@ -144,27 +188,7 @@ export function assemble_external_match(do_gas_estimation: boolean, allow_shared
* @param {string} symmetric_key
* @returns {Promise<any>}
*/
export function create_external_wallet(wallet_id: string, blinder_seed: string, share_seed: string, pk_root: string, sk_match: string, symmetric_key: string): Promise<any>;
/**
* @param {string} seed
* @returns {any}
*/
export function derive_blinder_share(seed: string): any;
/**
* @param {string} seed
* @returns {any}
*/
export function wallet_id(seed: string): any;
/**
* @param {string} wallet_str
* @returns {any}
*/
export function wallet_nullifier(wallet_str: string): any;
/**
* @param {Function} sign_message
* @returns {Promise<any>}
*/
export function generate_wallet_secrets(sign_message: Function): Promise<any>;
export function find_external_wallet(wallet_id: string, blinder_seed: string, share_seed: string, pk_root: string, sk_match: string, symmetric_key: string): Promise<any>;
/**
* @param {string} path
* @param {any} headers
Expand All @@ -178,27 +202,3 @@ export function create_request_signature(path: string, headers: any, body: strin
* @returns {string}
*/
export function b64_to_hex_hmac_key(b64_key: string): string;
/**
* @param {string} seed
* @param {bigint} nonce
* @returns {any}
*/
export function derive_sk_root_from_seed(seed: string, nonce: bigint): any;
/**
* @param {string} seed
* @param {bigint} nonce
* @returns {any}
*/
export function get_pk_root(seed: string, nonce: bigint): any;
/**
* @param {string | undefined} [seed]
* @param {bigint | undefined} [nonce]
* @param {string | undefined} [public_key]
* @returns {any[]}
*/
export function get_pk_root_scalars(seed?: string, nonce?: bigint, public_key?: string): any[];
/**
* @param {string} seed
* @returns {any}
*/
export function get_symmetric_key(seed: string): any;
6 changes: 6 additions & 0 deletions packages/external-match/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @renegade-fi/renegade-sdk

## 1.0.1

### Patch Changes

- Add external order validation logic w/ an exact-output external match example

## 1.0.0

### Major Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/external-match/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@renegade-fi/renegade-sdk",
"version": "1.0.0",
"version": "1.0.1",
"description": "A TypeScript client for interacting with the Renegade Darkpool API",
"repository": {
"type": "git",
Expand Down
43 changes: 43 additions & 0 deletions packages/external-match/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,40 @@ export class AssembleMalleableExternalMatchOptions extends AssembleExternalMatch
}
}

/**
* Validate that an external order has the required fields and exactly one sizing field set.
*/
function validateExternalOrder(order: ExternalOrder): void {
if (!order.base_mint) {
throw new ExternalMatchClientError("base_mint must be set");
}
if (!order.quote_mint) {
throw new ExternalMatchClientError("quote_mint must be set");
}
if (!order.side) {
throw new ExternalMatchClientError("side must be set");
}

const sizingFields = [
order.base_amount,
order.quote_amount,
order.exact_base_output,
order.exact_quote_output,
];
const numSet = sizingFields.filter((f) => f !== undefined && f !== BigInt(0)).length;

if (numSet === 0) {
throw new ExternalMatchClientError(
"exactly one of base_amount, quote_amount, exact_base_output, or exact_quote_output must be set",
);
}
if (numSet > 1) {
throw new ExternalMatchClientError(
"exactly one of base_amount, quote_amount, exact_base_output, or exact_quote_output must be set",
);
}
}

/**
* Error thrown by the ExternalMatchClient.
*/
Expand Down Expand Up @@ -501,6 +535,7 @@ export class ExternalMatchClient {
order: ExternalOrder,
options: RequestQuoteOptions,
): Promise<SignedExternalQuote | null> {
validateExternalOrder(order);
const request: ExternalQuoteRequest = {
external_order: order,
};
Expand All @@ -527,6 +562,7 @@ export class ExternalMatchClient {
order: ExternalOrder,
options: RequestExternalMatchOptions,
): Promise<ExternalMatchResponse | null> {
validateExternalOrder(order);
const request: ExternalMatchRequest = {
do_gas_estimation: options.doGasEstimation,
receiver_address: options.receiverAddress,
Expand Down Expand Up @@ -569,6 +605,7 @@ export class ExternalMatchClient {
order: ExternalOrder,
options: RequestExternalMatchOptions,
): Promise<MalleableExternalMatchResponse | null> {
validateExternalOrder(order);
const request: ExternalMatchRequest = {
do_gas_estimation: options.doGasEstimation,
receiver_address: options.receiverAddress,
Expand Down Expand Up @@ -610,6 +647,9 @@ export class ExternalMatchClient {
quote: SignedExternalQuote,
options: AssembleExternalMatchOptions,
): Promise<ExternalMatchResponse | null> {
if (options.updatedOrder) {
validateExternalOrder(options.updatedOrder);
}
const signedQuote: ApiSignedExternalQuote = {
quote: quote.quote,
signature: quote.signature,
Expand Down Expand Up @@ -655,6 +695,9 @@ export class ExternalMatchClient {
quote: SignedExternalQuote,
options: AssembleMalleableExternalMatchOptions,
): Promise<MalleableExternalMatchResponse | null> {
if (options.updatedOrder) {
validateExternalOrder(options.updatedOrder);
}
const signedQuote: ApiSignedExternalQuote = {
quote: quote.quote,
signature: quote.signature,
Expand Down
Loading