@haven-fi/solauto-sdk 1.0.626 → 1.0.628

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 (61) hide show
  1. package/README.md +73 -0
  2. package/dist/constants/marginfiAccounts.d.ts +3 -4
  3. package/dist/constants/marginfiAccounts.d.ts.map +1 -1
  4. package/dist/constants/marginfiAccounts.js +9 -37
  5. package/dist/constants/pythConstants.d.ts +4 -0
  6. package/dist/constants/pythConstants.d.ts.map +1 -1
  7. package/dist/constants/pythConstants.js +5 -1
  8. package/dist/services/flashLoans/marginfiFlProvider.d.ts.map +1 -1
  9. package/dist/services/flashLoans/marginfiFlProvider.js +7 -8
  10. package/dist/services/solauto/solautoClient.d.ts.map +1 -1
  11. package/dist/services/solauto/solautoClient.js +5 -6
  12. package/dist/services/solauto/solautoMarginfiClient.d.ts.map +1 -1
  13. package/dist/services/solauto/solautoMarginfiClient.js +4 -9
  14. package/dist/services/transactions/transactionUtils.d.ts.map +1 -1
  15. package/dist/services/transactions/transactionUtils.js +3 -2
  16. package/dist/solautoPosition/marginfiSolautoPositionEx.d.ts +5 -0
  17. package/dist/solautoPosition/marginfiSolautoPositionEx.d.ts.map +1 -1
  18. package/dist/solautoPosition/marginfiSolautoPositionEx.js +15 -2
  19. package/dist/solautoPosition/solautoPositionEx.d.ts +2 -0
  20. package/dist/solautoPosition/solautoPositionEx.d.ts.map +1 -1
  21. package/dist/solautoPosition/utils.js +1 -1
  22. package/dist/types/accounts.d.ts +0 -1
  23. package/dist/types/accounts.d.ts.map +1 -1
  24. package/dist/utils/generalUtils.d.ts +1 -0
  25. package/dist/utils/generalUtils.d.ts.map +1 -1
  26. package/dist/utils/generalUtils.js +10 -0
  27. package/dist/utils/index.d.ts +2 -0
  28. package/dist/utils/index.d.ts.map +1 -1
  29. package/dist/utils/index.js +2 -0
  30. package/dist/utils/instructionUtils.d.ts +15 -0
  31. package/dist/utils/instructionUtils.d.ts.map +1 -0
  32. package/dist/utils/instructionUtils.js +121 -0
  33. package/dist/utils/marginfiUtils.d.ts +7 -2
  34. package/dist/utils/marginfiUtils.d.ts.map +1 -1
  35. package/dist/utils/marginfiUtils.js +44 -11
  36. package/dist/utils/pythUtils.d.ts +21 -0
  37. package/dist/utils/pythUtils.d.ts.map +1 -0
  38. package/dist/utils/pythUtils.js +67 -0
  39. package/dist/utils/solautoUtils.js +1 -1
  40. package/local/txSandbox.ts +3 -3
  41. package/local/updateMarginfiLUT.ts +9 -15
  42. package/package.json +1 -1
  43. package/src/constants/marginfiAccounts.ts +13 -39
  44. package/src/constants/pythConstants.ts +8 -0
  45. package/src/services/flashLoans/marginfiFlProvider.ts +9 -9
  46. package/src/services/solauto/solautoClient.ts +6 -6
  47. package/src/services/solauto/solautoMarginfiClient.ts +5 -11
  48. package/src/services/transactions/transactionUtils.ts +1 -1
  49. package/src/solautoPosition/marginfiSolautoPositionEx.ts +22 -3
  50. package/src/solautoPosition/solautoPositionEx.ts +2 -0
  51. package/src/solautoPosition/utils.ts +1 -1
  52. package/src/types/accounts.ts +0 -1
  53. package/src/utils/generalUtils.ts +12 -0
  54. package/src/utils/index.ts +2 -0
  55. package/src/utils/instructionUtils.ts +181 -0
  56. package/src/utils/marginfiUtils.ts +75 -17
  57. package/src/utils/pythUtils.ts +84 -0
  58. package/src/utils/solautoUtils.ts +1 -1
  59. package/tests/transactions/shared.ts +22 -66
  60. package/tests/unit/accounts.ts +7 -13
  61. package/tests/unit/lookupTables.ts +27 -48
