@symmetry-hq/sdk 1.0.8 → 1.0.9
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/LICENSE +41 -0
- package/README.md +742 -0
- package/dist/src/instructions/automation/rebalanceSwap.d.ts +11 -0
- package/dist/src/instructions/automation/rebalanceSwap.js +42 -0
- package/package.json +3 -3
- package/dist/src/keeperRebalaneHandler.d.ts +0 -32
- package/dist/src/keeperRebalaneHandler.js +0 -278
package/LICENSE
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
Licensor: Symmetry Protocol
|
|
4
|
+
Licensed Work: @symmetry-hq/sdk
|
|
5
|
+
Change Date: None
|
|
6
|
+
Change License: None
|
|
7
|
+
|
|
8
|
+
Additional Use Grant
|
|
9
|
+
|
|
10
|
+
You may use the Licensed Work in production, including for commercial purposes,
|
|
11
|
+
to build applications, services, and products that integrate with the Symmetry
|
|
12
|
+
Protocol.
|
|
13
|
+
|
|
14
|
+
You may NOT use the Licensed Work to create a competing SDK, library, or
|
|
15
|
+
protocol infrastructure that replicates or substitutes the functionality of the
|
|
16
|
+
Licensed Work or the Symmetry Protocol.
|
|
17
|
+
|
|
18
|
+
Terms
|
|
19
|
+
|
|
20
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
21
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
22
|
+
Licensor may make an Additional Use Grant, above, permitting limited production
|
|
23
|
+
use.
|
|
24
|
+
|
|
25
|
+
Except for the Additional Use Grant and the right of non-production use, all
|
|
26
|
+
rights in the Licensed Work remain exclusively with the Licensor.
|
|
27
|
+
|
|
28
|
+
If the Licensor specifies a Change Date, the Licensed Work will be made
|
|
29
|
+
available under the Change License on the Change Date. No Change Date has been
|
|
30
|
+
specified for this Licensed Work.
|
|
31
|
+
|
|
32
|
+
Notice
|
|
33
|
+
|
|
34
|
+
Business Source License 1.1 is not an Open Source license. However, the
|
|
35
|
+
Licensed Work may eventually be made available under an Open Source License, as
|
|
36
|
+
stated in this License.
|
|
37
|
+
|
|
38
|
+
For the full text of the Business Source License 1.1, see:
|
|
39
|
+
https://mariadb.com/bsl11/
|
|
40
|
+
|
|
41
|
+
Copyright (c) 2026 Symmetry Protocol. All rights reserved.
|
package/README.md
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
# @symmetry-hq/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for the **Symmetry V3** protocol on Solana — on-chain infrastructure for creating and managing multi-token vaults with automated rebalancing, oracle-based pricing, and permissionless keeper execution.
|
|
4
|
+
|
|
5
|
+
[Documentation](https://docs.symmetry.fi) | [LLMs.txt](https://docs.symmetry.fi/llms.txt) | [App](https://beta.symmetry.fi) | [Discord](https://discord.gg/Bvgt87KTJB)
|
|
6
|
+
|
|
7
|
+
Have questions or need help integrating? [Join our Discord](https://discord.gg/Bvgt87KTJB).
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @symmetry-hq/sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Peer Dependencies
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @solana/web3.js @coral-xyz/anchor
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { Connection } from "@solana/web3.js";
|
|
25
|
+
import { SymmetryCore } from "@symmetry-hq/sdk";
|
|
26
|
+
|
|
27
|
+
const connection = new Connection("https://api.mainnet-beta.solana.com");
|
|
28
|
+
const sdk = new SymmetryCore({
|
|
29
|
+
connection,
|
|
30
|
+
network: "mainnet",
|
|
31
|
+
priorityFee: 50_000, // optional, micro-lamports (default: 25,000)
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Fetch all vaults
|
|
35
|
+
const vaults = await sdk.fetchAllVaults();
|
|
36
|
+
|
|
37
|
+
// Fetch a single vault and load its price
|
|
38
|
+
let vault = await sdk.fetchVault("<VAULT_PUBKEY>");
|
|
39
|
+
vault = await sdk.loadVaultPrice(vault);
|
|
40
|
+
console.log(vault.formatted);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Core Concepts
|
|
44
|
+
|
|
45
|
+
### Vaults
|
|
46
|
+
|
|
47
|
+
A vault is an on-chain account that holds a configurable set of SPL tokens with target weights. Each vault:
|
|
48
|
+
|
|
49
|
+
- Holds multiple SPL tokens with individually configurable target weights
|
|
50
|
+
- Mints its own SPL token — vault token holders have proportional ownership of the underlying tokens
|
|
51
|
+
- Supports multi-source oracle pricing (Pyth, Raydium CPMM; Raydium CLMM and Switchboard are currently disabled) with configurable aggregation
|
|
52
|
+
- Has a flexible fee structure (deposit, withdrawal, management, performance) split across creator, host platform, managers, and protocol. Management and performance fees are currently disabled at the protocol level.
|
|
53
|
+
- Supports up to 100 tokens, 10 managers, and 4 oracle sources per token
|
|
54
|
+
|
|
55
|
+
### Intents
|
|
56
|
+
|
|
57
|
+
All configuration changes to a vault go through an **intent system**. An intent is an on-chain change request — editing fees, metadata, token composition, weights, etc. — with optional time-locks and bounty incentives for keepers to execute them.
|
|
58
|
+
|
|
59
|
+
### Rebalance Intents
|
|
60
|
+
|
|
61
|
+
Deposits, withdrawals, and periodic rebalances are processed through the same on-chain flow. They progress through a multi-step auction:
|
|
62
|
+
|
|
63
|
+
1. **Deposit tokens** — user contributes tokens to the vault
|
|
64
|
+
2. **Lock deposits** — freezes contributions and starts the process
|
|
65
|
+
3. **Price updates** — oracle prices are refreshed on-chain
|
|
66
|
+
4. **Auction** — keepers execute flash swaps to settle the vault toward target weights
|
|
67
|
+
5. **Mint / Redeem** — vault tokens are minted (deposits) or underlying tokens are released (withdrawals)
|
|
68
|
+
6. **Claim bounty** — keepers collect their rewards
|
|
69
|
+
|
|
70
|
+
### Keepers
|
|
71
|
+
|
|
72
|
+
Keepers are off-chain agents that monitor the protocol, execute pending tasks (intents, price updates, auctions), and earn bounties. All keeper operations are permissionless — developers can build custom keeper bots using the SDK's transaction-building methods. The SDK also ships with `KeeperMonitor` and `RebalanceHandler` as reference implementations that work out of the box.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## API Reference
|
|
77
|
+
|
|
78
|
+
### Constructor
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const sdk = new SymmetryCore({
|
|
82
|
+
connection: Connection, // Solana RPC connection
|
|
83
|
+
network: "devnet" | "mainnet",
|
|
84
|
+
priorityFee?: number, // compute unit price in micro-lamports (default: 25,000)
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Configuration
|
|
89
|
+
|
|
90
|
+
#### `setPriorityFee(priorityFee: number): void`
|
|
91
|
+
|
|
92
|
+
Updates the priority fee for all subsequent transactions.
|
|
93
|
+
|
|
94
|
+
#### `fetchGlobalConfig(): Promise<GlobalConfig>`
|
|
95
|
+
|
|
96
|
+
Returns the protocol-wide configuration account (fee limits, bounty settings, rebalance parameters).
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### Fetching Vaults
|
|
101
|
+
|
|
102
|
+
#### `fetchVault(vaultPubkey: string): Promise<Vault>`
|
|
103
|
+
|
|
104
|
+
Fetch a single vault by its account public key.
|
|
105
|
+
|
|
106
|
+
#### `fetchMultipleVaults(vaultPubkeys: string[]): Promise<Map<string, Vault>>`
|
|
107
|
+
|
|
108
|
+
Batch-fetch multiple vaults. Returns a map keyed by public key.
|
|
109
|
+
|
|
110
|
+
#### `fetchAllVaults(filter?: VaultFilter): Promise<Vault[]>`
|
|
111
|
+
|
|
112
|
+
Fetch all vaults on-chain with an optional role filter:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
type VaultFilter = {
|
|
116
|
+
type: "creator" | "host" | "manager";
|
|
117
|
+
pubkey: string;
|
|
118
|
+
};
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### `fetchCreatedVaults(creatorPubkey: string): Promise<Vault[]>`
|
|
122
|
+
|
|
123
|
+
Fetch vaults created by a specific public key.
|
|
124
|
+
|
|
125
|
+
#### `fetchHostedVaults(hostPubkey: string): Promise<Vault[]>`
|
|
126
|
+
|
|
127
|
+
Fetch vaults hosted by a specific public key.
|
|
128
|
+
|
|
129
|
+
#### `fetchManagedVaults(managerPubkey: string): Promise<Vault[]>`
|
|
130
|
+
|
|
131
|
+
Fetch vaults managed by a specific public key.
|
|
132
|
+
|
|
133
|
+
#### `fetchVaultsFromMints(mints: string[]): Promise<Map<string, Vault>>`
|
|
134
|
+
|
|
135
|
+
Look up vaults by their SPL token mint addresses. Returns a map keyed by mint.
|
|
136
|
+
|
|
137
|
+
#### `deriveVaultsByMints(mints: string[]): Promise<Map<string, string>>`
|
|
138
|
+
|
|
139
|
+
Derive vault account addresses from mint addresses without RPC calls. Returns a mint-to-pubkey map.
|
|
140
|
+
|
|
141
|
+
#### `loadVaultPrice(vault: Vault): Promise<Vault>`
|
|
142
|
+
|
|
143
|
+
Loads live oracle prices for all tokens in the vault, computes the vault's total value, and returns the updated object.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### Fetching Intents
|
|
148
|
+
|
|
149
|
+
#### `fetchIntent(intentPubkey: string): Promise<Intent>`
|
|
150
|
+
|
|
151
|
+
Fetch a single intent by its public key.
|
|
152
|
+
|
|
153
|
+
#### `fetchMultipleIntents(intentPubkeys: string[]): Promise<Map<string, Intent>>`
|
|
154
|
+
|
|
155
|
+
Batch-fetch multiple intents.
|
|
156
|
+
|
|
157
|
+
#### `fetchAllIntents(filter?: IntentFilter): Promise<Intent[]>`
|
|
158
|
+
|
|
159
|
+
Fetch all intents with an optional filter:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
type IntentFilter = {
|
|
163
|
+
type: "manager" | "vault";
|
|
164
|
+
pubkey: string;
|
|
165
|
+
};
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### `fetchCreatedIntents(creatorPubkey: string): Promise<Intent[]>`
|
|
169
|
+
|
|
170
|
+
Fetch intents created by a specific manager.
|
|
171
|
+
|
|
172
|
+
#### `fetchVaultIntents(vaultPubkey: string): Promise<Intent[]>`
|
|
173
|
+
|
|
174
|
+
Fetch all intents for a specific vault.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
### Fetching Rebalance Intents
|
|
179
|
+
|
|
180
|
+
#### `fetchRebalanceIntent(pubkey: string): Promise<UIRebalanceIntent>`
|
|
181
|
+
|
|
182
|
+
Fetch a single rebalance intent.
|
|
183
|
+
|
|
184
|
+
#### `fetchMultipleRebalanceIntents(pubkeys: string[]): Promise<Map<string, UIRebalanceIntent>>`
|
|
185
|
+
|
|
186
|
+
Batch-fetch multiple rebalance intents.
|
|
187
|
+
|
|
188
|
+
#### `fetchAllRebalanceIntents(filter?: RebalanceIntentFilter): Promise<UIRebalanceIntent[]>`
|
|
189
|
+
|
|
190
|
+
Fetch all rebalance intents with an optional filter:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
type RebalanceIntentFilter = {
|
|
194
|
+
type: "owner" | "vault";
|
|
195
|
+
pubkey: string;
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### `fetchOwnerRebalanceIntents(ownerPubkey: string): Promise<UIRebalanceIntent[]>`
|
|
200
|
+
|
|
201
|
+
Fetch rebalance intents owned by a specific public key.
|
|
202
|
+
|
|
203
|
+
#### `fetchVaultRebalanceIntents(vaultPubkey: string): Promise<UIRebalanceIntent[]>`
|
|
204
|
+
|
|
205
|
+
Fetch rebalance intents for a specific vault.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
### Fetching Fee Accounts
|
|
210
|
+
|
|
211
|
+
#### `fetchWithdrawVaultFees(pubkey: string): Promise<WithdrawVaultFees>`
|
|
212
|
+
|
|
213
|
+
Fetch a single fee withdrawal account.
|
|
214
|
+
|
|
215
|
+
#### `fetchMultipleWithdrawVaultFees(pubkeys: string[]): Promise<Map<string, WithdrawVaultFees>>`
|
|
216
|
+
|
|
217
|
+
Batch-fetch multiple fee withdrawal accounts.
|
|
218
|
+
|
|
219
|
+
#### `fetchAllWithdrawVaultFees(filter?: WithdrawVaultFeesFilter): Promise<WithdrawVaultFees[]>`
|
|
220
|
+
|
|
221
|
+
Fetch all fee withdrawal accounts with an optional filter:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
type WithdrawVaultFeesFilter = {
|
|
225
|
+
type: "vault" | "manager" | "creator" | "host" | "symmetry";
|
|
226
|
+
pubkey: string;
|
|
227
|
+
};
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Convenience methods: `fetchVaultWithdrawVaultFees`, `fetchManagerWithdrawVaultFees`, `fetchCreatorWithdrawVaultFees`, `fetchHostWithdrawVaultFees`, `fetchSymmetryWithdrawVaultFees`.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### Creating Vaults
|
|
235
|
+
|
|
236
|
+
#### `createVaultTx(params): Promise<VaultCreationTx>`
|
|
237
|
+
|
|
238
|
+
Creates a new vault. Returns the vault's token mint, account address, and the transaction payload.
|
|
239
|
+
|
|
240
|
+
The `metadata_uri` is a URL (max 200 chars) pointing to a JSON file describing the vault. It should contain `name` (vault token name), `symbol` (vault token ticker), `description` (vault description), `image` (token image/icon URL), and `cover` (vault cover image URL). You can add any extra fields for your own integrations (social links, website, etc.):
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"name": "My Vault",
|
|
244
|
+
"symbol": "MVT",
|
|
245
|
+
"description": "A multi-token vault.",
|
|
246
|
+
"image": "https://arweave.net/token-image-url",
|
|
247
|
+
"cover": "https://arweave.net/cover-image-url"
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
const result = await sdk.createVaultTx({
|
|
253
|
+
creator: wallet.publicKey.toBase58(),
|
|
254
|
+
start_price: "1.0", // initial price in USDC
|
|
255
|
+
name: "My Vault",
|
|
256
|
+
symbol: "MVT",
|
|
257
|
+
metadata_uri: "https://arweave.net/your-metadata-json", // URL to JSON with name, symbol, description, image, cover
|
|
258
|
+
host_platform_params: { // optional
|
|
259
|
+
host_pubkey: "<HOST_PUBKEY>",
|
|
260
|
+
host_deposit_fee_bps: 10, // 0.1%
|
|
261
|
+
host_withdraw_fee_bps: 10,
|
|
262
|
+
host_management_fee_bps: 0, // currently disabled in global config
|
|
263
|
+
host_performance_fee_bps: 0, // currently disabled in global config
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
console.log("Vault mint:", result.mint);
|
|
268
|
+
console.log("Vault account:", result.vault);
|
|
269
|
+
|
|
270
|
+
await sdk.signAndSendTxPayloadBatchSequence({
|
|
271
|
+
txPayloadBatchSequence: result,
|
|
272
|
+
wallet,
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
### Editing Vault Settings
|
|
279
|
+
|
|
280
|
+
All edit methods create an intent that modifies the vault. If the setting has no modification delay and no scheduled activation, the intent is executed immediately in the same transaction.
|
|
281
|
+
|
|
282
|
+
Each method accepts a `TaskContext` and a settings object, returning a `TxPayloadBatchSequence`:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
interface TaskContext {
|
|
286
|
+
vault: string; // vault public key
|
|
287
|
+
manager: string; // authorized manager public key
|
|
288
|
+
activation_timestamp?: number; // optional scheduled activation (unix seconds)
|
|
289
|
+
expiration_timestamp?: number; // optional expiration
|
|
290
|
+
min_bounty?: number; // optional min bounty override
|
|
291
|
+
max_bounty?: number; // optional max bounty override
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
| Method | Settings Type | Description |
|
|
296
|
+
|--------|--------------|-------------|
|
|
297
|
+
| `editCreatorTx` | `EditCreatorSettings` | Transfer vault creator role |
|
|
298
|
+
| `editManagersTx` | `EditManagerSettings` | Edit managers, weights, and authority bitmasks |
|
|
299
|
+
| `editFeesTx` | `EditFeeSettings` | Update fee structure |
|
|
300
|
+
| `editScheduleTx` | `EditScheduleSettings` | Configure repeating time cycles that gate deposits, automation, and management windows |
|
|
301
|
+
| `editAutomationTx` | `EditAutomationSettings` | Configure rebalance automation |
|
|
302
|
+
| `editLpTx` | `EditLpSettings` | Configure LP settings |
|
|
303
|
+
| `editMetadataTx` | `EditMetadataSettings` | Update name, symbol, and metadata URI (JSON with name, symbol, description, image, cover) |
|
|
304
|
+
| `editDepositsTx` | `EditDepositsSettings` | Enable or disable deposits |
|
|
305
|
+
| `editForceRebalanceTx` | `EditForceRebalanceSettings` | Enable or disable force rebalance |
|
|
306
|
+
| `editCustomRebalanceTx` | `EditCustomRebalanceSettings` | Enable or disable custom rebalance |
|
|
307
|
+
| `editAddTokenDelayTx` | `EditAddTokenSettings` | Set time-lock for adding tokens |
|
|
308
|
+
| `editUpdateWeightsDelayTx` | `EditUpdateWeightsSettings` | Set time-lock for weight updates |
|
|
309
|
+
| `editSwapDelayTx` | `EditMakeDirectSwapSettings` | Set time-lock for direct swaps |
|
|
310
|
+
|
|
311
|
+
#### Token Composition
|
|
312
|
+
|
|
313
|
+
#### `addOrEditTokenTx(context: TaskContext, settings: AddOrEditTokenInput): Promise<TxPayloadBatchSequence>`
|
|
314
|
+
|
|
315
|
+
Add a new token to the vault or update an existing token's oracle configuration:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
const tx = await sdk.addOrEditTokenTx(
|
|
319
|
+
{ vault: "<VAULT_PUBKEY>", manager: "<MANAGER>" },
|
|
320
|
+
{
|
|
321
|
+
token_mint: "<TOKEN_MINT>",
|
|
322
|
+
active: true,
|
|
323
|
+
min_oracles_thresh: 1,
|
|
324
|
+
min_conf_bps: 10,
|
|
325
|
+
conf_thresh_bps: 200,
|
|
326
|
+
conf_multiplier: 1.0,
|
|
327
|
+
oracles: [
|
|
328
|
+
{
|
|
329
|
+
oracle_type: "pyth",
|
|
330
|
+
account_lut_id: 0,
|
|
331
|
+
account_lut_index: 0,
|
|
332
|
+
account: "<PYTH_PRICE_ACCOUNT>",
|
|
333
|
+
weight_bps: 10000,
|
|
334
|
+
is_required: true,
|
|
335
|
+
conf_thresh_bps: 200,
|
|
336
|
+
volatility_thresh_bps: 200,
|
|
337
|
+
max_slippage_bps: 1000,
|
|
338
|
+
min_liquidity: 0,
|
|
339
|
+
staleness_thresh: 120,
|
|
340
|
+
staleness_conf_rate_bps: 50,
|
|
341
|
+
token_decimals: 9,
|
|
342
|
+
twap_seconds_ago: 0,
|
|
343
|
+
twap_secondary_seconds_ago: 0,
|
|
344
|
+
quote_token: "usd",
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
#### `updateWeightsTx(context: TaskContext, settings: UpdateWeightsInput): Promise<TxPayloadBatchSequence>`
|
|
352
|
+
|
|
353
|
+
Update target weights for all tokens in the vault:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
const tx = await sdk.updateWeightsTx(
|
|
357
|
+
{ vault: "<VAULT_PUBKEY>", manager: "<MANAGER>" },
|
|
358
|
+
{
|
|
359
|
+
token_weights: [
|
|
360
|
+
{ mint: "<TOKEN_MINT_1>", weight_bps: 5000 },
|
|
361
|
+
{ mint: "<TOKEN_MINT_2>", weight_bps: 3000 },
|
|
362
|
+
{ mint: "<TOKEN_MINT_3>", weight_bps: 2000 },
|
|
363
|
+
], // basis points, must sum to 10000
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
#### `makeDirectSwapTx(context, settings, jup_swap_ix?): Promise<TxPayloadBatchSequence>`
|
|
369
|
+
|
|
370
|
+
Execute a direct token swap within the vault via Jupiter.
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
### Intent Execution & Cancellation
|
|
375
|
+
|
|
376
|
+
#### `executeVaultIntentTx(params): Promise<TxPayloadBatchSequence>`
|
|
377
|
+
|
|
378
|
+
Execute a pending intent after its time-lock has elapsed:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
await sdk.executeVaultIntentTx({
|
|
382
|
+
keeper: wallet.publicKey.toBase58(),
|
|
383
|
+
intent: "<INTENT_PUBKEY>",
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
#### `executeDirectSwapVaultIntentTx(params): Promise<TxPayloadBatchSequence>`
|
|
388
|
+
|
|
389
|
+
Execute a pending direct swap intent with a flash swap:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
await sdk.executeDirectSwapVaultIntentTx({
|
|
393
|
+
keeper: wallet.publicKey.toBase58(),
|
|
394
|
+
intent: "<INTENT_PUBKEY>",
|
|
395
|
+
jup_swap_ix: jupiterSwapInstruction,
|
|
396
|
+
jup_address_lookup_table_addresses: [...],
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
#### `cancelVaultIntentTx(params): Promise<TxPayloadBatchSequence>`
|
|
401
|
+
|
|
402
|
+
Cancel and close an intent account:
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
await sdk.cancelVaultIntentTx({
|
|
406
|
+
keeper: wallet.publicKey.toBase58(),
|
|
407
|
+
intent: "<INTENT_PUBKEY>",
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
### Deposits & Withdrawals
|
|
414
|
+
|
|
415
|
+
#### `buyVaultTx(params): Promise<TxPayloadBatchSequence>`
|
|
416
|
+
|
|
417
|
+
Deposit tokens into a vault to receive vault tokens:
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
const tx = await sdk.buyVaultTx({
|
|
421
|
+
buyer: wallet.publicKey.toBase58(),
|
|
422
|
+
vault_mint: "<VAULT_MINT>",
|
|
423
|
+
contributions: [
|
|
424
|
+
{ mint: "So11111111111111111111111111111111111111112", amount: 1_000_000_000 }, // 1 SOL
|
|
425
|
+
{ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", amount: 100_000_000 }, // 100 USDC
|
|
426
|
+
],
|
|
427
|
+
rebalance_slippage_bps: 100, // optional (default: 1%)
|
|
428
|
+
per_trade_rebalance_slippage_bps: 100, // optional
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
await sdk.signAndSendTxPayloadBatchSequence({
|
|
432
|
+
txPayloadBatchSequence: tx,
|
|
433
|
+
wallet,
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
#### `depositTokensTx(params): Promise<TxPayloadBatchSequence>`
|
|
438
|
+
|
|
439
|
+
Add more tokens to an existing deposit before locking:
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
const tx = await sdk.depositTokensTx({
|
|
443
|
+
buyer: wallet.publicKey.toBase58(),
|
|
444
|
+
contributions: [{ mint: "<MINT>", amount: 500_000_000 }],
|
|
445
|
+
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
#### `lockDepositsTx(params): Promise<TxPayloadBatchSequence>`
|
|
450
|
+
|
|
451
|
+
Lock deposits and start the auction process:
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
const tx = await sdk.lockDepositsTx({
|
|
455
|
+
buyer: wallet.publicKey.toBase58(),
|
|
456
|
+
vault_mint: "<VAULT_MINT>",
|
|
457
|
+
});
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
#### `sellVaultTx(params): Promise<TxPayloadBatchSequence>`
|
|
461
|
+
|
|
462
|
+
Withdraw from a vault by burning vault tokens:
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
const tx = await sdk.sellVaultTx({
|
|
466
|
+
seller: wallet.publicKey.toBase58(),
|
|
467
|
+
vault_mint: "<VAULT_MINT>",
|
|
468
|
+
withdraw_amount: 1_000_000, // vault tokens to burn
|
|
469
|
+
keep_tokens: [], // mints to receive directly without rebalancing
|
|
470
|
+
rebalance_slippage_bps: 100,
|
|
471
|
+
});
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
### Rebalancing
|
|
477
|
+
|
|
478
|
+
#### `rebalanceVaultTx(params): Promise<TxPayloadBatchSequence>`
|
|
479
|
+
|
|
480
|
+
Trigger a rebalance to bring the vault back to its target weights:
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
const tx = await sdk.rebalanceVaultTx({
|
|
484
|
+
keeper: wallet.publicKey.toBase58(),
|
|
485
|
+
vault_mint: "<VAULT_MINT>",
|
|
486
|
+
rebalance_slippage_bps: 100,
|
|
487
|
+
});
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
#### `cancelRebalanceIntentTx(params): Promise<TxPayloadBatchSequence>`
|
|
491
|
+
|
|
492
|
+
Cancel an active rebalance intent:
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
const tx = await sdk.cancelRebalanceIntentTx({
|
|
496
|
+
keeper: wallet.publicKey.toBase58(),
|
|
497
|
+
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
|
|
498
|
+
});
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
### Oracle Price Updates
|
|
504
|
+
|
|
505
|
+
#### `updateTokenPricesTx(params): Promise<TxPayloadBatchSequence>`
|
|
506
|
+
|
|
507
|
+
Refresh on-chain oracle prices for all tokens in a vault during a rebalance:
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
const tx = await sdk.updateTokenPricesTx({
|
|
511
|
+
keeper: wallet.publicKey.toBase58(),
|
|
512
|
+
vault: "<VAULT_PUBKEY>",
|
|
513
|
+
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
|
|
514
|
+
});
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
#### `updatePythPriceFeedsTx(params): Promise<TxPayloadBatchSequence>`
|
|
518
|
+
|
|
519
|
+
Update specific Pyth price feeds from on-chain price accounts:
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
const tx = await sdk.updatePythPriceFeedsTx({
|
|
523
|
+
keeper: wallet.publicKey.toBase58(),
|
|
524
|
+
accounts: ["<PYTH_PRICE_ACCOUNT_1>", "<PYTH_PRICE_ACCOUNT_2>"],
|
|
525
|
+
});
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
### Flash Swaps
|
|
531
|
+
|
|
532
|
+
#### `flashSwapTx(params): Promise<TxPayloadBatchSequence>`
|
|
533
|
+
|
|
534
|
+
Execute an atomic flash swap during rebalance auctions — withdraws tokens from the vault, performs a swap (e.g. via Jupiter), and deposits the result back in a single transaction:
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
const tx = await sdk.flashSwapTx({
|
|
538
|
+
keeper: wallet.publicKey.toBase58(),
|
|
539
|
+
vault: "<VAULT_PUBKEY>",
|
|
540
|
+
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
|
|
541
|
+
mint_in: "<DEPOSIT_TOKEN_MINT>",
|
|
542
|
+
mint_out: "<WITHDRAW_TOKEN_MINT>",
|
|
543
|
+
amount_in: 1_000_000,
|
|
544
|
+
amount_out: 1_000_000,
|
|
545
|
+
jup_swap_ix: jupiterSwapInstruction,
|
|
546
|
+
jup_token_ledger_ix: jupiterTokenLedgerInstruction,
|
|
547
|
+
jup_address_lookup_table_addresses: [...],
|
|
548
|
+
});
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
### Minting & Redeeming
|
|
554
|
+
|
|
555
|
+
#### `mintTx(params): Promise<TxPayloadBatchSequence>`
|
|
556
|
+
|
|
557
|
+
Mint vault tokens after a deposit completes its auctions:
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
const tx = await sdk.mintTx({
|
|
561
|
+
keeper: wallet.publicKey.toBase58(),
|
|
562
|
+
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
|
|
563
|
+
});
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
#### `redeemTokensTx(params): Promise<TxPayloadBatchSequence>`
|
|
567
|
+
|
|
568
|
+
Redeem underlying tokens from a completed withdrawal:
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
const tx = await sdk.redeemTokensTx({
|
|
572
|
+
keeper: wallet.publicKey.toBase58(),
|
|
573
|
+
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
|
|
574
|
+
});
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
### Bounties
|
|
580
|
+
|
|
581
|
+
#### `addBountyTx(params): Promise<TxPayloadBatchSequence>`
|
|
582
|
+
|
|
583
|
+
Add bounty to a vault for incentivizing keeper automation:
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
const tx = await sdk.addBountyTx({
|
|
587
|
+
keeper: wallet.publicKey.toBase58(),
|
|
588
|
+
vault: "<VAULT_PUBKEY>",
|
|
589
|
+
amount: 100_000_000, // lamports
|
|
590
|
+
});
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
#### `claimBountyTx(params): Promise<TxPayloadBatchSequence>`
|
|
594
|
+
|
|
595
|
+
Claim bounty rewards after completing rebalance tasks:
|
|
596
|
+
|
|
597
|
+
```typescript
|
|
598
|
+
const tx = await sdk.claimBountyTx({
|
|
599
|
+
keeper: wallet.publicKey.toBase58(),
|
|
600
|
+
rebalance_intent: "<REBALANCE_INTENT_PUBKEY>",
|
|
601
|
+
});
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
### Fee Management
|
|
607
|
+
|
|
608
|
+
#### `withdrawVaultFeesTx(params): Promise<TxPayloadBatchSequence>`
|
|
609
|
+
|
|
610
|
+
Withdraw and claim accumulated fees from a vault. Automatically detects which fee types the claimer is authorized to collect (protocol, creator, host, or manager fees):
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
const tx = await sdk.withdrawVaultFeesTx({
|
|
614
|
+
claimer: wallet.publicKey.toBase58(),
|
|
615
|
+
vault: "<VAULT_PUBKEY>",
|
|
616
|
+
});
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
#### `claimTokenFeesFromVaultTx(params): Promise<TxPayloadBatchSequence>`
|
|
620
|
+
|
|
621
|
+
Claim remaining fee tokens from an existing `WithdrawVaultFees` account:
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
const tx = await sdk.claimTokenFeesFromVaultTx({
|
|
625
|
+
claimer: wallet.publicKey.toBase58(),
|
|
626
|
+
withdrawVaultFees: "<WITHDRAW_VAULT_FEES_PUBKEY>",
|
|
627
|
+
});
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
### Lookup Tables
|
|
633
|
+
|
|
634
|
+
#### `rewriteLookupTablesTx(params): Promise<TxPayloadBatchSequence>`
|
|
635
|
+
|
|
636
|
+
Rebuild address lookup tables for a vault (needed when LUTs are full after adding tokens):
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
const tx = await sdk.rewriteLookupTablesTx({
|
|
640
|
+
signer: wallet.publicKey.toBase58(),
|
|
641
|
+
vault_mint: "<VAULT_MINT>",
|
|
642
|
+
additional_accounts: ["<ACCOUNT_1>", "<ACCOUNT_2>"],
|
|
643
|
+
});
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
### Sending Transactions
|
|
649
|
+
|
|
650
|
+
All transaction-building methods return a `TxPayloadBatchSequence`. Batches are sent sequentially; transactions within a batch are sent in parallel.
|
|
651
|
+
|
|
652
|
+
#### `signAndSendTxPayloadBatchSequence(params): Promise<TransactionSignature[][]>`
|
|
653
|
+
|
|
654
|
+
```typescript
|
|
655
|
+
const signatures = await sdk.signAndSendTxPayloadBatchSequence({
|
|
656
|
+
txPayloadBatchSequence: tx,
|
|
657
|
+
wallet,
|
|
658
|
+
simulateTransactions: false, // optional
|
|
659
|
+
});
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
#### `signAndSendVersionedTxs(params): Promise<TransactionSignature[][]>`
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
const signatures = await sdk.signAndSendVersionedTxs({
|
|
666
|
+
versionedTxs,
|
|
667
|
+
wallet,
|
|
668
|
+
});
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
## Keeper Monitor (Reference Implementation)
|
|
674
|
+
|
|
675
|
+
`KeeperMonitor` is an example keeper implementation included in the SDK. It provides automated monitoring and execution of all pending protocol tasks.
|
|
676
|
+
|
|
677
|
+
```typescript
|
|
678
|
+
import { KeeperMonitor } from "@symmetry-hq/sdk";
|
|
679
|
+
|
|
680
|
+
const keeper = new KeeperMonitor({
|
|
681
|
+
wallet, // wallet with signing capability
|
|
682
|
+
connection, // Solana RPC connection
|
|
683
|
+
network: "mainnet",
|
|
684
|
+
jupiterApiKey: "<JUP_API_KEY>",
|
|
685
|
+
maxAllowedAccounts: 64,
|
|
686
|
+
priorityFee: 50_000, // optional
|
|
687
|
+
simulateTransactions: false, // optional
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
while (true) {
|
|
691
|
+
await keeper.update();
|
|
692
|
+
await new Promise(r => setTimeout(r, 10_000));
|
|
693
|
+
}
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
The keeper automatically monitors and executes pending intents, handles the full rebalance lifecycle (price updates, Jupiter flash swaps, minting, redeeming, bounty claims), and manages concurrent task execution.
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
## Standalone Utilities
|
|
701
|
+
|
|
702
|
+
| Export | Description |
|
|
703
|
+
|--------|-------------|
|
|
704
|
+
| `getJupTokenLedgerAndSwapInstructions` | Build Jupiter swap instructions for flash swaps |
|
|
705
|
+
| `getSwapPairs` | Compute required swap pairs for a rebalance intent |
|
|
706
|
+
| `isRebalanceRequired` | Check whether a vault needs rebalancing based on its threshold settings |
|
|
707
|
+
|
|
708
|
+
---
|
|
709
|
+
|
|
710
|
+
## Constants
|
|
711
|
+
|
|
712
|
+
| Constant | Value | Description |
|
|
713
|
+
|----------|-------|-------------|
|
|
714
|
+
| `VAULTS_V3_PROGRAM_ID` | `BASKT7aKd8n7ibpUbwLP3Wiyxyi3yoiXsxBk4Hpumate` | Program address |
|
|
715
|
+
| `COMPUTE_UNITS` | `1,000,000` | Default compute unit limit |
|
|
716
|
+
| `PRIORITY_FEE` | `25,000` | Default priority fee (micro-lamports) |
|
|
717
|
+
| `MAX_SUPPORTED_TOKENS_PER_VAULT` | `100` | Max tokens per vault |
|
|
718
|
+
| `MAX_MANAGERS_PER_VAULT` | `10` | Max managers per vault |
|
|
719
|
+
| `MAX_ORACLES_PER_TOKEN` | `4` | Max oracle sources per token |
|
|
720
|
+
| `HUNDRED_PERCENT_BPS` | `10,000` | 100% in basis points |
|
|
721
|
+
|
|
722
|
+
## Supported Oracles
|
|
723
|
+
|
|
724
|
+
| Type | Enum | Description |
|
|
725
|
+
|------|------|-------------|
|
|
726
|
+
| Pyth | `0` | Pyth Network price feeds via Hermes ([Price Feed IDs](https://docs.pyth.network/price-feeds/core/price-feeds/price-feed-ids)) |
|
|
727
|
+
| Raydium CLMM | `1` | Raydium Concentrated Liquidity AMM TWAP (currently disabled on-chain) |
|
|
728
|
+
| Raydium CPMM | `2` | Raydium Constant Product AMM TWAP |
|
|
729
|
+
| Switchboard | `3` | Switchboard oracle feeds (currently disabled on-chain) |
|
|
730
|
+
|
|
731
|
+
Each token in a vault supports up to 4 oracle sources with configurable aggregation (e.g. weighted percentile).
|
|
732
|
+
|
|
733
|
+
## Networks
|
|
734
|
+
|
|
735
|
+
| Network | WSOL Mint | USDC Mint |
|
|
736
|
+
|---------|-----------|-----------|
|
|
737
|
+
| `mainnet` | `So11111111111111111111111111111111111111112` | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` |
|
|
738
|
+
| `devnet` | `So11111111111111111111111111111111111111112` | `USDCoctVLVnvTXBEuP9s8hntucdJokbo17RwHuNXemT` |
|
|
739
|
+
|
|
740
|
+
## License
|
|
741
|
+
|
|
742
|
+
BUSL-1.1 (Business Source License 1.1)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BN } from '@coral-xyz/anchor';
|
|
2
|
+
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
|
|
3
|
+
export declare function rebalanceSwapIx(params: {
|
|
4
|
+
keeper: PublicKey;
|
|
5
|
+
basket: PublicKey;
|
|
6
|
+
mintFrom: PublicKey;
|
|
7
|
+
mintTo: PublicKey;
|
|
8
|
+
amountIn: BN;
|
|
9
|
+
amountOut: BN;
|
|
10
|
+
mode: number;
|
|
11
|
+
}): TransactionInstruction;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rebalanceSwapIx = rebalanceSwapIx;
|
|
4
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
5
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
6
|
+
const constants_1 = require("../../constants");
|
|
7
|
+
const pda_1 = require("../pda");
|
|
8
|
+
const REBALANCE_SWAP_DISCRIMINATOR = Buffer.from([101, 122, 61, 201, 21, 165, 177, 213]);
|
|
9
|
+
function rebalanceSwapIx(params) {
|
|
10
|
+
const { keeper, basket, mintFrom, mintTo, amountIn, amountOut, mode } = params;
|
|
11
|
+
let rebalanceIntent = (0, pda_1.getRebalanceIntentPda)(basket, keeper); // TODO: incorrect. Pass intent as func param
|
|
12
|
+
let keeperFromATA = (0, pda_1.getAta)(keeper, mintFrom);
|
|
13
|
+
let keeperToATA = (0, pda_1.getAta)(keeper, mintTo);
|
|
14
|
+
let basketFromATA = (0, pda_1.getAta)(basket, mintFrom);
|
|
15
|
+
let basketToATA = (0, pda_1.getAta)(basket, mintTo);
|
|
16
|
+
const keys = [
|
|
17
|
+
{ pubkey: params.keeper, isWritable: true, isSigner: true },
|
|
18
|
+
{ pubkey: params.basket, isWritable: true, isSigner: false },
|
|
19
|
+
{ pubkey: rebalanceIntent, isWritable: true, isSigner: false },
|
|
20
|
+
{ pubkey: params.mintFrom, isWritable: false, isSigner: false },
|
|
21
|
+
{ pubkey: params.mintTo, isWritable: false, isSigner: false },
|
|
22
|
+
{ pubkey: keeperFromATA, isWritable: true, isSigner: false },
|
|
23
|
+
{ pubkey: keeperToATA, isWritable: true, isSigner: false },
|
|
24
|
+
{ pubkey: basketFromATA, isWritable: true, isSigner: false },
|
|
25
|
+
{ pubkey: basketToATA, isWritable: true, isSigner: false },
|
|
26
|
+
{ pubkey: (0, pda_1.getGlobalConfigPda)(), isWritable: false, isSigner: false },
|
|
27
|
+
{ pubkey: web3_js_1.SystemProgram.programId, isWritable: false, isSigner: false },
|
|
28
|
+
{ pubkey: spl_token_1.TOKEN_PROGRAM_ID, isWritable: false, isSigner: false },
|
|
29
|
+
{ pubkey: spl_token_1.TOKEN_2022_PROGRAM_ID, isWritable: false, isSigner: false },
|
|
30
|
+
{ pubkey: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, isWritable: false, isSigner: false },
|
|
31
|
+
];
|
|
32
|
+
const discriminator = REBALANCE_SWAP_DISCRIMINATOR;
|
|
33
|
+
const amountInBuffer = Buffer.from(amountIn.toArray("le", 8));
|
|
34
|
+
const amountOutBuffer = Buffer.from(amountOut.toArray("le", 8));
|
|
35
|
+
const modeBuffer = Buffer.from([mode]);
|
|
36
|
+
const data = Buffer.concat([discriminator, amountInBuffer, amountOutBuffer, modeBuffer]);
|
|
37
|
+
return new web3_js_1.TransactionInstruction({
|
|
38
|
+
keys,
|
|
39
|
+
programId: constants_1.BASKETS_V3_PROGRAM_ID,
|
|
40
|
+
data,
|
|
41
|
+
});
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symmetry-hq/sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Symmetry V3 SDK",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"vaults",
|
|
15
15
|
"sdk"
|
|
16
16
|
],
|
|
17
|
-
"author": "
|
|
18
|
-
"license": "
|
|
17
|
+
"author": "Symmetry Protocol",
|
|
18
|
+
"license": "BUSL-1.1",
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@coral-xyz/borsh": "^0.31.1",
|
|
21
21
|
"@pythnetwork/hermes-client": "^2.1.0",
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Connection, PublicKey } from "@solana/web3.js";
|
|
2
|
-
import { Wallet } from "./txUtils";
|
|
3
|
-
import { Basket, UIRebalanceIntent } from ".";
|
|
4
|
-
export declare class RebalanceHandler {
|
|
5
|
-
private params;
|
|
6
|
-
private intent;
|
|
7
|
-
private basket;
|
|
8
|
-
constructor(params: {
|
|
9
|
-
intent: UIRebalanceIntent;
|
|
10
|
-
basket: Basket;
|
|
11
|
-
wallet: Wallet;
|
|
12
|
-
connection: Connection;
|
|
13
|
-
network: "devnet" | "mainnet";
|
|
14
|
-
jupiterApiKey: string;
|
|
15
|
-
maxAllowedAccounts: number;
|
|
16
|
-
priorityFee?: number;
|
|
17
|
-
simulateTransactions?: boolean;
|
|
18
|
-
});
|
|
19
|
-
delay: (ms: number) => Promise<unknown>;
|
|
20
|
-
refresh(): Promise<void>;
|
|
21
|
-
static run(params: {
|
|
22
|
-
intentPubkey: PublicKey;
|
|
23
|
-
wallet: Wallet;
|
|
24
|
-
connection: Connection;
|
|
25
|
-
network: "devnet" | "mainnet";
|
|
26
|
-
jupiterApiKey: string;
|
|
27
|
-
maxAllowedAccounts: number;
|
|
28
|
-
priorityFee?: number;
|
|
29
|
-
simulateTransactions?: boolean;
|
|
30
|
-
}): Promise<void>;
|
|
31
|
-
private execute;
|
|
32
|
-
}
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.RebalanceHandler = void 0;
|
|
13
|
-
const web3_js_1 = require("@solana/web3.js");
|
|
14
|
-
const _1 = require(".");
|
|
15
|
-
const rebalanceIntent_1 = require("./states/intents/rebalanceIntent");
|
|
16
|
-
const constants_1 = require("./constants");
|
|
17
|
-
class RebalanceHandler {
|
|
18
|
-
constructor(params) {
|
|
19
|
-
var _a, _b;
|
|
20
|
-
this.delay = (ms) => __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => setTimeout(resolve, ms)); });
|
|
21
|
-
this.params = {
|
|
22
|
-
wallet: params.wallet,
|
|
23
|
-
connection: params.connection,
|
|
24
|
-
symmetryCore: new _1.SymmetryCore({
|
|
25
|
-
connection: params.connection,
|
|
26
|
-
network: params.network,
|
|
27
|
-
priorityFee: (_a = params.priorityFee) !== null && _a !== void 0 ? _a : constants_1.PRIORITY_FEE,
|
|
28
|
-
}),
|
|
29
|
-
network: params.network,
|
|
30
|
-
jupiterApiKey: params.jupiterApiKey,
|
|
31
|
-
maxAllowedAccounts: params.maxAllowedAccounts,
|
|
32
|
-
simulateTransactions: (_b = params.simulateTransactions) !== null && _b !== void 0 ? _b : false,
|
|
33
|
-
};
|
|
34
|
-
this.intent = params.intent;
|
|
35
|
-
this.basket = params.basket;
|
|
36
|
-
}
|
|
37
|
-
refresh() {
|
|
38
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
39
|
-
this.intent = yield this.params.symmetryCore.fetchRebalanceIntent(this.intent.formatted_data.pubkey);
|
|
40
|
-
this.basket = yield this.params.symmetryCore.fetchBasket(this.intent.formatted_data.basket);
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
static run(params) {
|
|
44
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
45
|
-
var _a;
|
|
46
|
-
let symmetryCore = new _1.SymmetryCore({
|
|
47
|
-
connection: params.connection,
|
|
48
|
-
network: params.network,
|
|
49
|
-
priorityFee: (_a = params.priorityFee) !== null && _a !== void 0 ? _a : constants_1.PRIORITY_FEE,
|
|
50
|
-
});
|
|
51
|
-
let intent = yield symmetryCore.fetchRebalanceIntent(params.intentPubkey.toBase58());
|
|
52
|
-
let basket = yield symmetryCore.fetchBasket(intent.formatted_data.basket);
|
|
53
|
-
let handler = new RebalanceHandler({
|
|
54
|
-
intent, basket,
|
|
55
|
-
wallet: params.wallet,
|
|
56
|
-
connection: params.connection,
|
|
57
|
-
network: params.network,
|
|
58
|
-
jupiterApiKey: params.jupiterApiKey,
|
|
59
|
-
maxAllowedAccounts: params.maxAllowedAccounts,
|
|
60
|
-
simulateTransactions: params.simulateTransactions,
|
|
61
|
-
});
|
|
62
|
-
handler.execute();
|
|
63
|
-
for (let i = 0; i < 20; i++) {
|
|
64
|
-
yield handler.delay(15 * 1000);
|
|
65
|
-
try {
|
|
66
|
-
yield handler.refresh();
|
|
67
|
-
}
|
|
68
|
-
catch (e) {
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
execute() {
|
|
75
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
-
var _a, _b;
|
|
77
|
-
console.log("Starting rebalance handler for intent:", this.intent.formatted_data.pubkey);
|
|
78
|
-
let nextCheckTime = 0;
|
|
79
|
-
let numTriesUpdatePrices = 0;
|
|
80
|
-
let numTriesMint = 0;
|
|
81
|
-
let numTriesRedeemTokens = 0;
|
|
82
|
-
let numTriesClaimBounty = 0;
|
|
83
|
-
let rebalancePairs = [];
|
|
84
|
-
let lastJupQuotesUpdate = 0;
|
|
85
|
-
let jupQuotes = [];
|
|
86
|
-
while (true) {
|
|
87
|
-
if (!this.intent)
|
|
88
|
-
break;
|
|
89
|
-
let intent = this.intent.formatted_data;
|
|
90
|
-
let chainData = this.intent.chain_data;
|
|
91
|
-
let now = Date.now() / 1000;
|
|
92
|
-
if (now < nextCheckTime) {
|
|
93
|
-
yield this.delay(Math.min(30 * 1000, Math.max(0, nextCheckTime - now + 0.2) * 1000));
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
nextCheckTime = now + 35;
|
|
97
|
-
if (intent.current_action == "not_active") {
|
|
98
|
-
console.log("Intent not active, stopping");
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
if (intent.current_action == "deposit_tokens") {
|
|
102
|
-
console.log("Waiting for deposit...");
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (intent.current_action == "update_prices" && intent.last_action_timestamp > now) {
|
|
106
|
-
nextCheckTime = intent.last_action_timestamp;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
if (intent.current_action == "update_prices") {
|
|
110
|
-
if (numTriesUpdatePrices >= 3) {
|
|
111
|
-
console.log("Max retries for update_prices");
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
numTriesUpdatePrices += 1;
|
|
115
|
-
try {
|
|
116
|
-
let tx = yield this.params.symmetryCore.updateTokenPricesTx({
|
|
117
|
-
keeper: this.params.wallet.publicKey.toBase58(),
|
|
118
|
-
basket: intent.basket,
|
|
119
|
-
rebalance_intent: intent.pubkey,
|
|
120
|
-
});
|
|
121
|
-
let res = yield this.params.symmetryCore.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet: this.params.wallet, simulateTransactions: this.params.simulateTransactions });
|
|
122
|
-
console.log("Update Prices:", res);
|
|
123
|
-
}
|
|
124
|
-
catch (e) {
|
|
125
|
-
if (numTriesUpdatePrices == 3) {
|
|
126
|
-
console.log("Stop - update prices failed:", e);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
if (intent.auctions[2].end_time > now) {
|
|
132
|
-
let auction0StartTime = intent.auctions[0].start_time;
|
|
133
|
-
let auction0EndTime = intent.auctions[0].end_time;
|
|
134
|
-
let auction1StartTime = intent.auctions[1].start_time;
|
|
135
|
-
let auction1EndTime = intent.auctions[1].end_time;
|
|
136
|
-
let auction2StartTime = intent.auctions[2].start_time;
|
|
137
|
-
let auction2EndTime = intent.auctions[2].end_time;
|
|
138
|
-
rebalancePairs = (0, rebalanceIntent_1.getSwapPairs)(chainData, this.basket);
|
|
139
|
-
if (Date.now() / 1000 > lastJupQuotesUpdate + 60) {
|
|
140
|
-
let usedValue = new Map();
|
|
141
|
-
jupQuotes = [];
|
|
142
|
-
for (let pair of rebalancePairs) {
|
|
143
|
-
let inValue = (_a = usedValue.get(pair.inMint)) !== null && _a !== void 0 ? _a : 0;
|
|
144
|
-
let outValue = (_b = usedValue.get(pair.outMint)) !== null && _b !== void 0 ? _b : 0;
|
|
145
|
-
let swapValue = Math.min(pair.value - inValue, pair.value - outValue);
|
|
146
|
-
if (swapValue < 0.005) {
|
|
147
|
-
jupQuotes.push(undefined);
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
usedValue.set(pair.inMint, inValue + swapValue);
|
|
151
|
-
usedValue.set(pair.outMint, outValue + swapValue);
|
|
152
|
-
let res = undefined;
|
|
153
|
-
if (this.params.network == "mainnet")
|
|
154
|
-
try {
|
|
155
|
-
res = Object.assign(Object.assign({}, (yield (0, _1.getJupTokenLedgerAndSwapInstructions)({
|
|
156
|
-
keeper: this.params.wallet.publicKey,
|
|
157
|
-
basketMintIn: new web3_js_1.PublicKey(pair.inMint),
|
|
158
|
-
basketMintOut: new web3_js_1.PublicKey(pair.outMint),
|
|
159
|
-
basketAmountIn: pair.inAmount,
|
|
160
|
-
basketAmountOut: pair.outAmount,
|
|
161
|
-
swapMode: "ioc",
|
|
162
|
-
apiKey: this.params.jupiterApiKey,
|
|
163
|
-
maxJupAccounts: this.params.maxAllowedAccounts,
|
|
164
|
-
}))), { inMint: pair.inMint, outMint: pair.outMint });
|
|
165
|
-
}
|
|
166
|
-
catch (_c) { }
|
|
167
|
-
;
|
|
168
|
-
jupQuotes.push(res);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
for (let index = 0; index < rebalancePairs.length; index++)
|
|
172
|
-
try {
|
|
173
|
-
let pair = rebalancePairs[index];
|
|
174
|
-
let quote = jupQuotes.find(q => q && q.inMint == pair.inMint && q.outMint == pair.outMint);
|
|
175
|
-
if (!quote)
|
|
176
|
-
continue;
|
|
177
|
-
if (pair.value < 0.005)
|
|
178
|
-
continue;
|
|
179
|
-
let { tokenLedgerInstruction, swapInstruction, addressLookupTableAddresses, quoteResponse } = quote;
|
|
180
|
-
if (!quoteResponse)
|
|
181
|
-
continue;
|
|
182
|
-
console.log(pair, "Jup Quote:", parseFloat(quoteResponse.outAmount), "Requested In:", pair.inAmount);
|
|
183
|
-
if (parseFloat(quoteResponse.outAmount) <= pair.inAmount && this.params.network == "mainnet")
|
|
184
|
-
continue;
|
|
185
|
-
try {
|
|
186
|
-
let tx = yield this.params.symmetryCore.flashSwapTx({
|
|
187
|
-
keeper: this.params.wallet.publicKey.toBase58(),
|
|
188
|
-
basket: this.basket.ownAddress.toBase58(),
|
|
189
|
-
rebalance_intent: intent.pubkey,
|
|
190
|
-
mint_in: pair.inMint,
|
|
191
|
-
mint_out: pair.outMint,
|
|
192
|
-
amount_in: pair.inAmount,
|
|
193
|
-
amount_out: pair.outAmount,
|
|
194
|
-
mode: 2,
|
|
195
|
-
jup_token_ledger_ix: tokenLedgerInstruction,
|
|
196
|
-
jup_swap_ix: swapInstruction,
|
|
197
|
-
jup_address_lookup_table_addresses: addressLookupTableAddresses,
|
|
198
|
-
});
|
|
199
|
-
let res = yield this.params.symmetryCore.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet: this.params.wallet, simulateTransactions: this.params.simulateTransactions });
|
|
200
|
-
console.log("Flash Swap:", res);
|
|
201
|
-
rebalancePairs = rebalancePairs.splice(index, 1);
|
|
202
|
-
index -= 1;
|
|
203
|
-
}
|
|
204
|
-
catch (_d) { }
|
|
205
|
-
}
|
|
206
|
-
catch (_e) { }
|
|
207
|
-
nextCheckTime = (Date.now() / 1000) + 10;
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
if (intent.rebalance_type == "deposit") {
|
|
211
|
-
lastJupQuotesUpdate = 0;
|
|
212
|
-
if (numTriesMint >= 3) {
|
|
213
|
-
console.log("Max retries for mint");
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
numTriesMint += 1;
|
|
217
|
-
try {
|
|
218
|
-
let tx = yield this.params.symmetryCore.mintTx({
|
|
219
|
-
keeper: this.params.wallet.publicKey.toBase58(),
|
|
220
|
-
rebalance_intent: intent.pubkey,
|
|
221
|
-
});
|
|
222
|
-
let res = yield this.params.symmetryCore.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet: this.params.wallet, simulateTransactions: this.params.simulateTransactions });
|
|
223
|
-
console.log("Mint:", res);
|
|
224
|
-
}
|
|
225
|
-
catch (e) {
|
|
226
|
-
if (numTriesMint == 3) {
|
|
227
|
-
console.log("Stop - mint failed:", e);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
let hasTokens = intent.tokens.find((token) => token.amount > 0);
|
|
233
|
-
if (hasTokens && intent.rebalance_type == "withdraw") {
|
|
234
|
-
if (numTriesRedeemTokens >= 3) {
|
|
235
|
-
console.log("Max retries for redeem");
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
238
|
-
numTriesRedeemTokens += 1;
|
|
239
|
-
try {
|
|
240
|
-
let tx = yield this.params.symmetryCore.redeemTokensTx({
|
|
241
|
-
keeper: this.params.wallet.publicKey.toBase58(),
|
|
242
|
-
rebalance_intent: intent.pubkey,
|
|
243
|
-
});
|
|
244
|
-
let res = yield this.params.symmetryCore.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet: this.params.wallet, simulateTransactions: this.params.simulateTransactions });
|
|
245
|
-
console.log("Redeem Tokens:", res);
|
|
246
|
-
}
|
|
247
|
-
catch (e) {
|
|
248
|
-
if (numTriesRedeemTokens == 3) {
|
|
249
|
-
console.log("Stop - redeem failed:", e);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
if (numTriesClaimBounty >= 3) {
|
|
255
|
-
console.log("Max retries for claim bounty");
|
|
256
|
-
break;
|
|
257
|
-
}
|
|
258
|
-
numTriesClaimBounty += 1;
|
|
259
|
-
try {
|
|
260
|
-
let tx = yield this.params.symmetryCore.claimBountyTx({
|
|
261
|
-
keeper: this.params.wallet.publicKey.toBase58(),
|
|
262
|
-
rebalance_intent: intent.pubkey,
|
|
263
|
-
});
|
|
264
|
-
let res = yield this.params.symmetryCore.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet: this.params.wallet, simulateTransactions: this.params.simulateTransactions });
|
|
265
|
-
console.log("Claim Bounty:", res);
|
|
266
|
-
nextCheckTime = now + 60;
|
|
267
|
-
}
|
|
268
|
-
catch (e) {
|
|
269
|
-
if (numTriesClaimBounty == 3) {
|
|
270
|
-
console.log("Stop - claim bounty failed:", e);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
console.log("Rebalance handler finished for intent:", this.intent.formatted_data.pubkey);
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
exports.RebalanceHandler = RebalanceHandler;
|