@ember-finance/sdk 2.38.0 → 2.39.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.
@@ -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,10 @@ 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";
21
25
  import { ChainType, PlatformEnv } from "./common/constants.js";
22
26
  /**
23
27
  * SUI-specific initialization options
@@ -46,7 +50,44 @@ export interface EvmInitOptions {
46
50
  environment?: PlatformEnv;
47
51
  chainIdentifier: string;
48
52
  }
49
- export type EmberVaultsInitOptions = SuiInitOptions | EvmInitOptions;
53
+ /**
54
+ * Solana-specific initialization options.
55
+ *
56
+ * `connection` rather than a provider: web3.js has no provider abstraction,
57
+ * and every read below goes through it. No signer yet - this slice is
58
+ * read-only, and the signer shape (wallet adapter vs Keypair) is settled when
59
+ * the write builders land.
60
+ */
61
+ export interface SolanaInitOptions {
62
+ chainType: "solana";
63
+ connection: SolanaConnection;
64
+ /**
65
+ * Unused on Solana and accepted only so the constructor can read it
66
+ * uniformly. The chain identifier is "solana" on every cluster; which
67
+ * cluster an environment talks to follows from the deployment it serves.
68
+ */
69
+ chainIdentifier?: string;
70
+ walletAddress?: string;
71
+ deployment?: ISolanaDeployment;
72
+ basePath?: string;
73
+ environment?: PlatformEnv;
74
+ }
75
+ export type EmberVaultsInitOptions = SuiInitOptions | EvmInitOptions | SolanaInitOptions;
76
+ /**
77
+ * Solana-specific EmberVaults instance type.
78
+ *
79
+ * Narrower than its Sui and EVM counterparts on purpose: reads only. The
80
+ * admin, operator, vaultAdmin, user and txBuilder surfaces follow, and the
81
+ * getters for them throw rather than returning undefined until they do.
82
+ */
83
+ export interface SolanaEmberVaults {
84
+ chainType: "solana";
85
+ connection: SolanaConnection;
86
+ parser: SolanaDeploymentParser;
87
+ walletAddress?: string;
88
+ reader: SolanaVaultReader;
89
+ deployment: ISolanaDeployment;
90
+ }
50
91
  /**
51
92
  * SUI-specific EmberVaults instance type
52
93
  */
