@temporalabs/treasury 0.1.1 → 0.1.3
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/NOTICE +4 -2
- package/README.md +100 -44
- package/THIRD_PARTY_NOTICES.connect-page.md +7836 -0
- package/THIRD_PARTY_NOTICES.md +543 -162
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +243 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +366 -239
- package/dist/client.js +202 -25
- package/dist/client.js.map +1 -1
- package/dist/config/earn.d.ts +48 -12
- package/dist/config/earn.js +48 -12
- package/dist/config/earn.js.map +1 -1
- package/dist/connect/ack.d.ts +76 -0
- package/dist/connect/ack.js +147 -0
- package/dist/connect/ack.js.map +1 -0
- package/dist/connect/commands.d.ts +81 -0
- package/dist/connect/commands.js +555 -0
- package/dist/connect/commands.js.map +1 -0
- package/dist/connect/config.d.ts +24 -0
- package/dist/connect/config.js +35 -0
- package/dist/connect/config.js.map +1 -0
- package/dist/connect/gate-solana.d.ts +8 -0
- package/dist/connect/gate-solana.js +147 -0
- package/dist/connect/gate-solana.js.map +1 -0
- package/dist/connect/gate.d.ts +37 -0
- package/dist/connect/gate.js +115 -0
- package/dist/connect/gate.js.map +1 -0
- package/dist/connect/http.d.ts +60 -0
- package/dist/connect/http.js +235 -0
- package/dist/connect/http.js.map +1 -0
- package/dist/connect/open.d.ts +2 -0
- package/dist/connect/open.js +35 -0
- package/dist/connect/open.js.map +1 -0
- package/dist/connect/page.d.ts +5 -0
- package/dist/connect/page.js +69 -0
- package/dist/connect/page.js.map +1 -0
- package/dist/connect/session.d.ts +39 -0
- package/dist/connect/session.js +198 -0
- package/dist/connect/session.js.map +1 -0
- package/dist/connect/solana.d.ts +73 -0
- package/dist/connect/solana.js +267 -0
- package/dist/connect/solana.js.map +1 -0
- package/dist/connect/verify.d.ts +37 -0
- package/dist/connect/verify.js +89 -0
- package/dist/connect/verify.js.map +1 -0
- package/dist/connect-page.js +5143 -0
- package/dist/disclosures.d.ts +22 -6
- package/dist/disclosures.js +34 -8
- package/dist/disclosures.js.map +1 -1
- package/dist/earn/commands.d.ts +46 -0
- package/dist/earn/commands.js +556 -0
- package/dist/earn/commands.js.map +1 -0
- package/dist/earn/solana.d.ts +278 -0
- package/dist/earn/solana.js +302 -0
- package/dist/earn/solana.js.map +1 -0
- package/dist/guide.d.ts +15 -0
- package/dist/guide.js +50 -0
- package/dist/guide.js.map +1 -0
- package/dist/links.d.ts +2 -0
- package/dist/links.js +40 -14
- package/dist/links.js.map +1 -1
- package/dist/position.d.ts +9 -6
- package/dist/position.js +39 -19
- package/dist/position.js.map +1 -1
- package/dist/preflight.d.ts +9 -0
- package/dist/preflight.js +14 -3
- package/dist/preflight.js.map +1 -1
- package/dist/quote.js +24 -7
- package/dist/quote.js.map +1 -1
- package/dist/redact.d.ts +3 -3
- package/dist/redact.js +3 -3
- package/dist/redact.js.map +1 -1
- package/dist/registry-schema.d.ts +82 -5
- package/dist/registry-schema.js +105 -15
- package/dist/registry-schema.js.map +1 -1
- package/dist/registry.d.ts +50 -7
- package/dist/registry.js +122 -21
- package/dist/registry.js.map +1 -1
- package/dist/solana/constants.d.ts +49 -0
- package/dist/solana/constants.js +50 -0
- package/dist/solana/constants.js.map +1 -0
- package/dist/solana/kvault.d.ts +99 -0
- package/dist/solana/kvault.js +192 -0
- package/dist/solana/kvault.js.map +1 -0
- package/dist/solana/layout.d.ts +143 -0
- package/dist/solana/layout.js +209 -0
- package/dist/solana/layout.js.map +1 -0
- package/dist/solana/rpc.d.ts +24 -0
- package/dist/solana/rpc.js +66 -0
- package/dist/solana/rpc.js.map +1 -0
- package/dist/solana/vault.d.ts +153 -0
- package/dist/solana/vault.js +324 -0
- package/dist/solana/vault.js.map +1 -0
- package/dist/{mcp-server.mjs → treasury.mjs} +41101 -49486
- package/dist/version.js +5 -10
- package/dist/version.js.map +1 -1
- package/llms.txt +621 -0
- package/package.json +12 -9
- package/registry/vaults.json +218 -4
- package/dist/mcp/server.d.ts +0 -8
- package/dist/mcp/server.js +0 -368
- package/dist/mcp/server.js.map +0 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decoders for the two accounts this client reads on Solana: the kVault `VaultState` and the lending
|
|
3
|
+
* program's `Reserve`.
|
|
4
|
+
*
|
|
5
|
+
* 🔴 HAND-DECLARED OFFSETS, PINNED TO KAMINO'S OWN SDK. `tests/solana-kvault.unit.test.ts` decodes a real
|
|
6
|
+
* mainnet snapshot (`tests/fixtures/solana/vault-snapshot.json`) and compares every field with what
|
|
7
|
+
* Kamino's SDK decoded from the same bytes (`vendor/kamino/decode-fixtures.json`, version in
|
|
8
|
+
* `vendor/kamino/PINNED`). The discriminator and the exact account length are checked on every decode, so a
|
|
9
|
+
* different account type, or a resized one, is refused rather than read as garbage. Kamino's SDK is not a
|
|
10
|
+
* dependency at runtime.
|
|
11
|
+
*
|
|
12
|
+
* Amounts are bigint base units. `*Sf` fields are the lending program's scaled fractions: value × 2^60.
|
|
13
|
+
*/
|
|
14
|
+
import { getAddressDecoder } from "@solana/kit";
|
|
15
|
+
import { DEFAULT_PUBKEY, MAX_RESERVES, RESERVE_SIZE, VAULT_STATE_SIZE } from "./constants.js";
|
|
16
|
+
/** klend's fixed-point scale: a `*_sf` field is value × 2^60. */
|
|
17
|
+
export const SF_ONE = 1n << 60n;
|
|
18
|
+
/** `sha256("account:VaultState")[0..8]` — Anchor's account discriminator. */
|
|
19
|
+
export const VAULT_STATE_DISCRIMINATOR = Uint8Array.from([228, 196, 82, 165, 98, 210, 235, 152]);
|
|
20
|
+
/** `sha256("account:Reserve")[0..8]`. */
|
|
21
|
+
export const RESERVE_DISCRIMINATOR = Uint8Array.from([43, 242, 204, 202, 26, 247, 59, 127]);
|
|
22
|
+
/** Byte offsets INTO THE ACCOUNT DATA (the 8-byte discriminator included). */
|
|
23
|
+
export const VAULT_STATE_OFFSETS = {
|
|
24
|
+
vaultAdminAuthority: 8,
|
|
25
|
+
baseVaultAuthority: 40,
|
|
26
|
+
tokenMint: 80,
|
|
27
|
+
tokenMintDecimals: 112,
|
|
28
|
+
tokenVault: 120,
|
|
29
|
+
tokenProgram: 152,
|
|
30
|
+
sharesMint: 184,
|
|
31
|
+
sharesMintDecimals: 216,
|
|
32
|
+
tokenAvailable: 224,
|
|
33
|
+
sharesIssued: 232,
|
|
34
|
+
unallocatedWeight: 248,
|
|
35
|
+
performanceFeeBps: 256,
|
|
36
|
+
managementFeeBps: 264,
|
|
37
|
+
pendingFeesSf: 296,
|
|
38
|
+
allocations: 312,
|
|
39
|
+
minInvestAmount: 58_424,
|
|
40
|
+
minInvestDelaySlots: 58_432,
|
|
41
|
+
pendingAdmin: 58_448,
|
|
42
|
+
name: 58_528,
|
|
43
|
+
unallocatedTokensCap: 58_640,
|
|
44
|
+
allocationAdmin: 58_648,
|
|
45
|
+
allowAllocationsInWhitelistedReservesOnly: 58_728,
|
|
46
|
+
allowInvestInWhitelistedReservesOnly: 58_729,
|
|
47
|
+
depositCap: 58_736,
|
|
48
|
+
permissioningAuthority: 58_840,
|
|
49
|
+
};
|
|
50
|
+
/** One `VaultAllocation` slot: its size, and each field's offset WITHIN the slot. */
|
|
51
|
+
export const ALLOCATION_SLOT = {
|
|
52
|
+
size: 2_160,
|
|
53
|
+
reserve: 0,
|
|
54
|
+
ctokenVault: 32,
|
|
55
|
+
targetAllocationWeight: 64,
|
|
56
|
+
tokenAllocationCap: 72,
|
|
57
|
+
ctokenAllocationCap: 88,
|
|
58
|
+
ctokenAllocation: 1_104,
|
|
59
|
+
lastInvestSlot: 1_112,
|
|
60
|
+
};
|
|
61
|
+
export const RESERVE_OFFSETS = {
|
|
62
|
+
lastUpdateSlot: 16,
|
|
63
|
+
lendingMarket: 32,
|
|
64
|
+
liquidityMint: 128,
|
|
65
|
+
liquiditySupplyVault: 160,
|
|
66
|
+
totalAvailableAmount: 224,
|
|
67
|
+
borrowedAmountSf: 232,
|
|
68
|
+
liquidityMintDecimals: 272,
|
|
69
|
+
accumulatedProtocolFeesSf: 344,
|
|
70
|
+
accumulatedReferrerFeesSf: 360,
|
|
71
|
+
pendingReferrerFeesSf: 376,
|
|
72
|
+
collateralMint: 2_560,
|
|
73
|
+
collateralMintTotalSupply: 2_592,
|
|
74
|
+
depositLimit: 5_016,
|
|
75
|
+
};
|
|
76
|
+
const addressDecoder = getAddressDecoder();
|
|
77
|
+
function view(data) {
|
|
78
|
+
return new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
79
|
+
}
|
|
80
|
+
const u64 = (d, o) => view(d).getBigUint64(o, true);
|
|
81
|
+
const u128 = (d, o) => u64(d, o) | (u64(d, o + 8) << 64n);
|
|
82
|
+
const pubkey = (d, o) => addressDecoder.decode(d.subarray(o, o + 32));
|
|
83
|
+
/** Refuse anything that is not exactly the account this decoder was written for. */
|
|
84
|
+
function assertAccount(data, size, disc, what) {
|
|
85
|
+
if (data.length !== size) {
|
|
86
|
+
throw new Error(`kamino: ${what} is ${data.length} bytes, expected ${size} (a different program version or account type)`);
|
|
87
|
+
}
|
|
88
|
+
for (let i = 0; i < 8; i++) {
|
|
89
|
+
if (data[i] !== disc[i])
|
|
90
|
+
throw new Error(`kamino: ${what} discriminator mismatch — not a ${what} account`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function decodeVaultState(data) {
|
|
94
|
+
assertAccount(data, VAULT_STATE_SIZE, VAULT_STATE_DISCRIMINATOR, "VaultState");
|
|
95
|
+
const o = VAULT_STATE_OFFSETS;
|
|
96
|
+
const allocations = [];
|
|
97
|
+
for (let i = 0; i < MAX_RESERVES; i++) {
|
|
98
|
+
const base = o.allocations + i * ALLOCATION_SLOT.size;
|
|
99
|
+
const reserve = pubkey(data, base + ALLOCATION_SLOT.reserve);
|
|
100
|
+
if (reserve === DEFAULT_PUBKEY)
|
|
101
|
+
continue;
|
|
102
|
+
allocations.push({
|
|
103
|
+
reserve,
|
|
104
|
+
ctokenVault: pubkey(data, base + ALLOCATION_SLOT.ctokenVault),
|
|
105
|
+
targetAllocationWeight: u64(data, base + ALLOCATION_SLOT.targetAllocationWeight),
|
|
106
|
+
tokenAllocationCap: u64(data, base + ALLOCATION_SLOT.tokenAllocationCap),
|
|
107
|
+
ctokenAllocationCap: u64(data, base + ALLOCATION_SLOT.ctokenAllocationCap),
|
|
108
|
+
ctokenAllocation: u64(data, base + ALLOCATION_SLOT.ctokenAllocation),
|
|
109
|
+
lastInvestSlot: u64(data, base + ALLOCATION_SLOT.lastInvestSlot),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const nameBytes = data.subarray(o.name, o.name + 40);
|
|
113
|
+
const nul = nameBytes.indexOf(0);
|
|
114
|
+
return {
|
|
115
|
+
vaultAdminAuthority: pubkey(data, o.vaultAdminAuthority),
|
|
116
|
+
baseVaultAuthority: pubkey(data, o.baseVaultAuthority),
|
|
117
|
+
tokenMint: pubkey(data, o.tokenMint),
|
|
118
|
+
tokenMintDecimals: u64(data, o.tokenMintDecimals),
|
|
119
|
+
tokenVault: pubkey(data, o.tokenVault),
|
|
120
|
+
tokenProgram: pubkey(data, o.tokenProgram),
|
|
121
|
+
sharesMint: pubkey(data, o.sharesMint),
|
|
122
|
+
sharesMintDecimals: u64(data, o.sharesMintDecimals),
|
|
123
|
+
tokenAvailable: u64(data, o.tokenAvailable),
|
|
124
|
+
sharesIssued: u64(data, o.sharesIssued),
|
|
125
|
+
unallocatedWeight: u64(data, o.unallocatedWeight),
|
|
126
|
+
performanceFeeBps: u64(data, o.performanceFeeBps),
|
|
127
|
+
managementFeeBps: u64(data, o.managementFeeBps),
|
|
128
|
+
pendingFeesSf: u128(data, o.pendingFeesSf),
|
|
129
|
+
allocations,
|
|
130
|
+
minInvestAmount: u64(data, o.minInvestAmount),
|
|
131
|
+
minInvestDelaySlots: u64(data, o.minInvestDelaySlots),
|
|
132
|
+
pendingAdmin: pubkey(data, o.pendingAdmin),
|
|
133
|
+
name: new TextDecoder().decode(nul === -1 ? nameBytes : nameBytes.subarray(0, nul)),
|
|
134
|
+
unallocatedTokensCap: u64(data, o.unallocatedTokensCap),
|
|
135
|
+
allocationAdmin: pubkey(data, o.allocationAdmin),
|
|
136
|
+
allowAllocationsInWhitelistedReservesOnly: data[o.allowAllocationsInWhitelistedReservesOnly],
|
|
137
|
+
allowInvestInWhitelistedReservesOnly: data[o.allowInvestInWhitelistedReservesOnly],
|
|
138
|
+
depositCap: u64(data, o.depositCap),
|
|
139
|
+
permissioningAuthority: pubkey(data, o.permissioningAuthority),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
export function decodeReserve(data) {
|
|
143
|
+
assertAccount(data, RESERVE_SIZE, RESERVE_DISCRIMINATOR, "Reserve");
|
|
144
|
+
const o = RESERVE_OFFSETS;
|
|
145
|
+
return {
|
|
146
|
+
lastUpdateSlot: u64(data, o.lastUpdateSlot),
|
|
147
|
+
lendingMarket: pubkey(data, o.lendingMarket),
|
|
148
|
+
liquidityMint: pubkey(data, o.liquidityMint),
|
|
149
|
+
liquiditySupplyVault: pubkey(data, o.liquiditySupplyVault),
|
|
150
|
+
totalAvailableAmount: u64(data, o.totalAvailableAmount),
|
|
151
|
+
borrowedAmountSf: u128(data, o.borrowedAmountSf),
|
|
152
|
+
liquidityMintDecimals: u64(data, o.liquidityMintDecimals),
|
|
153
|
+
accumulatedProtocolFeesSf: u128(data, o.accumulatedProtocolFeesSf),
|
|
154
|
+
accumulatedReferrerFeesSf: u128(data, o.accumulatedReferrerFeesSf),
|
|
155
|
+
pendingReferrerFeesSf: u128(data, o.pendingReferrerFeesSf),
|
|
156
|
+
collateralMint: pubkey(data, o.collateralMint),
|
|
157
|
+
collateralMintTotalSupply: u64(data, o.collateralMintTotalSupply),
|
|
158
|
+
depositLimit: u64(data, o.depositLimit),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* A reserve's total supply in liquidity units, scaled by 2^60: available + borrowed − every fee bucket.
|
|
163
|
+
* klend-sdk's `Reserve.getTotalSupplySf()`, the numerator of the collateral exchange rate.
|
|
164
|
+
*/
|
|
165
|
+
export function totalSupplySf(r) {
|
|
166
|
+
return (r.totalAvailableAmount * SF_ONE +
|
|
167
|
+
r.borrowedAmountSf -
|
|
168
|
+
r.accumulatedProtocolFeesSf -
|
|
169
|
+
r.accumulatedReferrerFeesSf -
|
|
170
|
+
r.pendingReferrerFeesSf);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* What `ctokens` of this reserve are worth in liquidity base units, at the reserve's STORED state
|
|
174
|
+
* (floored): ctokens × totalSupply / ctokenSupply. A reserve accrues interest only when refreshed, so this
|
|
175
|
+
* lags the true value by the interest since `lastUpdateSlot` — conservative, and what klend-sdk's
|
|
176
|
+
* `getCollateralExchangeRate()` (the "stale" rate) also reports.
|
|
177
|
+
*/
|
|
178
|
+
export function ctokensToLiquidity(ctokens, r) {
|
|
179
|
+
if (r.collateralMintTotalSupply === 0n)
|
|
180
|
+
return 0n;
|
|
181
|
+
return (ctokens * totalSupplySf(r)) / (r.collateralMintTotalSupply * SF_ONE);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* A reserve's share price — liquidity per ctoken — as an 18-decimal fixed-point bigint. `0n` for a reserve
|
|
185
|
+
* with no ctokens.
|
|
186
|
+
*/
|
|
187
|
+
export function reserveSharePrice1e18(r) {
|
|
188
|
+
if (r.collateralMintTotalSupply === 0n)
|
|
189
|
+
return 0n;
|
|
190
|
+
return (totalSupplySf(r) * 10n ** 18n) / (r.collateralMintTotalSupply * SF_ONE);
|
|
191
|
+
}
|
|
192
|
+
export function vaultHoldings(vault, reserves) {
|
|
193
|
+
const byReserve = new Map();
|
|
194
|
+
let invested = 0n;
|
|
195
|
+
for (const a of vault.allocations) {
|
|
196
|
+
const r = reserves.get(a.reserve);
|
|
197
|
+
if (r === undefined) {
|
|
198
|
+
// A missing reserve would silently drop its share of the AUM: refuse instead.
|
|
199
|
+
throw new Error(`kamino: the vault allocates to reserve ${a.reserve}, which was not read`);
|
|
200
|
+
}
|
|
201
|
+
const v = ctokensToLiquidity(a.ctokenAllocation, r);
|
|
202
|
+
byReserve.set(a.reserve, v);
|
|
203
|
+
invested += v;
|
|
204
|
+
}
|
|
205
|
+
const aumIncludingFees = vault.tokenAvailable + invested;
|
|
206
|
+
const pendingFees = vault.pendingFeesSf / SF_ONE;
|
|
207
|
+
return { idle: vault.tokenAvailable, byReserve, aumIncludingFees, pendingFees, netAum: aumIncludingFees - pendingFees };
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/solana/layout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Solana RPC: one client per command run, on the endpoint `solanaRpcUrlFromEnv` resolves, and the cluster
|
|
3
|
+
* check that is the counterpart of an EVM endpoint's chain id.
|
|
4
|
+
*/
|
|
5
|
+
import { type Rpc, type RpcTransport, type SolanaRpcApi } from "@solana/kit";
|
|
6
|
+
export type SolanaRpc = Rpc<SolanaRpcApi>;
|
|
7
|
+
/**
|
|
8
|
+
* A transport that waits out HTTP 429 instead of failing the command. Solana's public endpoint rate-limits
|
|
9
|
+
* after a handful of requests (measured 2026-10-11: a burst of about twenty reads answered 429), and one
|
|
10
|
+
* command makes several. Up to five retries, backing off from half a second, never past the limit above.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The wait between retries. The timer keeps the process alive: while a command waits out a 429 nothing else
|
|
14
|
+
* may be pending, and an unref'd timer let Node end mid-retry — exit 0 with no output at all.
|
|
15
|
+
*/
|
|
16
|
+
export declare const retrySleep: (ms: number) => Promise<void>;
|
|
17
|
+
export declare function withRateLimitRetry(base: RpcTransport, sleep?: (ms: number) => Promise<void>): RpcTransport;
|
|
18
|
+
export declare function makeSolanaRpc(url: string): SolanaRpc;
|
|
19
|
+
/**
|
|
20
|
+
* Which cluster an endpoint serves, by its genesis hash: `"mainnet"`, `"other"` (devnet, testnet, a local
|
|
21
|
+
* validator — any of them answers every read, and none of them holds the registry's vault), or `undefined`
|
|
22
|
+
* when it did not say within `ms`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function solanaCluster(rpc: SolanaRpc, ms?: number): Promise<"mainnet" | "other" | undefined>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Solana RPC: one client per command run, on the endpoint `solanaRpcUrlFromEnv` resolves, and the cluster
|
|
3
|
+
* check that is the counterpart of an EVM endpoint's chain id.
|
|
4
|
+
*/
|
|
5
|
+
import { createDefaultRpcTransport, createSolanaRpcFromTransport, isSolanaError, SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR } from "@solana/kit";
|
|
6
|
+
import { MAINNET_GENESIS_HASH } from "./constants.js";
|
|
7
|
+
/** The longest a single request waits out HTTP 429 before the 429 is reported (the EVM client's limit too). */
|
|
8
|
+
const RATE_LIMIT_WAIT_MS = 15_000;
|
|
9
|
+
/**
|
|
10
|
+
* A transport that waits out HTTP 429 instead of failing the command. Solana's public endpoint rate-limits
|
|
11
|
+
* after a handful of requests (measured 2026-10-11: a burst of about twenty reads answered 429), and one
|
|
12
|
+
* command makes several. Up to five retries, backing off from half a second, never past the limit above.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The wait between retries. The timer keeps the process alive: while a command waits out a 429 nothing else
|
|
16
|
+
* may be pending, and an unref'd timer let Node end mid-retry — exit 0 with no output at all.
|
|
17
|
+
*/
|
|
18
|
+
export const retrySleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
19
|
+
export function withRateLimitRetry(base, sleep = retrySleep) {
|
|
20
|
+
return (async (req) => {
|
|
21
|
+
let waited = 0;
|
|
22
|
+
for (let attempt = 0;; attempt++) {
|
|
23
|
+
try {
|
|
24
|
+
return await base(req);
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
const is429 = isSolanaError(e, SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR) && e.context.statusCode === 429;
|
|
28
|
+
if (!is429 || attempt >= 5 || waited >= RATE_LIMIT_WAIT_MS)
|
|
29
|
+
throw e;
|
|
30
|
+
const ms = Math.min(500 * 2 ** attempt, RATE_LIMIT_WAIT_MS - waited);
|
|
31
|
+
waited += ms;
|
|
32
|
+
await sleep(ms);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export function makeSolanaRpc(url) {
|
|
38
|
+
return createSolanaRpcFromTransport(withRateLimitRetry(createDefaultRpcTransport({ url: url })));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Which cluster an endpoint serves, by its genesis hash: `"mainnet"`, `"other"` (devnet, testnet, a local
|
|
42
|
+
* validator — any of them answers every read, and none of them holds the registry's vault), or `undefined`
|
|
43
|
+
* when it did not say within `ms`.
|
|
44
|
+
*/
|
|
45
|
+
export async function solanaCluster(rpc, ms = 3000) {
|
|
46
|
+
let timer;
|
|
47
|
+
try {
|
|
48
|
+
const hash = await Promise.race([
|
|
49
|
+
rpc.getGenesisHash().send(),
|
|
50
|
+
new Promise((resolve) => {
|
|
51
|
+
timer = setTimeout(() => resolve(undefined), ms);
|
|
52
|
+
}),
|
|
53
|
+
]);
|
|
54
|
+
if (hash === undefined)
|
|
55
|
+
return undefined;
|
|
56
|
+
return hash === MAINNET_GENESIS_HASH ? "mainnet" : "other";
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
if (timer)
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=rpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../src/solana/rpc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,yBAAyB,EAAE,4BAA4B,EAAE,aAAa,EAAE,uCAAuC,EAAkD,MAAM,aAAa,CAAC;AAC9L,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAItD,+GAA+G;AAC/G,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC;;;;GAIG;AACH;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EAAU,EAAiB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAE/F,MAAM,UAAU,kBAAkB,CAAC,IAAkB,EAAE,QAAuC,UAAU;IACtG,OAAO,CAAC,KAAK,EAAE,GAAgC,EAAE,EAAE;QACjD,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,KAAK,GAAG,aAAa,CAAC,CAAC,EAAE,uCAAuC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,KAAK,GAAG,CAAC;gBACxG,IAAI,CAAC,KAAK,IAAI,OAAO,IAAI,CAAC,IAAI,MAAM,IAAI,kBAAkB;oBAAE,MAAM,CAAC,CAAC;gBACpE,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAAC,CAAC;gBACrE,MAAM,IAAI,EAAE,CAAC;gBACb,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC,CAAiB,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,OAAO,4BAA4B,CAAC,kBAAkB,CAAC,yBAAyB,CAAC,EAAE,GAAG,EAAE,GAAY,EAAE,CAAC,CAAC,CAAc,CAAC;AACzH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAc,EAAE,EAAE,GAAG,IAAI;IAC3D,IAAI,KAAiC,CAAC;IACtC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YAC9B,GAAG,CAAC,cAAc,EAAE,CAAC,IAAI,EAAE;YAC3B,IAAI,OAAO,CAAY,CAAC,OAAO,EAAE,EAAE;gBACjC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC;YACnD,CAAC,CAAC;SACH,CAAC,CAAC;QACH,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QACzC,OAAO,IAAI,KAAK,oBAAoB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;YAAS,CAAC;QACT,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Kamino vault on Solana, read and simulated: what the earn commands report for `chain: "solana"`.
|
|
3
|
+
*
|
|
4
|
+
* Every verdict here comes from the chain. Amounts out of a quote, a pre-flight or a position's exit are
|
|
5
|
+
* read from a SIMULATED transaction's post-state — the token balance the user's account would hold after
|
|
6
|
+
* it — never computed from a rule this client restates. That matters on this program in particular:
|
|
7
|
+
* measured on mainnet on 2026-10-11, each `withdraw` INSTRUCTION keeps up to 0.001 USDC in the vault (one
|
|
8
|
+
* instruction: 10,000 base units paid 9,000, 2,000,000 kept 1,000; the same 2,000,000 as two instructions
|
|
9
|
+
* kept 1,929; a two-leg full exit kept 1,000), and one worth 0.001 USDC or less is refused (error 7049). The
|
|
10
|
+
* cause is the vault program's GLOBAL withdrawal penalty — its global config account
|
|
11
|
+
* (BKyTcUe6daNG8HbgBix2ugdRHbykG2dK9hPBBqhUyoEX) holds `withdrawalPenaltyLamports` = 1000 and
|
|
12
|
+
* `withdrawalPenaltyBps` = 0 — while this vault's own penalty settings read 0. Kamino can change the global
|
|
13
|
+
* value, and how it combines across legs is not a rule this client restates: a simulated quote is exact.
|
|
14
|
+
*/
|
|
15
|
+
import { type Instruction } from "@solana/kit";
|
|
16
|
+
import type { SolanaVaultEntry } from "../registry-schema.js";
|
|
17
|
+
import { type ReserveView, type VaultHoldings, type VaultStateView } from "./layout.js";
|
|
18
|
+
import { compileUnsigned, type Lifetime, type VaultAccounts } from "./kvault.js";
|
|
19
|
+
import type { SolanaRpc } from "./rpc.js";
|
|
20
|
+
/** The vault and every reserve it allocates to, read at (about) one slot. */
|
|
21
|
+
export interface VaultRead {
|
|
22
|
+
readonly entry: SolanaVaultEntry;
|
|
23
|
+
readonly state: VaultStateView;
|
|
24
|
+
readonly reserves: ReadonlyMap<string, ReserveView>;
|
|
25
|
+
readonly holdings: VaultHoldings;
|
|
26
|
+
readonly accounts: VaultAccounts;
|
|
27
|
+
readonly slot: bigint;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Reads the vault state and its reserves, and refuses a row the chain disagrees with: the registry's asset,
|
|
31
|
+
* share mint and decimals are claims, and the vault state is the authority over them.
|
|
32
|
+
*/
|
|
33
|
+
export declare function readVault(rpc: SolanaRpc, entry: SolanaVaultEntry): Promise<VaultRead>;
|
|
34
|
+
/** What one share is worth in the asset, as a decimal string at the asset's precision (stored reserve state). */
|
|
35
|
+
export declare function sharePrice(v: VaultRead): string;
|
|
36
|
+
/** An SPL token account's balance, or 0 when the account does not exist. */
|
|
37
|
+
export declare function tokenBalance(rpc: SolanaRpc, account: string): Promise<{
|
|
38
|
+
exists: boolean;
|
|
39
|
+
amount: bigint;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Compute-unit limits. Measured by simulation on mainnet, 2026-10-11, with two funded reserves: a deposit
|
|
43
|
+
* used about 115,000 units; a withdrawal about 144,000 paid from idle, 186,000 when it disinvests from one
|
|
44
|
+
* reserve, and 346,000 for a two-leg exit. The limits (400,000 per leg) leave room and cost nothing unless a
|
|
45
|
+
* priority fee is set.
|
|
46
|
+
*/
|
|
47
|
+
export declare const DEPOSIT_COMPUTE_UNITS = 400000;
|
|
48
|
+
export declare const WITHDRAW_COMPUTE_UNITS_PER_RESERVE = 400000;
|
|
49
|
+
/**
|
|
50
|
+
* Refuses while the vault requires a co-signer. Kamino's program lets a "permissioning authority" be set on a
|
|
51
|
+
* vault, after which it must co-sign every deposit AND every withdrawal; this client builds single-signer
|
|
52
|
+
* transactions only, so it cannot move money in or out while one is set. Unset today; checked on every build.
|
|
53
|
+
*/
|
|
54
|
+
export declare function assertNoCoSigner(v: VaultRead): void;
|
|
55
|
+
/** The deposit: create the user's share account if it is missing, then deposit `amount` base units. */
|
|
56
|
+
export declare function depositInstructions(v: VaultRead, user: string, amount: bigint): Promise<Instruction[]>;
|
|
57
|
+
/**
|
|
58
|
+
* The withdrawal of `shares`: the account's USDC account if it is missing, then one `withdraw` per reserve
|
|
59
|
+
* it needs. Each pays from the vault's idle balance
|
|
60
|
+
* first and then from its named reserve, so the shares are split across reserves in order of what each can
|
|
61
|
+
* release now (the vault's position in it, capped by the reserve's own available liquidity). A vault with no
|
|
62
|
+
* reserves cannot be withdrawn from by this program's instruction, and is refused.
|
|
63
|
+
*
|
|
64
|
+
* ⚠️ The program CAPS a leg that asks for more than it can pay rather than reverting it (measured: a leg
|
|
65
|
+
* oversized by 500,000 shares succeeded, paid the leg's maximum and left the excess shares with the user).
|
|
66
|
+
* So every build path simulates the withdrawal and refuses it unless every share burns, less rounding
|
|
67
|
+
* (`simulateWithdrawal`, `partialWithdrawal`). A reserve's state can still move between that simulation and the send, and then the
|
|
68
|
+
* transaction lands paying less, with no error; `verifySolanaLanded` reports that as a partial withdrawal.
|
|
69
|
+
*/
|
|
70
|
+
export declare function withdrawInstructions(v: VaultRead, user: string, shares: bigint): Promise<Instruction[]>;
|
|
71
|
+
export interface Simulated {
|
|
72
|
+
readonly ok: boolean;
|
|
73
|
+
/** The program's error, as the RPC reports it; `null` on success. */
|
|
74
|
+
readonly err: unknown;
|
|
75
|
+
readonly logs: readonly string[];
|
|
76
|
+
readonly unitsConsumed: bigint | null;
|
|
77
|
+
readonly slot: bigint;
|
|
78
|
+
/** Each watched token account's balance AFTER the transaction, or `null` if it would not exist. */
|
|
79
|
+
readonly after: ReadonlyMap<string, bigint | null>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Simulates an unsigned transaction against the RPC's current state. `sigVerify: false` lets the signer slot
|
|
83
|
+
* be any address (nothing is signed); `replaceRecentBlockhash` makes the blockhash irrelevant. READ-ONLY:
|
|
84
|
+
* nothing lands, nothing is paid.
|
|
85
|
+
*/
|
|
86
|
+
export declare function simulate(rpc: SolanaRpc, ixs: readonly Instruction[], feePayer: string, watch: readonly string[]): Promise<Simulated>;
|
|
87
|
+
/**
|
|
88
|
+
* Simulates a withdrawal and reads, from the account's own token accounts, what it would pay and how many shares
|
|
89
|
+
* it would burn. The program caps a leg it cannot pay in full rather than failing it (see `withdrawInstructions`),
|
|
90
|
+
* so `burned` short of the shares asked for means the transaction would land as a partial withdrawal. Both are
|
|
91
|
+
* `undefined` when the simulation fails or the account's USDC account would not exist. `legs` is the number of
|
|
92
|
+
* withdraw instructions.
|
|
93
|
+
*/
|
|
94
|
+
export declare function simulateWithdrawal(rpc: SolanaRpc, v: VaultRead, user: string, ixs: readonly Instruction[]): Promise<{
|
|
95
|
+
sim: Simulated;
|
|
96
|
+
received?: bigint;
|
|
97
|
+
burned?: bigint;
|
|
98
|
+
legs: number;
|
|
99
|
+
}>;
|
|
100
|
+
/** A kVault withdraw instruction: the program's address and the instruction's discriminator. */
|
|
101
|
+
export declare function isWithdrawInstruction(programAddress: string, data: ArrayLike<number> | undefined): boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Why a simulated withdrawal that burns fewer shares than asked for is refused, or `undefined` when it burns them
|
|
104
|
+
* all. Up to one share unit per withdraw instruction is rounding, not a capped leg: the program floors what a
|
|
105
|
+
* leg pays and then ceils the shares that payment burns, which comes out one unit short when a share is worth
|
|
106
|
+
* less than one base unit of the asset. A capped leg falls short by the part its reserve could not pay.
|
|
107
|
+
*/
|
|
108
|
+
export declare function partialWithdrawal(asked: bigint, burned: bigint, legs: number, shareDecimals: number, slot: bigint): string | undefined;
|
|
109
|
+
/** One line for a failed simulation: the program's error and the last log lines that say why. */
|
|
110
|
+
export declare function describeSimulation(s: Simulated): string;
|
|
111
|
+
export type SolanaPreflightStatus = "OPEN_READY" | "NEEDS_FUNDS" | "REVERTED_OTHER" | "UNRESOLVED";
|
|
112
|
+
/** What `earn status --account` and `earn quote --direction deposit` report on Solana. */
|
|
113
|
+
export declare function preflightDepositSolana(rpc: SolanaRpc, entry: SolanaVaultEntry, account: string, amountHuman: string | undefined): Promise<{
|
|
114
|
+
vault: string;
|
|
115
|
+
depositor: string;
|
|
116
|
+
status: SolanaPreflightStatus;
|
|
117
|
+
canDeposit: boolean;
|
|
118
|
+
findings: string[];
|
|
119
|
+
} | {
|
|
120
|
+
status: SolanaPreflightStatus;
|
|
121
|
+
canDeposit: boolean;
|
|
122
|
+
expectedSharesRaw: bigint;
|
|
123
|
+
findings: string[];
|
|
124
|
+
vault: string;
|
|
125
|
+
depositor: string;
|
|
126
|
+
balances: {
|
|
127
|
+
asset: string;
|
|
128
|
+
shares: string;
|
|
129
|
+
};
|
|
130
|
+
quotes: {
|
|
131
|
+
sharePriceInAssets: string;
|
|
132
|
+
};
|
|
133
|
+
measuredAtSlot: number;
|
|
134
|
+
} | {
|
|
135
|
+
status: SolanaPreflightStatus;
|
|
136
|
+
canDeposit: boolean;
|
|
137
|
+
findings: string[];
|
|
138
|
+
vault: string;
|
|
139
|
+
depositor: string;
|
|
140
|
+
balances: {
|
|
141
|
+
asset: string;
|
|
142
|
+
shares: string;
|
|
143
|
+
};
|
|
144
|
+
quotes: {
|
|
145
|
+
sharePriceInAssets: string;
|
|
146
|
+
};
|
|
147
|
+
measuredAtSlot: number;
|
|
148
|
+
}>;
|
|
149
|
+
/** A fresh blockhash: what an unsigned transaction is bound to, and the block height it expires after. */
|
|
150
|
+
export declare function freshLifetime(rpc: SolanaRpc): Promise<Lifetime & {
|
|
151
|
+
currentBlockHeight: bigint;
|
|
152
|
+
}>;
|
|
153
|
+
export { compileUnsigned };
|