@ember-finance/sdk 2.38.0 → 2.40.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 (32) hide show
  1. package/dist/src/common/constants.d.ts +1 -1
  2. package/dist/src/ember-vaults.d.ts +85 -5
  3. package/dist/src/ember-vaults.js +144 -0
  4. package/dist/src/index.d.ts +1 -0
  5. package/dist/src/index.js +1 -0
  6. package/dist/src/solana-vaults/index.d.ts +4 -0
  7. package/dist/src/solana-vaults/index.js +4 -0
  8. package/dist/src/solana-vaults/interfaces/index.d.ts +260 -0
  9. package/dist/src/solana-vaults/interfaces/index.js +37 -0
  10. package/dist/src/solana-vaults/on-chain-calls/index.d.ts +3 -0
  11. package/dist/src/solana-vaults/on-chain-calls/index.js +3 -0
  12. package/dist/src/solana-vaults/on-chain-calls/tx-builder.d.ts +62 -0
  13. package/dist/src/solana-vaults/on-chain-calls/tx-builder.js +190 -0
  14. package/dist/src/solana-vaults/on-chain-calls/user.d.ts +150 -0
  15. package/dist/src/solana-vaults/on-chain-calls/user.js +288 -0
  16. package/dist/src/solana-vaults/on-chain-calls/vault-reader.d.ts +190 -0
  17. package/dist/src/solana-vaults/on-chain-calls/vault-reader.js +432 -0
  18. package/dist/src/solana-vaults/signers/index.d.ts +1 -0
  19. package/dist/src/solana-vaults/signers/index.js +1 -0
  20. package/dist/src/solana-vaults/signers/solana-signer.d.ts +44 -0
  21. package/dist/src/solana-vaults/signers/solana-signer.js +61 -0
  22. package/dist/src/solana-vaults/utils/borsh.d.ts +65 -0
  23. package/dist/src/solana-vaults/utils/borsh.js +237 -0
  24. package/dist/src/solana-vaults/utils/deployment-parser.d.ts +94 -0
  25. package/dist/src/solana-vaults/utils/deployment-parser.js +174 -0
  26. package/dist/src/solana-vaults/utils/index.d.ts +3 -0
  27. package/dist/src/solana-vaults/utils/index.js +3 -0
  28. package/dist/src/solana-vaults/utils/math.d.ts +77 -0
  29. package/dist/src/solana-vaults/utils/math.js +111 -0
  30. package/dist/src/solana-vaults/utils/pdas.d.ts +63 -0
  31. package/dist/src/solana-vaults/utils/pdas.js +114 -0
  32. package/package.json +10 -2
@@ -1,5 +1,5 @@
1
1
  export type PlatformEnv = "prod" | "staging" | "dev";
