diff --git a/src/HavenoClient.test.ts b/src/HavenoClient.test.ts index 5dc12d08..901616fd 100644 --- a/src/HavenoClient.test.ts +++ b/src/HavenoClient.test.ts @@ -1,12 +1,29 @@ // --------------------------------- IMPORTS ---------------------------------- // haveno imports -import HavenoClient from "./HavenoClient"; -import HavenoError from "./utils/HavenoError"; -import HavenoUtils from "./utils/HavenoUtils"; -import { MarketPriceInfo, NotificationMessage, OfferInfo, TradeInfo, UrlConnection, XmrBalanceInfo } from "./protobuf/grpc_pb"; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb -import { Attachment, DisputeResult, PaymentMethod, PaymentAccountForm, PaymentAccountFormField, PaymentAccount, MoneroNodeSettings} from "./protobuf/pb_pb"; -import { XmrDestination, XmrTx, XmrIncomingTransfer, XmrOutgoingTransfer } from "./protobuf/grpc_pb"; +import { + HavenoClient, + HavenoError, + HavenoUtils, + OfferDirection, + MarketPriceInfo, + NotificationMessage, + OfferInfo, + TradeInfo, + UrlConnection, + XmrBalanceInfo, + Attachment, + DisputeResult, + PaymentMethod, + PaymentAccountForm, + PaymentAccountFormField, + PaymentAccount, + MoneroNodeSettings, + XmrDestination, + XmrTx, + XmrIncomingTransfer, + XmrOutgoingTransfer, +} from "./index"; import AuthenticationStatus = UrlConnection.AuthenticationStatus; import OnlineStatus = UrlConnection.OnlineStatus; @@ -37,6 +54,22 @@ let fundingWallet: moneroTs.MoneroWalletRpc; let user1Wallet: moneroTs.MoneroWalletRpc; let user2Wallet: moneroTs.MoneroWalletRpc; +enum TradeRole { + MAKER = "MAKER", + TAKER = "TAKER", +} + +enum SaleRole { + BUYER = "BUYER", + SELLER = "SELLER" +} + +enum DisputeContext { + NONE = "NONE", + OPEN_AFTER_DEPOSITS_UNLOCK = "OPEN_AFTER_DEPOSITS_UNLOCK", + OPEN_AFTER_PAYMENT_SENT = "OPEN_AFTER_PAYMENT_SENT" +} + /** * Test context for a single peer in a trade. */ @@ -73,7 +106,7 @@ const defaultTradeConfig: Partial = { makeOffer: true, takeOffer: true, awaitFundsToMakeOffer: true, - direction: "BUY", // buy or sell xmr + direction: OfferDirection.BUY, // buy or sell xmr offerAmount: BigInt("200000000000"), // amount of xmr to trade (0.2 XMR) offerMinAmount: undefined, assetCode: "usd", // counter asset to trade @@ -125,7 +158,7 @@ class TradeContext { // make offer awaitFundsToMakeOffer?: boolean - direction?: string; + direction?: OfferDirection; assetCode?: string; offerAmount?: bigint; // offer amount or max offerMinAmount?: bigint; @@ -195,15 +228,15 @@ class TradeContext { } getBuyer(): PeerContext { - return (this.direction?.toUpperCase() === "BUY" ? this.maker : this.taker) as PeerContext; + return (this.direction === OfferDirection.BUY ? this.maker : this.taker) as PeerContext; } getSeller(): PeerContext { - return (this.direction?.toUpperCase() === "BUY" ? this.taker : this.maker) as PeerContext; + return (this.direction === OfferDirection.BUY ? this.taker : this.maker) as PeerContext; } isBuyerMaker(): boolean { - return this.direction?.toUpperCase() === "BUY"; + return this.direction === OfferDirection.BUY; } getDisputeOpener(): PeerContext | undefined { @@ -236,7 +269,7 @@ class TradeContext { async toSummary(): Promise { let str: string = ""; - str += "Type: Maker/" + (this.direction!.toUpperCase() === "BUY" ? "Buyer" : "Seller") + ", Taker/" + (this.direction!.toUpperCase() === "BUY" ? "Seller" : "Buyer"); + str += "Type: Maker/" + (this.direction === OfferDirection.BUY ? "Buyer" : "Seller") + ", Taker/" + (this.direction === OfferDirection.BUY ? "Seller" : "Buyer"); str += "\nOffer id: " + this.offerId; if (this.maker.havenod) str += "\nMaker uri: " + this.maker?.havenod?.getUrl(); if (this.taker.havenod) str += "\nTaker uri: " + this.taker?.havenod?.getUrl(); @@ -256,7 +289,7 @@ class TradeContext { let tx = await monerod.getTx(this.arbitrator!.trade!.getMakerDepositTxId()); str += "\nMaker deposit tx fee: " + (tx ? tx?.getFee() : undefined); } - str += "\nMaker security deposit received: " + (this.direction == "BUY" ? this.arbitrator!.trade!.getBuyerSecurityDeposit() : this.arbitrator!.trade!.getSellerSecurityDeposit()); + str += "\nMaker security deposit received: " + (this.direction == OfferDirection.BUY ? this.arbitrator!.trade!.getBuyerSecurityDeposit() : this.arbitrator!.trade!.getSellerSecurityDeposit()); } str += "\nTaker balance before offer: " + this.taker.balancesBeforeOffer?.getBalance(); if (this.arbitrator && this.arbitrator!.trade) { @@ -266,7 +299,7 @@ class TradeContext { let tx = await monerod.getTx(this.arbitrator!.trade!.getTakerDepositTxId()); str += "\nTaker deposit tx fee: " + (tx ? tx?.getFee() : undefined); } - str += "\nTaker security deposit received: " + (this.direction == "BUY" ? this.arbitrator!.trade!.getSellerSecurityDeposit() : this.arbitrator!.trade!.getBuyerSecurityDeposit()); + str += "\nTaker security deposit received: " + (this.direction == OfferDirection.BUY ? this.arbitrator!.trade!.getSellerSecurityDeposit() : this.arbitrator!.trade!.getBuyerSecurityDeposit()); if (this.disputeWinner) str += "\nDispute winner: " + (this.disputeWinner == DisputeResult.Winner.BUYER ? "Buyer" : "Seller"); str += "\nPayout tx id: " + this.payoutTxId; if (this.payoutTxId) { @@ -407,22 +440,6 @@ interface HavenodContext { walletUrl?: string } -enum TradeRole { - MAKER = "MAKER", - TAKER = "TAKER", -} - -enum SaleRole { - BUYER = "BUYER", - SELLER = "SELLER" -} - -enum DisputeContext { - NONE = "NONE", - OPEN_AFTER_DEPOSITS_UNLOCK = "OPEN_AFTER_DEPOSITS_UNLOCK", - OPEN_AFTER_PAYMENT_SENT = "OPEN_AFTER_PAYMENT_SENT" -} - interface TxContext { isCreatedTx: boolean; } @@ -1065,13 +1082,13 @@ test("Can get market depth (CI, sanity check)", async () => { expect(marketDepth.getSellDepthList().length).toEqual(0); // post offers to buy and sell - await makeOffer({maker: {havenod: user1}, direction: "BUY", offerAmount: BigInt("150000000000"), assetCode: assetCode, price: 17.0}); - await makeOffer({maker: {havenod: user1}, direction: "BUY", offerAmount: BigInt("150000000000"), assetCode: assetCode, price: 17.2}); - await makeOffer({maker: {havenod: user1}, direction: "BUY", offerAmount: BigInt("200000000000"), assetCode: assetCode, price: 17.3}); - await makeOffer({maker: {havenod: user1}, direction: "BUY", offerAmount: BigInt("150000000000"), assetCode: assetCode, price: 17.3}); - await makeOffer({maker: {havenod: user1}, direction: "SELL", offerAmount: BigInt("300000000000"), assetCode: assetCode, priceMargin: 0.00}); - await makeOffer({maker: {havenod: user1}, direction: "SELL", offerAmount: BigInt("300000000000"), assetCode: assetCode, priceMargin: 0.02}); - await makeOffer({maker: {havenod: user1}, direction: "SELL", offerAmount: BigInt("400000000000"), assetCode: assetCode, priceMargin: 0.05}); + await makeOffer({maker: {havenod: user1}, direction: OfferDirection.BUY, offerAmount: BigInt("150000000000"), assetCode: assetCode, price: 17.0}); + await makeOffer({maker: {havenod: user1}, direction: OfferDirection.BUY, offerAmount: BigInt("150000000000"), assetCode: assetCode, price: 17.2}); + await makeOffer({maker: {havenod: user1}, direction: OfferDirection.BUY, offerAmount: BigInt("200000000000"), assetCode: assetCode, price: 17.3}); + await makeOffer({maker: {havenod: user1}, direction: OfferDirection.BUY, offerAmount: BigInt("150000000000"), assetCode: assetCode, price: 17.3}); + await makeOffer({maker: {havenod: user1}, direction: OfferDirection.SELL, offerAmount: BigInt("300000000000"), assetCode: assetCode, priceMargin: 0.00}); + await makeOffer({maker: {havenod: user1}, direction: OfferDirection.SELL, offerAmount: BigInt("300000000000"), assetCode: assetCode, priceMargin: 0.02}); + await makeOffer({maker: {havenod: user1}, direction: OfferDirection.SELL, offerAmount: BigInt("400000000000"), assetCode: assetCode, priceMargin: 0.05}); // get user2's market depth await wait(TestConfig.trade.maxTimePeerNoticeMs); @@ -1084,7 +1101,7 @@ test("Can get market depth (CI, sanity check)", async () => { expect(marketDepth.getSellPricesList().length).toEqual(marketDepth.getSellDepthList().length); // test buy prices and depths - const buyOffers = (await user1.getOffers(assetCode, "BUY")).concat(await user1.getMyOffers(assetCode, "BUY")).sort(function(a, b) { return parseFloat(a.getPrice()) - parseFloat(b.getPrice()) }); + const buyOffers = (await user1.getOffers(assetCode, OfferDirection.BUY)).concat(await user1.getMyOffers(assetCode, OfferDirection.BUY)).sort(function(a, b) { return parseFloat(a.getPrice()) - parseFloat(b.getPrice()) }); expect(marketDepth.getBuyPricesList()[0]).toEqual(1 / parseFloat(buyOffers[0].getPrice())); // TODO: price when posting offer is reversed. this assumes crypto counter currency expect(marketDepth.getBuyPricesList()[1]).toEqual(1 / parseFloat(buyOffers[1].getPrice())); expect(marketDepth.getBuyPricesList()[2]).toEqual(1 / parseFloat(buyOffers[2].getPrice())); @@ -1093,7 +1110,7 @@ test("Can get market depth (CI, sanity check)", async () => { expect(marketDepth.getBuyDepthList()[2]).toEqual(0.65); // test sell prices and depths - const sellOffers = (await user1.getOffers(assetCode, "SELL")).concat(await user1.getMyOffers(assetCode, "SELL")).sort(function(a, b) { return parseFloat(b.getPrice()) - parseFloat(a.getPrice()) }); + const sellOffers = (await user1.getOffers(assetCode, OfferDirection.SELL)).concat(await user1.getMyOffers(assetCode, OfferDirection.SELL)).sort(function(a, b) { return parseFloat(b.getPrice()) - parseFloat(a.getPrice()) }); expect(marketDepth.getSellPricesList()[0]).toEqual(1 / parseFloat(sellOffers[0].getPrice())); expect(marketDepth.getSellPricesList()[1]).toEqual(1 / parseFloat(sellOffers[1].getPrice())); expect(marketDepth.getSellPricesList()[2]).toEqual(1 / parseFloat(sellOffers[2].getPrice())); @@ -1345,7 +1362,7 @@ test("Can post and remove an offer (CI, sanity check)", async () => { await user1.removeOffer(offer.getId()); // offer is removed from my offers - if (getOffer(await user1.getMyOffers(assetCode, "BUY"), offer.getId())) throw new Error("Offer " + offer.getId() + " was found in my offers after removal"); + if (getOffer(await user1.getMyOffers(assetCode, OfferDirection.BUY), offer.getId())) throw new Error("Offer " + offer.getId() + " was found in my offers after removal"); // reserved balance released expect(BigInt((await user1.getBalances()).getAvailableBalance())).toEqual(availableBalanceBefore); @@ -1367,7 +1384,7 @@ test("Can post and remove an offer (CI, sanity check)", async () => { await user1.removeOffer(offer.getId()); // offer is removed from my offers - if (getOffer(await user1.getMyOffers(assetCode, "BUY"), offer.getId())) throw new Error("Offer " + offer.getId() + " was found in my offers after removal"); + if (getOffer(await user1.getMyOffers(assetCode, OfferDirection.BUY), offer.getId())) throw new Error("Offer " + offer.getId() + " was found in my offers after removal"); // reserved balance released expect(BigInt((await user1.getBalances()).getAvailableBalance())).toEqual(availableBalanceBefore); @@ -1393,7 +1410,7 @@ test("Can schedule offers with locked funds (CI)", async () => { // schedule offer const assetCode = "BCH"; - const direction = "BUY"; + const direction = OfferDirection.BUY; const ctx = new TradeContext({maker: {havenod: user3}, assetCode: assetCode, direction: direction, awaitFundsToMakeOffer: false}); let offer: OfferInfo = await makeOffer(ctx); assert.equal(offer.getState(), "SCHEDULED"); @@ -1498,7 +1515,7 @@ test("Cannot post offer exceeding trade limit (CI, sanity check)", async () => { try { await executeTrade({ offerAmount: BigInt("2100000000000"), - direction: "BUY", + direction: OfferDirection.BUY, assetCode: assetCode, makerPaymentAccountId: account.getId(), takeOffer: false @@ -1512,7 +1529,7 @@ test("Cannot post offer exceeding trade limit (CI, sanity check)", async () => { try { await executeTrade({ offerAmount: BigInt("2600000000000"), - direction: "SELL", + direction: OfferDirection.SELL, assetCode: assetCode, makerPaymentAccountId: account.getId(), takeOffer: false @@ -1525,7 +1542,7 @@ test("Cannot post offer exceeding trade limit (CI, sanity check)", async () => { // test that sell limit is higher than buy limit let offerId = await executeTrade({ offerAmount: BigInt("2100000000000"), - direction: "SELL", + direction: OfferDirection.SELL, assetCode: assetCode, makerPaymentAccountId: account.getId(), takeOffer: false @@ -1566,7 +1583,7 @@ test("Can complete all trade combinations (stress)", async () => { // generate trade context for each combination (buyer/seller, maker/taker, dispute(s), dispute winner) const ctxs: TradeContext[] = []; const MAKER_OPTS = [TradeRole.MAKER, TradeRole.TAKER]; - const DIRECTION_OPTS = ["BUY", "SELL"]; + const DIRECTION_OPTS = [OfferDirection.BUY, OfferDirection.SELL]; const BUYER_DISPUTE_OPTS = [DisputeContext.NONE, DisputeContext.OPEN_AFTER_DEPOSITS_UNLOCK, DisputeContext.OPEN_AFTER_PAYMENT_SENT]; const SELLER_DISPUTE_OPTS = [DisputeContext.NONE, DisputeContext.OPEN_AFTER_DEPOSITS_UNLOCK, DisputeContext.OPEN_AFTER_PAYMENT_SENT]; const DISPUTE_WINNER_OPTS = [DisputeResult.Winner.BUYER, DisputeResult.Winner.SELLER]; @@ -1858,7 +1875,7 @@ test("Invalidates offers when reserved funds are spent (CI)", async () => { // offer is available to peers await wait(TestConfig.trade.walletSyncPeriodMs * 2); - if (!getOffer(await user2.getOffers(assetCode, "BUY"), offer.getId())) throw new Error("Offer " + offer.getId() + " was not found in peer's offers after posting"); + if (!getOffer(await user2.getOffers(assetCode, OfferDirection.BUY), offer.getId())) throw new Error("Offer " + offer.getId() + " was not found in peer's offers after posting"); // spend one of offer's reserved outputs if (!reservedKeyImages.length) throw new Error("No reserved key images detected"); @@ -1871,10 +1888,10 @@ test("Invalidates offers when reserved funds are spent (CI)", async () => { // offer is removed from peer offers await wait(20000); - if (getOffer(await user2.getOffers(assetCode, "BUY"), offer.getId())) throw new Error("Offer " + offer.getId() + " was found in peer's offers after reserved funds spent"); + if (getOffer(await user2.getOffers(assetCode, OfferDirection.BUY), offer.getId())) throw new Error("Offer " + offer.getId() + " was found in peer's offers after reserved funds spent"); // offer is removed from my offers - if (getOffer(await user1.getMyOffers(assetCode, "BUY"), offer.getId())) throw new Error("Offer " + offer.getId() + " was found in my offers after reserved funds spent"); + if (getOffer(await user1.getMyOffers(assetCode, OfferDirection.BUY), offer.getId())) throw new Error("Offer " + offer.getId() + " was found in my offers after reserved funds spent"); // offer is automatically cancelled try { diff --git a/src/HavenoClient.ts b/src/HavenoClient.ts index e5d8f509..2dd03b4a 100644 --- a/src/HavenoClient.ts +++ b/src/HavenoClient.ts @@ -1,11 +1,11 @@ import console from "console"; -import HavenoError from "./utils/HavenoError"; +import HavenoError from "./types/HavenoError"; import HavenoUtils from "./utils/HavenoUtils"; import TaskLooper from "./utils/TaskLooper"; import type * as grpcWeb from "grpc-web"; import { GetVersionClient, AccountClient, MoneroConnectionsClient, DisputesClient, DisputeAgentsClient, NotificationsClient, WalletsClient, PriceClient, OffersClient, PaymentAccountsClient, TradesClient, ShutdownServerClient, MoneroNodeClient } from './protobuf/GrpcServiceClientPb'; import { GetVersionRequest, GetVersionReply, IsAppInitializedRequest, IsAppInitializedReply, RegisterDisputeAgentRequest, UnregisterDisputeAgentRequest, MarketPriceRequest, MarketPriceReply, MarketPricesRequest, MarketPricesReply, MarketPriceInfo, MarketDepthRequest, MarketDepthReply, MarketDepthInfo, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetMyOfferRequest, GetMyOfferReply, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentMethodsRequest, GetPaymentMethodsReply, GetPaymentAccountFormRequest, CreatePaymentAccountRequest, ValidateFormFieldRequest, CreatePaymentAccountReply, GetPaymentAccountFormReply, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, PostOfferRequest, PostOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetTradesRequest, GetTradesReply, GetXmrSeedRequest, GetXmrSeedReply, GetXmrPrimaryAddressRequest, GetXmrPrimaryAddressReply, GetXmrNewSubaddressRequest, GetXmrNewSubaddressReply, ConfirmPaymentSentRequest, ConfirmPaymentReceivedRequest, CompleteTradeRequest, XmrTx, GetXmrTxsRequest, GetXmrTxsReply, XmrDestination, CreateXmrTxRequest, CreateXmrTxReply, RelayXmrTxRequest, RelayXmrTxReply, CreateAccountRequest, AccountExistsRequest, AccountExistsReply, DeleteAccountRequest, OpenAccountRequest, IsAccountOpenRequest, IsAccountOpenReply, CloseAccountRequest, ChangePasswordRequest, BackupAccountRequest, BackupAccountReply, RestoreAccountRequest, StopRequest, NotificationMessage, RegisterNotificationListenerRequest, SendNotificationRequest, UrlConnection, AddConnectionRequest, RemoveConnectionRequest, GetConnectionRequest, GetConnectionsRequest, SetConnectionRequest, CheckConnectionRequest, CheckConnectionsReply, CheckConnectionsRequest, StartCheckingConnectionsRequest, StopCheckingConnectionsRequest, GetBestAvailableConnectionRequest, SetAutoSwitchRequest, CheckConnectionReply, GetConnectionsReply, GetConnectionReply, GetBestAvailableConnectionReply, GetDisputeRequest, GetDisputeReply, GetDisputesRequest, GetDisputesReply, OpenDisputeRequest, ResolveDisputeRequest, SendDisputeChatMessageRequest, SendChatMessageRequest, GetChatMessagesRequest, GetChatMessagesReply, StartMoneroNodeRequest, StopMoneroNodeRequest, IsMoneroNodeOnlineRequest, IsMoneroNodeOnlineReply, GetMoneroNodeSettingsRequest, GetMoneroNodeSettingsReply } from "./protobuf/grpc_pb"; -import { PaymentMethod, PaymentAccountForm, PaymentAccountFormField, PaymentAccount, PaymentAccountPayload, AvailabilityResult, Attachment, DisputeResult, Dispute, ChatMessage, MoneroNodeSettings } from "./protobuf/pb_pb"; +import { OfferDirection, PaymentMethod, PaymentAccountForm, PaymentAccountFormField, PaymentAccount, PaymentAccountPayload, AvailabilityResult, Attachment, DisputeResult, Dispute, ChatMessage, MoneroNodeSettings } from "./protobuf/pb_pb"; /** * Haveno daemon client. @@ -944,13 +944,13 @@ export default class HavenoClient { * Get available offers to buy or sell XMR. * * @param {string} assetCode - traded asset code - * @param {string|undefined} direction - "buy" or "sell" (default all) + * @param {OfferDirection|undefined} direction - "buy" or "sell" (default all) * @return {OfferInfo[]} the available offers */ - async getOffers(assetCode: string, direction?: string): Promise { + async getOffers(assetCode: string, direction?: OfferDirection): Promise { try { - if (!direction) return (await this.getOffers(assetCode, "buy")).concat(await this.getOffers(assetCode, "sell")); // TODO: implement in backend - return (await this._offersClient.getOffers(new GetOffersRequest().setDirection(direction).setCurrencyCode(assetCode), {password: this._password})).getOffersList(); + if (!direction) return (await this.getOffers(assetCode, OfferDirection.BUY)).concat(await this.getOffers(assetCode, OfferDirection.SELL)); // TODO: implement in backend + return (await this._offersClient.getOffers(new GetOffersRequest().setDirection(direction === OfferDirection.BUY ? "buy" : "sell").setCurrencyCode(assetCode), {password: this._password})).getOffersList(); } catch (e: any) { throw new HavenoError(e.message, e.code); } @@ -960,14 +960,14 @@ export default class HavenoClient { * Get the user's posted offers to buy or sell XMR. * * @param {string|undefined} assetCode - traded asset code - * @param {string|undefined} direction - "buy" or "sell" XMR (default all) + * @param {OfferDirection|undefined} direction - get offers to buy or sell XMR (default all) * @return {OfferInfo[]} the user's created offers */ - async getMyOffers(assetCode?: string, direction?: string): Promise { + async getMyOffers(assetCode?: string, direction?: OfferDirection): Promise { try { const req = new GetOffersRequest(); if (assetCode) req.setCurrencyCode(assetCode); - if (direction) req.setDirection(direction); + if (direction) req.setDirection(direction === OfferDirection.BUY ? "buy" : "sell"); // TODO: request should use OfferDirection too? return (await this._offersClient.getMyOffers(req, {password: this._password})).getOffersList(); } catch (e: any) { throw new HavenoError(e.message, e.code); @@ -991,7 +991,7 @@ export default class HavenoClient { /** * Post an offer. * - * @param {string} direction - "buy" or "sell" XMR + * @param {OfferDirection} direction - "buy" or "sell" XMR * @param {bigint} amount - amount of XMR to trade * @param {string} assetCode - asset code to trade for XMR * @param {string} paymentAccountId - payment account id @@ -1003,7 +1003,7 @@ export default class HavenoClient { * @param {number} reserveExactAmount - reserve exact amount needed for offer, incurring on-chain transaction and 10 confirmations before the offer goes live (default = false) * @return {OfferInfo} the posted offer */ - async postOffer(direction: string, + async postOffer(direction: OfferDirection, amount: bigint, assetCode: string, paymentAccountId: string, @@ -1016,7 +1016,7 @@ export default class HavenoClient { console.log("Posting offer with security deposit %: " + securityDepositPct) try { const request = new PostOfferRequest() - .setDirection(direction) + .setDirection(direction === OfferDirection.BUY ? "buy" : "sell") .setAmount(amount.toString()) .setCurrencyCode(assetCode) .setPaymentAccountId(paymentAccountId) diff --git a/src/index.ts b/src/index.ts index d58b6d63..0b68fe9a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import HavenoClient from "./HavenoClient"; -import HavenoError from "./utils/HavenoError"; +import HavenoError from "./types/HavenoError"; import HavenoUtils from "./utils/HavenoUtils"; export { HavenoClient }; diff --git a/src/utils/HavenoError.ts b/src/types/HavenoError.ts similarity index 100% rename from src/utils/HavenoError.ts rename to src/types/HavenoError.ts