Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/adapter/bridges/BinanceCEXBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
isDefined,
getTimestampForBlock,
getBinanceApiClient,
getBinanceDepositAddress,
floatToBN,
CHAIN_IDs,
compareAddressesSimple,
Expand Down Expand Up @@ -77,7 +78,7 @@ export class BinanceCEXBridge extends BaseBridgeAdapter {

const binanceApiClient = await this.getBinanceClient();

const depositAddress = await binanceApiClient.depositAddress({
const depositAddress = await getBinanceDepositAddress(binanceApiClient, {
coin: this.tokenSymbol,
network: "ETH",
});
Expand Down
14 changes: 12 additions & 2 deletions src/adapter/bridges/BinanceCEXNativeBridge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { Contract, BigNumber, Signer, Provider, EvmAddress, assert, bnZero, winston } from "../../utils";
import {
Contract,
BigNumber,
Signer,
Provider,
EvmAddress,
assert,
bnZero,
getBinanceDepositAddress,
winston,
} from "../../utils";
import { CONTRACT_ADDRESSES } from "../../common";
import { BridgeTransactionDetails } from "./BaseBridgeAdapter";
import { BinanceCEXBridge } from "./";
Expand Down Expand Up @@ -40,7 +50,7 @@ export class BinanceCEXNativeBridge extends BinanceCEXBridge {
// Fetch the deposit address from the binance API.

const binanceApiClient = await this.getBinanceClient();
const depositAddress = await binanceApiClient.depositAddress({
const depositAddress = await getBinanceDepositAddress(binanceApiClient, {
coin: this.tokenSymbol,
network: "ETH",
});
Expand Down
3 changes: 2 additions & 1 deletion src/adapter/l2Bridges/BinanceCEXBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Signer,
EvmAddress,
getBinanceApiClient,
getBinanceDepositAddress,
getTranslatedTokenAddress,
floatToBN,
getTimestampForBlock,
Expand Down Expand Up @@ -66,7 +67,7 @@ export class BinanceCEXBridge extends BaseL2BridgeAdapter {
): Promise<AugmentedTransaction[]> {
const binanceApiClient = await this.getBinanceClient();
const l2TokenInfo = getTokenInfo(l2Token, this.l2chainId);
const depositAddress = await binanceApiClient.depositAddress({
const depositAddress = await getBinanceDepositAddress(binanceApiClient, {
coin: this.l1TokenInfo.symbol,
network: this.depositNetwork,
});
Expand Down
3 changes: 2 additions & 1 deletion src/adapter/l2Bridges/BinanceCEXNativeBridge.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
BigNumber,
createFormatFunction,
getBinanceDepositAddress,
getNetworkName,
Signer,
Contract,
Expand All @@ -27,7 +28,7 @@ export class BinanceCEXNativeBridge extends BinanceCEXBridge {
const weth = new Contract(l2Token.toNative(), WETH_ABI, this.l2Signer);
const binanceApiClient = await this.getBinanceClient();
const l2TokenInfo = getTokenInfo(l2Token, this.l2chainId);
const depositAddress = await binanceApiClient.depositAddress({
const depositAddress = await getBinanceDepositAddress(binanceApiClient, {
coin: this.l1TokenInfo.symbol,
network: this.depositNetwork,
});
Expand Down
5 changes: 3 additions & 2 deletions src/finalizer/utils/binance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
getBinanceDepositType,
BinanceTransactionType,
getBinanceWithdrawalType,
submitBinanceWithdrawal,
isCompletedBinanceWithdrawal,
truncate,
} from "../../utils";
Expand Down Expand Up @@ -207,7 +208,7 @@ export async function binanceFinalizer(
amountToFinalize = Math.floor(amountToFinalize * DECIMAL_PRECISION) / DECIMAL_PRECISION;
// Balance from Binance is in 8 decimal places, so we need to truncate to 8 decimal places.
coinBalance = Number((coinBalance - amountToFinalize).toFixed(8));
const withdrawalId = await binanceApi.withdraw({
const withdrawalId = await submitBinanceWithdrawal(binanceApi, {
coin: symbol,
address,
network: withdrawNetwork,
Expand Down Expand Up @@ -243,7 +244,7 @@ export async function binanceFinalizer(
});
// Lastly, we need to truncate the amount to withdraw to 6 decimal places
const amountToSweep = truncate(cappedWithdraw, 6);
const withdrawalId = await binanceApi.withdraw({
const withdrawalId = await submitBinanceWithdrawal(binanceApi, {
coin: symbol,
address,
network: withdrawNetwork,
Expand Down
16 changes: 10 additions & 6 deletions src/rebalancer/adapters/binance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import {
forEachAsync,
fromWei,
getAccountCoins,
getBinanceAllOrders,
getBinanceApiClient,
getBinanceDepositAddress,
getBinanceTradeFees,
getBinanceTransactionTypeKey,
getBinanceWithdrawals,
getNetworkName,
Expand All @@ -24,6 +27,8 @@ import {
setBinanceDepositType,
setBinanceWithdrawalType,
Signer,
submitBinanceOrder,
submitBinanceWithdrawal,
toBNWei,
truncate,
winston,
Expand Down Expand Up @@ -678,7 +683,7 @@ export class BinanceStablecoinSwapAdapter extends BaseAdapter {
const { sourceToken, destinationToken, sourceChain, destinationChain } = rebalanceRoute;
const spotMarketMeta = this._getSpotMarketMetaForRoute(sourceToken, destinationToken);
// Commission is denominated in percentage points.
const tradeFeePct = (await this.binanceApiClient.tradeFee()).find(
const tradeFeePct = (await getBinanceTradeFees(this.binanceApiClient)).find(
(fee) => fee.symbol === spotMarketMeta.symbol
).takerCommission;
const tradeFee = toBNWei(tradeFeePct, 18).mul(amountToTransfer).div(toBNWei(100, 18));
Expand Down Expand Up @@ -848,7 +853,7 @@ export class BinanceStablecoinSwapAdapter extends BaseAdapter {

private async _depositToBinance(sourceToken: string, sourceChain: number, amountToDeposit: BigNumber): Promise<void> {
assert(isDefined(BINANCE_NETWORKS[sourceChain]), "Source chain should be a Binance network");
const depositAddress = await this.binanceApiClient.depositAddress({
const depositAddress = await getBinanceDepositAddress(this.binanceApiClient, {
coin: sourceToken,
network: BINANCE_NETWORKS[sourceChain],
});
Expand Down Expand Up @@ -958,7 +963,7 @@ export class BinanceStablecoinSwapAdapter extends BaseAdapter {
): Promise<{ matchingFill: QueryOrderResult; expectedAmountToReceive: string } | undefined> {
const orderDetails = await this._redisGetOrderDetails(cloid);
const spotMarketMeta = this._getSpotMarketMetaForRoute(orderDetails.sourceToken, orderDetails.destinationToken);
const allOrders = await this.binanceApiClient.allOrders({
const allOrders = await getBinanceAllOrders(this.binanceApiClient, {
symbol: spotMarketMeta.symbol,
});
const matchingFill = allOrders.find((order) => order.clientOrderId === cloid && order.status === "FILLED");
Expand All @@ -984,7 +989,6 @@ export class BinanceStablecoinSwapAdapter extends BaseAdapter {
side: spotMarketMeta.isBuy ? "BUY" : "SELL",
type: OrderType.MARKET,
quantity: szForOrder.toString(),
recvWindow: 60000,
};
this.logger.debug({
at: "BinanceStablecoinSwapAdapter._placeMarketOrder",
Expand All @@ -993,7 +997,7 @@ export class BinanceStablecoinSwapAdapter extends BaseAdapter {
} with size ${szForOrder}`,
orderStruct,
});
const response = await this.binanceApiClient.order(orderStruct as NewOrderSpot);
const response = await submitBinanceOrder(this.binanceApiClient, orderStruct as NewOrderSpot);
assert(response.status == "FILLED", `Market order was not filled: ${JSON.stringify(response)}`);
this.logger.info({
at: "BinanceStablecoinSwapAdapter._placeMarketOrder",
Expand Down Expand Up @@ -1139,7 +1143,7 @@ export class BinanceStablecoinSwapAdapter extends BaseAdapter {
// We need to truncate the amount to withdraw to the destination chain's decimal places.
const destinationTokenInfo = this._getTokenInfo(destinationToken, destinationEntrypointNetwork);
const amountToWithdraw = truncate(quantity, destinationTokenInfo.decimals);
const withdrawalId = await this.binanceApiClient.withdraw({
const withdrawalId = await submitBinanceWithdrawal(this.binanceApiClient, {
coin: destinationToken,
address: this.baseSignerAddress.toNative(),
amount: Number(amountToWithdraw),
Expand Down
72 changes: 68 additions & 4 deletions src/utils/BinanceUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,33 @@ const KNOWN_BINANCE_ERROR_REASONS = [
"TypeError: fetch failed",
];

// Binance only accepts a signed request while its timestamp remains within recvWindow.
// We keep write-state calls tight so delayed accepted requests cannot submit orders/withdrawals much later than intended.
// Signed reads can tolerate a much larger window because a delayed accepted request still returns current server data.
export const BINANCE_WRITE_RECV_WINDOW_MS = 5_000;
export const BINANCE_READ_RECV_WINDOW_MS = 60_000;

type WithdrawalQuota = {
wdQuota: number;
usedWdQuota: number;
};

type BinanceApiWithRecvWindow = BinanceApi & {
tradeFee(options?: { recvWindow?: number; useServerTime?: boolean }): ReturnType<BinanceApi["tradeFee"]>;
withdraw(
options: Parameters<BinanceApi["withdraw"]>[0] & {
recvWindow?: number;
withdrawOrderId?: string;
}
): ReturnType<BinanceApi["withdraw"]>;
withdrawHistory(
options: Parameters<BinanceApi["withdrawHistory"]>[0] & {
recvWindow?: number;
}
): ReturnType<BinanceApi["withdrawHistory"]>;
accountCoins(options?: { recvWindow?: number }): Promise<unknown>;
};

// Alias for Binance network symbols.
export const BINANCE_NETWORKS: { [chainId: number]: string } = {
[CHAIN_IDs.ARBITRUM]: "ARBITRUM",
Expand Down Expand Up @@ -142,13 +164,47 @@ async function retrieveBinanceSecretKeyFromCLIArgs(): Promise<string | undefined
* available to rebalance per day and the amount already used.
*/
export async function getBinanceWithdrawalLimits(binanceApi: BinanceApi): Promise<WithdrawalQuota> {
const unparsedQuota = await binanceApi.privateRequest("GET" as HttpMethod, "/sapi/v1/capital/withdraw/quota", {});
const unparsedQuota = await binanceApi.privateRequest("GET" as HttpMethod, "/sapi/v1/capital/withdraw/quota", {
recvWindow: BINANCE_READ_RECV_WINDOW_MS,
});
return {
wdQuota: unparsedQuota["wdQuota"],
usedWdQuota: unparsedQuota["usedWdQuota"],
};
}

export async function getBinanceTradeFees(binanceApi: BinanceApi): ReturnType<BinanceApi["tradeFee"]> {
return (binanceApi as BinanceApiWithRecvWindow).tradeFee({ recvWindow: BINANCE_READ_RECV_WINDOW_MS });
}

export async function getBinanceDepositAddress(
binanceApi: BinanceApi,
options: Parameters<BinanceApi["depositAddress"]>[0]
): ReturnType<BinanceApi["depositAddress"]> {
return binanceApi.depositAddress({ ...options, recvWindow: BINANCE_READ_RECV_WINDOW_MS });
}

export async function getBinanceAllOrders(
binanceApi: BinanceApi,
options: Parameters<BinanceApi["allOrders"]>[0]
): ReturnType<BinanceApi["allOrders"]> {
return binanceApi.allOrders({ ...options, recvWindow: BINANCE_READ_RECV_WINDOW_MS });
}

export async function submitBinanceOrder(
binanceApi: BinanceApi,
options: Parameters<BinanceApi["order"]>[0]
): ReturnType<BinanceApi["order"]> {
return binanceApi.order({ ...options, recvWindow: BINANCE_WRITE_RECV_WINDOW_MS });
}

export async function submitBinanceWithdrawal(
binanceApi: BinanceApi,
options: Parameters<BinanceApi["withdraw"]>[0]
): ReturnType<BinanceApi["withdraw"]> {
return (binanceApi as BinanceApiWithRecvWindow).withdraw({ ...options, recvWindow: BINANCE_WRITE_RECV_WINDOW_MS });
}

export enum BinanceTransactionType {
BRIDGE, // A deposit into Binance from one network designed to be withdrawn to another network.
SWAP, // A deposit into Binance from one network designed to be swapped and then withdrawn to another network.
Expand Down Expand Up @@ -255,7 +311,7 @@ export async function getBinanceDeposits(
): Promise<BinanceDeposit[]> {
let depositHistory: DepositHistoryResponse;
try {
depositHistory = await binanceApi.depositHistory({ startTime });
depositHistory = await binanceApi.depositHistory({ startTime, recvWindow: BINANCE_READ_RECV_WINDOW_MS });
} catch (_err) {
const err = _err.toString();
if (KNOWN_BINANCE_ERROR_REASONS.some((errorReason) => err.includes(errorReason)) && nRetries < maxRetries) {
Expand Down Expand Up @@ -290,7 +346,11 @@ export async function getBinanceWithdrawals(
): Promise<BinanceWithdrawal[]> {
let withdrawHistory: WithdrawHistoryResponse;
try {
withdrawHistory = await binanceApi.withdrawHistory({ coin, startTime });
withdrawHistory = await (binanceApi as BinanceApiWithRecvWindow).withdrawHistory({
coin,
startTime,
recvWindow: BINANCE_READ_RECV_WINDOW_MS,
});
} catch (_err) {
const err = _err.toString();
if (KNOWN_BINANCE_ERROR_REASONS.some((errorReason) => err.includes(errorReason)) && nRetries < maxRetries) {
Expand Down Expand Up @@ -321,7 +381,11 @@ export async function getBinanceWithdrawals(
* @returns A typed `AccountCoins` response.
*/
export async function getAccountCoins(binanceApi: BinanceApi): Promise<ParsedAccountCoins> {
const coins = Object.values(await binanceApi["accountCoins"]());
const coins = Object.values(
await (binanceApi as BinanceApiWithRecvWindow).accountCoins({
recvWindow: BINANCE_READ_RECV_WINDOW_MS,
})
);
return coins.map((coin) => {
const networkList = coin["networkList"]?.map((network) => {
return {
Expand Down
Loading
Loading