@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.
- package/accounts/fetchers.ts +79 -9
- package/accounts/list.ts +12 -6
- package/accounts/resolve-option.ts +60 -19
- package/package.json +1 -1
package/accounts/fetchers.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
110
|
+
const data = await fetchAccountData(rpc, address);
|
|
45
111
|
if (!data) return null;
|
|
46
|
-
|
|
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]
|
|
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]
|
|
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]
|
|
233
|
+
result.set(keys[i]!, optionDecoder.decode(data));
|
|
164
234
|
} catch {
|
|
165
|
-
result.set(keys[i]
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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 {
|
|
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?:
|
|
38
|
-
optionAccountData?:
|
|
39
|
-
collateralPoolData?:
|
|
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
|
|
88
|
+
const optionPoolDecoder = getOptionPoolDecoder();
|
|
89
|
+
const optionAccountDecoder = getOptionAccountDecoder();
|
|
90
|
+
const collateralPoolDecoder = getCollateralPoolDecoder();
|
|
91
|
+
const [optionPoolBytes, optionAccountBytes, collateralPoolBytes] =
|
|
80
92
|
await Promise.all([
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
fetchAccountData(params.rpc, optionPool),
|
|
94
|
+
fetchAccountData(params.rpc, optionAccount),
|
|
95
|
+
fetchAccountData(params.rpc, collateralPool),
|
|
84
96
|
]);
|
|
85
97
|
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 (
|
|
94
|
-
|
|
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 (
|
|
98
|
-
|
|
99
|
-
|
|
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