@drift-labs/vaults-sdk 0.1.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.
- package/.env.example +2 -0
- package/README.md +58 -0
- package/cli/cli.ts +105 -0
- package/cli/commands/applyProfitShare.ts +48 -0
- package/cli/commands/deposit.ts +35 -0
- package/cli/commands/index.ts +13 -0
- package/cli/commands/initVault.ts +61 -0
- package/cli/commands/initVaultDepositor.ts +43 -0
- package/cli/commands/listDepositorsForVault.ts +33 -0
- package/cli/commands/managerCancelWithdraw.ts +25 -0
- package/cli/commands/managerDeposit.ts +34 -0
- package/cli/commands/managerRequestWithdraw.ts +27 -0
- package/cli/commands/managerUpdateVault.ts +36 -0
- package/cli/commands/managerWithdraw.ts +25 -0
- package/cli/commands/requestWithdraw.ts +29 -0
- package/cli/commands/vaultDeposit.ts +43 -0
- package/cli/commands/vaultWithdraw.ts +43 -0
- package/cli/commands/viewVault.ts +30 -0
- package/cli/commands/viewVaultDepositor.ts +37 -0
- package/cli/commands/withdraw.ts +25 -0
- package/cli/utils.ts +119 -0
- package/lib/accountSubscribers/index.d.ts +2 -0
- package/lib/accountSubscribers/index.js +14 -0
- package/lib/accountSubscribers/pollingVaultDepositorSubscriber.d.ts +7 -0
- package/lib/accountSubscribers/pollingVaultDepositorSubscriber.js +44 -0
- package/lib/accountSubscribers/pollingVaultSubscriber.d.ts +7 -0
- package/lib/accountSubscribers/pollingVaultSubscriber.js +44 -0
- package/lib/accountSubscribers/pollingVaultsProgramAccountSubscriber.d.ts +28 -0
- package/lib/accountSubscribers/pollingVaultsProgramAccountSubscriber.js +68 -0
- package/lib/accounts/index.d.ts +2 -0
- package/lib/accounts/index.js +14 -0
- package/lib/accounts/vaultAccount.d.ts +15 -0
- package/lib/accounts/vaultAccount.js +53 -0
- package/lib/accounts/vaultDepositorAccount.d.ts +18 -0
- package/lib/accounts/vaultDepositorAccount.js +45 -0
- package/lib/accounts/vaultsProgramAccount.d.ts +13 -0
- package/lib/accounts/vaultsProgramAccount.js +25 -0
- package/lib/addresses.d.ts +4 -0
- package/lib/addresses.js +46 -0
- package/lib/constants/index.d.ts +3 -0
- package/lib/constants/index.js +5 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.js +21 -0
- package/lib/name.d.ts +3 -0
- package/lib/name.js +19 -0
- package/lib/parsers/index.d.ts +1 -0
- package/lib/parsers/index.js +13 -0
- package/lib/parsers/logParser.d.ts +14 -0
- package/lib/parsers/logParser.js +21 -0
- package/lib/types/drift_vaults.d.ts +1475 -0
- package/lib/types/drift_vaults.js +1477 -0
- package/lib/types/types.d.ts +137 -0
- package/lib/types/types.js +20 -0
- package/lib/utils.d.ts +7 -0
- package/lib/utils.js +43 -0
- package/lib/vaultClient.d.ts +100 -0
- package/lib/vaultClient.js +596 -0
- package/package.json +29 -0
- package/src/accountSubscribers/index.ts +2 -0
- package/src/accountSubscribers/pollingVaultDepositorSubscriber.ts +68 -0
- package/src/accountSubscribers/pollingVaultSubscriber.ts +62 -0
- package/src/accountSubscribers/pollingVaultsProgramAccountSubscriber.ts +111 -0
- package/src/accounts/index.ts +2 -0
- package/src/accounts/vaultAccount.ts +86 -0
- package/src/accounts/vaultDepositorAccount.ts +67 -0
- package/src/accounts/vaultsProgramAccount.ts +37 -0
- package/src/addresses.ts +43 -0
- package/src/constants/index.ts +3 -0
- package/src/idl/drift_vaults.json +1547 -0
- package/src/index.ts +9 -0
- package/src/name.ts +18 -0
- package/src/parsers/index.ts +1 -0
- package/src/parsers/logParser.ts +28 -0
- package/src/types/drift_vaults.ts +2949 -0
- package/src/types/types.ts +158 -0
- package/src/utils.ts +33 -0
- package/src/vaultClient.ts +904 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,904 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BN,
|
|
3
|
+
DriftClient,
|
|
4
|
+
getUserAccountPublicKey,
|
|
5
|
+
getUserAccountPublicKeySync,
|
|
6
|
+
getUserStatsAccountPublicKey,
|
|
7
|
+
User,
|
|
8
|
+
} from '@drift-labs/sdk';
|
|
9
|
+
import { BorshAccountsCoder, Program, ProgramAccount } from '@coral-xyz/anchor';
|
|
10
|
+
import { DriftVaults } from './types/drift_vaults';
|
|
11
|
+
import {
|
|
12
|
+
getTokenVaultAddressSync,
|
|
13
|
+
getVaultAddressSync,
|
|
14
|
+
getVaultDepositorAddressSync,
|
|
15
|
+
} from './addresses';
|
|
16
|
+
import {
|
|
17
|
+
ComputeBudgetProgram,
|
|
18
|
+
PublicKey,
|
|
19
|
+
SetComputeUnitLimitParams,
|
|
20
|
+
SystemProgram,
|
|
21
|
+
SYSVAR_RENT_PUBKEY,
|
|
22
|
+
Transaction,
|
|
23
|
+
TransactionInstruction,
|
|
24
|
+
TransactionSignature,
|
|
25
|
+
} from '@solana/web3.js';
|
|
26
|
+
import {
|
|
27
|
+
getAssociatedTokenAddressSync,
|
|
28
|
+
TOKEN_PROGRAM_ID,
|
|
29
|
+
} from '@solana/spl-token';
|
|
30
|
+
import { Vault, VaultDepositor, WithdrawUnit } from './types/types';
|
|
31
|
+
import { bs58 } from '@coral-xyz/anchor/dist/cjs/utils/bytes';
|
|
32
|
+
|
|
33
|
+
export class VaultClient {
|
|
34
|
+
driftClient: DriftClient;
|
|
35
|
+
program: Program<DriftVaults>;
|
|
36
|
+
cliMode: boolean;
|
|
37
|
+
|
|
38
|
+
constructor({
|
|
39
|
+
driftClient,
|
|
40
|
+
program,
|
|
41
|
+
cliMode,
|
|
42
|
+
}: {
|
|
43
|
+
driftClient: DriftClient;
|
|
44
|
+
program: Program<DriftVaults>;
|
|
45
|
+
cliMode?: boolean;
|
|
46
|
+
}) {
|
|
47
|
+
this.driftClient = driftClient;
|
|
48
|
+
this.program = program;
|
|
49
|
+
this.cliMode = !!cliMode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public async getVault(vault: PublicKey): Promise<Vault> {
|
|
53
|
+
return await this.program.account.vault.fetch(vault);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public async getVaultDepositor(vaultDepositor: PublicKey): Promise<any> {
|
|
57
|
+
return await this.program.account.vaultDepositor.fetch(vaultDepositor);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public async getAllVaultDepositors(
|
|
61
|
+
vault: PublicKey
|
|
62
|
+
): Promise<ProgramAccount<VaultDepositor>[]> {
|
|
63
|
+
const filters = [
|
|
64
|
+
{
|
|
65
|
+
// discriminator = VaultDepositor
|
|
66
|
+
memcmp: {
|
|
67
|
+
offset: 0,
|
|
68
|
+
bytes: bs58.encode(
|
|
69
|
+
BorshAccountsCoder.accountDiscriminator('VaultDepositor')
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
// vault = vault
|
|
75
|
+
memcmp: {
|
|
76
|
+
offset: 8,
|
|
77
|
+
bytes: vault.toBase58(),
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
// last_withdraw_request_ts = 0
|
|
82
|
+
memcmp: {
|
|
83
|
+
offset: 144,
|
|
84
|
+
bytes: bs58.encode(Uint8Array.from([0])),
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
// @ts-ignore
|
|
89
|
+
return (await this.program.account.vaultDepositor.all(
|
|
90
|
+
filters
|
|
91
|
+
)) as ProgramAccount<VaultDepositor>[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
*
|
|
96
|
+
* @param vault pubkey
|
|
97
|
+
* @returns vault equity, in QUOTE_PRECISION
|
|
98
|
+
*/
|
|
99
|
+
public async calculateVaultEquity(params: {
|
|
100
|
+
address?: PublicKey;
|
|
101
|
+
vault?: Vault;
|
|
102
|
+
}): Promise<BN> {
|
|
103
|
+
let vaultAccount: Vault;
|
|
104
|
+
if (params.address !== undefined) {
|
|
105
|
+
vaultAccount = await this.program.account.vault.fetch(params.address);
|
|
106
|
+
} else if (params.vault !== undefined) {
|
|
107
|
+
vaultAccount = params.vault;
|
|
108
|
+
} else {
|
|
109
|
+
throw new Error('Must supply address or vault');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const user = new User({
|
|
113
|
+
driftClient: this.driftClient,
|
|
114
|
+
userAccountPublicKey: vaultAccount.user,
|
|
115
|
+
});
|
|
116
|
+
await user.subscribe();
|
|
117
|
+
|
|
118
|
+
const netSpotValue = user.getNetSpotMarketValue();
|
|
119
|
+
const unrealizedPnl = user.getUnrealizedPNL(true, undefined, undefined);
|
|
120
|
+
|
|
121
|
+
return netSpotValue.add(unrealizedPnl);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public async initializeVault(params: {
|
|
125
|
+
name: number[];
|
|
126
|
+
spotMarketIndex: number;
|
|
127
|
+
redeemPeriod: BN;
|
|
128
|
+
maxTokens: BN;
|
|
129
|
+
minDepositAmount: BN;
|
|
130
|
+
managementFee: BN;
|
|
131
|
+
profitShare: number;
|
|
132
|
+
hurdleRate: number;
|
|
133
|
+
permissioned: boolean;
|
|
134
|
+
}): Promise<TransactionSignature> {
|
|
135
|
+
const vault = getVaultAddressSync(this.program.programId, params.name);
|
|
136
|
+
const tokenAccount = getTokenVaultAddressSync(
|
|
137
|
+
this.program.programId,
|
|
138
|
+
vault
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const driftState = await this.driftClient.getStatePublicKey();
|
|
142
|
+
const spotMarket = this.driftClient.getSpotMarketAccount(
|
|
143
|
+
params.spotMarketIndex
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const userStatsKey = getUserStatsAccountPublicKey(
|
|
147
|
+
this.driftClient.program.programId,
|
|
148
|
+
vault
|
|
149
|
+
);
|
|
150
|
+
const userKey = getUserAccountPublicKeySync(
|
|
151
|
+
this.driftClient.program.programId,
|
|
152
|
+
vault
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const accounts = {
|
|
156
|
+
driftSpotMarket: spotMarket.pubkey,
|
|
157
|
+
driftSpotMarketMint: spotMarket.mint,
|
|
158
|
+
driftUserStats: userStatsKey,
|
|
159
|
+
driftUser: userKey,
|
|
160
|
+
driftState,
|
|
161
|
+
vault,
|
|
162
|
+
tokenAccount,
|
|
163
|
+
driftProgram: this.driftClient.program.programId,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
return await this.program.methods
|
|
167
|
+
.initializeVault(params)
|
|
168
|
+
.accounts(accounts)
|
|
169
|
+
.rpc();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Updates the delegate address for a vault. The delegate address will be allowed to trade
|
|
174
|
+
* on behalf of the vault.
|
|
175
|
+
* @param vault vault address to update
|
|
176
|
+
* @param delegate delegate address to update to
|
|
177
|
+
* @returns
|
|
178
|
+
*/
|
|
179
|
+
public async updateDelegate(
|
|
180
|
+
vault: PublicKey,
|
|
181
|
+
delegate: PublicKey
|
|
182
|
+
): Promise<TransactionSignature> {
|
|
183
|
+
const vaultAccount = await this.program.account.vault.fetch(vault);
|
|
184
|
+
return await this.program.methods
|
|
185
|
+
.updateDelegate(delegate)
|
|
186
|
+
.accounts({
|
|
187
|
+
vault: vault,
|
|
188
|
+
driftUser: vaultAccount.user,
|
|
189
|
+
driftProgram: this.driftClient.program.programId,
|
|
190
|
+
})
|
|
191
|
+
.rpc();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
*
|
|
196
|
+
* @param vault vault address to deposit to
|
|
197
|
+
* @param amount amount to deposit
|
|
198
|
+
* @returns
|
|
199
|
+
*/
|
|
200
|
+
public async managerDeposit(
|
|
201
|
+
vault: PublicKey,
|
|
202
|
+
amount: BN
|
|
203
|
+
): Promise<TransactionSignature> {
|
|
204
|
+
const vaultAccount = await this.program.account.vault.fetch(vault);
|
|
205
|
+
const driftSpotMarket = this.driftClient.getSpotMarketAccount(
|
|
206
|
+
vaultAccount.spotMarketIndex
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const user = new User({
|
|
210
|
+
driftClient: this.driftClient,
|
|
211
|
+
userAccountPublicKey: vaultAccount.user,
|
|
212
|
+
});
|
|
213
|
+
await user.subscribe();
|
|
214
|
+
|
|
215
|
+
const remainingAccounts = this.driftClient.getRemainingAccounts({
|
|
216
|
+
userAccounts: [user.getUserAccount()],
|
|
217
|
+
writableSpotMarketIndexes: [vaultAccount.spotMarketIndex],
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return await this.program.methods
|
|
221
|
+
.managerDeposit(amount)
|
|
222
|
+
.accounts({
|
|
223
|
+
vault,
|
|
224
|
+
vaultTokenAccount: vaultAccount.tokenAccount,
|
|
225
|
+
driftUser: await getUserAccountPublicKey(
|
|
226
|
+
this.driftClient.program.programId,
|
|
227
|
+
vault
|
|
228
|
+
),
|
|
229
|
+
driftProgram: this.driftClient.program.programId,
|
|
230
|
+
driftUserStats: getUserStatsAccountPublicKey(
|
|
231
|
+
this.driftClient.program.programId,
|
|
232
|
+
vault
|
|
233
|
+
),
|
|
234
|
+
driftState: await this.driftClient.getStatePublicKey(),
|
|
235
|
+
driftSpotMarketVault: driftSpotMarket.vault,
|
|
236
|
+
userTokenAccount: getAssociatedTokenAddressSync(
|
|
237
|
+
driftSpotMarket.mint,
|
|
238
|
+
this.driftClient.wallet.publicKey
|
|
239
|
+
),
|
|
240
|
+
tokenProgram: TOKEN_PROGRAM_ID,
|
|
241
|
+
})
|
|
242
|
+
.remainingAccounts(remainingAccounts)
|
|
243
|
+
.rpc();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
public async managerRequestWithdraw(
|
|
247
|
+
vault: PublicKey,
|
|
248
|
+
amount: BN,
|
|
249
|
+
withdrawUnit: WithdrawUnit
|
|
250
|
+
): Promise<TransactionSignature> {
|
|
251
|
+
const vaultAccount = (await this.program.account.vault.fetch(
|
|
252
|
+
vault
|
|
253
|
+
)) as Vault;
|
|
254
|
+
|
|
255
|
+
if (!this.driftClient.wallet.publicKey.equals(vaultAccount.manager)) {
|
|
256
|
+
throw new Error(`Only the manager of the vault can request a withdraw.`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const user = new User({
|
|
260
|
+
driftClient: this.driftClient,
|
|
261
|
+
userAccountPublicKey: vaultAccount.user,
|
|
262
|
+
});
|
|
263
|
+
await user.subscribe();
|
|
264
|
+
const remainingAccounts = this.driftClient.getRemainingAccounts({
|
|
265
|
+
userAccounts: [user.getUserAccount()],
|
|
266
|
+
writableSpotMarketIndexes: [vaultAccount.spotMarketIndex],
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const userStatsKey = getUserStatsAccountPublicKey(
|
|
270
|
+
this.driftClient.program.programId,
|
|
271
|
+
vault
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const driftStateKey = await this.driftClient.getStatePublicKey();
|
|
275
|
+
|
|
276
|
+
const accounts = {
|
|
277
|
+
vault,
|
|
278
|
+
driftUserStats: userStatsKey,
|
|
279
|
+
driftUser: vaultAccount.user,
|
|
280
|
+
driftState: driftStateKey,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
if (this.cliMode) {
|
|
284
|
+
return await this.program.methods
|
|
285
|
+
.managerRequestWithdraw(amount, withdrawUnit)
|
|
286
|
+
.accounts(accounts)
|
|
287
|
+
.remainingAccounts(remainingAccounts)
|
|
288
|
+
.rpc();
|
|
289
|
+
} else {
|
|
290
|
+
const requestWithdrawIx = this.program.instruction.managerRequestWithdraw(
|
|
291
|
+
amount,
|
|
292
|
+
withdrawUnit,
|
|
293
|
+
{
|
|
294
|
+
accounts: {
|
|
295
|
+
manager: this.driftClient.wallet.publicKey,
|
|
296
|
+
...accounts,
|
|
297
|
+
},
|
|
298
|
+
remainingAccounts,
|
|
299
|
+
}
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
return await this.createAndSendTxn([requestWithdrawIx]);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
public async managerCancelWithdrawRequest(
|
|
307
|
+
vault: PublicKey
|
|
308
|
+
): Promise<TransactionSignature> {
|
|
309
|
+
const vaultAccount = await this.program.account.vault.fetch(vault);
|
|
310
|
+
|
|
311
|
+
const userStatsKey = getUserStatsAccountPublicKey(
|
|
312
|
+
this.driftClient.program.programId,
|
|
313
|
+
vault
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const driftStateKey = await this.driftClient.getStatePublicKey();
|
|
317
|
+
|
|
318
|
+
const accounts = {
|
|
319
|
+
manager: this.driftClient.wallet.publicKey,
|
|
320
|
+
vault,
|
|
321
|
+
driftUserStats: userStatsKey,
|
|
322
|
+
driftUser: vaultAccount.user,
|
|
323
|
+
driftState: driftStateKey,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const user = new User({
|
|
327
|
+
driftClient: this.driftClient,
|
|
328
|
+
userAccountPublicKey: vaultAccount.user,
|
|
329
|
+
});
|
|
330
|
+
await user.subscribe();
|
|
331
|
+
const remainingAccounts = this.driftClient.getRemainingAccounts({
|
|
332
|
+
userAccounts: [user.getUserAccount()],
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (this.cliMode) {
|
|
336
|
+
return await this.program.methods
|
|
337
|
+
.mangerCancelWithdrawRequest()
|
|
338
|
+
.accounts(accounts)
|
|
339
|
+
.remainingAccounts(remainingAccounts)
|
|
340
|
+
.rpc();
|
|
341
|
+
} else {
|
|
342
|
+
const cancelRequestWithdrawIx =
|
|
343
|
+
this.program.instruction.mangerCancelWithdrawRequest({
|
|
344
|
+
accounts: {
|
|
345
|
+
manager: this.driftClient.wallet.publicKey,
|
|
346
|
+
...accounts,
|
|
347
|
+
},
|
|
348
|
+
remainingAccounts,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return await this.createAndSendTxn([cancelRequestWithdrawIx]);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
public async managerWithdraw(
|
|
356
|
+
vault: PublicKey
|
|
357
|
+
): Promise<TransactionSignature> {
|
|
358
|
+
const vaultAccount = await this.program.account.vault.fetch(vault);
|
|
359
|
+
|
|
360
|
+
if (!this.driftClient.wallet.publicKey.equals(vaultAccount.manager)) {
|
|
361
|
+
throw new Error(`Only the manager of the vault can request a withdraw.`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const user = new User({
|
|
365
|
+
driftClient: this.driftClient,
|
|
366
|
+
userAccountPublicKey: vaultAccount.user,
|
|
367
|
+
});
|
|
368
|
+
await user.subscribe();
|
|
369
|
+
|
|
370
|
+
const remainingAccounts = this.driftClient.getRemainingAccounts({
|
|
371
|
+
userAccounts: [user.getUserAccount()],
|
|
372
|
+
writableSpotMarketIndexes: [vaultAccount.spotMarketIndex],
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const spotMarket = this.driftClient.getSpotMarketAccount(
|
|
376
|
+
vaultAccount.spotMarketIndex
|
|
377
|
+
);
|
|
378
|
+
if (!spotMarket) {
|
|
379
|
+
throw new Error(
|
|
380
|
+
`Spot market ${vaultAccount.spotMarketIndex} not found on driftClient`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const ix = this.program.instruction.managerWithdraw({
|
|
385
|
+
accounts: {
|
|
386
|
+
vault,
|
|
387
|
+
manager: this.driftClient.wallet.publicKey,
|
|
388
|
+
vaultTokenAccount: vaultAccount.tokenAccount,
|
|
389
|
+
driftUser: await getUserAccountPublicKey(
|
|
390
|
+
this.driftClient.program.programId,
|
|
391
|
+
vault
|
|
392
|
+
),
|
|
393
|
+
driftProgram: this.driftClient.program.programId,
|
|
394
|
+
driftUserStats: getUserStatsAccountPublicKey(
|
|
395
|
+
this.driftClient.program.programId,
|
|
396
|
+
vault
|
|
397
|
+
),
|
|
398
|
+
driftState: await this.driftClient.getStatePublicKey(),
|
|
399
|
+
driftSpotMarketVault: spotMarket.vault,
|
|
400
|
+
userTokenAccount: getAssociatedTokenAddressSync(
|
|
401
|
+
spotMarket.mint,
|
|
402
|
+
this.driftClient.wallet.publicKey
|
|
403
|
+
),
|
|
404
|
+
driftSigner: this.driftClient.getStateAccount().signer,
|
|
405
|
+
tokenProgram: TOKEN_PROGRAM_ID,
|
|
406
|
+
},
|
|
407
|
+
remainingAccounts,
|
|
408
|
+
});
|
|
409
|
+
return this.createAndSendTxn([ix], {
|
|
410
|
+
units: 1_000_000,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
public async managerUpdateVault(
|
|
415
|
+
vault: PublicKey,
|
|
416
|
+
params: {
|
|
417
|
+
redeemPeriod: BN | null;
|
|
418
|
+
maxTokens: BN | null;
|
|
419
|
+
managementFee: BN | null;
|
|
420
|
+
minDepositAmount: BN | null;
|
|
421
|
+
profitShare: number | null;
|
|
422
|
+
hurdleRate: number | null;
|
|
423
|
+
permissioned: boolean | null;
|
|
424
|
+
}
|
|
425
|
+
): Promise<TransactionSignature> {
|
|
426
|
+
return await this.program.methods
|
|
427
|
+
.updateVault(params)
|
|
428
|
+
.accounts({
|
|
429
|
+
vault,
|
|
430
|
+
manager: this.driftClient.wallet.publicKey,
|
|
431
|
+
})
|
|
432
|
+
.rpc();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
public async getApplyProfitShareIx(
|
|
436
|
+
vault: PublicKey,
|
|
437
|
+
vaultDepositor: PublicKey
|
|
438
|
+
): Promise<TransactionInstruction> {
|
|
439
|
+
const vaultAccount = await this.program.account.vault.fetch(vault);
|
|
440
|
+
|
|
441
|
+
const user = new User({
|
|
442
|
+
driftClient: this.driftClient,
|
|
443
|
+
userAccountPublicKey: vaultAccount.user,
|
|
444
|
+
});
|
|
445
|
+
await user.subscribe();
|
|
446
|
+
|
|
447
|
+
const spotMarket = this.driftClient.getSpotMarketAccount(
|
|
448
|
+
vaultAccount.spotMarketIndex
|
|
449
|
+
);
|
|
450
|
+
if (!spotMarket) {
|
|
451
|
+
throw new Error(
|
|
452
|
+
`Spot market ${vaultAccount.spotMarketIndex} not found on driftClient`
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const remainingAccounts = this.driftClient.getRemainingAccounts({
|
|
457
|
+
userAccounts: [user.getUserAccount()],
|
|
458
|
+
writableSpotMarketIndexes: [vaultAccount.spotMarketIndex],
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const accounts = {
|
|
462
|
+
vault,
|
|
463
|
+
vaultDepositor,
|
|
464
|
+
manager: this.driftClient.wallet.publicKey,
|
|
465
|
+
driftUserStats: getUserStatsAccountPublicKey(
|
|
466
|
+
this.driftClient.program.programId,
|
|
467
|
+
vault
|
|
468
|
+
),
|
|
469
|
+
driftUser: await getUserAccountPublicKey(
|
|
470
|
+
this.driftClient.program.programId,
|
|
471
|
+
vault
|
|
472
|
+
),
|
|
473
|
+
driftState: await this.driftClient.getStatePublicKey(),
|
|
474
|
+
driftSigner: this.driftClient.getStateAccount().signer,
|
|
475
|
+
driftProgram: this.driftClient.program.programId,
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
return this.program.instruction.applyProfitShare({
|
|
479
|
+
accounts: {
|
|
480
|
+
...accounts,
|
|
481
|
+
},
|
|
482
|
+
remainingAccounts,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private createInitVaultDepositorIx(vault: PublicKey, authority?: PublicKey) {
|
|
487
|
+
const vaultDepositor = getVaultDepositorAddressSync(
|
|
488
|
+
this.program.programId,
|
|
489
|
+
vault,
|
|
490
|
+
authority
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
const accounts = {
|
|
494
|
+
vaultDepositor,
|
|
495
|
+
vault,
|
|
496
|
+
authority: authority || this.driftClient.wallet.publicKey,
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
const initIx = this.program.instruction.initializeVaultDepositor({
|
|
500
|
+
accounts: {
|
|
501
|
+
...accounts,
|
|
502
|
+
payer: authority || this.driftClient.wallet.publicKey,
|
|
503
|
+
rent: SYSVAR_RENT_PUBKEY,
|
|
504
|
+
systemProgram: SystemProgram.programId,
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
return initIx;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Initializes the vault depositor account. This account is used to deposit funds into a vault.
|
|
513
|
+
* @param vault the vault address to deposit into
|
|
514
|
+
* @param authority the authority allowed to make deposits into the vault
|
|
515
|
+
* @returns
|
|
516
|
+
*/
|
|
517
|
+
public async initializeVaultDepositor(
|
|
518
|
+
vault: PublicKey,
|
|
519
|
+
authority?: PublicKey
|
|
520
|
+
): Promise<TransactionSignature> {
|
|
521
|
+
const vaultDepositor = getVaultDepositorAddressSync(
|
|
522
|
+
this.program.programId,
|
|
523
|
+
vault,
|
|
524
|
+
authority
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
const accounts = {
|
|
528
|
+
vaultDepositor,
|
|
529
|
+
vault,
|
|
530
|
+
authority: authority || this.driftClient.wallet.publicKey,
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
if (this.cliMode) {
|
|
534
|
+
return await this.program.methods
|
|
535
|
+
.initializeVaultDepositor()
|
|
536
|
+
.accounts(accounts)
|
|
537
|
+
.rpc();
|
|
538
|
+
} else {
|
|
539
|
+
const initIx = this.createInitVaultDepositorIx(vault, authority);
|
|
540
|
+
return await this.createAndSendTxn([initIx]);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Depositor funds into the specified vault.
|
|
546
|
+
* @param vaultDepositor
|
|
547
|
+
* @param amount
|
|
548
|
+
* @param initVaultDepositor If true, will initialize the vault depositor account
|
|
549
|
+
* @returns
|
|
550
|
+
*/
|
|
551
|
+
public async deposit(
|
|
552
|
+
vaultDepositor: PublicKey,
|
|
553
|
+
amount: BN,
|
|
554
|
+
initVaultDepositor?: {
|
|
555
|
+
authority: PublicKey;
|
|
556
|
+
vault: PublicKey;
|
|
557
|
+
}
|
|
558
|
+
): Promise<TransactionSignature> {
|
|
559
|
+
let vaultPubKey: PublicKey;
|
|
560
|
+
if (initVaultDepositor) {
|
|
561
|
+
vaultPubKey = initVaultDepositor.vault;
|
|
562
|
+
} else {
|
|
563
|
+
const vaultDepositorAccount =
|
|
564
|
+
await this.program.account.vaultDepositor.fetch(vaultDepositor);
|
|
565
|
+
vaultPubKey = vaultDepositorAccount.vault;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const vaultAccount = await this.program.account.vault.fetch(vaultPubKey);
|
|
569
|
+
|
|
570
|
+
const user = new User({
|
|
571
|
+
driftClient: this.driftClient,
|
|
572
|
+
userAccountPublicKey: vaultAccount.user,
|
|
573
|
+
});
|
|
574
|
+
await user.subscribe();
|
|
575
|
+
const remainingAccounts = this.driftClient.getRemainingAccounts({
|
|
576
|
+
userAccounts: [user.getUserAccount()],
|
|
577
|
+
writableSpotMarketIndexes: [vaultAccount.spotMarketIndex],
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const userStatsKey = getUserStatsAccountPublicKey(
|
|
581
|
+
this.driftClient.program.programId,
|
|
582
|
+
vaultPubKey
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
const driftStateKey = await this.driftClient.getStatePublicKey();
|
|
586
|
+
|
|
587
|
+
const spotMarket = this.driftClient.getSpotMarketAccount(
|
|
588
|
+
vaultAccount.spotMarketIndex
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
const accounts = {
|
|
592
|
+
vault: vaultPubKey,
|
|
593
|
+
vaultDepositor,
|
|
594
|
+
vaultTokenAccount: vaultAccount.tokenAccount,
|
|
595
|
+
driftUserStats: userStatsKey,
|
|
596
|
+
driftUser: vaultAccount.user,
|
|
597
|
+
driftState: driftStateKey,
|
|
598
|
+
driftSpotMarketVault: spotMarket.vault,
|
|
599
|
+
userTokenAccount: getAssociatedTokenAddressSync(
|
|
600
|
+
spotMarket.mint,
|
|
601
|
+
this.driftClient.wallet.publicKey,
|
|
602
|
+
true
|
|
603
|
+
),
|
|
604
|
+
driftProgram: this.driftClient.program.programId,
|
|
605
|
+
tokenProgram: TOKEN_PROGRAM_ID,
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
if (this.cliMode) {
|
|
609
|
+
if (initVaultDepositor) {
|
|
610
|
+
await this.initializeVaultDepositor(
|
|
611
|
+
vaultAccount.pubkey,
|
|
612
|
+
initVaultDepositor.authority
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
return await this.program.methods
|
|
616
|
+
.deposit(amount)
|
|
617
|
+
.accounts(accounts)
|
|
618
|
+
.remainingAccounts(remainingAccounts)
|
|
619
|
+
.rpc();
|
|
620
|
+
} else {
|
|
621
|
+
const depositIx = this.program.instruction.deposit(amount, {
|
|
622
|
+
accounts: {
|
|
623
|
+
authority: this.driftClient.wallet.publicKey,
|
|
624
|
+
...accounts,
|
|
625
|
+
},
|
|
626
|
+
remainingAccounts,
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
if (initVaultDepositor) {
|
|
630
|
+
const initIx = this.createInitVaultDepositorIx(
|
|
631
|
+
vaultAccount.pubkey,
|
|
632
|
+
initVaultDepositor.authority
|
|
633
|
+
);
|
|
634
|
+
return await this.createAndSendTxn([initIx, depositIx]);
|
|
635
|
+
} else {
|
|
636
|
+
return await this.createAndSendTxn([depositIx]);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
public async requestWithdraw(
|
|
642
|
+
vaultDepositor: PublicKey,
|
|
643
|
+
amount: BN,
|
|
644
|
+
withdrawUnit: WithdrawUnit
|
|
645
|
+
): Promise<TransactionSignature> {
|
|
646
|
+
const vaultDepositorAccount =
|
|
647
|
+
await this.program.account.vaultDepositor.fetch(vaultDepositor);
|
|
648
|
+
const vaultAccount = await this.program.account.vault.fetch(
|
|
649
|
+
vaultDepositorAccount.vault
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
const user = new User({
|
|
653
|
+
driftClient: this.driftClient,
|
|
654
|
+
userAccountPublicKey: vaultAccount.user,
|
|
655
|
+
});
|
|
656
|
+
await user.subscribe();
|
|
657
|
+
const remainingAccounts = this.driftClient.getRemainingAccounts({
|
|
658
|
+
userAccounts: [user.getUserAccount()],
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
const userStatsKey = getUserStatsAccountPublicKey(
|
|
662
|
+
this.driftClient.program.programId,
|
|
663
|
+
vaultDepositorAccount.vault
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
const driftStateKey = await this.driftClient.getStatePublicKey();
|
|
667
|
+
|
|
668
|
+
const accounts = {
|
|
669
|
+
vault: vaultDepositorAccount.vault,
|
|
670
|
+
vaultDepositor,
|
|
671
|
+
driftUserStats: userStatsKey,
|
|
672
|
+
driftUser: vaultAccount.user,
|
|
673
|
+
driftState: driftStateKey,
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
if (this.cliMode) {
|
|
677
|
+
return await this.program.methods
|
|
678
|
+
.requestWithdraw(amount, withdrawUnit)
|
|
679
|
+
.accounts(accounts)
|
|
680
|
+
.remainingAccounts(remainingAccounts)
|
|
681
|
+
.rpc();
|
|
682
|
+
} else {
|
|
683
|
+
const requestWithdrawIx = this.program.instruction.requestWithdraw(
|
|
684
|
+
amount,
|
|
685
|
+
withdrawUnit,
|
|
686
|
+
{
|
|
687
|
+
accounts: {
|
|
688
|
+
authority: this.driftClient.wallet.publicKey,
|
|
689
|
+
...accounts,
|
|
690
|
+
},
|
|
691
|
+
remainingAccounts,
|
|
692
|
+
}
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
return await this.createAndSendTxn([requestWithdrawIx]);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
public async withdraw(
|
|
700
|
+
vaultDepositor: PublicKey
|
|
701
|
+
): Promise<TransactionSignature> {
|
|
702
|
+
const vaultDepositorAccount =
|
|
703
|
+
await this.program.account.vaultDepositor.fetch(vaultDepositor);
|
|
704
|
+
const vaultAccount = await this.program.account.vault.fetch(
|
|
705
|
+
vaultDepositorAccount.vault
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
const user = new User({
|
|
709
|
+
driftClient: this.driftClient,
|
|
710
|
+
userAccountPublicKey: vaultAccount.user,
|
|
711
|
+
});
|
|
712
|
+
await user.subscribe();
|
|
713
|
+
const remainingAccounts = this.driftClient.getRemainingAccounts({
|
|
714
|
+
userAccounts: [user.getUserAccount()],
|
|
715
|
+
writableSpotMarketIndexes: [vaultAccount.spotMarketIndex],
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
const userStatsKey = getUserStatsAccountPublicKey(
|
|
719
|
+
this.driftClient.program.programId,
|
|
720
|
+
vaultDepositorAccount.vault
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
const driftStateKey = await this.driftClient.getStatePublicKey();
|
|
724
|
+
|
|
725
|
+
const spotMarket = this.driftClient.getSpotMarketAccount(
|
|
726
|
+
vaultAccount.spotMarketIndex
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
const accounts = {
|
|
730
|
+
vault: vaultDepositorAccount.vault,
|
|
731
|
+
vaultDepositor,
|
|
732
|
+
vaultTokenAccount: vaultAccount.tokenAccount,
|
|
733
|
+
driftUserStats: userStatsKey,
|
|
734
|
+
driftUser: vaultAccount.user,
|
|
735
|
+
driftState: driftStateKey,
|
|
736
|
+
driftSpotMarketVault: spotMarket.vault,
|
|
737
|
+
driftSigner: this.driftClient.getStateAccount().signer,
|
|
738
|
+
userTokenAccount: getAssociatedTokenAddressSync(
|
|
739
|
+
spotMarket.mint,
|
|
740
|
+
this.driftClient.wallet.publicKey,
|
|
741
|
+
true
|
|
742
|
+
),
|
|
743
|
+
driftProgram: this.driftClient.program.programId,
|
|
744
|
+
tokenProgram: TOKEN_PROGRAM_ID,
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
if (this.cliMode) {
|
|
748
|
+
return await this.program.methods
|
|
749
|
+
.withdraw()
|
|
750
|
+
.accounts(accounts)
|
|
751
|
+
.remainingAccounts(remainingAccounts)
|
|
752
|
+
.rpc();
|
|
753
|
+
} else {
|
|
754
|
+
const withdrawIx = this.program.instruction.withdraw({
|
|
755
|
+
accounts: {
|
|
756
|
+
authority: this.driftClient.wallet.publicKey,
|
|
757
|
+
...accounts,
|
|
758
|
+
},
|
|
759
|
+
remainingAccounts,
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
return await this.createAndSendTxn([withdrawIx]);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
public async cancelRequestWithdraw(
|
|
767
|
+
vaultDepositor: PublicKey
|
|
768
|
+
): Promise<TransactionSignature> {
|
|
769
|
+
const vaultDepositorAccount =
|
|
770
|
+
await this.program.account.vaultDepositor.fetch(vaultDepositor);
|
|
771
|
+
const vaultAccount = await this.program.account.vault.fetch(
|
|
772
|
+
vaultDepositorAccount.vault
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
const userStatsKey = getUserStatsAccountPublicKey(
|
|
776
|
+
this.driftClient.program.programId,
|
|
777
|
+
vaultDepositorAccount.vault
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
const driftStateKey = await this.driftClient.getStatePublicKey();
|
|
781
|
+
|
|
782
|
+
const accounts = {
|
|
783
|
+
vault: vaultDepositorAccount.vault,
|
|
784
|
+
vaultDepositor,
|
|
785
|
+
driftUserStats: userStatsKey,
|
|
786
|
+
driftUser: vaultAccount.user,
|
|
787
|
+
driftState: driftStateKey,
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
const user = new User({
|
|
791
|
+
driftClient: this.driftClient,
|
|
792
|
+
userAccountPublicKey: vaultAccount.user,
|
|
793
|
+
});
|
|
794
|
+
await user.subscribe();
|
|
795
|
+
const remainingAccounts = this.driftClient.getRemainingAccounts({
|
|
796
|
+
userAccounts: [user.getUserAccount()],
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
if (this.cliMode) {
|
|
800
|
+
return await this.program.methods
|
|
801
|
+
.cancelRequestWithdraw()
|
|
802
|
+
.accounts(accounts)
|
|
803
|
+
.remainingAccounts(remainingAccounts)
|
|
804
|
+
.rpc();
|
|
805
|
+
} else {
|
|
806
|
+
const cancelRequestWithdrawIx =
|
|
807
|
+
this.program.instruction.cancelRequestWithdraw({
|
|
808
|
+
accounts: {
|
|
809
|
+
authority: this.driftClient.wallet.publicKey,
|
|
810
|
+
...accounts,
|
|
811
|
+
},
|
|
812
|
+
remainingAccounts,
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
return await this.createAndSendTxn([cancelRequestWithdrawIx]);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Liquidates (become delegate for) a vault.
|
|
821
|
+
* @param
|
|
822
|
+
* @param
|
|
823
|
+
* @returns
|
|
824
|
+
*/
|
|
825
|
+
public async liquidate(
|
|
826
|
+
vaultDepositor: PublicKey
|
|
827
|
+
): Promise<TransactionSignature> {
|
|
828
|
+
const vaultDepositorAccount =
|
|
829
|
+
await this.program.account.vaultDepositor.fetch(vaultDepositor);
|
|
830
|
+
const vaultPubKey = vaultDepositorAccount.vault;
|
|
831
|
+
|
|
832
|
+
const vaultAccount = await this.program.account.vault.fetch(vaultPubKey);
|
|
833
|
+
|
|
834
|
+
const user = new User({
|
|
835
|
+
driftClient: this.driftClient,
|
|
836
|
+
userAccountPublicKey: vaultAccount.user,
|
|
837
|
+
});
|
|
838
|
+
await user.subscribe();
|
|
839
|
+
const remainingAccounts = this.driftClient.getRemainingAccounts({
|
|
840
|
+
userAccounts: [user.getUserAccount()],
|
|
841
|
+
writableSpotMarketIndexes: [vaultAccount.spotMarketIndex],
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
const userStatsKey = getUserStatsAccountPublicKey(
|
|
845
|
+
this.driftClient.program.programId,
|
|
846
|
+
vaultPubKey
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
const driftStateKey = await this.driftClient.getStatePublicKey();
|
|
850
|
+
|
|
851
|
+
const accounts = {
|
|
852
|
+
vault: vaultPubKey,
|
|
853
|
+
vaultDepositor,
|
|
854
|
+
vaultTokenAccount: vaultAccount.tokenAccount,
|
|
855
|
+
driftUserStats: userStatsKey,
|
|
856
|
+
driftUser: vaultAccount.user,
|
|
857
|
+
driftState: driftStateKey,
|
|
858
|
+
driftProgram: this.driftClient.program.programId,
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
if (this.cliMode) {
|
|
862
|
+
return await this.program.methods
|
|
863
|
+
.liquidate()
|
|
864
|
+
.accounts(accounts)
|
|
865
|
+
.remainingAccounts(remainingAccounts)
|
|
866
|
+
.rpc();
|
|
867
|
+
} else {
|
|
868
|
+
const liquidateIx = this.program.instruction.liquidate({
|
|
869
|
+
accounts: {
|
|
870
|
+
authority: this.driftClient.wallet.publicKey,
|
|
871
|
+
...accounts,
|
|
872
|
+
},
|
|
873
|
+
remainingAccounts,
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
return await this.createAndSendTxn([liquidateIx]);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Used for UI wallet adapters compatibility
|
|
882
|
+
*/
|
|
883
|
+
public async createAndSendTxn(
|
|
884
|
+
ixs: TransactionInstruction[],
|
|
885
|
+
computeUnitParams?: SetComputeUnitLimitParams
|
|
886
|
+
): Promise<TransactionSignature> {
|
|
887
|
+
const tx = new Transaction();
|
|
888
|
+
tx.add(
|
|
889
|
+
ComputeBudgetProgram.setComputeUnitLimit(
|
|
890
|
+
computeUnitParams || {
|
|
891
|
+
units: 400_000,
|
|
892
|
+
}
|
|
893
|
+
)
|
|
894
|
+
);
|
|
895
|
+
tx.add(...ixs);
|
|
896
|
+
const { txSig } = await this.driftClient.sendTransaction(
|
|
897
|
+
tx,
|
|
898
|
+
[],
|
|
899
|
+
this.driftClient.opts
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
return txSig;
|
|
903
|
+
}
|
|
904
|
+
}
|