@@ -9,20 +9,22 @@ import { PositionState, PositionTokenState } from "../generated";
9
9
  import {
10
10
  ALL_SUPPORTED_TOKENS,
11
11
  getMarginfiAccounts,
12
- MARGINFI_PROD_PROGRAM,
13
- MARGINFI_STAGING_PROGRAM,
14
- MarginfiAccountsMap,
12
+ MARGINFI_SPONSORED_SHARD_ID,
13
+ MarginfiBankAccountsMap,
14
+ PYTH_SPONSORED_SHARD_ID,
15
15
  TOKEN_INFO,
16
16
  USD_DECIMALS,
17
17
  } from "../constants";
18
18
  import {
19
19
  Bank,
20
20
  deserializeMarginfiAccount,
21
+ fetchBank,
21
22
  getMarginfiAccountSize,
22
23
  getMarginfiErrorFromCode,
23
24
  getMarginfiErrorFromName,
24
25
  MarginfiAccount,
25
26
  OracleSetup,
27
+ safeFetchAllBank,
26
28
  safeFetchBank,
27
29
  safeFetchMarginfiAccount,
28
30
  } from "../marginfi-sdk";
@@ -38,22 +40,15 @@ import {
38
40
  toBps,
39
41
  } from "./numberUtils";
40
42
  import { getTokenAccountData } from "./accountUtils";
41
-
42
- export function getMarginfiProgram(env: ProgramEnv) {
43
- return env === "Prod" ? MARGINFI_PROD_PROGRAM : MARGINFI_STAGING_PROGRAM;
44
- }
45
-
46
- export function isMarginfiProgram(programId: PublicKey) {
47
- return (
48
- programId.equals(MARGINFI_PROD_PROGRAM) ||
49
- programId.equals(MARGINFI_STAGING_PROGRAM)
50
- );
51
- }
43
+ import {
44
+ getMostUpToDatePythOracle,
45
+ getPythPushOracleAddress,
46
+ } from "./pythUtils";
52
47
 
53
48
  export function createDynamicMarginfiProgram(env?: ProgramEnv): Program {
54
49
  return {
55
50
  name: "marginfi",
56
- publicKey: publicKey(getMarginfiProgram(env ?? "Prod")),
51
+ publicKey: publicKey(getMarginfiAccounts(env ?? "Prod").program),
57
52
  getErrorFromCode(code: number, cause?: Error) {
58
53
  return getMarginfiErrorFromCode(code, this, cause);
59
54
  },
@@ -77,18 +72,81 @@ export function umiWithMarginfiProgram(umi: Umi, marginfiEnv?: ProgramEnv) {
77
72
  });
78
73
  }
79
74
 