2
- export type ChainType = "sui" | "evm";
2
+ export type ChainType = "sui" | "evm" | "solana";
3
3
  export type EnvironmentConfig = {
4
4
  [K in PlatformEnv]: {
5
5
  apiHost: string;
@@ -18,6 +18,12 @@ import { EVMDeploymentParser } from "./evm-vaults/utils/deployment-parser.js";
18
18
  import { EVMVaultReader } from "./evm-vaults/on-chain-calls/vault-reader.js";
19
19
  import { AccountsApi, VaultsApi } from "./api/index.js";
20
20
  import { AccountsApi as AccountsV2Api, VaultsApi as VaultsV2Api } from "./api/v2/index.js";
21
+ import type { Connection as SolanaConnection } from "@solana/web3.js";
22
+ import type { ISolanaDeployment } from "./solana-vaults/interfaces/index.js";
23
+ import { SolanaDeploymentParser } from "./solana-vaults/utils/deployment-parser.js";
24
+ import { SolanaVaultReader } from "./solana-vaults/on-chain-calls/vault-reader.js";
25
+ import { SolanaUserCalls } from "./solana-vaults/on-chain-calls/user.js";
26
+ import type { SolanaSigner } from "./solana-vaults/signers/solana-signer.js";
21
27
  import { ChainType, PlatformEnv } from "./common/constants.js";
22
28
  /**
23
29
  * SUI-specific initialization options
@@ -46,7 +52,48 @@ export interface EvmInitOptions {
46
52
  environment?: PlatformEnv;
47
53
  chainIdentifier: string;
48
54
  }
49
- export type EmberVaultsInitOptions = SuiInitOptions | EvmInitOptions;
55
+ /**
56
+ * Solana-specific initialization options.
57
+ *
58
+ * `connection` rather than a provider: web3.js has no provider abstraction,
59
+ * and every read goes through it.
60
+ *
61
+ * `signer` takes either shape - a raw Keypair for scripts and ops tooling, or
62
+ * a connected browser wallet - and is optional so a page can read before
63
+ * anyone connects. A write with no signer anywhere throws.
64
+ */
65
+ export interface SolanaInitOptions {
66
+ chainType: "solana";
67
+ connection: SolanaConnection;
68
+ signer?: SolanaSigner;
69
+ /**
70
+ * Unused on Solana and accepted only so the constructor can read it
71
+ * uniformly. The chain identifier is "solana" on every cluster; which
72
+ * cluster an environment talks to follows from the deployment it serves.
73
+ */
74
+ chainIdentifier?: string;
75
+ walletAddress?: string;
76
+ deployment?: ISolanaDeployment;
77
+ basePath?: string;
78
+ environment?: PlatformEnv;
79
+ }
80
+ export type EmberVaultsInitOptions = SuiInitOptions | EvmInitOptions | SolanaInitOptions;
81
+ /**
82
+ * Solana-specific EmberVaults instance type.
83
+ *
84
+ * Narrower than its Sui and EVM counterparts: reads plus the user writes.
85
+ * The admin, operator, vaultAdmin and txBuilder surfaces follow, and the
86
+ * getters for them throw rather than returning undefined until they do.
87
+ */
88
+ export interface SolanaEmberVaults {
89
+ chainType: "solana";
90
+ connection: SolanaConnection;
91
+ parser: SolanaDeploymentParser;
92
+ walletAddress?: string;
93
+ reader: SolanaVaultReader;
94
+ user: SolanaUserCalls;
95
+ deployment: ISolanaDeployment;
96
+ }
50
97
  /**
51
98
  * SUI-specific EmberVaults instance type
52
99
  */
@@ -133,9 +180,23 @@ export declare class EmberVaults {
133
180
  private _evmUser?;
134
181
  private _evmDeployment?;
135
182
  private _evmVaultReader?;
183
+ private _solanaConnection?;
184
+ private _solanaParser?;
185
+ private _solanaDeployment?;
186
+ private _solanaVaultReader?;
187
+ private _solanaUser?;
188
+ private _solanaSigner?;
136
189
  private _walletAddress?;
137
190
  private _chainIdentifier?;
138
191
  constructor(options: EmberVaultsInitOptions);
192
+ /**
193
+ * Solana setup.
194
+ *
195
+ * The deployment is optional here as it is on the other chains: pass one
196
+ * to work offline, or call `init()` to fetch it from the API. Without
197
+ * either, the reader has nothing to resolve addresses against.
198
+ */
199
+ private _initSolana;
139
200
  private _initSui;
140
201
  private _initEvm;
141
202
  /**
@@ -146,19 +207,30 @@ export declare class EmberVaults {
146
207
  * Updates the deployment configuration
147
208
  * @param newDeployment The new deployment configuration (ISuiDeployment or EvmProtocolInfo)
148
209
  */
149
- updateDeployment(newDeployment: ISuiDeployment | EvmProtocolInfo): void;
210
+ updateDeployment(newDeployment: ISuiDeployment | EvmProtocolInfo | ISolanaDeployment): void;
150
211
  get suiClient(): SuiClient;
151
212
  get network(): string;
152
213
  get provider(): EvmProvider | undefined;
153
214
  get walletAddress(): string | undefined;
215
+ /**
216
+ * Read-only vault access. Sui has no reader, so this is EVM or Solana.
217
+ */
218
+ get reader(): EVMVaultReader | SolanaVaultReader;
219
+ /**
220
+ * Solana's write surfaces are not built yet. Thrown rather than returning
221
+ * undefined so the failure names what is missing instead of surfacing as
222
+ * "cannot read property of undefined" several frames later.
223
+ */
224
+ private notOnSolana;
225
+ get connection(): SolanaConnection;
154
226
  get signer(): SuiSigner | EvmSigner | undefined;
155
- get parser(): SuiDeploymentParser | EVMDeploymentParser;
227
+ get parser(): SuiDeploymentParser | EVMDeploymentParser | SolanaDeploymentParser;
156
228
  get txBuilder(): SuiTxBuilder | EVMTxBuilder;
157
229
  get admin(): SuiAdminCalls | EVMAdminCalls;
158
230
  get operator(): SuiOperatorCalls | EVMOperatorCalls;
159
231
  get vaultAdmin(): SuiVaultAdminCalls | EVMVaultAdminCalls;
160
- get user(): SuiUserCalls | EVMUserCalls;
161
- get deployment(): ISuiDeployment | EvmProtocolInfo;
232
+ get user(): SuiUserCalls | EVMUserCalls | SolanaUserCalls;
233
+ get deployment(): ISuiDeployment | EvmProtocolInfo | ISolanaDeployment;
162
234
  /**
163
235
  * Check if this is a SUI instance
164
236
  */
@@ -166,6 +238,10 @@ export declare class EmberVaults {
166
238
  /**
167
239
  * Check if this is an EVM instance
168
240
  */
241
+ /**
242
+ * Check if this is a Solana instance
243
+ */
244
+ isSolana(): this is EmberVaults & SolanaEmberVaults;
169
245
  isEvm(): this is EmberVaults & EvmEmberVaults;
170
246
  /**
171
247
  * Get SUI-specific instance (throws if not SUI)
@@ -175,4 +251,8 @@ export declare class EmberVaults {
175
251
  * Get EVM-specific instance (throws if not EVM)
176
252
  */
177
253
  asEvm(): EvmEmberVaults;
254
+ /**
255
+ * Get Solana-specific instance (throws if not Solana)
256
+ */
257
+ asSolana(): SolanaEmberVaults;
178
258
  }
@@ -14,6 +14,9 @@ import { EVMVaultReader } from "./evm-vaults/on-chain-calls/vault-reader.js";
14
14
  // API imports
15
15
  import { AccountsApi, Configuration, VaultsApi } from "./api/index.js";
16
16
  import { AccountsApi as AccountsV2Api, Configuration as ConfigurationV2, VaultsApi as VaultsV2Api } from "./api/v2/index.js";
17
+ import { SolanaDeploymentParser } from "./solana-vaults/utils/deployment-parser.js";
18
+ import { SolanaVaultReader } from "./solana-vaults/on-chain-calls/vault-reader.js";
19
+ import { SolanaUserCalls } from "./solana-vaults/on-chain-calls/user.js";
17
20
  import { environmentConfig } from "./common/constants.js";
18
21
  /**
19
22
  * EmberVaults - Unified interface for both SUI and EVM chains
@@ -54,10 +57,31 @@ export class EmberVaults {
54
57
  if (options.chainType === "sui") {
55
58
  this._initSui(options);
56
59
  }
60
+ else if (options.chainType === "solana") {
61
+ this._initSolana(options);
62
+ }
57
63
  else {
58
64
  this._initEvm(options);
59
65
  }
60
66
  }
67
+ /**
68
+ * Solana setup.
69
+ *
70
+ * The deployment is optional here as it is on the other chains: pass one
71
+ * to work offline, or call `init()` to fetch it from the API. Without
72
+ * either, the reader has nothing to resolve addresses against.
73
+ */
74
+ _initSolana(options) {
75
+ this._solanaConnection = options.connection;
76
+ this._walletAddress = options.walletAddress;
77
+ this._solanaSigner = options.signer;
78
+ if (options.deployment) {
79
+ this._solanaDeployment = options.deployment;
80
+ this._solanaParser = new SolanaDeploymentParser(options.deployment);
81
+ this._solanaVaultReader = new SolanaVaultReader(options.connection, this._solanaParser);
82
+ this._solanaUser = new SolanaUserCalls(options.connection, this._solanaParser, options.signer);
83
+ }
84
+ }
61
85
  _initSui(options) {
62
86
  this._suiNetwork = options.network;
63
87
  this._suiClient = options.suiClient;
@@ -97,6 +121,23 @@ export class EmberVaults {
97
121
  throw new Error(`Deployment data not found for chain ${this._chainIdentifier}`);
98
122
  }
99
123
  }
124
+ else if (this.chainType === "solana") {
125
+ // One Solana deployment per environment, so there is nothing to
126
+ // disambiguate by chain identifier - it is "solana" on every
127
+ // cluster, and which cluster follows from the environment.
128
+ // Compared as a string: the API spec vendored under src/api/v2 was
129
+ // copied before bfvs added "solana" to VaultsDeploymentConfig's
130
+ // chainType enum, so the generated union does not list it yet. The
131
+ // wire value is correct and staging already serves it; this reads
132
+ // as written rather than as typed until the client is regenerated.
133
+ const deploymentData = deployment.data.find(d => d.chainType === "solana");
134
+ if (deploymentData) {
135
+ this.updateDeployment(deploymentData.config);
136
+ }
137
+ else {
138
+ throw new Error("Deployment data not found for chain solana");
139
+ }
140
+ }
100
141
  else {
101
142
  const deploymentData = deployment.data.find(d => d.chainType === "evm" && d.chain === this._chainIdentifier);
102
143
  if (deploymentData) {
@@ -112,6 +153,27 @@ export class EmberVaults {
112
153
  * @param newDeployment The new deployment configuration (ISuiDeployment or EvmProtocolInfo)
113
154
  */
114
155
  updateDeployment(newDeployment) {
156
+ if (this.chainType === "solana") {
157
+ const solanaDeployment = newDeployment;
158
+ this._solanaDeployment = solanaDeployment;
159
+ // The parser holds the program id that every address derives from,
160
+ // so it is rebuilt rather than mutated - a deployment pointing at a
161
+ // different program is a different parser.
162
+ this._solanaParser = new SolanaDeploymentParser(solanaDeployment);
163
+ if (this._solanaUser) {
164
+ this._solanaUser.updateDeployment(this._solanaParser);
165
+ }
166
+ else {
167
+ this._solanaUser = new SolanaUserCalls(this._solanaConnection, this._solanaParser, this._solanaSigner);
168
+ }
169
+ if (this._solanaVaultReader) {
170
+ this._solanaVaultReader.updateDeployment(this._solanaParser);
171
+ }
172
+ else if (this._solanaConnection) {
173
+ this._solanaVaultReader = new SolanaVaultReader(this._solanaConnection, this._solanaParser);
174
+ }
175
+ return;
176
+ }
115
177
  if (this.chainType === "sui") {
116
178
  const suiDeployment = newDeployment;
117
179
  this._suiDeployment = suiDeployment;
@@ -161,28 +223,82 @@ export class EmberVaults {
161
223
  get walletAddress() {
162
224
  return this._walletAddress;
163
225
  }
226
+ /**
227
+ * Read-only vault access. Sui has no reader, so this is EVM or Solana.
228
+ */
229
+ get reader() {
230
+ if (this.chainType === "solana") {
231
+ if (!this._solanaVaultReader)
232
+ throw new Error("No Solana deployment loaded. Pass one, or call init().");
233
+ return this._solanaVaultReader;
234
+ }
235
+ if (this.chainType === "sui")
236
+ throw new Error("reader is not available for SUI chain");
237
+ return this._evmVaultReader;
238
+ }
239
+ /**
240
+ * Solana's write surfaces are not built yet. Thrown rather than returning
241
+ * undefined so the failure names what is missing instead of surfacing as
242
+ * "cannot read property of undefined" several frames later.
243
+ */
244
+ notOnSolana(surface) {
245
+ throw new Error(`${surface} is not available for Solana yet. ` +
246
+ `The Solana module exposes reads and the user writes: ` +
247
+ `use .reader, .user, .parser or .deployment.`);
248
+ }
249
+ // ============================================
250
+ // Solana Getters
251
+ // ============================================
252
+ get connection() {
253
+ if (this.chainType !== "solana")
254
+ throw new Error("connection is only available for Solana chain");
255
+ return this._solanaConnection;
256
+ }
164
257
  get signer() {
165
258
  return this.chainType === "sui" ? this._suiSigner : this._evmSigner;
166
259
  }
167
260
  get parser() {
261
+ if (this.chainType === "solana") {
262
+ if (!this._solanaParser)
263
+ throw new Error("No Solana deployment loaded. Pass one, or call init().");
264
+ return this._solanaParser;
265
+ }
168
266
  return this.chainType === "sui" ? this._suiParser : this._evmParser;
169
267
  }
170
268
  get txBuilder() {
269
+ if (this.chainType === "solana")
270
+ this.notOnSolana("txBuilder");
171
271
  return this.chainType === "sui" ? this._suiTxBuilder : this._evmTxBuilder;
172
272
  }
173
273
  get admin() {
274
+ if (this.chainType === "solana")
275
+ this.notOnSolana("admin");
174
276
  return this.chainType === "sui" ? this._suiAdmin : this._evmAdmin;
175
277
  }
176
278
  get operator() {
279
+ if (this.chainType === "solana")
280
+ this.notOnSolana("operator");
177
281
  return this.chainType === "sui" ? this._suiOperator : this._evmOperator;
178
282
  }
179
283
  get vaultAdmin() {
284
+ if (this.chainType === "solana")
285
+ this.notOnSolana("vaultAdmin");
180
286
  return this.chainType === "sui" ? this._suiVaultAdmin : this._evmVaultAdmin;
181
287
  }
182
288
  get user() {
289
+ if (this.chainType === "solana") {
290
+ if (!this._solanaUser)
291
+ throw new Error("No Solana deployment loaded. Pass one, or call init().");
292
+ return this._solanaUser;
293
+ }
183
294
  return this.chainType === "sui" ? this._suiUser : this._evmUser;
184
295
  }
185
296
  get deployment() {
297
+ if (this.chainType === "solana") {
298
+ if (!this._solanaDeployment)
299
+ throw new Error("No Solana deployment loaded. Pass one, or call init().");
300
+ return this._solanaDeployment;
301
+ }
186
302
  return this.chainType === "sui" ? this._suiDeployment : this._evmDeployment;
187
303
  }
188
304
  // ============================================
@@ -197,6 +313,12 @@ export class EmberVaults {
197
313
  /**
198
314
  * Check if this is an EVM instance
199
315
  */
316
+ /**
317
+ * Check if this is a Solana instance
318
+ */
319
+ isSolana() {
320
+ return this.chainType === "solana";
321
+ }
200
322
  isEvm() {
201
323
  return this.chainType === "evm";
202
324
  }
@@ -242,4 +364,26 @@ export class EmberVaults {
242
364
  reader: this._evmVaultReader
243
365
  };
244
366
  }
367
+ /**
368
+ * Get Solana-specific instance (throws if not Solana)
369
+ */
370
+ asSolana() {
371
+ if (!this.isSolana())
372
+ throw new Error("This EmberVaults instance is not configured for Solana");
373
+ if (!this._solanaParser || !this._solanaVaultReader || !this._solanaDeployment) {
374
+ throw new Error("No Solana deployment loaded. Pass one, or call init().");
375
+ }
376
+ if (!this._solanaUser) {
377
+ throw new Error("No Solana deployment loaded. Pass one, or call init().");
378
+ }
379
+ return {
380
+ chainType: "solana",
381
+ connection: this._solanaConnection,
382
+ parser: this._solanaParser,
383
+ walletAddress: this._walletAddress,
384
+ reader: this._solanaVaultReader,
385
+ user: this._solanaUser,
386
+ deployment: this._solanaDeployment
387
+ };
388
+ }
245
389
  }
@@ -1,4 +1,5 @@
1
1
  export * from "./api/index.js";
2
2
  export * from "./sui-vaults/index.js";
3
+ export * from "./solana-vaults/index.js";
3
4
  export * from "./ember-vaults.js";
4
5
  export type { PlatformEnv } from "./common/constants.js";
package/dist/src/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./api/index.js";
2
2
  export * from "./sui-vaults/index.js";
3
+ export * from "./solana-vaults/index.js";
3
4
  export * from "./ember-vaults.js";
@@ -0,0 +1,4 @@
1
+ export * from "./interfaces/index.js";
2
+ export * from "./on-chain-calls/index.js";
3
+ export * from "./signers/index.js";
4
+ export * from "./utils/index.js";
@@ -0,0 +1,4 @@
1
+ export * from "./interfaces/index.js";
2
+ export * from "./on-chain-calls/index.js";
3
+ export * from "./signers/index.js";
4
+ export * from "./utils/index.js";
@@ -0,0 +1,260 @@
1
+ import type { PublicKey } from "@solana/web3.js";
2
+ /**
3
+ * Solana vault interfaces.
4
+ *
5
+ * The on-chain program is `ember_vaults`, the Anchor port of the Move
6
+ * `vault` module. Three things differ from Sui and EVM and shape everything
7
+ * here:
8
+ *
9
+ * - **Every account a transaction touches must be named up front.** There is
10
+ * no equivalent of Sui reaching through a shared object or EVM reading
11
+ * storage mid-call, so the builders below take addresses the other chains
12
+ * derive internally.
13
+ * - **The share mint is the vault's identity.** The vault PDA is
14
+ * `["vault", share_mint]`, so one mint is one vault and every other address
15
+ * follows from it.
16
+ * - **Withdrawals are asynchronous.** Redeeming queues a request and escrows
17
+ * the shares; the operator settles the queue in FIFO order afterwards.
18
+ */
19
+ /** 100% in the program's fixed point. Rates and percentages both use it. */
20
+ export declare const RATE_ONE = 1000000000n;
21
+ /** `PERCENT_DECIMALS` in the program: 1% is 1e7. */
22
+ export declare const PERCENT_DECIMALS = 7;
23
+ /** Seeds for every PDA the program owns, verbatim from `constants.rs`. */
24
+ export declare const SOLANA_SEEDS: {
25
+ readonly protocolConfig: "protocol_config";
26
+ readonly vault: "vault";
27
+ readonly vaultCollateral: "vault_collateral";
28
+ readonly vaultPendingShares: "vault_pending_shares";
29
+ readonly withdrawalQueue: "withdrawal_queue";
30
+ readonly account: "account";
31
+ readonly withdrawalRequest: "withdrawal_request";
32
+ readonly parkingLot: "parking_lot";
33
+ readonly parkingLotEscrow: "parking_lot_escrow";
34
+ readonly depositRequest: "deposit_request";
35
+ };
36
+ /** A cluster the deployment manifest can describe. */
37
+ export type SolanaCluster = "localnet" | "devnet" | "testnet" | "mainnet";
38
+ /**
39
+ * The deployed program, as `deployments/<cluster>.json` records it in
40
+ * ember-contracts. Addresses are base58 strings rather than `PublicKey`s so
41
+ * the manifest round-trips through JSON and the API unchanged.
42
+ */
43
+ export interface ISolanaProgramInfo {
44
+ /** The only field the API guarantees: everything else is a PDA of it. */
45
+ programId: string;
46
+ programDataAddress?: string;
47
+ upgradeAuthority?: string | null;
48
+ /** Manifest only. */
49
+ programSize?: number;
50
+ deployedAt?: string;
51
+ /** Where an indexer starts its backfill. EVM's deploymentBlockNumber. */
52
+ deploySlot?: number;
53
+ /** Manifest only. */
54
+ gitCommit?: string;
55
+ }
56
+ /** The protocol-wide config PDA and the values it holds. */
57
+ export interface ISolanaProtocolConfig {
58
+ address: string;
59
+ version: string;
60
+ /**
61
+ * The fields below are manifest only. All of them change without a
62
+ * redeployment - `transfer_protocol_admin`, `set_guardian`,
63
+ * `update_platform_fee_recipient` - so a config file is the wrong place to
64
+ * trust them. Read them from the chain with {@link decodeProtocolConfig}.
65
+ */
66
+ admin?: string;
67
+ guardian?: string | null;
68
+ platformFeeRecipient?: string;
69
+ initializedAt?: string;
70
+ slot?: number;
71
+ }
72
+ /** A collateral mint the protocol knows about, keyed by symbol. */
73
+ export interface ISolanaTokenInfo {
74
+ mint: string;
75
+ symbol: string;
76
+ name: string;
77
+ decimals: number;
78
+ /** SPL Token or Token-2022; the two mints of a vault may differ. */
79
+ tokenProgram: string;
80
+ /** Manifest only: live state, readable from the mint. */
81
+ mintAuthority?: string | null;
82
+ freezeAuthority?: string | null;
83
+ }
84
+ /**
85
+ * A vault, keyed in the manifest by its share mint.
86
+ *
87
+ * The four PDAs are recorded rather than re-derived so ops tooling and
88
+ * indexers agree with the SDK byte for byte, but every one of them is a pure
89
+ * function of `shareMint` — see {@link deriveVaultPdas}.
90
+ */
91
+ export interface ISolanaVaultInfo {
92
+ name: string;
93
+ vault: string;
94
+ collateralMint: string;
95
+ shareMint: string;
96
+ /** Null when the vault was created with `--no-metadata`. */
97
+ shareName: string | null;
98
+ shareSymbol: string | null;
99
+ /**
100
+ * The Metaplex Token Metadata PDA holding those two. Manifest only: it is
101
+ * a PDA of the share mint, so {@link metadataAddress} derives it.
102
+ */
103
+ shareMetadata?: string | null;
104
+ admin: string;
105
+ operator: string;
106
+ /** Unset until the admin calls `update_vault_rate_manager`. */
107
+ rateManager: string | null;
108
+ subAccounts: string[];
109
+ /**
110
+ * Manifest only. All three are PDAs of the share mint, so
111
+ * {@link deriveVaultPdas} produces them; the API does not serve a second
112
+ * source of truth that could disagree with the chain.
113
+ */
114
+ vaultCollateral?: string;
115
+ vaultPendingShares?: string;
116
+ withdrawalQueue?: string;
117
+ /** Raw u64 strings, at the collateral mint's decimals. */
118
+ feePercentage: string;
119
+ maxRateChangePerUpdate: string;
120
+ maxTvl: string;
121
+ minWithdrawalShares: string;
122
+ /** Manifest only: readable from the vault account. */
123
+ rateUpdateInterval?: string;
124
+ }
125
+ /** The whole Solana deployment, as the manifest and the API carry it. */
126
+ export interface ISolanaDeployment {
127
+ /**
128
+ * Which cluster this deployment lives on. Optional because a manifest read
129
+ * from disk may predate the field, not because the API omits it - the
130
+ * chain identifier is "solana" everywhere, so this is the only thing that
131
+ * distinguishes devnet from mainnet.
132
+ */
133
+ cluster?: SolanaCluster;
134
+ /** Manifest only: the endpoint the deploy ran through, not a property of
135
+ * the deployment, so the API does not serve it. */
136
+ rpcUrl?: string;
137
+ program: ISolanaProgramInfo;
138
+ protocolConfig?: ISolanaProtocolConfig;
139
+ /** Keyed by symbol. */
140
+ tokens?: Record<string, ISolanaTokenInfo>;
141
+ /** Keyed by share mint. */
142
+ vaults?: Record<string, ISolanaVaultInfo>;
143
+ }
144
+ /** Alias matching the EVM and Sui naming, for the unified entry point. */
145
+ export type SolanaProtocolInfo = ISolanaDeployment;
146
+ /** The PDAs that hang off a vault, all derived from its share mint. */
147
+ export interface ISolanaVaultPdas {
148
+ vault: PublicKey;
149
+ vaultCollateral: PublicKey;
150
+ vaultPendingShares: PublicKey;
151
+ withdrawalQueue: PublicKey;
152
+ }
153
+ /** `state::Rate`. */
154
+ export interface ISolanaRate {
155
+ /** Shares per unit of collateral, 1e9 fixed point. Inverse to share value. */
156
+ value: bigint;
157
+ maxRateChangePerUpdate: bigint;
158
+ rateUpdateInterval: bigint;
159
+ lastUpdatedAt: bigint;
160
+ }
161
+ /**
162
+ * `state::WithdrawalFee`, the tail of `Vault`.
163
+ *
164
+ * Two fees that stack: a permanent one charged on every redemption, and a
165
+ * time-based one charged only while the holder is inside
166
+ * `timeBasedFeeThreshold` of their last deposit. Both are 1e9 fixed point,
167
+ * so 1% is 1e7.
168
+ */
169
+ export interface ISolanaWithdrawalFee {
170
+ permanentFeePercentage: bigint;
171
+ timeBasedFeePercentage: bigint;
172
+ /** Milliseconds since the holder's last deposit. */
173
+ timeBasedFeeThreshold: bigint;
174
+ /** Addresses that pay neither fee. */
175
+ feeExempt: PublicKey[];
176
+ }
177
+ /** `state::PlatformFee`. */
178
+ export interface ISolanaPlatformFee {
179
+ accrued: bigint;
180
+ lastChargedAt: bigint;
181
+ }
182
+ /** `state::Vault`, decoded. */
183
+ export interface ISolanaVaultState {
184
+ name: string;
185
+ admin: PublicKey;
186
+ operator: PublicKey;
187
+ rateManager: PublicKey;
188
+ collateralMint: PublicKey;
189
+ shareMint: PublicKey;
190
+ blacklisted: PublicKey[];
191
+ paused: boolean;
192
+ pausedDeposits: boolean;
193
+ pausedWithdrawals: boolean;
194
+ pausedPrivileged: boolean;
195
+ subAccounts: PublicKey[];
196
+ rate: ISolanaRate;
197
+ feePercentage: bigint;
198
+ /** Mirrors the custody token account; the two must always agree. */
199
+ balance: bigint;
200
+ pendingSharesToBurn: bigint;
201
+ fee: ISolanaPlatformFee;
202
+ minWithdrawalShares: bigint;
203
+ maxTvl: bigint;
204
+ sequenceNumber: bigint;
205
+ /** `PublicKey.default` when no lot is bound. */
206
+ parkingLot: PublicKey;
207
+ depositUserList: PublicKey[];
208
+ withdrawalUserList: PublicKey[];
209
+ withdrawalFee: ISolanaWithdrawalFee;
210
+ }
211
+ /** `state::WithdrawalRequest`, the entry stored in the queue. */
212
+ export interface ISolanaWithdrawalRequest {
213
+ owner: PublicKey;
214
+ receiver: PublicKey;
215
+ shares: bigint;
216
+ estimatedWithdrawAmount: bigint;
217
+ timestamp: bigint;
218
+ sequenceNumber: bigint;
219
+ }
220
+ /** `state::VaultAccount`: one per (vault, owner). */
221
+ export interface ISolanaVaultAccount {
222
+ /** When this owner last deposited, for the time-based withdrawal fee. */
223
+ lastDepositTs: bigint;
224
+ totalPendingWithdrawalShares: bigint;
225
+ pendingWithdrawalRequests: ISolanaWithdrawalRequest[];
226
+ cancelWithdrawRequest: bigint[];
227
+ /** Seeds the owner's next request PDA; only ever increases. */
228
+ nextRequestNonce: bigint;
229
+ }
230
+ /** `state::WithdrawalRequestAccount`, one account per queued request. */
231
+ export interface ISolanaWithdrawalRequestAccount {
232
+ address: PublicKey;
233
+ vault: PublicKey;
234
+ nonce: bigint;
235
+ queueIndex: bigint;
236
+ request: ISolanaWithdrawalRequest;
237
+ }
238
+ /** `state::WithdrawalQueue`: head and tail only, the entries live elsewhere. */
239
+ export interface ISolanaWithdrawalQueue {
240
+ vault: PublicKey;
241
+ head: bigint;
242
+ tail: bigint;
243
+ }
244
+ /** `state::ProtocolConfig`, decoded. */
245
+ export interface ISolanaProtocolConfigState {
246
+ admin: PublicKey;
247
+ /** Set by `transfer_protocol_admin`; `accept_protocol_admin` completes it. */
248
+ pendingAdmin: PublicKey;
249
+ guardian: PublicKey;
250
+ version: bigint;
251
+ /** The guardian's kill switch. Admin operations keep working. */
252
+ pauseNonAdminOperations: boolean;
253
+ platformFeeRecipient: PublicKey;
254
+ minRate: bigint;
255
+ maxRate: bigint;
256
+ defaultRate: bigint;
257
+ minRateInterval: bigint;
258
+ maxRateInterval: bigint;
259
+ maxFeePercentage: bigint;
260
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Solana vault interfaces.
3
+ *
4
+ * The on-chain program is `ember_vaults`, the Anchor port of the Move
5
+ * `vault` module. Three things differ from Sui and EVM and shape everything
6
+ * here:
7
+ *
8
+ * - **Every account a transaction touches must be named up front.** There is
9
+ * no equivalent of Sui reaching through a shared object or EVM reading
10
+ * storage mid-call, so the builders below take addresses the other chains
11
+ * derive internally.
12
+ * - **The share mint is the vault's identity.** The vault PDA is
13
+ * `["vault", share_mint]`, so one mint is one vault and every other address
14
+ * follows from it.
15
+ * - **Withdrawals are asynchronous.** Redeeming queues a request and escrows
16
+ * the shares; the operator settles the queue in FIFO order afterwards.
17
+ */
18
+ /** 100% in the program's fixed point. Rates and percentages both use it. */
19
+ export const RATE_ONE = 1000000000n;
20
+ /** `PERCENT_DECIMALS` in the program: 1% is 1e7. */
21
+ export const PERCENT_DECIMALS = 7;
22
+ /** Seeds for every PDA the program owns, verbatim from `constants.rs`. */
23
+ export const SOLANA_SEEDS = {
24
+ protocolConfig: "protocol_config",
25
+ vault: "vault",
26
+ vaultCollateral: "vault_collateral",
27
+ vaultPendingShares: "vault_pending_shares",
28
+ withdrawalQueue: "withdrawal_queue",
29
+ account: "account",
30
+ withdrawalRequest: "withdrawal_request",
31
+ parkingLot: "parking_lot",
32
+ parkingLotEscrow: "parking_lot_escrow",
33
+ depositRequest: "deposit_request"
34
+ };
35
+ // Call options and response types ship with the builders that take them, in
36
+ // the phase that adds writes. Declaring them here first would be a public
37
+ // surface nothing implements.