@velocity-exchange/sdk 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/lib/browser/adminClient.d.ts +4 -3
  2. package/lib/browser/adminClient.js +5 -13
  3. package/lib/browser/equityFloorManager.d.ts +9 -5
  4. package/lib/browser/equityFloorManager.js +9 -5
  5. package/lib/browser/idl/velocity.d.ts +119 -3
  6. package/lib/browser/idl/velocity.json +119 -3
  7. package/lib/browser/index.d.ts +2 -0
  8. package/lib/browser/index.js +3 -0
  9. package/lib/browser/jupiter/jupiterClient.d.ts +31 -27
  10. package/lib/browser/jupiter/jupiterClient.js +61 -35
  11. package/lib/browser/math/amm.d.ts +6 -5
  12. package/lib/browser/math/amm.js +39 -19
  13. package/lib/browser/math/margin.d.ts +12 -10
  14. package/lib/browser/math/margin.js +16 -14
  15. package/lib/browser/math/superStake.d.ts +9 -9
  16. package/lib/browser/math/superStake.js +8 -8
  17. package/lib/browser/swap/UnifiedSwapClient.d.ts +38 -76
  18. package/lib/browser/swap/UnifiedSwapClient.js +46 -117
  19. package/lib/browser/swap/routeInstructions.d.ts +19 -0
  20. package/lib/browser/swap/routeInstructions.js +43 -0
  21. package/lib/browser/swap/types.d.ts +196 -0
  22. package/lib/browser/swap/types.js +88 -0
  23. package/lib/browser/titan/titanClient.d.ts +32 -68
  24. package/lib/browser/titan/titanClient.js +128 -102
  25. package/lib/browser/types.d.ts +2 -0
  26. package/lib/browser/user.d.ts +16 -18
  27. package/lib/browser/user.js +24 -26
  28. package/lib/browser/velocityClient.d.ts +122 -101
  29. package/lib/browser/velocityClient.js +221 -289
  30. package/lib/node/adminClient.d.ts +4 -3
  31. package/lib/node/adminClient.d.ts.map +1 -1
  32. package/lib/node/adminClient.js +5 -13
  33. package/lib/node/equityFloorManager.d.ts +9 -5
  34. package/lib/node/equityFloorManager.d.ts.map +1 -1
  35. package/lib/node/equityFloorManager.js +9 -5
  36. package/lib/node/idl/velocity.d.ts +119 -3
  37. package/lib/node/idl/velocity.d.ts.map +1 -1
  38. package/lib/node/idl/velocity.json +119 -3
  39. package/lib/node/index.d.ts +2 -0
  40. package/lib/node/index.d.ts.map +1 -1
  41. package/lib/node/index.js +3 -0
  42. package/lib/node/jupiter/jupiterClient.d.ts +31 -27
  43. package/lib/node/jupiter/jupiterClient.d.ts.map +1 -1
  44. package/lib/node/jupiter/jupiterClient.js +61 -35
  45. package/lib/node/math/amm.d.ts +6 -5
  46. package/lib/node/math/amm.d.ts.map +1 -1
  47. package/lib/node/math/amm.js +39 -19
  48. package/lib/node/math/margin.d.ts +12 -10
  49. package/lib/node/math/margin.d.ts.map +1 -1
  50. package/lib/node/math/margin.js +16 -14
  51. package/lib/node/math/superStake.d.ts +9 -9
  52. package/lib/node/math/superStake.d.ts.map +1 -1
  53. package/lib/node/math/superStake.js +8 -8
  54. package/lib/node/swap/UnifiedSwapClient.d.ts +38 -76
  55. package/lib/node/swap/UnifiedSwapClient.d.ts.map +1 -1
  56. package/lib/node/swap/UnifiedSwapClient.js +46 -117
  57. package/lib/node/swap/routeInstructions.d.ts +20 -0
  58. package/lib/node/swap/routeInstructions.d.ts.map +1 -0
  59. package/lib/node/swap/routeInstructions.js +43 -0
  60. package/lib/node/swap/types.d.ts +197 -0
  61. package/lib/node/swap/types.d.ts.map +1 -0
  62. package/lib/node/swap/types.js +88 -0
  63. package/lib/node/titan/titanClient.d.ts +32 -68
  64. package/lib/node/titan/titanClient.d.ts.map +1 -1
  65. package/lib/node/titan/titanClient.js +128 -102
  66. package/lib/node/types.d.ts +2 -0
  67. package/lib/node/types.d.ts.map +1 -1
  68. package/lib/node/user.d.ts +16 -18
  69. package/lib/node/user.d.ts.map +1 -1
  70. package/lib/node/user.js +24 -26
  71. package/lib/node/velocityClient.d.ts +122 -101
  72. package/lib/node/velocityClient.d.ts.map +1 -1
  73. package/lib/node/velocityClient.js +221 -289
  74. package/package.json +1 -1
@@ -45,7 +45,7 @@ const spotPosition_1 = require("./math/spotPosition");
45
45
  const market_1 = require("./math/market");
46
46
  const fetch_1 = require("./accounts/fetch");
47
47
  const spotMarket_1 = require("./math/spotMarket");
48
- const jupiterClient_1 = require("./jupiter/jupiterClient");
48
+ const UnifiedSwapClient_1 = require("./swap/UnifiedSwapClient");
49
49
  const memcmp_1 = require("./memcmp");
50
50
  const marinade_1 = require("./marinade");
51
51
  const orderParams_1 = require("./orderParams");
@@ -61,8 +61,6 @@ const oracleId_1 = require("./oracles/oracleId");
61
61
  const utils_3 = require("./oracles/utils");
62
62
  const orders_1 = require("./math/orders");
63
63
  const builder_1 = require("./math/builder");