75
+ export async function getAllBankRelatedAccounts(
76
+ umi: Umi,
77
+ bankAccountsMap: MarginfiBankAccountsMap
78
+ ): Promise<PublicKey[]> {
79
+ const banks = Object.values(bankAccountsMap).flatMap((group) =>
80
+ Object.values(group).map((accounts) => accounts.bank)
81
+ );
82
+ const banksData = await safeFetchAllBank(
83
+ umi,
84
+ banks.map((x) => publicKey(x))
85
+ );
86
+
87
+ const oracles = banksData
88
+ .map((bank) => {
89
+ const oracleKey = toWeb3JsPublicKey(bank.config.oracleKeys[0]);
90
+ return bank.config.oracleSetup === OracleSetup.PythPushOracle
91
+ ? [
92
+ getPythPushOracleAddress(oracleKey, PYTH_SPONSORED_SHARD_ID),
93
+ getPythPushOracleAddress(oracleKey, MARGINFI_SPONSORED_SHARD_ID),
94
+ ]
95
+ : [oracleKey];
96
+
97
+ })
98
+ .flat()
99
+ .map((x) => x.toString());
100
+
101
+ const otherAccounts = Object.entries(bankAccountsMap).flatMap(
102
+ ([groupName, tokenMap]) =>
103
+ Object.values(tokenMap).flatMap((accounts) => [
104
+ groupName,
105
+ accounts.liquidityVault,
106
+ accounts.vaultAuthority,
107
+ ])
108
+ );
109
+
110
+ return Array.from(new Set([...banks, ...oracles, ...otherAccounts]))
111
+ .filter((x) => x !== PublicKey.default.toString())
112
+ .map((x) => new PublicKey(x));
113
+ }
114
+
80
115
  export async function fetchBankAddresses(umi: Umi, bankPk: PublicKey) {
81
- const bank = await safeFetchBank(umi, fromWeb3JsPublicKey(bankPk));
116
+ const bank = await fetchBank(umi, fromWeb3JsPublicKey(bankPk));
82
117
  const liquidityVault = toWeb3JsPublicKey(bank!.liquidityVault);
83
118
  const vaultAuthority = (await getTokenAccountData(umi, liquidityVault))
84
119
  ?.owner;
120
+ const priceOracle = await getMarginfiPriceOracle(umi, { data: bank });
121
+
85
122
  return {
86
123
  bank: bankPk,
87
124
  liquidityVault,
88
125
  vaultAuthority,
126
+ priceOracle,
89
127
  };
90
128
  }
91
129
 
130
+ export async function getMarginfiPriceOracle(
131
+ umi: Umi,
132
+ bank: { pk?: PublicKey; data?: Bank }
133
+ ) {
134
+ if (!bank.data) {
135
+ bank.data = await fetchBank(umi, fromWeb3JsPublicKey(bank.pk!));
136
+ }
137
+
138
+ const oracleKey = toWeb3JsPublicKey(bank.data.config.oracleKeys[0]);
139
+ const priceOracle =
140
+ bank.data.config.oracleSetup === OracleSetup.PythPushOracle
141
+ ? await getMostUpToDatePythOracle(umi, [
142
+ getPythPushOracleAddress(oracleKey, PYTH_SPONSORED_SHARD_ID),
143
+ getPythPushOracleAddress(oracleKey, MARGINFI_SPONSORED_SHARD_ID),
144
+ ])
145
+ : oracleKey;
146
+
147
+ return priceOracle;
148
+ }
149
+
92
150
  interface AllMarginfiAssetAccounts extends MarginfiAssetAccounts {
93
151
  mint: PublicKey;
94
152
  }
