@epicentral/sos-sdk 0.2.1 → 0.2.2

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/index.ts CHANGED
@@ -24,3 +24,11 @@ export * from "./short/pool";
24
24
 
25
25
  export * from "./omlp/builders";
26
26
  export * from "./omlp/service";
27
+
28
+ export {
29
+ getWrapSOLInstructions,
30
+ getUnwrapSOLInstructions,
31
+ getSyncNativeInstruction,
32
+ getCloseAccountInstruction,
33
+ NATIVE_MINT,
34
+ } from "./wsol/instructions";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicentral/sos-sdk",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
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",
@@ -17,6 +17,7 @@
17
17
  "dependencies": {
18
18
  "@solana-program/address-lookup-table": "^0.11.0",
19
19
  "@solana-program/compute-budget": "^0.13.0",
20
+ "@solana-program/system": "^0.11.0",
20
21
  "@solana/kit": "^6.1.0",
21
22
  "bs58": "^6.0.0",
22
23
  "decimal.js": "^10.4.3"
@@ -0,0 +1,221 @@
1
+ import {
2
+ AccountRole,
3
+ address,
4
+ isTransactionSigner,
5
+ type AccountMeta,
6
+ type Address,
7
+ type Instruction,
8
+ type TransactionSigner,
9
+ upgradeRoleToSigner,
10
+ } from "@solana/kit";
11
+ import { getTransferSolInstruction } from "@solana-program/system";
12
+ import { deriveAssociatedTokenAddress } from "../accounts/pdas";
13
+ import { toAddress } from "../client/program";
14
+ import type { AddressLike, KitRpc } from "../client/types";
15
+
16
+ /** Wrapped SOL mint address (WSOL). */
17
+ export const NATIVE_MINT = address("So11111111111111111111111111111111111111112");
18
+
19
+ const TOKEN_PROGRAM_ADDRESS = address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
20
+ const ASSOCIATED_TOKEN_PROGRAM_ADDRESS = address(
21
+ "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
22
+ );
23
+ const SYSTEM_PROGRAM_ADDRESS = address("11111111111111111111111111111111");
24
+
25
+ /** SPL Token instruction discriminator: SyncNative. */
26
+ const SYNC_NATIVE_DISCRIMINATOR = 17;
27
+ /** SPL Token instruction discriminator: CloseAccount. */
28
+ const CLOSE_ACCOUNT_DISCRIMINATOR = 9;
29
+ /** Associated Token Program instruction discriminator: CreateIdempotent. */
30
+ const CREATE_ASSOCIATED_TOKEN_IDEMPOTENT_DISCRIMINATOR = 1;
31
+
32
+ /** SPL Token account data: amount field offset (u64 LE). */
33
+ const TOKEN_ACCOUNT_AMOUNT_OFFSET = 64;
34
+
35
+ function accountMeta(
36
+ addr: Address,
37
+ role: AccountRole,
38
+ signer?: TransactionSigner
39
+ ): AccountMeta<string> {
40
+ const base = { address: addr, role };
41
+ if (signer !== undefined) {
42
+ return Object.freeze({ ...base, role: upgradeRoleToSigner(role), signer }) as AccountMeta<string>;
43
+ }
44
+ return Object.freeze(base) as AccountMeta<string>;
45
+ }
46
+
47
+ /**
48
+ * Builds the SPL Token SyncNative instruction (for WSOL wrap). Syncs the native token
49
+ * account's amount with its lamport balance.
50
+ */
51
+ export function getSyncNativeInstruction(
52
+ account: AddressLike,
53
+ tokenProgram: AddressLike = TOKEN_PROGRAM_ADDRESS
54
+ ): Instruction<string> {
55
+ const programAddress = toAddress(tokenProgram);
56
+ return Object.freeze({
57
+ programAddress,
58
+ accounts: [
59
+ accountMeta(toAddress(account), AccountRole.WRITABLE),
60
+ ],
61
+ data: new Uint8Array([SYNC_NATIVE_DISCRIMINATOR]),
62
+ });
63
+ }
64
+
65
+ /**
66
+ * Builds the SPL Token CloseAccount instruction (for WSOL unwrap). Closes the token
67
+ * account and sends lamports to destination. Owner must sign the transaction.
68
+ */
69
+ export function getCloseAccountInstruction(
70
+ account: AddressLike,
71
+ destination: AddressLike,
72
+ owner: AddressLike | TransactionSigner<string>,
73
+ tokenProgram: AddressLike = TOKEN_PROGRAM_ADDRESS
74
+ ): Instruction<string> {
75
+ const programAddress = toAddress(tokenProgram);
76
+ const ownerAddress = toAddress(
77
+ typeof owner === "object" && owner !== null && "address" in owner
78
+ ? (owner as TransactionSigner<string>).address
79
+ : owner
80
+ );
81
+ const ownerSigner: TransactionSigner<string> | undefined =
82
+ typeof owner === "object" && owner !== null && "address" in owner && isTransactionSigner(owner)
83
+ ? owner
84
+ : undefined;
85
+ const ownerMeta: AccountMeta<string> = ownerSigner
86
+ ? Object.freeze({
87
+ address: ownerAddress,
88
+ role: upgradeRoleToSigner(AccountRole.READONLY),
89
+ signer: ownerSigner,
90
+ }) as AccountMeta<string>
91
+ : accountMeta(ownerAddress, AccountRole.READONLY);
92
+ return Object.freeze({
93
+ programAddress,
94
+ accounts: [
95
+ accountMeta(toAddress(account), AccountRole.WRITABLE),
96
+ accountMeta(toAddress(destination), AccountRole.WRITABLE),
97
+ ownerMeta,
98
+ ],
99
+ data: new Uint8Array([CLOSE_ACCOUNT_DISCRIMINATOR]),
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Builds the Associated Token Program CreateIdempotent instruction. Safe to add
105
+ * even if the ATA already exists.
106
+ */
107
+ async function getCreateAssociatedTokenIdempotentInstruction(
108
+ payer: TransactionSigner<string>,
109
+ owner: AddressLike,
110
+ mint: AddressLike,
111
+ associatedToken: Address
112
+ ): Promise<Instruction<string>> {
113
+ const programAddress = ASSOCIATED_TOKEN_PROGRAM_ADDRESS;
114
+ return Object.freeze({
115
+ programAddress,
116
+ accounts: [
117
+ accountMeta(payer.address as Address, AccountRole.WRITABLE, payer),
118
+ accountMeta(associatedToken, AccountRole.WRITABLE),
119
+ accountMeta(toAddress(owner), AccountRole.READONLY),
120
+ accountMeta(toAddress(mint), AccountRole.READONLY),
121
+ accountMeta(SYSTEM_PROGRAM_ADDRESS, AccountRole.READONLY),
122
+ accountMeta(TOKEN_PROGRAM_ADDRESS, AccountRole.READONLY),
123
+ ],
124
+ data: new Uint8Array([CREATE_ASSOCIATED_TOKEN_IDEMPOTENT_DISCRIMINATOR]),
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Returns instructions to wrap SOL into WSOL: create WSOL ATA (idempotent),
130
+ * transfer lamports to it, then sync native. Uses SDK's deriveAssociatedTokenAddress.
131
+ */
132
+ export async function getWrapSOLInstructions(params: {
133
+ payer: TransactionSigner<string>;
134
+ lamports: bigint;
135
+ owner?: AddressLike;
136
+ tokenProgram?: AddressLike;
137
+ wsolMint?: AddressLike;
138
+ }): Promise<Instruction<string>[]> {
139
+ const owner = params.owner ?? params.payer.address;
140
+ const wsolMint = params.wsolMint ?? NATIVE_MINT;
141
+ const tokenProgram = params.tokenProgram ?? TOKEN_PROGRAM_ADDRESS;
142
+
143
+ const wsolAta = await deriveAssociatedTokenAddress(owner, wsolMint);
144
+
145
+ const createAta = await getCreateAssociatedTokenIdempotentInstruction(
146
+ params.payer,
147
+ owner,
148
+ wsolMint,
149
+ wsolAta
150
+ );
151
+
152
+ const transfer = getTransferSolInstruction({
153
+ source: params.payer,
154
+ destination: wsolAta,
155
+ amount: params.lamports,
156
+ });
157
+
158
+ const syncNative = getSyncNativeInstruction(wsolAta, tokenProgram);
159
+
160
+ return [createAta, transfer, syncNative];
161
+ }
162
+
163
+ /**
164
+ * Reads token account amount from raw account data (SPL token account layout).
165
+ */
166
+ function decodeTokenAccountAmount(data: Uint8Array): bigint {
167
+ if (data.length < TOKEN_ACCOUNT_AMOUNT_OFFSET + 8) return BigInt(0);
168
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
169
+ return view.getBigUint64(TOKEN_ACCOUNT_AMOUNT_OFFSET, true);
170
+ }
171
+
172
+ /**
173
+ * Fetches WSOL ATA balance via RPC. Returns 0n if account missing or invalid.
174
+ */
175
+ async function fetchWsolAtaBalance(rpc: KitRpc, ata: Address): Promise<bigint> {
176
+ const response = await rpc.getAccountInfo(ata, { encoding: "base64" }).send();
177
+ const accountInfo = response.value;
178
+ if (!accountInfo) return BigInt(0);
179
+ const [b64] = accountInfo.data;
180
+ if (!b64) return BigInt(0);
181
+ const binary = atob(b64);
182
+ const data = new Uint8Array(binary.length);
183
+ for (let i = 0; i < binary.length; i++) data[i] = binary.charCodeAt(i);
184
+ return decodeTokenAccountAmount(data);
185
+ }
186
+
187
+ /**
188
+ * Returns the instruction to unwrap WSOL (close WSOL ATA and send SOL to destination),
189
+ * or null if the WSOL ATA has zero balance. Uses SDK's deriveAssociatedTokenAddress.
190
+ * Owner must be provided as a signer when the app signs the transaction.
191
+ */
192
+ export async function getUnwrapSOLInstructions(params: {
193
+ owner: AddressLike | TransactionSigner<string>;
194
+ rpc: KitRpc;
195
+ destination?: AddressLike;
196
+ tokenProgram?: AddressLike;
197
+ wsolMint?: AddressLike;
198
+ }): Promise<Instruction<string>[] | null> {
199
+ const destination = params.destination ?? (typeof params.owner === "object" && params.owner !== null && "address" in params.owner
200
+ ? (params.owner as TransactionSigner<string>).address
201
+ : toAddress(params.owner));
202
+ const wsolMint = params.wsolMint ?? NATIVE_MINT;
203
+ const tokenProgram = params.tokenProgram ?? TOKEN_PROGRAM_ADDRESS;
204
+
205
+ const ownerAddress = toAddress(
206
+ typeof params.owner === "object" && params.owner !== null && "address" in params.owner
207
+ ? (params.owner as TransactionSigner<string>).address
208
+ : params.owner
209
+ );
210
+ const wsolAta = await deriveAssociatedTokenAddress(ownerAddress, wsolMint);
211
+ const balance = await fetchWsolAtaBalance(params.rpc, wsolAta);
212
+ if (balance === BigInt(0)) return null;
213
+
214
+ const close = getCloseAccountInstruction(
215
+ wsolAta,
216
+ destination,
217
+ params.owner,
218
+ tokenProgram
219
+ );
220
+ return [close];
221
+ }