@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.
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The program's fixed-point arithmetic, mirrored exactly.
3
+ *
4
+ * Every quote the reader answers locally has an on-chain counterpart in
5
+ * `quote.rs`, and the two must agree to the unit: a preview that rounds the
6
+ * other way sends a user into a transaction the program then rejects. So
7
+ * these are transcribed from `math.rs` rather than reimplemented, including
8
+ * which operations floor and which ceil.
9
+ *
10
+ * `bigint` throughout. The program works in u128 internally and casts back to
11
+ * u64, so doing this in `number` would lose precision above 2^53 — a vault
12
+ * holding more than ~9 million tokens at 9 decimals.
13
+ */
14
+ /** `math::BASE`: 1.0 in the program's fixed point. */
15
+ export const BASE = 1000000000n;
16
+ const U64_MAX = 18446744073709551615n;
17
+ /** `math::safely_cast_to_u64`. */
18
+ function castToU64(value, op) {
19
+ if (value > U64_MAX) {
20
+ throw new Error(`${op} overflowed a u64: ${value}`);
21
+ }
22
+ return value;
23
+ }
24
+ /** `math::mul`: `a * b / BASE`, flooring. */
25
+ export function mul(a, b) {
26
+ return castToU64((a * b) / BASE, "mul");
27
+ }
28
+ /** `math::mul_ceil`. */
29
+ export function mulCeil(a, b) {
30
+ return castToU64((a * b + BASE - 1n) / BASE, "mul_ceil");
31
+ }
32
+ /** `math::div`: `a * BASE / b`, flooring. */
33
+ export function div(a, b) {
34
+ if (b <= 0n)
35
+ throw new Error("division by zero");
36
+ return castToU64((a * BASE) / b, "div");
37
+ }
38
+ /** `math::div_ceil`. */
39
+ export function divCeil(a, b) {
40
+ if (b <= 0n)
41
+ throw new Error("division by zero");
42
+ return castToU64((a * BASE + b - 1n) / b, "div_ceil");
43
+ }
44
+ // --- the vault's derived quantities, from state.rs ---------------------------------
45
+ /** `Vault::get_vault_tvl`. */
46
+ export const vaultTvl = (totalShares, rate) => div(totalShares, rate);
47
+ /** `Vault::calculate_shares_from_amount`. */
48
+ export const sharesFromAmount = (amount, rate) => mul(amount, rate);
49
+ /** `Vault::calculate_shares_from_amount_ceil`. */
50
+ export const sharesFromAmountCeil = (amount, rate) => mulCeil(amount, rate);
51
+ /** `Vault::calculate_amount_from_shares_floor`: what settlement actually pays. */
52
+ export const amountFromSharesFloor = (shares, rate) => div(shares, rate);
53
+ /** `Vault::calculate_amount_from_shares`: ceil, what minting costs. */
54
+ export const amountFromShares = (shares, rate) => divCeil(shares, rate);
55
+ /**
56
+ * `Vault::max_mint_shares`: shares still issuable under `maxTvl`.
57
+ *
58
+ * Saturating, not checked: a vault already over its cap (because the cap was
59
+ * lowered, or the rate moved) reports 0 rather than erroring.
60
+ */
61
+ export function maxMintShares(totalShares, rate, maxTvl) {
62
+ const maxSupply = (maxTvl * rate) / BASE;
63
+ const capped = maxSupply > U64_MAX ? U64_MAX : maxSupply;
64
+ return capped > totalShares ? capped - totalShares : 0n;
65
+ }
66
+ /**
67
+ * `Vault::max_deposit_amount`: collateral still depositable under `maxTvl`.
68
+ *
69
+ * The `+1 ... -1` is the program's, not a transcription slip: it is the
70
+ * largest amount whose floored share conversion still fits the headroom, so
71
+ * depositing exactly this much lands on the cap rather than one unit over.
72
+ */
73
+ export function maxDepositAmount(totalShares, rate, maxTvl) {
74
+ if (rate <= 0n)
75
+ throw new Error("division by zero");
76
+ const headroom = maxMintShares(totalShares, rate, maxTvl);
77
+ if (headroom === 0n)
78
+ return 0n;
79
+ const maxAmount = ((headroom + 1n) * BASE - 1n) / rate;
80
+ return maxAmount > U64_MAX ? U64_MAX : maxAmount;
81
+ }
82
+ /**
83
+ * The fee settlement deducts, for one holder redeeming `gross`.
84
+ *
85
+ * Both fees are taken off the gross amount and stay in the vault, so they
86
+ * accrue to the remaining holders rather than leaving. A fee-exempt owner
87
+ * pays neither.
88
+ *
89
+ * The time-based fee is charged while the holder is still *inside*
90
+ * `threshold` of their last deposit — it penalises leaving early, so it
91
+ * applies to recent depositors, not long-standing ones. A holder with no
92
+ * recorded deposit at all (`lastDepositTs === 0`, which is how shares received
93
+ * by transfer look) is treated as inside the window and pays it.
94
+ */
95
+ export function withdrawalFee(gross, fee, options) {
96
+ if (options.exempt) {
97
+ return { permanent: 0n, timeBased: 0n, net: gross };
98
+ }
99
+ const permanent = fee.permanentFeePercentage > 0n ? mul(gross, fee.permanentFeePercentage) : 0n;
100
+ let timeBased = 0n;
101
+ if (fee.timeBasedFeePercentage > 0n && fee.timeBasedFeeThreshold > 0n) {
102
+ // Written as the program writes it, to avoid overflowing
103
+ // lastDepositTs + threshold.
104
+ const inWindow = options.lastDepositTs === 0n ||
105
+ options.nowMs < options.lastDepositTs ||
106
+ fee.timeBasedFeeThreshold > options.nowMs - options.lastDepositTs;
107
+ if (inWindow)
108
+ timeBased = mul(gross, fee.timeBasedFeePercentage);
109
+ }
110
+ return { permanent, timeBased, net: gross - permanent - timeBased };
111
+ }
@@ -0,0 +1,54 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import { type ISolanaVaultPdas } from "../interfaces/index.js";
3
+ /** Little-endian u64, for the nonce and sequence-number seeds. */
4
+ export declare function u64le(v: bigint | number): Buffer;
5
+ /** The protocol-wide config. One per program id, and it can never be closed. */
6
+ export declare const deriveProtocolConfig: (programId: PublicKey) => PublicKey;
7
+ /**
8
+ * The vault PDA and the three accounts hanging off it.
9
+ *
10
+ * `["vault", share_mint]` is the only seed pair, which is what makes the share
11
+ * mint the vault's identity: one mint is one vault, enforced on chain because
12
+ * a second `create_vault` on the same mint hits an already-initialised
13
+ * account.
14
+ */
15
+ export declare function deriveVaultPdas(programId: PublicKey, shareMint: PublicKey): ISolanaVaultPdas;
16
+ /** An owner's per-vault account: their deposit clock and pending requests. */
17
+ export declare const deriveVaultAccount: (programId: PublicKey, vault: PublicKey, owner: PublicKey) => PublicKey;
18
+ /**
19
+ * A withdrawal request's account.
20
+ *
21
+ * Seeded by the owner's `next_request_nonce` *at the time of the request*, so
22
+ * a caller must read the owner's `VaultAccount` first. A fresh owner starts at
23
+ * nonce 0.
24
+ */
25
+ export declare const deriveWithdrawalRequest: (programId: PublicKey, vault: PublicKey, owner: PublicKey, nonce: bigint) => PublicKey;
26
+ /**
27
+ * The event authority `emit_cpi!` signs with. Anchor requires it and the
28
+ * program id as the last two accounts of every instruction that emits.
29
+ */
30
+ export declare const deriveEventAuthority: (programId: PublicKey) => PublicKey;
31
+ /** Those last two accounts, ready to append to an instruction's keys. */
32
+ export declare const eventCpiAccounts: (programId: PublicKey) => {
33
+ pubkey: PublicKey;
34
+ isSigner: boolean;
35
+ isWritable: boolean;
36
+ }[];
37
+ /** `sha256("global:<name>")[..8]`, the first eight bytes of an instruction. */
38
+ export declare const instructionDiscriminator: (name: string) => Buffer;
39
+ /** `sha256("account:<Name>")[..8]`, the first eight bytes of an account. */
40
+ export declare const accountDiscriminator: (name: string) => Buffer;
41
+ export declare const TOKEN_PROGRAM_ID: PublicKey;
42
+ export declare const TOKEN_2022_PROGRAM_ID: PublicKey;
43
+ export declare const ASSOCIATED_TOKEN_PROGRAM_ID: PublicKey;
44
+ export declare const TOKEN_METADATA_PROGRAM_ID: PublicKey;
45
+ /**
46
+ * An associated token account.
47
+ *
48
+ * `tokenProgram` matters: the same owner and mint give different addresses
49
+ * under SPL Token and Token-2022, and a vault's collateral and share mints may
50
+ * live under different programs.
51
+ */
52
+ export declare const associatedTokenAddress: (owner: PublicKey, mint: PublicKey, tokenProgram?: PublicKey) => PublicKey;
53
+ /** The Metaplex metadata account carrying a mint's name and symbol. */
54
+ export declare const metadataAddress: (mint: PublicKey) => PublicKey;
@@ -0,0 +1,93 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ // Not node:crypto. This module is reachable from the SDK root through
3
+ // EmberVaults, so a Node-only import would break browser builds for every
4
+ // consumer, Sui and EVM included - not just Solana ones.
5
+ import { sha256 } from "@noble/hashes/sha2";
6
+ import { SOLANA_SEEDS } from "../interfaces/index.js";
7
+ /**
8
+ * PDA derivation for `ember_vaults`.
9
+ *
10
+ * Every address below is a pure function of the program id and, where it
11
+ * applies, the share mint. Nothing here touches the network, and the results
12
+ * are stable across redeploys: upgrading a program leaves its id unchanged,
13
+ * so its PDAs and the accounts at them survive. Only a new program id moves
14
+ * them.
15
+ */
16
+ const seed = (s) => Buffer.from(s, "utf8");
17
+ /** Little-endian u64, for the nonce and sequence-number seeds. */
18
+ export function u64le(v) {
19
+ const b = Buffer.alloc(8);
20
+ b.writeBigUInt64LE(BigInt(v));
21
+ return b;
22
+ }
23
+ /** The protocol-wide config. One per program id, and it can never be closed. */
24
+ export const deriveProtocolConfig = (programId) => PublicKey.findProgramAddressSync([seed(SOLANA_SEEDS.protocolConfig)], programId)[0];
25
+ /**
26
+ * The vault PDA and the three accounts hanging off it.
27
+ *
28
+ * `["vault", share_mint]` is the only seed pair, which is what makes the share
29
+ * mint the vault's identity: one mint is one vault, enforced on chain because
30
+ * a second `create_vault` on the same mint hits an already-initialised
31
+ * account.
32
+ */
33
+ export function deriveVaultPdas(programId, shareMint) {
34
+ const pda = (...seeds) => PublicKey.findProgramAddressSync(seeds, programId)[0];
35
+ const vault = pda(seed(SOLANA_SEEDS.vault), shareMint.toBuffer());
36
+ return {
37
+ vault,
38
+ vaultCollateral: pda(seed(SOLANA_SEEDS.vaultCollateral), vault.toBuffer()),
39
+ vaultPendingShares: pda(seed(SOLANA_SEEDS.vaultPendingShares), vault.toBuffer()),
40
+ withdrawalQueue: pda(seed(SOLANA_SEEDS.withdrawalQueue), vault.toBuffer())
41
+ };
42
+ }
43
+ /** An owner's per-vault account: their deposit clock and pending requests. */
44
+ export const deriveVaultAccount = (programId, vault, owner) => PublicKey.findProgramAddressSync([seed(SOLANA_SEEDS.account), vault.toBuffer(), owner.toBuffer()], programId)[0];
45
+ /**
46
+ * A withdrawal request's account.
47
+ *
48
+ * Seeded by the owner's `next_request_nonce` *at the time of the request*, so
49
+ * a caller must read the owner's `VaultAccount` first. A fresh owner starts at
50
+ * nonce 0.
51
+ */
52
+ export const deriveWithdrawalRequest = (programId, vault, owner, nonce) => PublicKey.findProgramAddressSync([
53
+ seed(SOLANA_SEEDS.withdrawalRequest),
54
+ vault.toBuffer(),
55
+ owner.toBuffer(),
56
+ u64le(nonce)
57
+ ], programId)[0];
58
+ // The parking lot's PDAs - the lot, its escrow and a deposit request - are
59
+ // derived in the phase that adds decoders for DepositParkingLot and
60
+ // DepositRequest. Deriving an address nothing can read is a surface that
61
+ // looks usable and is not. Note for then: a request's seed is the lot's
62
+ // sequence_number + 1, the slot the next park will take.
63
+ /**
64
+ * The event authority `emit_cpi!` signs with. Anchor requires it and the
65
+ * program id as the last two accounts of every instruction that emits.
66
+ */
67
+ export const deriveEventAuthority = (programId) => PublicKey.findProgramAddressSync([seed("__event_authority")], programId)[0];
68
+ /** Those last two accounts, ready to append to an instruction's keys. */
69
+ export const eventCpiAccounts = (programId) => [
70
+ { pubkey: deriveEventAuthority(programId), isSigner: false, isWritable: false },
71
+ { pubkey: programId, isSigner: false, isWritable: false }
72
+ ];
73
+ // --- Anchor discriminators --------------------------------------------------------
74
+ const digest8 = (preimage) => Buffer.from(sha256(new TextEncoder().encode(preimage))).subarray(0, 8);
75
+ /** `sha256("global:<name>")[..8]`, the first eight bytes of an instruction. */
76
+ export const instructionDiscriminator = (name) => digest8(`global:${name}`);
77
+ /** `sha256("account:<Name>")[..8]`, the first eight bytes of an account. */
78
+ export const accountDiscriminator = (name) => digest8(`account:${name}`);
79
+ // --- SPL addresses ----------------------------------------------------------------
80
+ export const TOKEN_PROGRAM_ID = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
81
+ export const TOKEN_2022_PROGRAM_ID = new PublicKey("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
82
+ export const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
83
+ export const TOKEN_METADATA_PROGRAM_ID = new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s");
84
+ /**
85
+ * An associated token account.
86
+ *
87
+ * `tokenProgram` matters: the same owner and mint give different addresses
88
+ * under SPL Token and Token-2022, and a vault's collateral and share mints may
89
+ * live under different programs.
90
+ */
91
+ export const associatedTokenAddress = (owner, mint, tokenProgram = TOKEN_PROGRAM_ID) => PublicKey.findProgramAddressSync([owner.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
92
+ /** The Metaplex metadata account carrying a mint's name and symbol. */
93
+ export const metadataAddress = (mint) => PublicKey.findProgramAddressSync([Buffer.from("metadata"), TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer()], TOKEN_METADATA_PROGRAM_ID)[0];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ember-finance/sdk",
3
3
  "description": "Ember Protocol SDK",
4
- "version": "2.38.0",
4
+ "version": "2.39.0",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
7
7
  "main": "./dist/index.js",
@@ -85,6 +85,7 @@
85
85
  "dependencies": {
86
86
  "@aws-sdk/client-kms": "^3.1044.0",
87
87
  "@noble/curves": "^2.2.0",
88
+ "@noble/hashes": "^1.8.0",
88
89
  "asn1.js": "^5.4.1",
89
90
  "axios": "1.13.6",
90
91
  "yarn": "^1.22.19"
@@ -93,6 +94,7 @@
93
94
  "@firefly-exchange/library-sui": "^3.0.0",
94
95
  "@mysten/sui": "^2.0.0",
95
96
  "@openapitools/openapi-generator-cli": "^2.41.0",
97
+ "@solana/web3.js": "^1.95.0",
96
98
  "@types/chai": "^4.3.3",
97
99
  "@types/chai-as-promised": "^7.1.5",
98
100
  "@types/dotenv-parse-variables": "^2.0.1",
@@ -118,6 +120,7 @@
118
120
  "peerDependencies": {
119
121
  "@firefly-exchange/library-sui": "^3.0.0",
120
122
  "@mysten/sui": "^2.0.0",
123
+ "@solana/web3.js": "^1.95.0",
121
124
  "ethers": "^6.13.4"
122
125
  },
123
126
  "repository": {
@@ -143,5 +146,10 @@
143
146
  "shell-quote": "^1.10.0",
144
147
  "tar": "^7.5.19"
145
148
  },
146
- "packageManager": "yarn@4.14.1"
149
+ "packageManager": "yarn@4.14.1",
150
+ "peerDependenciesMeta": {
151
+ "@solana/web3.js": {
152
+ "optional": true
153
+ }
154
+ }
147
155
  }