@@ -96,7 +154,7 @@ interface AllMarginfiAssetAccounts extends MarginfiAssetAccounts {
96
154
  export function findMarginfiAccounts(
97
155
  bank: PublicKey
98
156
  ): AllMarginfiAssetAccounts {
99
- const search = (bankAccounts: MarginfiAccountsMap) => {
157
+ const search = (bankAccounts: MarginfiBankAccountsMap) => {
100
158
  for (const group in bankAccounts) {
101
159
  for (const key in bankAccounts[group]) {
102
160
  const account = bankAccounts[group][key];
@@ -0,0 +1,84 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import { PYTH_PUSH_PROGRAM } from "../constants";
3
+ import { u16ToArrayBufferLE, zip } from "./generalUtils";
4
+ import * as borsh from "borsh";
5
+ import { Umi } from "@metaplex-foundation/umi";
6
+ import { fromWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
7
+
8
+ type PriceUpdateV2 = {
9
+ writeAuthority: Buffer;
10
+ verificationLevel: number;
11
+ priceMessage: {
12
+ feedId: Buffer;
13
+ price: bigint;
14
+ conf: bigint;
15
+ exponent: number;
16
+ publishTime: bigint;
17
+ prevPublishTime: bigint;
18
+ emaPrice: bigint;
19
+ emaConf: bigint;
20
+ };
21
+ };
22
+
23
+ const priceUpdateV2Schema = {
24
+ struct: {
25
+ writeAuthority: {
26
+ array: { type: "u8", len: 32 },
27
+ },
28
+ verificationLevel: "u8",
29
+ priceMessage: {
30
+ struct: {
31
+ feedId: { array: { type: "u8", len: 32 } },
32
+ price: "i64",
33
+ conf: "u64",
34
+ exponent: "i32",
35
+ publishTime: "i64",
36
+ prevPublishTime: "i64",
37
+ emaPrice: "i64",
38
+ emaConf: "u64",
39
+ },
40
+ },
41
+ postedSlot: "u64",
42
+ },
43
+ };
44
+
45
+ export function parsePriceInfo(data: Uint8Array): PriceUpdateV2 {
46
+ let decoded: PriceUpdateV2 = borsh.deserialize(
47
+ priceUpdateV2Schema,
48
+ data
49
+ ) as any;
50
+ return decoded;
51
+ }
52
+
53
+ export async function getMostUpToDatePythOracle(
54
+ umi: Umi,
55
+ oracleKeys: PublicKey[]
56
+ ) {
57
+ const oracles = zip(
58
+ oracleKeys,
59
+ (
60
+ await umi.rpc.getAccounts(
61
+ oracleKeys.map((x) => fromWeb3JsPublicKey(x)),
62
+ { commitment: "confirmed" }
63
+ )
64
+ ).map((x) => (x.exists ? parsePriceInfo(x!.data.slice(8)) : undefined))
65
+ ).sort(
66
+ (a, b) =>
67
+ Number(b[1]?.priceMessage.publishTime ?? 0) -
68
+ Number(a[1]?.priceMessage.publishTime ?? 0)
69
+ );
70
+
71
+ return oracles[0][0];
72
+ }
73
+
74
+ export function getPythPushOracleAddress(
75
+ feedId: PublicKey,
76
+ shardId: number,
77
+ programId: PublicKey = PYTH_PUSH_PROGRAM
78
+ ): PublicKey {
79
+ const shardBytes = u16ToArrayBufferLE(shardId);
80
+ return PublicKey.findProgramAddressSync(
81
+ [shardBytes, feedId.toBuffer()],
82
+ programId
83
+ )[0];
84
+ }
@@ -453,7 +453,7 @@ export function getClient(
453
453
  export function isMarginfiClient(
454
454
  client: SolautoClient
455
455
  ): client is SolautoMarginfiClient {
456
- return client.lendingPlatform == LendingPlatform.Marginfi;
456
+ return client.lendingPlatform === LendingPlatform.Marginfi;
457
457
  }
458
458
  // TODO: PF
459
459
 
@@ -18,6 +18,12 @@ import {
18
18
  TransactionItem,
19
19
  TransactionsManager,
20
20
  USDC,
21
+ deposit,
22
+ openSolautoPosition,
23
+ borrow,
24
+ rebalance,
25
+ withdraw,
26
+ closeSolautoPosition,
21
27
  } from "../../src";
22
28
 
23
29
  export async function e2eTransactionTest(
@@ -51,77 +57,27 @@ export async function e2eTransactionTest(
51
57
  repayGap: 50,
52
58
  };
53
59
 
54
- const transactionItems: TransactionItem[] = [];
55
-
56
- transactionItems.push(
57
- new TransactionItem(async () => {
58
- return {
59
- tx: client.openPositionIx(settings),
60
- };
61
- }, "open position")
62
- );
63
-
60
+ const supplyUsd = 100;
61
+ const debtUsd = withFlashLoan ? 60 : 10;
64
62
  const [supplyPrice, debtPrice] = await fetchTokenPrices([
65
63
  supplyMint,
66
64
  debtMint,
67
65
  ]);
68
66
 
69
- const supplyUsd = 100;
70
- transactionItems.push(
71
- new TransactionItem(async () => {
72
- return {
73
- tx: client.protocolInteractionIx(
74
- solautoAction("Deposit", [
75
- toBaseUnit(
76
- supplyUsd / supplyPrice,
77
- client.pos.supplyMintInfo().decimals
78
- ),
79
- ])
80
- ),
81
- };
82
- }, "deposit")
83
- );
84
-
85
- const debtUsd = withFlashLoan ? 60 : 10;
86
- transactionItems.push(
87
- new TransactionItem(async () => {
88
- return {
89
- tx: client.protocolInteractionIx(
90
- solautoAction("Borrow", [
91
- toBaseUnit(debtUsd / debtPrice, client.pos.debtMintInfo().decimals),
92
- ])
93
- ),
94
- };
95
- }, "borrow")
96
- );
97
-
98
- transactionItems.push(
99
- new TransactionItem(
100
- async (attemptNum) =>
101
- await new RebalanceTxBuilder(client, 0).buildRebalanceTx(attemptNum),
102
- "rebalance"
103
- )
104
- );
105
-
106
- transactionItems.push(
107
- new TransactionItem(
108
- async () => ({
109
- tx: client.protocolInteractionIx(
110
- solautoAction("Withdraw", [{ __kind: "All" }])
111
- ),
112
- }),
113
- "withdraw"
114
- )
115
- );
116
-
117
- transactionItems.push(
118
- new TransactionItem(
119
- async () => ({
120
- tx: client.closePositionIx(),
121
- }),
122
- "close position"
123
- )
124
- );
67
+ const transactionItems = [
68
+ openSolautoPosition(client, settings),
69
+ deposit(
70
+ client,
71
+ toBaseUnit(supplyUsd / supplyPrice, client.pos.supplyMintInfo().decimals)
72
+ ),
73
+ borrow(
74
+ client,
75
+ toBaseUnit(debtUsd / debtPrice, client.pos.debtMintInfo().decimals)
76
+ ),
77
+ rebalance(client, 0),
78
+ withdraw(client, "All"),
79
+ closeSolautoPosition(client),
80
+ ];
125
81
 
126
82
  const txManager = new TransactionsManager(client, undefined, "only-simulate");
127
83
  const statuses = await txManager.clientSend(transactionItems);
@@ -6,21 +6,17 @@ import { toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
6
6
  import {
7
7
  ALL_SUPPORTED_TOKENS,
8
8
  TOKEN_INFO,
9
- MARGINFI_ACCOUNTS,
10
9
  SOLAUTO_FEES_WALLET,
11
10
  SOLAUTO_MANAGER,
12
11
  LOCAL_IRONFORGE_API_URL,
13
- } from "../../src/constants";
14
- import {
12
+ getMarginfiAccounts,
15
13
  getSolanaRpcConnection,
16
14
  getEmptyMarginfiAccountsByAuthority,
17
15
  getTokenAccount,
18
- } from "../../src/utils";
16
+ } from "../../src";
19
17
 
20
18
  async function hasTokenAccounts(wallet: PublicKey) {
21
- let [_, umi] = getSolanaRpcConnection(
22
- LOCAL_IRONFORGE_API_URL
23
- );
19
+ let [_, umi] = getSolanaRpcConnection(LOCAL_IRONFORGE_API_URL);
24
20
 
25
21
  const tokenAccounts = await umi.rpc.getAccounts(
26
22
  ALL_SUPPORTED_TOKENS.map((x) =>
@@ -45,17 +41,15 @@ describe("Assert Solauto fee token accounts are created", async () => {
45
41
  });
46
42
 
47
43
  it("ISM accounts for every supported Marginfi group", async () => {
48
- let [_, umi] = getSolanaRpcConnection(
49
- LOCAL_IRONFORGE_API_URL
50
- );
44
+ let [_, umi] = getSolanaRpcConnection(LOCAL_IRONFORGE_API_URL);
51
45
 
52
46
  const ismAccounts = await getEmptyMarginfiAccountsByAuthority(
53
47
  umi,
54
48
  SOLAUTO_MANAGER
55
49
  );
56
- const supportedMarginfiGroups = Object.keys(MARGINFI_ACCOUNTS).map(
57
- (x) => new PublicKey(x)
58
- );
50
+ const supportedMarginfiGroups = Object.keys(
51
+ getMarginfiAccounts("Prod").bankAccounts
52
+ ).map((x) => new PublicKey(x));
59
53
  const missingIsmAccounts = supportedMarginfiGroups.filter(
60
54
  (group) =>
61
55
  !ismAccounts.find((x) => group.equals(toWeb3JsPublicKey(x.group)))
@@ -1,64 +1,43 @@
1
1
  import { describe, it } from "mocha";
2
- import { PublicKey } from "@solana/web3.js";
3
2
  import {
3
+ getMarginfiAccounts,
4
4
  LOCAL_IRONFORGE_API_URL,
5
- MARGINFI_ACCOUNTS,
6
- MARGINFI_ACCOUNTS_LOOKUP_TABLE,
7
5
  SOLAUTO_MANAGER,
8
- } from "../../src/constants";
9
- import {
6
+ getAllBankRelatedAccounts,
10
7
  getEmptyMarginfiAccountsByAuthority,
11
8
  getSolanaRpcConnection,
12
- } from "../../src/utils";
9
+ ProgramEnv,
10
+ } from "../../src";
13
11
 
14
- const [conn, umi] = getSolanaRpcConnection(
15
- LOCAL_IRONFORGE_API_URL
16
- );
12
+ const [conn, umi] = getSolanaRpcConnection(LOCAL_IRONFORGE_API_URL);
17
13
 
18
- describe("Assert lookup tables up-to-date", async () => {
19
- it("marginfi accounts LUT should have everything", async () => {
20
- const lookupTable = await conn.getAddressLookupTable(
21
- new PublicKey(MARGINFI_ACCOUNTS_LOOKUP_TABLE)
22
- );
23
- if (lookupTable === null) {
24
- throw new Error("Lookup table not found");
25
- }
14
+ async function checkLookupTableAccounts(programEnv: ProgramEnv) {
15
+ const data = getMarginfiAccounts(programEnv);
16
+ const lookupTable = await conn.getAddressLookupTable(data.lookupTable);
17
+ if (lookupTable === null) {
18
+ throw new Error("Lookup table not found");
19
+ }
26
20
 
27
- const ismAccounts = await getEmptyMarginfiAccountsByAuthority(
28
- umi,
29
- SOLAUTO_MANAGER
30
- );
21
+ const ismAccounts = (
22
+ await getEmptyMarginfiAccountsByAuthority(umi, SOLAUTO_MANAGER)
23
+ ).map((x) => x.publicKey.toString());
31
24
 
32
- const existingAccounts =
33
- lookupTable.value?.state.addresses.map((x) => x.toString()) ?? [];
25
+ const bankAccounts = (
26
+ await getAllBankRelatedAccounts(umi, data.bankAccounts)
27
+ ).map((x) => x.toString());
34
28
 
35
- for (const group in MARGINFI_ACCOUNTS) {
36
- for (const key in MARGINFI_ACCOUNTS[group]) {
37
- if (key === PublicKey.default.toString()) {
38
- continue;
39
- }
29
+ const accountsRequired = [...ismAccounts, ...bankAccounts];
40
30
 
41
- const groupIsmAccounts = ismAccounts
42
- .filter((x) => x.group.toString() === group)
43
- .map((x) => x.publicKey.toString());
44
- if (groupIsmAccounts.length === 0) {
45
- throw new Error(`Missing ISM account for group: ${group}`);
46
- }
31
+ const existingAccounts =
32
+ lookupTable.value?.state.addresses.map((x) => x.toString()) ?? [];
47
33
 
48
- const accounts = MARGINFI_ACCOUNTS[group][key];
49
- const addresses = [
50
- group,
51
- accounts.bank,
52
- accounts.liquidityVault,
53
- accounts.vaultAuthority,
54
- accounts.priceOracle,
55
- ...groupIsmAccounts,
56
- ];
34
+ if (accountsRequired.find((x) => !existingAccounts.includes(x.toString()))) {
35
+ throw new Error("Marginfi accounts lookup table missing an account");
36
+ }
37
+ }
57
38
 
58
- if (addresses.find((x) => !existingAccounts.includes(x.toString()))) {
59
- throw new Error("Marginfi accounts lookup table missing an account");
60
- }
61
- }
62
- }
39
+ describe("Assert lookup tables up-to-date", async () => {
40
+ it("marginfi accounts LUT should have everything", async () => {
41
+ await checkLookupTableAccounts("Prod");
63
42
  });
64
43
  });