@bluefin-exchange/bluefin-v2-client
Version:
The Bluefin client Library allows traders to sign, create, retrieve and listen to orders on Bluefin Exchange.
974 lines (972 loc) • 124 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BluefinClient = void 0;
const library_sui_1 = require("@firefly-exchange/library-sui");
const blv_1 = require("@firefly-exchange/library-sui/blv");
const interpolate_1 = __importDefault(require("interpolate"));
const verify_1 = require("@mysten/sui/verify");
const zklogin_1 = require("@mysten/zklogin");
const sha256_1 = require("@noble/hashes/sha256");
const cryptography_1 = require("@mysten/sui/cryptography");
const utils_1 = require("../utils/utils");
const constants_1 = require("./constants");
const apiService_1 = require("./exchange/apiService");
const apiUrls_1 = require("./exchange/apiUrls");
const contractErrorHandling_service_1 = require("./exchange/contractErrorHandling.service");
const contractService_1 = require("./exchange/contractService");
const interactorService_1 = require("./exchange/interactorService");
const sockets_1 = require("./exchange/sockets");
const WebSocket_1 = require("./exchange/WebSocket");
class BluefinClient {
/**
* initializes the class instance
* @param _isTermAccepted boolean indicating if exchange terms and conditions are accepted
* @param _network containing network rpc url and chain id
* @param _account accepts either privateKey or AWS-KMS-SIGNER object if user intend to sign using kms
* @param _scheme signature scheme to be used
* @param _isUI (optional) is initialized from UI
* @param _uiWalletType (optional) wallet type connected on the UI i.e SUI, Nightly etc
*/
constructor(_isTermAccepted, _network, _account, _scheme, _isUI, _uiWalletType, _uiSignerObject) {
this.marketSymbols = []; // to save array market symbols [DOT-PERP, SOL-PERP]
this.walletAddress = ""; // to save user's public address when connecting from UI
this.isZkLogin = false;
this.isTermAccepted = false;
this.maxSaltLimit = 2 ** 60;
// the number of decimals supported by USDC contract
this.MarginTokenPrecision = 6;
this.is_wallet_extension = false;
/**
* @description
* initializes the required objects
* @param userOnboarding boolean indicating if user onboarding is required
* @param deployment
*/
this.init = (...args_1) => __awaiter(this, [...args_1], void 0, function* (userOnboarding = true, deployment = null, apiToken = "") {
var _a;
try {
if (apiToken) {
this.apiService.setWalletAddress(this.getPublicAddress());
this.apiService.setApiToken(apiToken);
// for socket
this.sockets.setApiToken(apiToken);
(_a = this.webSockets) === null || _a === void 0 ? void 0 : _a.setApiToken(apiToken);
}
else {
if (!this.signer) {
throw Error("Signer not initialized");
}
yield this.initContractCalls(deployment);
// for BLV contract calls
yield this.initInteractorCalls();
this.walletAddress = this.isZkLogin
? this.walletAddress
: this.signer.toSuiAddress
? this.signer.toSuiAddress()
: this.signer.getAddress
? this.signer.getAddress()
: this.walletAddress;
// onboard user if not onboarded
if (userOnboarding) {
yield this.userOnBoarding();
}
}
if (this.network.UUID) {
this.setUUID(this.network.UUID);
}
}
catch (error) {
throw (0, utils_1.throwCustomError)({
error,
code: constants_1.Errors.FAILED_TO_INITIALIZE_CLIENT,
});
}
});
this.initializeWithHook = (uiSignerObject, walletAddress) => __awaiter(this, void 0, void 0, function* () {
try {
this.uiWallet = uiSignerObject;
this.signer = uiSignerObject;
this.walletAddress = walletAddress;
this.isZkLogin = false;
this.is_wallet_extension = true;
}
catch (error) {
throw (0, utils_1.throwCustomError)({
error,
code: constants_1.Errors.FAILED_TO_INITIALIZE_CLIENT_FOR_UI_WALLET,
});
}
});
this.initializeForZkLogin = ({ _account, walletAddress, maxEpoch, proof, decodedJWT, salt, }) => {
try {
const keyPair = (0, library_sui_1.getKeyPairFromPvtKey)(_account, "ZkLogin");
this.signer = keyPair;
this.walletAddress = walletAddress;
this.maxEpoch = maxEpoch;
this.decodedJWT = decodedJWT;
this.proof = proof;
this.salt = salt;
this.isZkLogin = true;
this.is_wallet_extension = false;
}
catch (error) {
throw (0, utils_1.throwCustomError)({
error,
code: constants_1.Errors.FAILED_TO_INITIALIZE_CLIENT_FOR_ZK_ACCOUNT,
});
}
};
/** *
* Set UUID to api headers for colocation partners
*/
this.setUUID = (uuid) => {
this.apiService.setUUID(uuid);
};
/**
* @description
* initializes web3 and wallet with the given account private key
* @param keypair key pair for the account to be used for placing orders
*/
this.initializeWithKeyPair = (keypair) => __awaiter(this, void 0, void 0, function* () {
this.signer = keypair;
this.walletAddress = this.signer.toSuiAddress();
this.initOrderSigner(keypair);
});
/**
* @description
* initializes web3 and wallet with the given account private key
* @param seed seed for the account to be used for placing orders
* @param scheme signature scheme to be used
* @returns void
*/
this.initializeWithSeed = (seed, scheme) => {
switch (scheme) {
case "ED25519":
this.signer = library_sui_1.Ed25519Keypair.deriveKeypair(seed);
this.initOrderSigner(library_sui_1.Ed25519Keypair.deriveKeypair(seed));
break;
case "Secp256k1":
this.signer = library_sui_1.Secp256k1Keypair.deriveKeypair(seed);
this.initOrderSigner(library_sui_1.Secp256k1Keypair.deriveKeypair(seed));
break;
default:
throw new Error("Provided scheme is invalid");
}
};
/**
* @description
* initializes contract calls
* @param deployment (optional) The deployment json provided by deployer
*/
this.initContractCalls = (deployment) => __awaiter(this, void 0, void 0, function* () {
if (!this.signer) {
throw Error("Signer not Initialized");
}
const _deployment = deployment || (yield this.getDeploymentJson());
this.contractCalls = new contractService_1.ContractCalls(this.getSigner(), _deployment, this.provider, this.network, this.isZkLogin, this.getZkPayload(), this.walletAddress, this.is_wallet_extension);
});
/**
* @description
* initializes contract calls
* @param deployment (optional) The deployment json provided by deployer
*/
this.initInteractorCalls = () => __awaiter(this, void 0, void 0, function* () {
if (!this.signer) {
throw Error("Signer not Initialized");
}
const _deployment = yield this.getVaultConfigsForInteractor();
this.vaultConfig = _deployment;
this.interactorCalls = new interactorService_1.InteractorCalls(this.getSigner(), _deployment, this.provider, this.is_wallet_extension, this.isZkLogin, this.getZkPayload(), this.walletAddress);
});
/**
* @description
* Gets the RawSigner of the client
* @returns RawSigner
* */
this.getSigner = () => {
if (!this.signer) {
throw Error("Signer not initialized");
}
return this.signer;
};
/**
* @description
* Gets the RPC Provider of the client
* @returns JsonRPCProvider
* */
this.getProvider = () => {
return this.provider;
};
/**
* Generate and receive readOnlyToken, this can only be accessed at the time of generation
* @returns readOnlyToken string
*/
this.generateReadOnlyToken = () => __awaiter(this, void 0, void 0, function* () {
const response = yield this.apiService.post(apiUrls_1.SERVICE_URLS.USER.GENERATE_READONLY_TOKEN, {}, { isAuthenticationRequired: true });
return response;
});
/**
* @description
* Creates message to be signed, creates signature and authorize it from dapi
* @returns auth token
*/
this.userOnBoarding = (token, useDeprecatedSigningMethod) => __awaiter(this, void 0, void 0, function* () {
var _a;
this.apiService.setWalletAddress(this.getPublicAddress()); // setting before auth call
let userAuthToken = token;
if (!userAuthToken) {
const signature = yield this.createOnboardingSignature({
useDeprecatedSigningMethod,
});
// authorize signature created by dAPI
const authTokenResponse = yield this.authorizeSignedHash(signature);
if (!authTokenResponse.ok || !authTokenResponse.data) {
throw Error(`Authorization error: ${authTokenResponse.response.message} sig: ${signature}`);
}
userAuthToken = authTokenResponse.data.token;
}
// for api
this.apiService.setAuthToken(userAuthToken);
// for socket
this.sockets.setAuthToken(userAuthToken);
(_a = this.webSockets) === null || _a === void 0 ? void 0 : _a.setAuthToken(userAuthToken);
// TODO: remove this when all endpoints on frontend are integrated from client library
return userAuthToken;
});
this.getZkPayload = () => {
return {
decodedJWT: this.decodedJWT,
proof: this.proof,
salt: this.salt,
maxEpoch: this.maxEpoch,
};
};
this.createOnboardingSignature = (_a) => __awaiter(this, [_a], void 0, function* ({ useDeprecatedSigningMethod, }) {
let signature;
const onboardingSignature = {
onboardingUrl: this.network.onboardingUrl,
};
if (this.uiWallet) {
try {
console.log(`[TempLog] createOnboardingSignature: NORMAL WALLET`);
signature = yield library_sui_1.OrderSigner.signPayloadUsingWallet(onboardingSignature, this.uiWallet, useDeprecatedSigningMethod);
}
catch (error) {
(0, utils_1.throwCustomError)({ error, code: constants_1.Errors.WALLET_PAYLOAD_SIGNING_FAILED });
}
}
else if (this.isZkLogin) {
try {
console.log(`[TempLog] createOnboardingSignature: ZK Login`);
signature = yield library_sui_1.OrderSigner.signPayloadUsingZKSignature({
payload: onboardingSignature,
signer: this.signer,
zkPayload: this.getZkPayload(),
});
}
catch (error) {
(0, utils_1.throwCustomError)({ error, code: constants_1.Errors.ZK_PAYLOAD_SIGNING_FAILED });
}
}
else {
try {
console.log(`[TempLog] createOnboardingSignature: In ELSE Block`);
signature = yield this.orderSigner.signPayload(onboardingSignature);
}
catch (error) {
(0, utils_1.throwCustomError)({
error,
code: constants_1.Errors.KEYPAIR_PAYLOAD_SIGNING_FAILED,
});
}
}
return `${signature === null || signature === void 0 ? void 0 : signature.signature}${(signature === null || signature === void 0 ? void 0 : signature.publicAddress) ? signature === null || signature === void 0 ? void 0 : signature.publicAddress : signature === null || signature === void 0 ? void 0 : signature.publicKey}`;
});
/**
* @description
* Gets the payload containing key and mesasge to sign
* @returns SigPK
* */
this.signPayloadUsingZkWallet = (payload) => __awaiter(this, void 0, void 0, function* () {
const signature = yield library_sui_1.OrderSigner.signPayloadUsingZKSignature({
payload,
signer: this.signer,
zkPayload: this.getZkPayload(),
});
return signature;
});
/**
* @description
* Gets the payload containing bytes mesasge to sign
* @returns SigPK
* */
this.signBytesPayloadUsingZkWallet = (payload) => __awaiter(this, void 0, void 0, function* () {
const signature = yield library_sui_1.OrderSigner.signBytesPayloadUsingZKSignature({
payload,
signer: this.signer,
zkPayload: this.getZkPayload(),
});
return signature;
});
/**
* @description
* Gets the wallets Public address
* @returns string
* */
this.getPublicAddress = () => {
if (!this.signer) {
Error("Signer not initialized");
}
return this.walletAddress;
};
this.parseAndShapeSignedData = ({ signature, isParsingRequired = true, }) => {
let data;
const parsedSignature = (0, cryptography_1.parseSerializedSignature)(signature);
// this method doesn't support MultiSig signatures because it can't parse the public key
if (parsedSignature.signatureScheme === "MultiSig") {
throw new Error("Unexpected MultiSig signature in ZkLogin flow");
}
if (isParsingRequired && parsedSignature.signatureScheme === "ZkLogin") {
// zk login signature
const { userSignature } = parsedSignature.zkLogin;
// convert user sig to b64
const convertedUserSignature = (0, blv_1.toBase64)(userSignature);
// reparse b64 converted user sig
const parsedUserSignature = (0, cryptography_1.parseSerializedSignature)(convertedUserSignature);
data = {
signature: `${Buffer.from(parsedSignature.signature).toString("hex")}3`,
publicKey: (0, verify_1.publicKeyFromRawBytes)(parsedUserSignature.signatureScheme, parsedUserSignature.publicKey).toBase64(),
};
}
else {
data = {
signature: Buffer.from(parsedSignature.signature).toString("hex") +
library_sui_1.SIGNER_TYPES.UI_ED25519,
publicKey: (0, verify_1.publicKeyFromRawBytes)(parsedSignature.signatureScheme, parsedSignature.publicKey).toBase64(),
};
}
return data;
};
this.signOrder = (orderToSign) => __awaiter(this, void 0, void 0, function* () {
let signature;
if (this.uiWallet) {
signature = yield library_sui_1.OrderSigner.signOrderUsingWallet(orderToSign, this.uiWallet);
}
else if (this.isZkLogin) {
signature = yield library_sui_1.OrderSigner.signOrderUsingZkSignature({
order: orderToSign,
signer: this.signer,
zkPayload: this.getZkPayload(),
});
}
else if (this.orderSigner.signOrder)
signature = yield this.orderSigner.signOrder(orderToSign);
else
throw Error("On of OrderSigner or uiWallet needs to be initialized before signing order ");
return signature;
});
/**
* @description
* Gets a signed order from the client
* @returns OrderSignatureResponse
* @param order OrderSignatureRequest
* */
this.createSignedOrder = (order, parentAddress) => __awaiter(this, void 0, void 0, function* () {
if (!this.orderSigner && !this.uiWallet && !this.isZkLogin) {
throw Error("Order Signer not initialized");
}
const orderToSign = this.createOrderToSign(order, parentAddress);
let signature;
try {
signature = yield this.signOrder(orderToSign);
}
catch (e) {
throw Error("Failed to Sign Order: User Rejected Signature");
}
const signedOrder = {
symbol: order.symbol,
price: order.price,
quantity: order.quantity,
side: order.side,
orderType: order.orderType,
triggerPrice: order.orderType === library_sui_1.ORDER_TYPE.STOP_MARKET ||
order.orderType === library_sui_1.ORDER_TYPE.STOP_LIMIT ||
order.orderType === library_sui_1.ORDER_TYPE.STOP_LOSS_LIMIT ||
order.orderType === library_sui_1.ORDER_TYPE.TAKE_PROFIT_LIMIT ||
order.orderType === library_sui_1.ORDER_TYPE.STOP_LOSS_MARKET ||
order.orderType === library_sui_1.ORDER_TYPE.TAKE_PROFIT_MARKET
? order.triggerPrice || 0
: 0,
postOnly: orderToSign.postOnly,
cancelOnRevert: orderToSign.cancelOnRevert,
leverage: (0, library_sui_1.toBaseNumber)(orderToSign.leverage),
reduceOnly: orderToSign.reduceOnly,
salt: Number(orderToSign.salt),
expiration: Number(orderToSign.expiration),
maker: orderToSign.maker,
orderSignature: `${signature === null || signature === void 0 ? void 0 : signature.signature}${(signature === null || signature === void 0 ? void 0 : signature.publicAddress)
? signature === null || signature === void 0 ? void 0 : signature.publicAddress
: signature === null || signature === void 0 ? void 0 : signature.publicKey}`,
orderbookOnly: orderToSign.orderbookOnly,
timeInForce: order.timeInForce || library_sui_1.TIME_IN_FORCE.GOOD_TILL_TIME,
};
return signedOrder;
});
/**
* @description
* Places a signed order on bluefin exchange
* @param params PlaceOrderRequest containing the signed order created using createSignedOrder
* @returns PlaceOrderResponse containing status and data. If status is not 201, order placement failed.
*/
this.placeSignedOrder = (params) => __awaiter(this, void 0, void 0, function* () {
const response = yield this.apiService.post(apiUrls_1.SERVICE_URLS.ORDERS.ORDERS, {
symbol: params.symbol,
userAddress: params.maker,
orderType: params.orderType,
price: (0, library_sui_1.toBigNumberStr)(params.price),
triggerPrice: (0, library_sui_1.toBigNumberStr)(params.triggerPrice || "0", constants_1.POST_ORDER_BASE),
quantity: (0, library_sui_1.toBigNumberStr)(params.quantity),
leverage: (0, library_sui_1.toBigNumberStr)(params.leverage),
side: params.side,
reduceOnly: params.reduceOnly,
salt: params.salt,
expiration: params.expiration,
orderSignature: params.orderSignature,
timeInForce: params.timeInForce || library_sui_1.TIME_IN_FORCE.GOOD_TILL_TIME,
orderbookOnly: true,
postOnly: params.postOnly == true,
cancelOnRevert: params.cancelOnRevert == true,
clientId: params.clientId
? `bluefin-client: ${params.clientId}`
: "bluefin-client",
}, { isAuthenticationRequired: true });
return response;
});
/**
* @description
* Given an order payload, signs it on chain and submits to exchange for placement
* @param params PostOrderRequest
* @returns PlaceOrderResponse
*/
this.postOrder = (params) => __awaiter(this, void 0, void 0, function* () {
if (params.reduceOnly) {
console.warn("Warning: Reduce Only feature is deprecated until further notice. Reduce Only orders will be rejected from the API.");
}
const signedOrder = yield this.createSignedOrder(params, params.parentAddress);
const response = yield this.placeSignedOrder(Object.assign(Object.assign({}, signedOrder), { timeInForce: params.timeInForce, postOnly: params.postOnly == true, cancelOnRevert: params.cancelOnRevert == true, clientId: params.clientId, orderbookOnly: true }));
return response;
});
/**
* @description
* Creates signature for cancelling orders
* @param params OrderCancelSignatureRequest containing market symbol and order hashes to be cancelled
* @returns generated signature string
*/
this.createOrderCancellationSignature = (params) => __awaiter(this, void 0, void 0, function* () {
// TODO: serialize correctly, this is the default method from suiet wallet docs
// const serialized = new TextEncoder().encode(JSON.stringify(params));
// return this.signer.signData(serialized);
try {
let signature;
// taking the hash of list of hashes of cancel signature
const hashOfHash = Buffer.from((0, sha256_1.sha256)(JSON.stringify(params.hashes))).toString("hex");
const payloadValue = [];
payloadValue.push(hashOfHash);
if (this.uiWallet) {
// connected via UI
signature = yield library_sui_1.OrderSigner.signPayloadUsingWallet({ orderHashes: payloadValue }, this.uiWallet);
}
else if (this.isZkLogin) {
signature = yield library_sui_1.OrderSigner.signPayloadUsingZKSignature({
payload: { orderHashes: payloadValue },
signer: this.signer,
zkPayload: {
decodedJWT: this.decodedJWT,
proof: this.proof,
salt: this.salt,
maxEpoch: this.maxEpoch,
},
});
}
else {
signature = yield this.orderSigner.signPayload({
orderHashes: payloadValue,
});
}
return `${signature === null || signature === void 0 ? void 0 : signature.signature}${(signature === null || signature === void 0 ? void 0 : signature.publicAddress)
? signature === null || signature === void 0 ? void 0 : signature.publicAddress
: signature === null || signature === void 0 ? void 0 : signature.publicKey}`;
}
catch (_a) {
throw Error("Signing cancelled by user");
}
});
/**
* @description
* Posts to exchange for cancellation of provided orders with signature
* @param params OrderCancellationRequest containing order hashes to be cancelled and cancellation signature
* @returns response from exchange server
*/
this.placeCancelOrder = (params) => __awaiter(this, void 0, void 0, function* () {
const response = yield this.apiService.delete(apiUrls_1.SERVICE_URLS.ORDERS.ORDERS_HASH_V2, {
symbol: params.symbol,
orderHashes: params.hashes,
cancelSignature: params.signature,
parentAddress: params.parentAddress,
fromUI: true,
}, { isAuthenticationRequired: true });
return response;
});
/**
* @description
* Creates signature and posts order for cancellation on exchange of provided orders
* @param params OrderCancelSignatureRequest containing order hashes to be cancelled
* @returns response from exchange server
*/
this.postCancelOrder = (params) => __awaiter(this, void 0, void 0, function* () {
if (params.hashes.length <= 0) {
throw Error(`No orders to cancel`);
}
const signature = yield this.createOrderCancellationSignature(params);
const response = yield this.placeCancelOrder(Object.assign(Object.assign({}, params), { signature }));
return response;
});
/**
* @description
* Cancels all open orders for a given market
* @param symbol DOT-PERP, market symbol
* @returns cancellation response
*/
this.cancelAllOpenOrders = (symbol, parentAddress) => __awaiter(this, void 0, void 0, function* () {
var _a;
const openOrders = yield this.getUserOrders({
symbol,
statuses: [
library_sui_1.ORDER_STATUS.OPEN,
library_sui_1.ORDER_STATUS.PARTIAL_FILLED,
library_sui_1.ORDER_STATUS.PENDING,
],
parentAddress,
});
const hashes = (_a = openOrders.data) === null || _a === void 0 ? void 0 : _a.map((order) => order.hash);
const response = yield this.postCancelOrder({
hashes,
symbol,
parentAddress,
});
return response;
});
/**
* @description
* Returns the USDC balance of user in USDC contract
* @returns list of User's coins in USDC contract
*/
this.getUSDCCoins = (amount, limit, cursor) => __awaiter(this, void 0, void 0, function* () {
if (amount) {
const coin = yield this.contractCalls.onChainCalls.getUSDCoinHavingBalance({
amount,
address: this.is_wallet_extension || this.isZkLogin
? this.walletAddress
: this.signer.toSuiAddress(),
currencyID: this.contractCalls.onChainCalls.getCurrencyID(),
limit,
cursor,
});
if (coin) {
coin.balance = (0, library_sui_1.usdcToBaseNumber)(coin.balance);
}
return coin;
}
const coins = yield this.contractCalls.onChainCalls.getUSDCCoins({
address: yield this.signer.toSuiAddress(),
});
coins.data.forEach((coin) => {
coin.balance = (0, library_sui_1.usdcToBaseNumber)(coin.balance);
});
return coins;
});
/**
* @description
* Returns the usdc Balance(Free Collateral) of the account in Margin Bank contract
* @param contract (optional) address of Margin Bank contract
* @returns Number representing balance of user in Margin Bank contract
*/
this.getMarginBankBalance = () => __awaiter(this, void 0, void 0, function* () {
return this.contractCalls.getMarginBankBalance();
});
/**
* @description
* Returns the usdc Balance(Free Collateral) of the account in USDC contract
* @returns Number representing balance of user in USDC contract
*/
this.getUSDCBalance = () => __awaiter(this, void 0, void 0, function* () {
return this.contractCalls.onChainCalls.getUSDCBalance({
address: this.walletAddress,
currencyID: this.contractCalls.onChainCalls.getCurrencyID(),
}, this.signer);
});
/**
* @description
* fetch user sui balance
* @param walletAddress wallet address of the user
* @returns string
* */
this.getSUIBalance = (walletAddress) => __awaiter(this, void 0, void 0, function* () {
return this.contractCalls.getSUIBalance(walletAddress);
});
/**
* @description
* Faucet function, mints 10K USDC to wallet - Only works on Testnet
* Assumes that the user wallet has native gas Tokens on Testnet
* @returns Boolean true if user is funded, false otherwise
*/
this.mintTestUSDC = (amount) => __awaiter(this, void 0, void 0, function* () {
if (this.network === constants_1.Networks.PRODUCTION_SUI) {
throw Error(`Function does not work on PRODUCTION`);
}
// mint 10000 USDC
const mintAmount = amount || 10000;
const txResponse = yield this.contractCalls.onChainCalls.mintUSDC({
amount: (0, library_sui_1.toBigNumberStr)(mintAmount, this.MarginTokenPrecision),
to: yield this.signer.toSuiAddress(),
gasBudget: 1000000000,
});
if (library_sui_1.Transaction.getStatus(txResponse) === "success") {
return true;
}
return false;
});
/**
* @description
* Updates user's leverage to given leverage
* @param symbol market symbol get information about
* @param leverage new leverage you want to change to
* @returns ResponseSchema
*/
this.adjustLeverage = (params) => __awaiter(this, void 0, void 0, function* () {
const userPosition = yield this.getUserPosition({
symbol: params.symbol,
parentAddress: params.parentAddress,
});
if (!userPosition.data) {
throw Error(`User positions data doesn't exist`);
}
const position = userPosition.data;
// Open Position case
if (Object.keys(position).length > 0) {
if (params.sponsorTx) {
// create sponsored adjust leverage call
let errorMsg = "";
const sponsorPayload = yield this.contractCalls.adjustLeverageContractCall(params.leverage, params.symbol, params.parentAddress, true);
// only sign the sponsored tx
const sponsorTxResponse = yield this.signAndExecuteAdjustLeverageSponsoredTx(sponsorPayload, false // execute
);
errorMsg = sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.message;
if (sponsorTxResponse && sponsorTxResponse.ok) {
// make dapi call
// Encode to hex for transmission
const encodedSignature = (0, utils_1.combineAndEncode)({
bytes: sponsorTxResponse.data.signedTxb.bytes,
signature: sponsorTxResponse.data.signedTxb.signature,
});
const { ok, data, response: { errorCode, message }, } = yield this.updateLeverage({
symbol: params.symbol,
leverage: params.leverage,
parentAddress: params.parentAddress,
signedTransaction: encodedSignature,
sponsorSignature: sponsorTxResponse.data.signedTxb.sponsorSignature,
});
const response = {
ok,
data,
code: errorCode,
message,
};
// If API is successful return response else make direct contract call to update the leverage
if (response.ok) {
return response;
}
// fallback to make old sponsored call
const sponsorTxResponseFallback = yield this.signAndExecuteSponsoredTx(sponsorPayload);
if (sponsorTxResponseFallback === null || sponsorTxResponseFallback === void 0 ? void 0 : sponsorTxResponseFallback.ok) {
return {
ok: true,
code: 200,
message: "Leverage Updated",
data: "",
};
}
errorMsg = sponsorTxResponseFallback === null || sponsorTxResponseFallback === void 0 ? void 0 : sponsorTxResponseFallback.message;
}
// recursive call if sponsor fails
if (errorMsg && errorMsg !== constants_1.USER_REJECTED_MESSAGE)
return this.adjustLeverage(Object.assign(Object.assign({}, params), { sponsorTx: false }));
throw new Error((sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.message) || "Error Adjust Leverage");
}
else {
// TODO: fix zk login call also via dapi later, leaving for now as we have moved to sponsored calls above
if (this.isZkLogin) {
return yield this.contractCalls.adjustLeverageContractCall(params.leverage, params.symbol, params.parentAddress);
}
// sign the transaction only
const signedTx = yield this.contractCalls.adjustLeverageContractCallRawTransaction(params.leverage, params.symbol, params.parentAddress);
// execute on dapi
const { ok, data, response: { errorCode, message }, } = yield this.updateLeverage({
symbol: params.symbol,
leverage: params.leverage,
parentAddress: params.parentAddress,
signedTransaction: signedTx,
});
const response = { ok, data, code: errorCode, message };
// If API is successful return response else make direct contract call to update the leverage
if (response.ok) {
return response;
}
// fall back for simple adjust leverage call
return yield this.contractCalls.adjustLeverageContractCall(params.leverage, params.symbol, params.parentAddress);
}
}
// NO position case
else {
const { ok, data, response: { errorCode, message }, } = yield this.updateLeverage({
symbol: params.symbol,
leverage: params.leverage,
parentAddress: params.parentAddress,
});
const response = { ok, data, code: errorCode, message };
return response;
}
});
/**
* @description
* Whitelist subaccount and/or remove the already exists subaccounts for One Click Trading
* @param subAccountAddress
* @param accountsToRemove (optional)
* @returns ResponseSchema
*/
this.upsertSubAccount = (params, sponsorTx) => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e;
const apiResponse = yield this.getExpiredAccountsFor1CT();
const signedTx = yield this.contractCalls.upsertSubAccountContractCallRawTransaction(params.subAccountAddress, (_b = (_a = apiResponse === null || apiResponse === void 0 ? void 0 : apiResponse.data) === null || _a === void 0 ? void 0 : _a.expiredSubAccounts) !== null && _b !== void 0 ? _b : [], undefined, undefined, sponsorTx);
if (sponsorTx) {
const sponsorPayload = signedTx;
const sponsorTxResponse = yield this.signAndExecuteSponsoredTx({
data: sponsorPayload,
ok: true,
code: 200,
message: "",
}, false);
if (sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.ok) {
const signedTransaction = (0, utils_1.combineAndEncode)(
// @ts-ignore
(_c = sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.data) === null || _c === void 0 ? void 0 : _c.signedTxb);
const request = {
subAccountAddress: params.subAccountAddress,
accountsToRemove: params.accountsToRemove,
signedTransaction,
sponsorSignature:
// @ts-ignore
(_e = (_d = sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.data) === null || _d === void 0 ? void 0 : _d.signedTxb) === null || _e === void 0 ? void 0 : _e.sponsorSignature,
};
const { ok, data, response: { errorCode, message }, } = yield this.addSubAccountFor1CT(request);
if (ok) {
const response = {
ok,
data,
code: errorCode,
message,
};
return response;
}
}
// recursive call if sponsor fails
if (!(sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.ok) &&
(sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.message) !== constants_1.USER_REJECTED_MESSAGE) {
return this.upsertSubAccount(params, false);
}
}
const request = {
subAccountAddress: params.subAccountAddress,
accountsToRemove: params.accountsToRemove,
signedTransaction: signedTx,
};
const { ok, data, response: { errorCode, message }, } = yield this.addSubAccountFor1CT(request);
const response = {
ok,
data,
code: errorCode,
message,
};
return response;
});
/**
* @description
* Add or remove margin from the open position
* @param symbol market symbol of the open position
* @param operationType operation you want to perform `Add` | `Remove` margin
* @param amount (number) amount user wants to add or remove from the position
* @returns ResponseSchema
*/
this.adjustMargin = (symbol, operationType, amount, sponsorTx) => __awaiter(this, void 0, void 0, function* () {
if (sponsorTx) {
const sponsorPayload = yield this.contractCalls.adjustMarginContractCall(symbol, operationType, amount, sponsorTx);
if (sponsorPayload.ok) {
const sponsorTxResponse = yield this.signAndExecuteSponsoredTx(sponsorPayload);
if (!(sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.ok) &&
(sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.message) !== constants_1.USER_REJECTED_MESSAGE) {
return this.adjustMargin(symbol, operationType, amount, false);
}
if (!(sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.ok)) {
throw new Error((sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.message) || "Error adjusting margin");
}
}
}
return this.contractCalls.adjustMarginContractCall(symbol, operationType, amount, sponsorTx);
});
/**
* @description
* Deposits USDC to Margin Bank contract
* @param amount amount of USDC to deposit
* @param coinID coinID of USDC coin to use
* @returns ResponseSchema
*/
this.depositToMarginBank = (amount, coinID, sponsorTx) => __awaiter(this, void 0, void 0, function* () {
const verifyStatus = yield this.verifyWalletStatus(amount);
if (verifyStatus.ok &&
verifyStatus.data &&
verifyStatus.data.verificationStatus != "Success") {
(0, utils_1.throwCustomError)({
error: `Deposit Unavailable: Your account is currently ${verifyStatus.data.verificationStatus} from depositing funds`,
});
}
if (sponsorTx) {
const sponsorTxPayload = yield this.depositToMarginBankSponsored(amount, coinID, true);
const sponsorTxResponse = yield this.signAndExecuteSponsoredTx(sponsorTxPayload);
if (sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.ok) {
return {
ok: true,
code: 200,
data: sponsorTxResponse,
message: "Deposit Successful",
};
}
// recursive call if sponsor fails
if (!(sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.ok) &&
(sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.message) !== constants_1.USER_REJECTED_MESSAGE)
return this.depositToMarginBank(amount, coinID, false);
}
return this.depositToMarginBankSponsored(amount, coinID, false);
});
this.depositToMarginBankSponsored = (amount, coinID, sponsorTx) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (!amount)
(0, utils_1.throwCustomError)({ error: "No amount specified for deposit" });
// if CoinID provided
if (coinID) {
const contractCall = yield this.contractCalls.depositToMarginBankContractCall(amount, coinID, this.getPublicAddress, sponsorTx);
if (sponsorTx) {
yield this.signAndExecuteSponsoredTx(contractCall.data);
}
else {
return contractCall;
}
}
// if no coin id provided
// Check for a single coin containing enough balance
const coinHavingBalance = (_a = (yield this.contractCalls.getUSDCHavingBalance(amount))) === null || _a === void 0 ? void 0 : _a.coinObjectId;
if (coinHavingBalance) {
return yield this.contractCalls.depositToMarginBankContractCall(amount, coinHavingBalance, this.getPublicAddress, sponsorTx);
}
// Try merging users' coins if they have more than one coins
const usdcCoins = yield this.contractCalls.getUSDCCoins(this.walletAddress);
if (usdcCoins.data.length > 1) {
if (sponsorTx) {
const sponsorPayload = yield this.contractCalls.mergeAllUSDCCOins(sponsorTx);
yield this.signAndExecuteSponsoredTx({
ok: true,
data: sponsorPayload,
message: "",
});
}
else {
yield this.contractCalls.mergeAllUSDCCOins(sponsorTx);
}
let coinHavingBalanceAfterMerge;
let retries = 5;
while (!coinHavingBalanceAfterMerge && retries--) {
// sleep for 1 second to merge the coins
yield new Promise((resolve) => setTimeout(resolve, 1000));
coinHavingBalanceAfterMerge = (_b = (yield this.contractCalls.getUSDCHavingBalance(amount))) === null || _b === void 0 ? void 0 : _b.coinObjectId;
}
if (coinHavingBalanceAfterMerge) {
return this.contractCalls.depositToMarginBankContractCall(amount, coinHavingBalanceAfterMerge, this.getPublicAddress, sponsorTx);
}
}
(0, utils_1.throwCustomError)({
error: `User has no coin with amount ${amount} to deposit`,
});
});
/**
* @description
* withdraws USDC from Margin Bank contract
* @param amount amount of USDC to withdraw
* @returns ResponseSchema
*/
this.withdrawFromMarginBank = (amount, sponsorTx) => __awaiter(this, void 0, void 0, function* () {
if (sponsorTx) {
if (amount) {
try {
const sponsorTxPayload = yield this.contractCalls.withdrawFromMarginBankContractCall(amount, true);
const sponsorTxResponse = yield this.signAndExecuteSponsoredTx(sponsorTxPayload);
if (sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.ok) {
return {
ok: true,
code: 200,
data: sponsorTxResponse,
message: "Withdraw Successful",
};
}
// recursive call if sponsor fails and not rejected
if (!(sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.ok) &&
(sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.message) !== constants_1.USER_REJECTED_MESSAGE) {
return this.withdrawFromMarginBank(amount);
}
throw new Error((sponsorTxResponse === null || sponsorTxResponse === void 0 ? void 0 : sponsorTxResponse.message) || "Error completing withdraw");
}
catch (e) {
return {
ok: false,
code: "Withdraw unsuccessful",
data: "",
message: e.message,
};
}
}
else {
return this.contractCalls.withdrawAllFromMarginBankContractCall();
}
}
if (amount) {
return this.contractCalls.withdrawFromMarginBankContractCall(amount);
}
return this.contractCalls.withdrawAllFromMarginBankContractCall();
});
this.closeDelistedPosition = (symbol, args) => __awaiter(this, void 0, void 0, function* () {
return this.contractCalls.closeDelistedPositionContractCall(symbol, args);
});
this.closeAllDelistedPositionsAndWithdrawMargin = (args) => __awaiter(this, void 0, void 0, function* () {
try {
// get exchange info of all markets to read the status
const exchangeInfo = yield this.getExchangeInfo();