64
- const titanClient_1 = require("./titan/titanClient");
65
- const UnifiedSwapClient_1 = require("./swap/UnifiedSwapClient");
66
64
  /**
67
65
  * # VelocityClient
68
66
  * Main entry point for interacting with Velocity Exchange from TypeScript. A single instance wraps
@@ -932,6 +930,75 @@ class VelocityClient {
932
930
  });
933
931
  return resizeUserAccountIx;
934
932
  }
933
+ /**
934
+ * Grows a zero-copy account (User, PerpMarket, SpotMarket, State, ...) to the size the
935
+ * deployed program compiles in for its type, as part of an account-size migration after a
936
+ * program upgrade that appended fields. Requires `HotRole.AccountExtension` (cold, warm, or
937
+ * the configured account-extension hot key) — the wallet must hold that role. The payer
938
+ * covers the rent-exempt shortfall and the program zero-fills the new tail. No-op when the
939
+ * account is already at size, so cranking is idempotent. See `docs/ACCOUNT-EXTENSION.md`.
940
+ * @param account - The account to extend.
941
+ * @param txParams - Optional compute-unit/priority-fee overrides for the transaction.
942
+ * @returns The transaction signature.
943
+ */
944
+ async extendAccount(account, txParams) {
945
+ const extendAccountIx = await this.getExtendAccountIx(account);
946
+ const tx = await this.buildTransaction([extendAccountIx], txParams);
947
+ const { txSig } = await this.sendTransaction(tx, [], this.opts);
948
+ return txSig;
949
+ }
950
+ /**
951
+ * Builds the `extendAccount` instruction. See `extendAccount` for semantics.
952
+ * @param account - The account to extend.
953
+ * @param payer - Rent payer; defaults to the wallet.
954
+ * @param authority - `HotRole.AccountExtension` holder; defaults to the wallet.
955
+ * @returns The extend instruction.
956
+ */
957
+ async getExtendAccountIx(account, payer, authority) {
958
+ return await this.program.instruction.extendAccount({
959
+ accounts: {
960
+ state: await this.getStatePublicKey(),
961
+ payer: payer !== null && payer !== void 0 ? payer : this.wallet.publicKey,
962
+ authority: authority !== null && authority !== void 0 ? authority : this.wallet.publicKey,
963
+ account,
964
+ systemProgram: web3_js_1.SystemProgram.programId,
965
+ },
966
+ });
967
+ }
968
+ /**
969
+ * Devnet/test only: grows a zero-copy account to an arbitrary larger size, simulating the
970
+ * state right after a struct-growing program upgrade. Requires `HotRole.AccountExtension`
971
+ * like `extendAccount`. The instruction is compiled out of mainnet program builds.
972
+ * @param account - The account to extend.
973
+ * @param newLen - Target account data length in bytes (must be >= the current length).
974
+ * @param txParams - Optional compute-unit/priority-fee overrides for the transaction.
975
+ * @returns The transaction signature.
976
+ */
977
+ async extendAccountDevnet(account, newLen, txParams) {
978
+ const extendAccountIx = await this.getExtendAccountDevnetIx(account, newLen);
979
+ const tx = await this.buildTransaction([extendAccountIx], txParams);
980
+ const { txSig } = await this.sendTransaction(tx, [], this.opts);
981
+ return txSig;
982
+ }
983
+ /**
984
+ * Builds the `extendAccountDevnet` instruction. See `extendAccountDevnet` for semantics.
985
+ * @param account - The account to extend.
986
+ * @param newLen - Target account data length in bytes.
987
+ * @param payer - Rent payer; defaults to the wallet.
988
+ * @param authority - `HotRole.AccountExtension` holder; defaults to the wallet.
989
+ * @returns The extend instruction.
990
+ */
991
+ async getExtendAccountDevnetIx(account, newLen, payer, authority) {
992
+ return await this.program.instruction.extendAccountDevnet(new anchor_1.BN(newLen), {
993
+ accounts: {
994
+ state: await this.getStatePublicKey(),
995
+ payer: payer !== null && payer !== void 0 ? payer : this.wallet.publicKey,
996
+ authority: authority !== null && authority !== void 0 ? authority : this.wallet.publicKey,
997
+ account,
998
+ systemProgram: web3_js_1.SystemProgram.programId,
999
+ },
1000
+ });
1001
+ }
935
1002
  /**
936
1003
  * Initializes `authority`'s `SignedMsgWsDelegates` account, which lists wallets authorized to
937
1004
  * co-sign/relay that authority's signed-message ("swift") orders over a websocket connection
@@ -3155,16 +3222,19 @@ class VelocityClient {
3155
3222
  * @param fromSubAccountId - Sub-account id to debit.
3156
3223
  * @param toSubAccountId - Sub-account id to credit.
3157
3224
  * @param equityFloorDelta - Equity floor (QUOTE_PRECISION) to move from the debited to the credited
3158
- * sub-account along with the funds, keeping the sum of floors constant. The debited side must not
3225
+ * sub-account along with the funds, keeping the sum of floors constant. A proportional share of
3226
+ * the debited side's `equityFloorBuffer` travels with the floor (rounded up on the debited side,
3227
+ * so shedding the whole floor also sheds the whole buffer; no orphan buffer is left on a
3228
+ * check-disabled sub-account), keeping the sum of buffers constant too. The debited side must not
3159
3229
  * already be below the floor being reduced (a below-floor sub-account cannot shed floor to defuse a
3160
- * pending equity-breaker trip), must stay at/above its reduced floor plus its `equityFloorBuffer`,
3161
- * and the credited side's collateral (after the transfer lands) must back its increased floor plus
3162
- * its own buffer, else the transfer reverts with `InvalidEquityFloorTransfer`. Pass `'auto'`
3230
+ * pending equity-breaker trip), must stay at/above its reduced floor plus its reduced buffer,
3231
+ * and the credited side's net equity (after the transfer lands) must back its increased floor plus
3232
+ * its increased buffer, else the transfer reverts with `InvalidEquityFloorTransfer`. Pass `'auto'`
3163
3233
  * (quote market only) to move the minimal floor needed for the debited side to stay at/above its
3164
- * buffered floor: `max(0, amount - max(0, collateral - (floor + buffer)))`, capped at the debited
3234
+ * buffered floor: `max(0, amount - max(0, netEquity - (floor + buffer)))`, capped at the debited
3165
3235
  * side's floor (see `calculateEquityFloorAutoDelta`). The auto delta never exceeds `amount`, so the
3166
3236
  * credited side stays backed whenever it was before. Client-side pricing can differ slightly from
3167
- * the on-chain strict check at the exact boundary; retry with an explicit padded delta if an
3237
+ * the onchain check at the exact boundary; retry with an explicit padded delta if an
3168
3238
  * `'auto'` transfer reverts. Defaults to zero.
3169
3239
  * @param txParams - Optional compute-unit/priority-fee overrides for the transaction.
3170
3240
  * @returns The transaction signature.
@@ -3203,7 +3273,7 @@ class VelocityClient {
3203
3273
  }
3204
3274
  const fromUserClass = this.getUser(fromSubAccountId, this.authority);
3205
3275
  const fromUserAccount = fromUserClass.getUserAccountOrThrow();
3206
- resolvedFloorDelta = (0, margin_1.calculateEquityFloorAutoDelta)(amount, fromUserClass.getTotalCollateral('Initial', true), fromUserAccount.equityFloor, fromUserAccount.equityFloorBuffer);
3276
+ resolvedFloorDelta = (0, margin_1.calculateEquityFloorAutoDelta)(amount, fromUserClass.getNetUsdValue(), fromUserAccount.equityFloor, fromUserAccount.equityFloorBuffer);
3207
3277
  }
3208
3278
  else {
3209
3279
  resolvedFloorDelta = equityFloorDelta;
@@ -4717,234 +4787,182 @@ class VelocityClient {
4717
4787
  * instructions in a single transaction, so the swap is settled directly against the user's
4718
4788
  * deposits/vault balances rather than the wallet's own token accounts. Sends and confirms the
4719
4789
  * transaction.
4720
- * @param swapClient - Swap client used to fetch routes/instructions (`UnifiedSwapClient` or a
4721
- * `TitanClient`); dispatches to `getSwapIxV2` or `getTitanSwapIx` respectively.
4722
- * @param jupiterClient - @deprecated Use `swapClient` instead. When passed (and `swapClient` is
4723
- * not), dispatches to `getJupiterSwapIxV6`.
4790
+ * @param swapClient - Provider used to quote the swap and build its route: a
4791
+ * `UnifiedSwapClient`, or a `TitanClient`/`JupiterClient` directly. See `getProviderSwapIx`.
4792
+ * @param jupiterClient - @deprecated Use `swapClient` instead. Used only when `swapClient` is
4793
+ * not passed.
4724
4794
  * @param outMarketIndex - Spot market index of the token being bought.
4725
4795
  * @param inMarketIndex - Spot market index of the token being sold.
4726
4796
  * @param outAssociatedTokenAccount - Token account to receive the bought token; created
4727
4797
  * idempotently if omitted.
4728
4798
  * @param inAssociatedTokenAccount - Token account to source the sold token from; created
4729
4799
  * idempotently if omitted.
4730
- * @param amount - Amount of the "in" token (or "out" token when `swapMode` is `ExactOut`, in
4731
- * which case this is the desired output amount), in the token's own mint decimals — not a
4732
- * fixed protocol precision.
4800
+ * @param amount - Amount of the "in" token (or "out" token when the effective mode is
4801
+ * `ExactOut`, in which case this is the desired output amount), in the token's own mint
4802
+ * decimals — not a fixed protocol precision.
4733
4803
  * @param slippageBps - Max slippage in basis points passed to the swap provider's routing API.
4734
- * @param swapMode - `ExactIn` (default) or `ExactOut`.
4804
+ * @param swapMode - `ExactIn` (default) or `ExactOut`. Ignored when `quote` is passed — the
4805
+ * quote's own mode wins.
4735
4806
  * @param reduceOnly - Whether the in/out token's position on the velocity account must reduce
4736
4807
  * (not flip sign); enforced by `endSwap` after the swap completes.
4737
- * @param v6 - @deprecated Use `quote` instead. Pre-fetched Jupiter v6 quote response.
4738
- * @param quote - Pre-fetched quote response (skips an extra round-trip to the swap provider).
4808
+ * @param quote - Pre-fetched quote (skips an extra round-trip to the swap provider). Must be
4809
+ * for this pair and this `amount`.
4739
4810
  * @param txParams - Optional compute-unit/priority-fee overrides.
4740
- * @throws If neither `swapClient` nor `jupiterClient` is provided, or if `swapClient` is not a
4741
- * recognized client type.
4811
+ * @throws If neither `swapClient` nor `jupiterClient` is provided.
4742
4812
  * @returns The transaction signature.
4743
4813
  */