@@ -133,9 +174,21 @@ export declare class EmberVaults {
133
174
  private _evmUser?;
134
175
  private _evmDeployment?;
135
176
  private _evmVaultReader?;
177
+ private _solanaConnection?;
178
+ private _solanaParser?;
179
+ private _solanaDeployment?;
180
+ private _solanaVaultReader?;
136
181
  private _walletAddress?;
137
182
  private _chainIdentifier?;
138
183
  constructor(options: EmberVaultsInitOptions);
184
+ /**
185
+ * Solana setup.
186
+ *
187
+ * The deployment is optional here as it is on the other chains: pass one
188
+ * to work offline, or call `init()` to fetch it from the API. Without
189
+ * either, the reader has nothing to resolve addresses against.
190
+ */
191
+ private _initSolana;
139
192
  private _initSui;
140
193
  private _initEvm;
141
194
  /**
@@ -146,19 +199,30 @@ export declare class EmberVaults {
146
199
  * Updates the deployment configuration
147
200
  * @param newDeployment The new deployment configuration (ISuiDeployment or EvmProtocolInfo)
148
201
  */
149
- updateDeployment(newDeployment: ISuiDeployment | EvmProtocolInfo): void;
202
+ updateDeployment(newDeployment: ISuiDeployment | EvmProtocolInfo | ISolanaDeployment): void;
150
203
  get suiClient(): SuiClient;
151
204
  get network(): string;
152
205
  get provider(): EvmProvider | undefined;
153
206
  get walletAddress(): string | undefined;
207
+ /**
208
+ * Read-only vault access. Sui has no reader, so this is EVM or Solana.
209
+ */
210
+ get reader(): EVMVaultReader | SolanaVaultReader;
211
+ /**
212
+ * Solana's write surfaces are not built yet. Thrown rather than returning
213
+ * undefined so the failure names what is missing instead of surfacing as
214
+ * "cannot read property of undefined" several frames later.
215
+ */
216
+ private notOnSolana;
217
+ get connection(): SolanaConnection;
154
218
  get signer(): SuiSigner | EvmSigner | undefined;
155
- get parser(): SuiDeploymentParser | EVMDeploymentParser;
219
+ get parser(): SuiDeploymentParser | EVMDeploymentParser | SolanaDeploymentParser;
156
220
  get txBuilder(): SuiTxBuilder | EVMTxBuilder;
157
221
  get admin(): SuiAdminCalls | EVMAdminCalls;
158
222
  get operator(): SuiOperatorCalls | EVMOperatorCalls;
159
223
  get vaultAdmin(): SuiVaultAdminCalls | EVMVaultAdminCalls;
160
224
  get user(): SuiUserCalls | EVMUserCalls;
161
- get deployment(): ISuiDeployment | EvmProtocolInfo;
225
+ get deployment(): ISuiDeployment | EvmProtocolInfo | ISolanaDeployment;
162
226
  /**
163
227
  * Check if this is a SUI instance
164
228
  */
@@ -166,6 +230,10 @@ export declare class EmberVaults {
166
230
  /**
167
231
  * Check if this is an EVM instance
168
232
  */
233
+ /**
234
+ * Check if this is a Solana instance
235
+ */
236
+ isSolana(): this is EmberVaults & SolanaEmberVaults;
169
237
  isEvm(): this is EmberVaults & EvmEmberVaults;
170
238
  /**
171
239
  * Get SUI-specific instance (throws if not SUI)
@@ -175,4 +243,8 @@ export declare class EmberVaults {
175
243
  * Get EVM-specific instance (throws if not EVM)
176
244
  */
177
245
  asEvm(): EvmEmberVaults;
246
+ /**
247
+ * Get Solana-specific instance (throws if not Solana)
248
+ */
249
+ asSolana(): SolanaEmberVaults;
178
250
  }
@@ -14,6 +14,8 @@ 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";
17
19
  import { environmentConfig } from "./common/constants.js";
18
20
  /**
19
21
  * EmberVaults - Unified interface for both SUI and EVM chains
@@ -54,10 +56,29 @@ export class EmberVaults {
54
56
  if (options.chainType === "sui") {
55
57
  this._initSui(options);
56
58
  }
59
+ else if (options.chainType === "solana") {
60
+ this._initSolana(options);
61
+ }
57
62
  else {
58
63
  this._initEvm(options);
59
64
  }
60
65
  }
66
+ /**
67
+ * Solana setup.
68
+ *
69
+ * The deployment is optional here as it is on the other chains: pass one
70
+ * to work offline, or call `init()` to fetch it from the API. Without
71
+ * either, the reader has nothing to resolve addresses against.
72
+ */
73
+ _initSolana(options) {
74
+ this._solanaConnection = options.connection;
75
+ this._walletAddress = options.walletAddress;
76
+ if (options.deployment) {
77
+ this._solanaDeployment = options.deployment;
78
+ this._solanaParser = new SolanaDeploymentParser(options.deployment);
79
+ this._solanaVaultReader = new SolanaVaultReader(options.connection, this._solanaParser);
80
+ }
81
+ }
61
82
  _initSui(options) {
62
83
  this._suiNetwork = options.network;
63
84
  this._suiClient = options.suiClient;
@@ -97,6 +118,23 @@ export class EmberVaults {
97
118
  throw new Error(`Deployment data not found for chain ${this._chainIdentifier}`);
98
119
  }
99
120
  }
121
+ else if (this.chainType === "solana") {
122
+ // One Solana deployment per environment, so there is nothing to
123
+ // disambiguate by chain identifier - it is "solana" on every
124
+ // cluster, and which cluster follows from the environment.
125
+ // Compared as a string: the API spec vendored under src/api/v2 was
126
+ // copied before bfvs added "solana" to VaultsDeploymentConfig's
127
+ // chainType enum, so the generated union does not list it yet. The
128
+ // wire value is correct and staging already serves it; this reads
129
+ // as written rather than as typed until the client is regenerated.
130
+ const deploymentData = deployment.data.find(d => d.chainType === "solana");
131
+ if (deploymentData) {
132
+ this.updateDeployment(deploymentData.config);
133
+ }
134
+ else {
135
+ throw new Error("Deployment data not found for chain solana");
136
+ }
137
+ }
100
138
  else {
101
139
  const deploymentData = deployment.data.find(d => d.chainType === "evm" && d.chain === this._chainIdentifier);
102
140
  if (deploymentData) {
@@ -112,6 +150,21 @@ export class EmberVaults {
112
150
  * @param newDeployment The new deployment configuration (ISuiDeployment or EvmProtocolInfo)
113
151
  */
114
152
  updateDeployment(newDeployment) {
153
+ if (this.chainType === "solana") {
154
+ const solanaDeployment = newDeployment;
155
+ this._solanaDeployment = solanaDeployment;
156
+ // The parser holds the program id that every address derives from,
157
+ // so it is rebuilt rather than mutated - a deployment pointing at a
158
+ // different program is a different parser.
159
+ this._solanaParser = new SolanaDeploymentParser(solanaDeployment);
160
+ if (this._solanaVaultReader) {
161
+ this._solanaVaultReader.updateDeployment(this._solanaParser);
162
+ }
163
+ else if (this._solanaConnection) {
164
+ this._solanaVaultReader = new SolanaVaultReader(this._solanaConnection, this._solanaParser);
165
+ }
166
+ return;
167
+ }
115
168
  if (this.chainType === "sui") {
116
169
  const suiDeployment = newDeployment;
117
170
  this._suiDeployment = suiDeployment;
@@ -161,28 +214,78 @@ export class EmberVaults {
161
214
  get walletAddress() {
162
215
  return this._walletAddress;
163
216
  }
217
+ /**
218
+ * Read-only vault access. Sui has no reader, so this is EVM or Solana.
219
+ */
220
+ get reader() {
221
+ if (this.chainType === "solana") {
222
+ if (!this._solanaVaultReader)
223
+ throw new Error("No Solana deployment loaded. Pass one, or call init().");
224
+ return this._solanaVaultReader;
225
+ }
226
+ if (this.chainType === "sui")
227
+ throw new Error("reader is not available for SUI chain");
228
+ return this._evmVaultReader;
229
+ }
230
+ /**
231
+ * Solana's write surfaces are not built yet. Thrown rather than returning
232
+ * undefined so the failure names what is missing instead of surfacing as
233
+ * "cannot read property of undefined" several frames later.
234
+ */
235
+ notOnSolana(surface) {
236
+ throw new Error(`${surface} is not available for Solana yet. ` +
237
+ `The Solana module currently exposes reads only: use .reader, .parser or .deployment.`);
238
+ }
239
+ // ============================================
240
+ // Solana Getters
241
+ // ============================================
242
+ get connection() {
243
+ if (this.chainType !== "solana")
244
+ throw new Error("connection is only available for Solana chain");
245
+ return this._solanaConnection;
246
+ }
164
247
  get signer() {
165
248
  return this.chainType === "sui" ? this._suiSigner : this._evmSigner;
166
249
  }
167
250
  get parser() {
251
+ if (this.chainType === "solana") {
252
+ if (!this._solanaParser)
253
+ throw new Error("No Solana deployment loaded. Pass one, or call init().");
254
+ return this._solanaParser;
255
+ }
168
256
  return this.chainType === "sui" ? this._suiParser : this._evmParser;
169
257
  }
170
258
  get txBuilder() {
259
+ if (this.chainType === "solana")
260
+ this.notOnSolana("txBuilder");
171
261
  return this.chainType === "sui" ? this._suiTxBuilder : this._evmTxBuilder;
172
262
  }
173
263
  get admin() {
264
+ if (this.chainType === "solana")
265
+ this.notOnSolana("admin");
174
266
  return this.chainType === "sui" ? this._suiAdmin : this._evmAdmin;
175
267
  }
176
268
  get operator() {
269
+ if (this.chainType === "solana")
270
+ this.notOnSolana("operator");
177
271
  return this.chainType === "sui" ? this._suiOperator : this._evmOperator;
178
272
  }
179
273
  get vaultAdmin() {
274
+ if (this.chainType === "solana")
275
+ this.notOnSolana("vaultAdmin");
180
276
  return this.chainType === "sui" ? this._suiVaultAdmin : this._evmVaultAdmin;
181
277
  }
182
278
  get user() {
279
+ if (this.chainType === "solana")
280
+ this.notOnSolana("user");
183
281
  return this.chainType === "sui" ? this._suiUser : this._evmUser;
184
282
  }
185
283
  get deployment() {
284
+ if (this.chainType === "solana") {
285
+ if (!this._solanaDeployment)
286
+ throw new Error("No Solana deployment loaded. Pass one, or call init().");
287
+ return this._solanaDeployment;
288
+ }
186
289
  return this.chainType === "sui" ? this._suiDeployment : this._evmDeployment;
187
290
  }
188
291
  // ============================================
@@ -197,6 +300,12 @@ export class EmberVaults {
197
300
  /**
198
301
  * Check if this is an EVM instance
199
302
  */
303
+ /**
304
+ * Check if this is a Solana instance
305
+ */
306
+ isSolana() {
307
+ return this.chainType === "solana";
308
+ }
200
309
  isEvm() {
201
310
  return this.chainType === "evm";
202
311
  }
@@ -242,4 +351,22 @@ export class EmberVaults {
242
351
  reader: this._evmVaultReader
243
352
  };
244
353
  }
354
+ /**
355
+ * Get Solana-specific instance (throws if not Solana)
356
+ */
357
+ asSolana() {
358
+ if (!this.isSolana())
359
+ throw new Error("This EmberVaults instance is not configured for Solana");
360
+ if (!this._solanaParser || !this._solanaVaultReader || !this._solanaDeployment) {
361
+ throw new Error("No Solana deployment loaded. Pass one, or call init().");
362
+ }
363
+ return {
364
+ chainType: "solana",
365
+ connection: this._solanaConnection,
366
+ parser: this._solanaParser,
367
+ walletAddress: this._walletAddress,
368
+ reader: this._solanaVaultReader,
369
+ deployment: this._solanaDeployment
370
+ };
371
+ }
245
372
  }
@@ -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,3 @@
1
+ export * from "./interfaces/index.js";
2
+ export * from "./on-chain-calls/index.js";
3
+ export * from "./utils/index.js";
@@ -0,0 +1,3 @@
1
+ export * from "./interfaces/index.js";
2
+ export * from "./on-chain-calls/index.js";
3
+ 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.
@@ -0,0 +1 @@
1
+ export * from "./vault-reader.js";
@@ -0,0 +1 @@
1
+ export * from "./vault-reader.js";