UNPKG

@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
"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();