4744
- async swap({ swapClient, jupiterClient, outMarketIndex, inMarketIndex, outAssociatedTokenAccount, inAssociatedTokenAccount, amount, slippageBps, swapMode, reduceOnly, txParams, v6, quote, onlyDirectRoutes = false, }) {
4745
- // Handle backward compatibility: use jupiterClient if swapClient is not provided
4746
- const clientToUse = swapClient || jupiterClient;
4747
- if (!clientToUse) {
4814
+ async swap({ swapClient, outMarketIndex, inMarketIndex, outAssociatedTokenAccount, inAssociatedTokenAccount, amount, slippageBps, swapMode, reduceOnly, txParams, quote, onlyDirectRoutes = false, }) {
4815
+ if (!swapClient) {
4748
4816
  throw new Error('Either swapClient or jupiterClient must be provided');
4749
4817
  }
4750
- let res;
4751
- // Use unified SwapClient if available
4752
- if (clientToUse instanceof UnifiedSwapClient_1.UnifiedSwapClient) {
4753
- res = await this.getSwapIxV2({
4754
- swapClient: clientToUse,
4755
- outMarketIndex,
4756
- inMarketIndex,
4757
- outAssociatedTokenAccount,
4758
- inAssociatedTokenAccount,
4759
- amount,
4760
- slippageBps,
4761
- swapMode,
4762
- onlyDirectRoutes,
4763
- reduceOnly,
4764
- quote,
4765
- v6,
4766
- });
4767
- }
4768
- else if (clientToUse instanceof titanClient_1.TitanClient) {
4769
- res = await this.getTitanSwapIx({
4770
- titanClient: clientToUse,
4771
- outMarketIndex,
4772
- inMarketIndex,
4773
- outAssociatedTokenAccount,
4774
- inAssociatedTokenAccount,
4775
- amount,
4776
- slippageBps,
4777
- swapMode,
4778
- onlyDirectRoutes,
4779
- reduceOnly,
4780
- });
4781
- }
4782
- else if (clientToUse instanceof jupiterClient_1.JupiterClient) {
4783
- const quoteToUse = quote !== null && quote !== void 0 ? quote : v6 === null || v6 === void 0 ? void 0 : v6.quote;
4784
- res = await this.getJupiterSwapIxV6({
4785
- jupiterClient: clientToUse,
4786
- outMarketIndex,
4787
- inMarketIndex,
4788
- outAssociatedTokenAccount,
4789
- inAssociatedTokenAccount,
4790
- amount,
4791
- slippageBps,
4792
- swapMode,
4793
- quote: quoteToUse,
4794
- reduceOnly,
4795
- onlyDirectRoutes,
4796
- });
4797
- }
4798
- else {
4799
- throw new Error('Invalid swap client type. Must be SwapClient, TitanClient, or JupiterClient.');
4800
- }
4801
- const ixs = res.ixs;
4802
- const lookupTables = res.lookupTables;
4818
+ const { ixs, lookupTables } = await this.getProviderSwapIx({
4819
+ swapProvider: swapClient,
4820
+ outMarketIndex,
4821
+ inMarketIndex,
4822
+ outAssociatedTokenAccount,
4823
+ inAssociatedTokenAccount,
4824
+ amount,
4825
+ slippageBps,
4826
+ swapMode,
4827
+ onlyDirectRoutes,
4828
+ reduceOnly,
4829
+ quote,
4830
+ });
4803
4831
  const tx = (await this.buildTransaction(ixs, txParams, 0, lookupTables));
4804
4832
  const { txSig, slot } = await this.sendTransaction(tx);
4805
4833
  this.cacheSpotMarketSlot(slot, outMarketIndex, inMarketIndex);
4806
4834
  return txSig;
4807
4835
  }
4808
4836
  /**
4809
- * Builds the instruction list for a Titan-routed swap: creates any missing associated token
4810
- * accounts, wraps Titan's routing instructions between `beginSwap`/`endSwap`. See `swap` for
4811
- * parameter semantics; `amount` is in the "in" token's mint decimals.
4812
- * @param userAccountPublicKey - Optional user account override (e.g. when the account is being
4813
- * created in the same transaction and not yet resolvable via `getUserAccountPublicKey`).
4814
- * @returns `ixs` — instruction list (ATA creation, `beginSwap`, Titan swap instructions,
4815
- * `endSwap`, in order) and `lookupTables` needed to fit it in a versioned transaction.
4837
+ * Throws unless a quote swaps exactly the pair the `beginSwap`/`endSwap` pair is being built
4838
+ * for. A mismatched quote routes and executes normally, but deposits its output into a token
4839
+ * account `endSwap` isn't watching, so it reverts with `InvalidSwap: amount_out must be
4840
+ * greater than 0` only after the funds have already moved.
4816
4841
  */
4817
- async getTitanSwapIx({ titanClient, outMarketIndex, inMarketIndex, outAssociatedTokenAccount, inAssociatedTokenAccount, amount, slippageBps, swapMode, onlyDirectRoutes, reduceOnly, userAccountPublicKey, }) {
4818
- const outMarket = this.getSpotMarketAccountOrThrow(outMarketIndex);
4819
- const inMarket = this.getSpotMarketAccountOrThrow(inMarketIndex);
4842
+ assertQuoteMatchesMarkets(quote, inMarket, outMarket) {
4843
+ if (!new web3_js_1.PublicKey(quote.inputMint).equals(inMarket.mint)) {
4844
+ throw new Error(`Quote sells ${quote.inputMint} but spot market ${inMarket.marketIndex} is ${inMarket.mint.toString()}.`);
4845
+ }
4846
+ if (!new web3_js_1.PublicKey(quote.outputMint).equals(outMarket.mint)) {
4847
+ throw new Error(`Quote buys ${quote.outputMint} but spot market ${outMarket.marketIndex} is ${outMarket.mint.toString()}.`);
4848
+ }
4849
+ }
4850
+ /**
4851
+ * Throws unless a quote is for the size the caller asked to swap. `beginSwap` releases funds
4852
+ * sized off the quote, so a quote for a different size moves the wrong amount out of the user's
4853
+ * deposits.
4854
+ */
4855
+ assertQuoteMatchesAmount(quote, amount, swapMode) {
4820
4856
  const isExactOut = swapMode === 'ExactOut';
4821
- const exactOutBufferedAmountIn = amount.muln(1001).divn(1000); // Add 10bp buffer
4822
- const preInstructions = [];
4823
- if (!outAssociatedTokenAccount) {
4824
- const tokenProgram = this.getTokenProgramForSpotMarket(outMarket);
4825
- outAssociatedTokenAccount = await this.getAssociatedTokenAccount(outMarket.marketIndex, false, tokenProgram);
4826
- const accountInfo = await this.connection.getAccountInfo(outAssociatedTokenAccount);
4827
- if (!accountInfo) {
4828
- preInstructions.push(this.createAssociatedTokenAccountIdempotentInstruction(outAssociatedTokenAccount, this.provider.wallet.publicKey, this.provider.wallet.publicKey, outMarket.mint, tokenProgram));
4829
- }
4857
+ const quoted = isExactOut ? quote.outAmount : quote.inAmount;
4858
+ if (quoted !== amount.toString()) {
4859
+ throw new Error(`Quote is for ${quoted} ${isExactOut ? 'out' : 'in'} but the swap asked for ${amount.toString()} (${swapMode}).`);
4830
4860
  }
4831
- if (!inAssociatedTokenAccount) {
4832
- const tokenProgram = this.getTokenProgramForSpotMarket(inMarket);
4833
- inAssociatedTokenAccount = await this.getAssociatedTokenAccount(inMarket.marketIndex, false, tokenProgram);
4834
- const accountInfo = await this.connection.getAccountInfo(inAssociatedTokenAccount);
4835
- if (!accountInfo) {
4836
- preInstructions.push(this.createAssociatedTokenAccountIdempotentInstruction(inAssociatedTokenAccount, this.provider.wallet.publicKey, this.provider.wallet.publicKey, inMarket.mint, tokenProgram));
4837
- }
4861
+ }
4862
+ /**
4863
+ * Resolves the wallet's associated token account for a spot market, plus the instruction that
4864
+ * creates it when it doesn't exist yet.
4865
+ */
4866
+ async getOrCreateSwapTokenAccount(market) {
4867
+ const tokenProgram = this.getTokenProgramForSpotMarket(market);
4868
+ const tokenAccount = await this.getAssociatedTokenAccount(market.marketIndex, false, tokenProgram);
4869
+ if (await this.connection.getAccountInfo(tokenAccount)) {
4870
+ return { tokenAccount };
4838
4871
  }
4839
- const { beginSwapIx, endSwapIx } = await this.getSwapIx({
4840
- outMarketIndex,
4841
- inMarketIndex,
4842
- amountIn: isExactOut ? exactOutBufferedAmountIn : amount,
4843
- inTokenAccount: inAssociatedTokenAccount,
4844
- outTokenAccount: outAssociatedTokenAccount,
4845
- reduceOnly,
4846
- userAccountPublicKey,
4847
- });
4848
- const { transactionMessage, lookupTables } = await titanClient.getSwap({
4849
- inputMint: inMarket.mint,
4850
- outputMint: outMarket.mint,
4851
- amount,
4852
- userPublicKey: this.provider.wallet.publicKey,
4853
- slippageBps,
4854
- swapMode: isExactOut ? titanClient_1.SwapMode.ExactOut : titanClient_1.SwapMode.ExactIn,
4855
- onlyDirectRoutes,
4856
- sizeConstraint: utils_2.MAX_TX_BYTE_SIZE - 375, // buffer for velocity instructions
4857
- });
4858
- const titanInstructions = titanClient.getTitanInstructions({
4859
- transactionMessage,
4860
- inputMint: inMarket.mint,
4861
- outputMint: outMarket.mint,
4862
- });
4863
- const ixs = [
4864
- ...preInstructions,
4865
- beginSwapIx,
4866
- ...titanInstructions,
4867
- endSwapIx,
4868
- ];
4869
- return { ixs, lookupTables };
4872
+ return {
4873
+ tokenAccount,
4874
+ createIx: this.createAssociatedTokenAccountIdempotentInstruction(tokenAccount, this.provider.wallet.publicKey, this.provider.wallet.publicKey, market.mint, tokenProgram),
4875
+ };
4870
4876
  }
4871
4877
  /**
4872
- * Builds the instruction list for a Jupiter v6-routed swap: fetches a quote if none is passed,
4873
- * creates any missing associated token accounts, and wraps Jupiter's routing instructions
4874
- * between `beginSwap`/`endSwap`. See `swap` for parameter semantics; `amount` is in the "in"
4875
- * token's mint decimals.
4878
+ * Builds the instruction list for a swap routed through any `SwapProvider` (Jupiter, Titan, or
4879
+ * a `UnifiedSwapClient` wrapping either): creates any missing associated token accounts and
4880
+ * wraps the provider's routing instructions between `beginSwap`/`endSwap`.
4881
+ * @param swapProvider - Provider that quotes the swap and builds its route instructions.
4882
+ * @param outMarketIndex - Spot market index of the token being bought.
4883
+ * @param inMarketIndex - Spot market index of the token being sold.
4884
+ * @param outAssociatedTokenAccount - Token account to receive the bought token; created
4885
+ * idempotently if omitted.
4886
+ * @param inAssociatedTokenAccount - Token account to source the sold token from; created
4887
+ * idempotently if omitted.
4888
+ * @param amount - Amount in the "in" token's mint decimals, or the "out" token's when the
4889
+ * effective mode is `ExactOut`.
4890
+ * @param slippageBps - Max slippage in basis points; only used when a quote has to be fetched.
4891
+ * @param swapMode - `ExactIn` (default) or `ExactOut`. The mode a quote is fetched at; the
4892
+ * resulting quote's own mode is what sizes the swap, so it is ignored when `quote` is passed.
4893
+ * @param onlyDirectRoutes - Restricts a fetched quote to single-hop routes.
4894
+ * @param maxAccounts - Account budget for a fetched route.
4895
+ * @param reduceOnly - Which side must not increase in magnitude; enforced by `endSwap`.
4896
+ * @param quote - Pre-fetched quote. Authoritative when passed: its `swapMode` is the effective
4897
+ * mode, and it must be for this pair and this `amount`.
4876
4898
  * @param userAccountPublicKey - Optional user account override (e.g. when the account is being
4877
- * created in the same transaction).
4878
- * @throws If no quote is passed and Jupiter's quote API returns none.
4879
- * @returns `ixs` instruction list (ATA creation, `beginSwap`, Jupiter swap instructions,
4880
- * `endSwap`, in order) and `lookupTables` needed to fit it in a versioned transaction.
4899
+ * created in the same transaction and not yet resolvable via `getUserAccountPublicKey`).
4900
+ * @throws If the quote passed in or freshly fetched is for a different pair or a different
4901
+ * size than the swap being built.
4902
+ * @returns `ixs` ATA creation, `beginSwap`, the route's instructions, `endSwap`, in order
4903
+ * and the `lookupTables` needed to fit them in a versioned transaction.
4881
4904
  */
4882
- async getJupiterSwapIxV6({ jupiterClient, outMarketIndex, inMarketIndex, outAssociatedTokenAccount, inAssociatedTokenAccount, amount, slippageBps, swapMode, onlyDirectRoutes, quote, reduceOnly, userAccountPublicKey, }) {
4905
+ async getProviderSwapIx({ swapProvider, outMarketIndex, inMarketIndex, outAssociatedTokenAccount, inAssociatedTokenAccount, amount, slippageBps, swapMode, onlyDirectRoutes, maxAccounts, reduceOnly, quote, userAccountPublicKey, }) {
4906
+ var _a, _b;
4883
4907
  const outMarket = this.getSpotMarketAccountOrThrow(outMarketIndex);
4884
4908
  const inMarket = this.getSpotMarketAccountOrThrow(inMarketIndex);
4885
- if (!quote) {
4886
- const fetchedQuote = await jupiterClient.getQuote({
4887
- inputMint: inMarket.mint,
4888
- outputMint: outMarket.mint,
4889
- amount,
4890
- slippageBps,
4891
- swapMode,
4892
- onlyDirectRoutes,
4893
- });
4894
- quote = fetchedQuote;
4895
- }
4896
- if (!quote) {
4897
- throw new Error('Could not fetch swap quote. Please try again.');
4898
- }
4899
- const isExactOut = swapMode === 'ExactOut' || quote.swapMode === 'ExactOut';
4900
- const amountIn = new anchor_1.BN(quote.inAmount);
4901
- const exactOutBufferedAmountIn = amountIn.muln(1001).divn(1000); // Add 10bp buffer
4902
- const transaction = await jupiterClient.getSwap({
4903
- quote,
4904
- userPublicKey: this.provider.wallet.publicKey,
4905
- slippageBps,
4906
- });
4907
- const { transactionMessage, lookupTables } = await jupiterClient.getTransactionMessageAndLookupTables({
4908
- transaction,
4909
- });
4910
- const jupiterInstructions = jupiterClient.getJupiterInstructions({
4911
- transactionMessage,
4909
+ const quoteToUse = quote !== null && quote !== void 0 ? quote : (await swapProvider.getQuote({
4912
4910
  inputMint: inMarket.mint,
4913
4911
  outputMint: outMarket.mint,
4914
- });
4912
+ amount,
4913
+ userPublicKey: this.provider.wallet.publicKey,
4914
+ slippageBps,
4915
+ swapMode: swapMode !== null && swapMode !== void 0 ? swapMode : 'ExactIn',
4916
+ onlyDirectRoutes,
4917
+ maxAccounts,
4918
+ sizeConstraint: UnifiedSwapClient_1.DEFAULT_ROUTE_SIZE_CONSTRAINT,
4919
+ }));
4920
+ // The quote's own mode decides which side `amount` names and how `beginSwap` is sized, so it
4921
+ // wins over `swapMode` whether the quote was passed in or just fetched — a provider that
4922
+ // answers in the other mode is then caught by the size check rather than sizing the wrong side.
4923
+ const effectiveSwapMode = (_b = (_a = quoteToUse.swapMode) !== null && _a !== void 0 ? _a : swapMode) !== null && _b !== void 0 ? _b : 'ExactIn';
4924
+ // Both paths, identically: a freshly fetched quote is no more trustworthy about what it
4925
+ // priced than one handed to us.
4926
+ this.assertQuoteMatchesMarkets(quoteToUse, inMarket, outMarket);
4927
+ this.assertQuoteMatchesAmount(quoteToUse, amount, effectiveSwapMode);
4928
+ // Size `beginSwap` off the quote's own input: under ExactOut `amount` is the requested
4929
+ // output, so buffering it would be a guess at what the route consumes.
4930
+ const quotedAmountIn = new anchor_1.BN(quoteToUse.inAmount);
4931
+ const amountIn = effectiveSwapMode === 'ExactOut'
4932
+ ? quotedAmountIn.muln(1001).divn(1000) // Add 10bp buffer
4933
+ : quotedAmountIn;
4915
4934
  const preInstructions = [];
4916
4935
  if (!outAssociatedTokenAccount) {
4917
- const tokenProgram = this.getTokenProgramForSpotMarket(outMarket);
4918
- outAssociatedTokenAccount = await this.getAssociatedTokenAccount(outMarket.marketIndex, false, tokenProgram);
4919
- const accountInfo = await this.connection.getAccountInfo(outAssociatedTokenAccount);
4920
- if (!accountInfo) {
4921
- preInstructions.push(this.createAssociatedTokenAccountIdempotentInstruction(outAssociatedTokenAccount, this.provider.wallet.publicKey, this.provider.wallet.publicKey, outMarket.mint, tokenProgram));
4936
+ const { tokenAccount, createIx } = await this.getOrCreateSwapTokenAccount(outMarket);
4937
+ outAssociatedTokenAccount = tokenAccount;
4938
+ if (createIx) {
4939
+ preInstructions.push(createIx);
4922
4940
  }
4923
4941
  }
4924
4942
  if (!inAssociatedTokenAccount) {
4925
- const tokenProgram = this.getTokenProgramForSpotMarket(inMarket);
4926
- inAssociatedTokenAccount = await this.getAssociatedTokenAccount(inMarket.marketIndex, false, tokenProgram);
4927
- const accountInfo = await this.connection.getAccountInfo(inAssociatedTokenAccount);
4928
- if (!accountInfo) {
4929
- preInstructions.push(this.createAssociatedTokenAccountIdempotentInstruction(inAssociatedTokenAccount, this.provider.wallet.publicKey, this.provider.wallet.publicKey, inMarket.mint, tokenProgram));
4943
+ const { tokenAccount, createIx } = await this.getOrCreateSwapTokenAccount(inMarket);
4944
+ inAssociatedTokenAccount = tokenAccount;
4945
+ if (createIx) {
4946
+ preInstructions.push(createIx);
4930
4947
  }
4931
4948
  }
4932
4949
  const { beginSwapIx, endSwapIx } = await this.getSwapIx({
4933
4950
  outMarketIndex,
4934
4951
  inMarketIndex,
4935
- amountIn: isExactOut ? exactOutBufferedAmountIn : amountIn,
4952
+ amountIn,
4936
4953
  inTokenAccount: inAssociatedTokenAccount,
4937
4954
  outTokenAccount: outAssociatedTokenAccount,
4938
4955
  reduceOnly,
4939
4956
  userAccountPublicKey,
4940
4957
  });
4941
- const ixs = [
4942
- ...preInstructions,
4943
- beginSwapIx,
4944
- ...jupiterInstructions,
4945
- endSwapIx,
4946
- ];
4947
- return { ixs, lookupTables };
4958
+ const { instructions: routeInstructions, lookupTables } = await swapProvider.getRouteInstructions({
4959
+ quote: quoteToUse,
4960
+ userPublicKey: this.provider.wallet.publicKey,
4961
+ });
4962
+ return {
4963
+ ixs: [...preInstructions, beginSwapIx, ...routeInstructions, endSwapIx],
4964
+ lookupTables,
4965
+ };
4948
4966
  }
4949
4967
  /**
4950
4968
  * Builds the `beginSwap`/`endSwap` instruction pair that must bracket an external swap
@@ -5049,88 +5067,6 @@ class VelocityClient {
5049
5067
  });
5050
5068
  return { beginSwapIx, endSwapIx };
5051
5069
  }
5052
- /**
5053
- * Builds the instruction list for a swap routed through a `UnifiedSwapClient` (the current
5054
- * preferred swap path). Creates any missing associated token accounts and wraps the client's
5055
- * routing instructions between `beginSwap`/`endSwap`. See `swap` for parameter semantics;
5056
- * `amount` is in the "in" token's mint decimals (or "out" token's decimals when `swapMode` is
5057
- * `ExactOut`).
5058
- * @param userAccountPublicKey - Optional user account override (e.g. when the account is being
5059
- * created in the same transaction).
5060
- * @returns `ixs` — instruction list (ATA creation, `beginSwap`, routed swap instructions,
5061
- * `endSwap`, in order) and `lookupTables` needed to fit it in a versioned transaction.
5062
- */
5063
- async getSwapIxV2({ swapClient, outMarketIndex, inMarketIndex, outAssociatedTokenAccount, inAssociatedTokenAccount, amount, slippageBps, swapMode, onlyDirectRoutes, reduceOnly, quote, v6, userAccountPublicKey, }) {
5064
- // Get market accounts to determine mints
5065
- const outMarket = this.getSpotMarketAccountOrThrow(outMarketIndex);
5066
- const inMarket = this.getSpotMarketAccountOrThrow(inMarketIndex);
5067
- const isExactOut = swapMode === 'ExactOut';
5068
- const preInstructions = [];
5069
- // Handle token accounts if not provided
5070
- let finalOutAssociatedTokenAccount = outAssociatedTokenAccount;
5071
- let finalInAssociatedTokenAccount = inAssociatedTokenAccount;
5072
- if (!finalOutAssociatedTokenAccount) {
5073
- const tokenProgram = this.getTokenProgramForSpotMarket(outMarket);
5074
- finalOutAssociatedTokenAccount = await this.getAssociatedTokenAccount(outMarket.marketIndex, false, tokenProgram);
5075
- const accountInfo = await this.connection.getAccountInfo(finalOutAssociatedTokenAccount);
5076
- if (!accountInfo) {
5077
- preInstructions.push(this.createAssociatedTokenAccountIdempotentInstruction(finalOutAssociatedTokenAccount, this.provider.wallet.publicKey, this.provider.wallet.publicKey, outMarket.mint, tokenProgram));
5078
- }
5079
- }
5080
- if (!finalInAssociatedTokenAccount) {
5081
- const tokenProgram = this.getTokenProgramForSpotMarket(inMarket);
5082
- finalInAssociatedTokenAccount = await this.getAssociatedTokenAccount(inMarket.marketIndex, false, tokenProgram);
5083
- const accountInfo = await this.connection.getAccountInfo(finalInAssociatedTokenAccount);
5084
- if (!accountInfo) {
5085
- preInstructions.push(this.createAssociatedTokenAccountIdempotentInstruction(finalInAssociatedTokenAccount, this.provider.wallet.publicKey, this.provider.wallet.publicKey, inMarket.mint, tokenProgram));
5086
- }
5087
- }
5088
- let amountInForBeginSwap;
5089
- if (isExactOut) {
5090
- if (quote || (v6 === null || v6 === void 0 ? void 0 : v6.quote)) {
5091
- amountInForBeginSwap = (v6 === null || v6 === void 0 ? void 0 : v6.quote)
5092
- ? new anchor_1.BN(v6.quote.inAmount)
5093
- : new anchor_1.BN(quote.inAmount);
5094
- }
5095
- else {
5096
- amountInForBeginSwap = amount.muln(1001).divn(1000);
5097
- }
5098
- }
5099
- else {
5100
- amountInForBeginSwap = amount;
5101
- }
5102
- // Get velocity swap instructions for begin and end
5103
- const { beginSwapIx, endSwapIx } = await this.getSwapIx({
5104
- outMarketIndex,
5105
- inMarketIndex,
5106
- amountIn: amountInForBeginSwap,
5107
- inTokenAccount: finalInAssociatedTokenAccount,
5108
- outTokenAccount: finalOutAssociatedTokenAccount,
5109
- reduceOnly,
5110
- userAccountPublicKey,
5111
- });
5112
- // Get core swap instructions from SwapClient
5113
- const swapResult = await swapClient.getSwapInstructions({
5114
- inputMint: inMarket.mint,
5115
- outputMint: outMarket.mint,
5116
- amount,
5117
- userPublicKey: this.provider.wallet.publicKey,
5118
- slippageBps,
5119
- swapMode,
5120
- onlyDirectRoutes,
5121
- quote: quote !== null && quote !== void 0 ? quote : v6 === null || v6 === void 0 ? void 0 : v6.quote,
5122
- });
5123
- const allInstructions = [
5124
- ...preInstructions,
5125
- beginSwapIx,
5126
- ...swapResult.instructions,
5127
- endSwapIx,
5128
- ];
5129
- return {
5130
- ixs: allInstructions,
5131
- lookupTables: swapResult.lookupTables,
5132
- };
5133
- }
5134
5070
  /**
5135
5071
  * Converts a portion of the user's deposited wSOL (spot market index 1) into mSOL (spot market
5136
5072
  * index 2) by staking it with Marinade Finance, then swapping the resulting mSOL back into the
@@ -5274,11 +5210,12 @@ class VelocityClient {
5274
5210
  }
5275
5211
  /**
5276
5212
  * Keeper instruction: trips the authority-wide equity floor breaker. Proves on-chain that the
5277
- * given subaccount's cross-margin total collateral is below its `equityFloor` (reverts with
5278
- * `SufficientCollateral` otherwise, or if no floor is set) and sets `equityBreakerTripped` on the
5279
- * authority's `UserStats` every subaccount of the authority then rejects risk-increasing fills,
5280
- * withdrawals and transfers out until the warm admin calls `resetEquityFloorBreaker`.
5281
- * Permissionless any signer may trip it; the margin calculation is the proof.
5213
+ * given subaccount's net equity (unweighted assets and perp PnL minus spot liabilities) is below
5214
+ * its `equityFloor` (reverts with `SufficientCollateral` otherwise, if no floor is set, or with
5215
+ * `InvalidOracle` if any of the subaccount's oracles is invalid) and sets `equityBreakerTripped`
5216
+ * on the authority's `UserStats` every subaccount of the authority then rejects risk-increasing
5217
+ * fills, withdrawals and transfers out until the warm admin calls `resetEquityFloorBreaker`.
5218
+ * Permissionless — any signer may trip it; the equity calculation is the proof.
5282
5219
  * @param userAccountPublicKey - Public key of the breached subaccount's user account.
5283
5220
  * @param user - Decoded user account of the breached subaccount.
5284
5221
  * @param txParams - Optional compute-unit/priority-fee overrides.
@@ -6798,11 +6735,13 @@ class VelocityClient {
6798
6735
  * @param userAccountPublicKey - Public key of the user account being liquidated.
6799
6736
  * @param liquidatorSubAccountId - Liquidator's sub-account to credit; defaults to the active sub-account.
6800
6737
  * @param maxAccounts - Caps the number of accounts Jupiter's route may use.
6801
- * @throws If no quote can be fetched and `quote` was not supplied.
6738
+ * @throws If no quote can be fetched and `quote` was not supplied, or if the quote — passed in or
6739
+ * freshly fetched — is for a different pair or a different size than the swap being built.
6802
6740
  * @returns The ordered instructions (pre-instructions, `beginSwap`, Jupiter swap, `endSwap`) and
6803
6741
  * any address lookup tables the Jupiter route requires.
6804
6742
  */
6805
6743
  async getJupiterLiquidateSpotWithSwapIxV6({ jupiterClient, liabilityMarketIndex, assetMarketIndex, swapAmount, assetTokenAccount, liabilityTokenAccount, slippageBps, swapMode, onlyDirectRoutes, quote, userAccount, userAccountPublicKey, liquidatorSubAccountId, maxAccounts, }) {
6744
+ var _a, _b;
6806
6745
  const liabilityMarket = this.getSpotMarketAccountOrThrow(liabilityMarketIndex);
6807
6746
  const assetMarket = this.getSpotMarketAccountOrThrow(assetMarketIndex);
6808
6747
  if (!quote) {
@@ -6820,19 +6759,12 @@ class VelocityClient {
6820
6759
  if (!quote) {
6821
6760
  throw new Error('Could not fetch swap quote. Please try again.');
6822
6761
  }
6762
+ this.assertQuoteMatchesMarkets(quote, assetMarket, liabilityMarket);
6763
+ this.assertQuoteMatchesAmount(quote, swapAmount, (_b = (_a = quote.swapMode) !== null && _a !== void 0 ? _a : swapMode) !== null && _b !== void 0 ? _b : 'ExactIn');
6823
6764
  const amountIn = new anchor_1.BN(quote.inAmount);
6824
- const transaction = await jupiterClient.getSwap({
6765
+ const { instructions: jupiterInstructions, lookupTables } = await jupiterClient.getRouteInstructions({
6825
6766
  quote,
6826
6767
  userPublicKey: this.provider.wallet.publicKey,
6827
- slippageBps,
6828
- });
6829
- const { transactionMessage, lookupTables } = await jupiterClient.getTransactionMessageAndLookupTables({
6830
- transaction,
6831
- });
6832
- const jupiterInstructions = jupiterClient.getJupiterInstructions({
6833
- transactionMessage,
6834
- inputMint: assetMarket.mint,
6835
- outputMint: liabilityMarket.mint,
6836
6768
  });
6837
6769
  const preInstructions = [];
6838
6770
  if (!liabilityTokenAccount) {