@epicentral/sos-sdk 0.10.0-beta → 0.10.1-beta

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.
@@ -1,4 +1,6 @@
1
1
  import {
2
+ COLLATERAL_POOL_DISCRIMINATOR,
3
+ OPTION_POOL_DISCRIMINATOR,
2
4
  getCollateralPoolDecoder,
3
5
  getLenderPositionDecoder,
4
6
  getMarketDataAccountDecoder,
@@ -19,10 +21,74 @@ import {
19
21
  type WriterPosition,
20
22
  } from "../generated/accounts";
21
23
  import type { Address } from "@solana/kit";
24
+ import bs58 from "bs58";
22
25
  import { toAddress } from "../client/program";
23
26
  import type { AddressLike, KitRpc } from "../client/types";
24
27
 
25
- async function fetchRawAccount(
28
+ /** Anchor `CollateralPool`: discriminator + option_account (32) + collateral_mint (32), then collateral_vault. */
29
+ const COLLATERAL_POOL_COLLATERAL_VAULT_OFFSET =
30
+ COLLATERAL_POOL_DISCRIMINATOR.length + 32 + 32;
31
+
32
+ /** Sequential pubkeys before numeric fields (`option_pool` Anchor layout). */
33
+ const OPTION_POOL_UNDERLYING_MINT_OFFSET =
34
+ OPTION_POOL_DISCRIMINATOR.length + 32 + 32 + 32;
35
+ const OPTION_POOL_ESCROW_LONG_OFFSET = OPTION_POOL_UNDERLYING_MINT_OFFSET + 32;
36
+ const OPTION_POOL_PREMIUM_VAULT_OFFSET = OPTION_POOL_ESCROW_LONG_OFFSET + 32;
37
+
38
+ function discriminatorMatches(expected: Uint8Array, data: Uint8Array): boolean {
39
+ if (data.length < expected.length) return false;
40
+ for (let i = 0; i < expected.length; i++) {
41
+ if (data[i] !== expected[i]!) return false;
42
+ }
43
+ return true;
44
+ }
45
+
46
+ function collateralPoolDiscriminatorMatches(data: Uint8Array): boolean {
47
+ return discriminatorMatches(COLLATERAL_POOL_DISCRIMINATOR, data);
48
+ }
49
+
50
+ function optionPoolDiscriminatorMatches(data: Uint8Array): boolean {
51
+ return discriminatorMatches(OPTION_POOL_DISCRIMINATOR, data);
52
+ }
53
+
54
+ /**
55
+ * If full `OptionPool` decode fails, read the pubkey fields layout-traders need for resolving pool routes.
56
+ */
57
+ export function decodeOptionPoolTradePubkeysFromRawData(data: Uint8Array): {
58
+ underlyingMint: Address;
59
+ escrowLongAccount: Address;
60
+ premiumVault: Address;
61
+ } | undefined {
62
+ const end = OPTION_POOL_PREMIUM_VAULT_OFFSET + 32;
63
+ if (data.length < end) return undefined;
64
+ if (!optionPoolDiscriminatorMatches(data)) return undefined;
65
+ return {
66
+ underlyingMint: toAddress(
67
+ bs58.encode(data.subarray(OPTION_POOL_UNDERLYING_MINT_OFFSET, OPTION_POOL_UNDERLYING_MINT_OFFSET + 32)),
68
+ ),
69
+ escrowLongAccount: toAddress(
70
+ bs58.encode(data.subarray(OPTION_POOL_ESCROW_LONG_OFFSET, OPTION_POOL_ESCROW_LONG_OFFSET + 32)),
71
+ ),
72
+ premiumVault: toAddress(
73
+ bs58.encode(data.subarray(OPTION_POOL_PREMIUM_VAULT_OFFSET, OPTION_POOL_PREMIUM_VAULT_OFFSET + 32)),
74
+ ),
75
+ };
76
+ }
77
+
78
+ /**
79
+ * If full Codama decode fails (IDL / deployed account size mismatch), read `collateral_vault`
80
+ * from the fixed Anchor field order. This is sufficient for txs that only need vault + pool PDAs.
81
+ */
82
+ export function decodeCollateralVaultFromCollateralPoolRawData(
83
+ data: Uint8Array
84
+ ): Address | undefined {
85
+ const end = COLLATERAL_POOL_COLLATERAL_VAULT_OFFSET + 32;
86
+ if (data.length < end) return undefined;
87
+ if (!collateralPoolDiscriminatorMatches(data)) return undefined;
88
+ return toAddress(bs58.encode(data.subarray(COLLATERAL_POOL_COLLATERAL_VAULT_OFFSET, end)));
89
+ }
90
+
91
+ export async function fetchAccountData(
26
92
  rpc: KitRpc,
27
93
  address: AddressLike
28
94
  ): Promise<Uint8Array | null> {
@@ -32,7 +98,7 @@ async function fetchRawAccount(
32
98
  const [data] = accountInfo.data;
33
99
  const binary = atob(data);
34
100
  const bytes = new Uint8Array(binary.length);
35
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
101
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)!;
36
102
  return bytes;
37
103
  }
38
104
 
@@ -41,9 +107,13 @@ async function decodeAccount<T>(
41
107
  address: AddressLike,
42
108
  decoder: { decode: (value: Uint8Array) => T }
43
109
  ): Promise<T | null> {
44
- const data = await fetchRawAccount(rpc, address);
110
+ const data = await fetchAccountData(rpc, address);
45
111
  if (!data) return null;
46
- return decoder.decode(data);
112
+ try {
113
+ return decoder.decode(data);
114
+ } catch {
115
+ return null;
116
+ }
47
117
  }
48
118
 
49
119
  export async function fetchOptionAccount(
@@ -148,21 +218,21 @@ export async function fetchOptionAccounts(
148
218
  for (let i = 0; i < keys.length; i++) {
149
219
  const accountInfo = infos[i];
150
220
  if (!accountInfo) {
151
- result.set(keys[i], null);
221
+ result.set(keys[i]!, null);
152
222
  continue;
153
223
  }
154
224
  const [b64] = accountInfo.data;
155
225
  if (!b64) {
156
- result.set(keys[i], null);
226
+ result.set(keys[i]!, null);
157
227
  continue;
158
228
  }
159
229
  const binary = atob(b64);
160
230
  const data = new Uint8Array(binary.length);
161
- for (let j = 0; j < binary.length; j++) data[j] = binary.charCodeAt(j);
231
+ for (let j = 0; j < binary.length; j++) data[j] = binary.charCodeAt(j)!;
162
232
  try {
163
- result.set(keys[i], optionDecoder.decode(data));
233
+ result.set(keys[i]!, optionDecoder.decode(data));
164
234
  } catch {
165
- result.set(keys[i], null);
235
+ result.set(keys[i]!, null);
166
236
  }
167
237
  }
168
238
  return result;
package/accounts/list.ts CHANGED
@@ -97,14 +97,20 @@ async function fetchAndDecodeProgramAccounts<T>(
97
97
  ? (response as Array<ProgramAccountResponse>)
98
98
  : (response as { value: Array<ProgramAccountResponse> }).value;
99
99
 
100
- return rawAccounts.map(({ pubkey, account }) => {
100
+ const out: Array<ListedAccount<T>> = [];
101
+ for (const { pubkey, account } of rawAccounts) {
101
102
  const base64Data =
102
103
  Array.isArray(account.data) ? account.data[0] : account.data;
103
- return {
104
- address: pubkey,
105
- data: decoder.decode(decodeBase64Data(base64Data)),
106
- };
107
- });
104
+ try {
105
+ out.push({
106
+ address: pubkey,
107
+ data: decoder.decode(decodeBase64Data(base64Data)),
108
+ });
109
+ } catch {
110
+ /** Skip malformed / IDL-size mismatches instead of throwing from getProgramAccounts. */
111
+ }
112
+ }
113
+ return out;
108
114
  }
109
115
 
110
116
  export async function fetchWriterPositionsByWriter(
@@ -1,7 +1,12 @@
1
1
  import type { Address } from "@solana/kit";
2
2
  import { OptionType } from "../generated/types";
3
- import { toAddress } from "../client/program";
4
3
  import type { AddressLike, KitRpc } from "../client/types";
4
+ import type { CollateralPool, OptionAccount, OptionPool } from "../generated/accounts";
5
+ import {
6
+ getCollateralPoolDecoder,
7
+ getOptionAccountDecoder,
8
+ getOptionPoolDecoder,
9
+ } from "../generated/accounts";
5
10
  import {
6
11
  deriveCollateralPoolPda,
7
12
  deriveLongMintPda,
@@ -11,7 +16,11 @@ import {
11
16
  deriveOptionPoolPda,
12
17
  deriveShortMintPda,
13
18
  } from "./pdas";
14
- import { fetchCollateralPool, fetchOptionAccount, fetchOptionPool } from "./fetchers";
19
+ import {
20
+ decodeCollateralVaultFromCollateralPoolRawData,
21
+ decodeOptionPoolTradePubkeysFromRawData,
22
+ fetchAccountData,
23
+ } from "./fetchers";
15
24
 
16
25
  export interface ResolveOptionAccountsParams {
17
26
  underlyingAsset: AddressLike;
@@ -34,9 +43,9 @@ export interface ResolvedOptionAccounts {
34
43
  escrowLongAccount?: Address;
35
44
  premiumVault?: Address;
36
45
  collateralVault?: Address;
37
- optionPoolData?: Awaited<ReturnType<typeof fetchOptionPool>>;
38
- optionAccountData?: Awaited<ReturnType<typeof fetchOptionAccount>>;
39
- collateralPoolData?: Awaited<ReturnType<typeof fetchCollateralPool>>;
46
+ optionPoolData?: OptionPool;
47
+ optionAccountData?: OptionAccount;
48
+ collateralPoolData?: CollateralPool;
40
49
  }
41
50
 
42
51
  /**
@@ -76,27 +85,59 @@ export async function resolveOptionAccounts(
76
85
  };
77
86
 
78
87
  if (params.rpc) {
79
- const [optionPoolFetched, optionAccountFetched, collateralPoolFetched] =
88
+ const optionPoolDecoder = getOptionPoolDecoder();
89
+ const optionAccountDecoder = getOptionAccountDecoder();
90
+ const collateralPoolDecoder = getCollateralPoolDecoder();
91
+ const [optionPoolBytes, optionAccountBytes, collateralPoolBytes] =
80
92
  await Promise.all([
81
- fetchOptionPool(params.rpc, optionPool),
82
- fetchOptionAccount(params.rpc, optionAccount),
83
- fetchCollateralPool(params.rpc, collateralPool),
93
+ fetchAccountData(params.rpc, optionPool),
94
+ fetchAccountData(params.rpc, optionAccount),
95
+ fetchAccountData(params.rpc, collateralPool),
84
96
  ]);
85
97
 
86
- if (optionPoolFetched) {
87
- result.optionPoolData = optionPoolFetched;
88
- result.escrowLongAccount = optionPoolFetched.escrowLongAccount;
89
- result.premiumVault = optionPoolFetched.premiumVault;
90
- result.underlyingMint = optionPoolFetched.underlyingMint;
98
+ if (optionPoolBytes) {
99
+ let optionPoolDecoded: OptionPool | null = null;
100
+ try {
101
+ optionPoolDecoded = optionPoolDecoder.decode(optionPoolBytes);
102
+ } catch {
103
+ optionPoolDecoded = null;
104
+ }
105
+ if (optionPoolDecoded) {
106
+ result.optionPoolData = optionPoolDecoded;
107
+ result.escrowLongAccount = optionPoolDecoded.escrowLongAccount;
108
+ result.premiumVault = optionPoolDecoded.premiumVault;
109
+ result.underlyingMint = optionPoolDecoded.underlyingMint;
110
+ } else {
111
+ const tradePubkeys =
112
+ decodeOptionPoolTradePubkeysFromRawData(optionPoolBytes);
113
+ if (tradePubkeys) {
114
+ result.escrowLongAccount = tradePubkeys.escrowLongAccount;
115
+ result.premiumVault = tradePubkeys.premiumVault;
116
+ result.underlyingMint = tradePubkeys.underlyingMint;
117
+ }
118
+ }
91
119
  }
92
120
 
93
- if (optionAccountFetched) {
94
- result.optionAccountData = optionAccountFetched;
121
+ if (optionAccountBytes) {
122
+ try {
123
+ result.optionAccountData = optionAccountDecoder.decode(optionAccountBytes);
124
+ } catch {
125
+ /* layout drift: PDAs remain valid; Greeks-only consumers see no OptionAccount snapshot */
126
+ }
95
127
  }
96
128
 
97
- if (collateralPoolFetched) {
98
- result.collateralPoolData = collateralPoolFetched;
99
- result.collateralVault = collateralPoolFetched.collateralVault;
129
+ if (collateralPoolBytes) {
130
+ try {
131
+ const collateralPoolFetched = collateralPoolDecoder.decode(collateralPoolBytes);
132
+ result.collateralPoolData = collateralPoolFetched;
133
+ result.collateralVault = collateralPoolFetched.collateralVault;
134
+ } catch {
135
+ const vaultFallback =
136
+ decodeCollateralVaultFromCollateralPoolRawData(collateralPoolBytes);
137
+ if (vaultFallback) {
138
+ result.collateralVault = vaultFallback;
139
+ }
140
+ }
100
141
  }
101
142
  }
102
143
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicentral/sos-sdk",
3
- "version": "0.10.0-beta",
3
+ "version": "0.10.1-beta",
4
4
  "private": false,
5
5
  "description": "Solana Option Standard SDK. The frontend-first SDK for Native Options Trading on Solana. Created by Epicentral Labs.",
6
6
  "type": "module",